Java has two kinds of proxy, one is static proxy, the other is dynamic proxy. In the case of static proxies, the object is encapsulated through dependency injection without letting the outside world know the details of the implementation. Many apis are encapsulated in this form.

Proxy pattern structure diagram (picture from “Dahua Design Pattern”)

Here is a conceptual explanation of both:

Static agent

  • Static proxy classes: created by programmers or generated by third-party tools and then compiled; The.class file for the proxy class already exists before the program runs.
  • A static proxy class typically proxies only one class.
  • Static agents know in advance what they are proiding.

A dynamic proxy

  • Dynamic proxy classes: Dynamically generated by reflection while the program is running.
  • Dynamic proxy classes typically proxy all classes under an interface.
  • Dynamic proxies do not know in advance what is being proxied, only at run time.
  • A dynamic Proxy InvocationHandler must first InvocationHandler interface and dynamically create the Proxy class using the newProxyInstance method in the Proxy class.
  • Java dynamic proxy can only proxy interfaces. To proxy classes, you need to use third-party libraries such as CLIGB.

Benefits of dynamic proxies

Java dynamic proxies have the advantage of non-invasive code extensions, that is, method enhancements; Allows you to enhance some methods without modifying the source code; You can do whatever you want before or after a method (even without executing the method). In addition, it can also reduce the amount of code, if the use of static proxy, class methods more when you have to write a lot of code.

Dynamic proxy instance

Examples of static proxies are not mentioned here and are relatively simple. Java’s java.lang.Reflect package provides a Proxy class and an InvocationHandler interface through which JDK dynamic Proxy classes and dynamic Proxy objects can be generated. Let’s talk about dynamic proxy implementation.

Define an interface:

public interface Person {
    void setName(String name);
}
Copy the code

Define a Student class to implement the Person interface, with each Student having its own name:

public class Student implements Person { private String mName; public Student(String name) { mName = name; } public void setName(String name) { mName = name; }}Copy the code

In the Student class, a private variable mName is defined to represent the name of Student. Next we define a proxy handler to help us handle the proxy:

Public class PersonHandler<T> implements InvocationHandler {private T mTarget; public PersonHandler(T target) { mTarget = target; } @Override public Object invoke(Object o, Method method, Object[] objects) throws Throwable {// call proxyutil.start (); Call method Object result = method.invoke(mTarget, objects); Proxyutil.log (objects[0].toString()); Proxyutil.finish (); return result; }}Copy the code

As you can see, we do something before and after we call the code, and we can even intercept the method without letting it run. We define a ProxyUtil class to do a few things:

public class ProxyUtil { private static final String TAG = "ProxyUtil"; public static void start() { Log.d(TAG, "start: " + System.currentTimeMillis()); } public static void finish() { Log.d(TAG, "finish: " + System.currentTimeMillis()); } public static void log(String name) { Log.d(TAG, "log: " + name); }}Copy the code

Let’s start writing the proxy implementation:

public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Person Zhangsan = new Student(" zhangsan "); Person Zhangsan = new Student(" zhangsan "); // Create a proxy object associated with InvocationHandler PersonHandler stuHandler = new PersonHandler<>(zhangsan); // Create a proxy object stuProxy to delegate Zhangsan, Each execution method of the proxy object replaces the Invoke method from Invocation Person stuProxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<? >[]{Person.class}, stuHandler); // Invoke stuHandler (stuproxy.setName); // Invoke stuproxy.setname (" King five "); // Invoke stuProxy.setName(" King five "); }Copy the code

Take a look at the printout:

You can see that the agent succeeded. And we did something before and after we called the method. Spring AOP is implemented through the dynamic proxy mechanism.

 

Where, we print out the stuProxy class name:

Log.d(TAG, "onCreate: " + stuProxy.getClass().getCanonicalName());
Copy the code

The name is $Proxy0. The reasons for this will be explained below.

Source code analysis

Create a dynamic Proxy object using the newProxyInstance method of the Proxy class.

/** * Returns an instance of a proxy class for the specified interfaces * that dispatches method invocations to the specified invocation * handler. * * <p>{@code Proxy.newProxyInstance} throws * {@code IllegalArgumentException} for the same reasons that * {@code Proxy.getProxyClass} does. * * @param loader the class loader to define the proxy class * @param interfaces the list of interfaces for the proxy class * to implement * @param h the invocation handler to dispatch method invocations to * @return a proxy instance with the specified invocation handler of a * proxy class that is defined by the specified class loader * and that implements the specified interfaces * @throws IllegalArgumentException if any of the restrictions on the * parameters that may be passed to {@code getProxyClass} * are violated * @throws SecurityException if a security manager, <em>s</em>, is present * and any of the following conditions is met: * <ul> * <li> the given {@code loader} is {@code null} and * the caller's class loader is not {@code null} and the * invocation of {@link SecurityManager#checkPermission * s.checkPermission} with * {@code RuntimePermission("getClassLoader")} permission * denies access; </li> * <li> for each proxy interface, {@code intf}, * the caller's class loader is not the same as or an * ancestor of the class loader for {@code intf} and * invocation of  {@link SecurityManager#checkPackageAccess * s.checkPackageAccess()} denies access to {@code intf}; </li> * <li> any of the given proxy interfaces is non-public and the * caller class is not in the same {@linkplain Package runtime package} * as the non-public interface and the invocation of * {@link SecurityManager#checkPermission s.checkPermission} with * {@code ReflectPermission("newProxyInPackage.{package name}")} * permission denies access.</li>  * </ul> * @throws NullPointerException if the {@code interfaces} array * argument or any of its elements are {@code null}, or * if the invocation handler, {@code h}, is * {@code null} */ @CallerSensitive public static Object newProxyInstance(ClassLoader loader, Class<? >[] interfaces, InvocationHandler h) throws IllegalArgumentException { For null, throw NullPointerException Objects.requirenonNULL (h); final Class<? >[] intfs = interfaces.clone(); final SecurityManager sm = System.getSecurityManager(); if (sm ! = null) {/ / packet access, such as class loader permission check checkProxyAccess (Reflection) getCallerClass (), loader, intfs); } /* * Look up or generate the designated proxy class. */ Class<? > cl = getProxyClass0(loader, intfs); /* * Invoke its constructor with the designated invocation handler. */ try { if (sm ! = null) { checkNewProxyPermission(Reflection.getCallerClass(), cl); } final Constructor<? > cons = cl.getConstructor(constructorParams); final InvocationHandler ih = h; if (! Modifier.isPublic(cl.getModifiers())) { AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() {  cons.setAccessible(true); return null; }}); } return cons.newInstance(new Object[]{h}); } catch (IllegalAccessException|InstantiationException e) { throw new InternalError(e.toString(), e); } catch (InvocationTargetException e) { Throwable t = e.getCause(); if (t instanceof RuntimeException) { throw (RuntimeException) t; } else { throw new InternalError(t.toString(), t); } } catch (NoSuchMethodException e) { throw new InternalError(e.toString(), e); }}Copy the code

During the generation of the proxy class, a number of column checks are made, such as access rights. GetProxyClass0 = getProxyClass0;

/** * Generate a proxy class. Must call the checkProxyAccess method * to perform permission checks before calling this. */ private static Class<? > getProxyClass0(ClassLoader loader, Class<? >... Interfaces) {// Throw an exception if the number exceeds 65535, If (interfaces.length > 65535) {throw new IllegalArgumentException("interface limit exceeded"); } // If the proxy class defined by the given loader implementing // the given interfaces exists, this will simply return the cached copy; // otherwise, it will create the proxy class via the ProxyClassFactory return proxyClassCache.get(loader, interfaces); }Copy the code

If there is a proxy class, it does not return it directly. If there is no proxy class, it still needs to generate proxy class.

proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
Copy the code

The key is the ProxyClassFactory class, whose name can also be inferred. Take a look at the code:

/** * A factory function that generates, defines and returns the proxy class given * the ClassLoader and array of interfaces. */ private static final class ProxyClassFactory implements BiFunction<ClassLoader, Class<? >[], Class<? >> {// prefix for all proxy class names define prefix private static Final String proxyClassNamePrefix = "$proxy "; // Next number to use for generation of unique proxy class names Private Static Final AtomicLong nextUniqueNumber = new AtomicLong(); @Override public Class<? > apply(ClassLoader loader, Class<? >[] interfaces) { Map<Class<? >, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length); for (Class<? > intf : interfaces) { /* * Verify that the class loader resolves the name of this * interface to the same Class object. */ Class<? > interfaceClass = null; InterfaceClass = class.forname (intf.getName(), false, loader); } catch (ClassNotFoundException e) {} // If (interfaceClass! = intf) { throw new IllegalArgumentException( intf + " is not visible from class loader"); } /* * Verify that the Class object actually represents an * interface. */ if (! interfaceClass.isInterface()) { throw new IllegalArgumentException( interfaceClass.getName() + " is not an interface"); } /* * Verify that this interface is not a duplicate. */ if (interfaceSet.put(interfaceClass, Boolean.TRUE) ! = null) { throw new IllegalArgumentException( "repeated interface: " + interfaceClass.getName()); } } String proxyPkg = null; // package to define proxy class in int accessFlags = Modifier.PUBLIC | Modifier.FINAL; /* * Record the package of a non-public proxy interface so that the * proxy class will be defined in the same package. Verify that * all non-public proxy interfaces are in the same package. */ for (Class<? > intf : interfaces) { int flags = intf.getModifiers(); if (! Modifier.isPublic(flags)) { accessFlags = Modifier.FINAL; String name = intf.getName(); int n = name.lastIndexOf('.'); String pkg = ((n == -1) ? "" : name.substring(0, n + 1)); if (proxyPkg == null) { proxyPkg = pkg; } else if (! pkg.equals(proxyPkg)) { throw new IllegalArgumentException( "non-public interfaces from different packages"); } } } if (proxyPkg == null) { // if no non-public proxy interfaces, use com.sun.proxy package proxyPkg = ReflectUtil.PROXY_PACKAGE + "."; } / * * Choose a name for the proxy class to generate the. * / long num = nextUniqueNumber. GetAndIncrement (); String proxyName = proxyPkg + proxyClassNamePrefix + num; /* * Generate the specified proxy class. */ byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces, accessFlags); try { return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length); } catch (ClassFormatError e) { /* * A ClassFormatError here means that (barring bugs in the * proxy class generation code) there was some other * invalid aspect of the arguments supplied to the proxy * class creation (such as virtual machine limitations * exceeded). */ throw new IllegalArgumentException(e.toString()); }}}Copy the code

Here will be through reflection interface for all kinds of modifiers, the package name, etc., and then according to the rules for naming the proxy class, the last call ProxyGenerator. GenerateProxyClass generated proxy class.

ProxyGenerator. GenerateProxyClass on the concrete implementation on the eclipse, is can’t find the source code:

However, part of the code was found elsewhere:

public static byte[] generateProxyClass(final String name, Class[] interfaces) { ProxyGenerator gen = new ProxyGenerator(name, interfaces); Final byte[] classFile = Gen. GenerateClassFile (); final byte[] classFile = Gen. // If saveGeneratedFiles is true, Will put the generated proxy class bytecode if saved to the hard disk (saveGeneratedFiles) {Java. Security. The AccessController. DoPrivileged (new java.security.PrivilegedAction<Void>() { public Void run() { try { FileOutputStream file = new FileOutputStream(dotToSlash(name) + ".class"); file.write(classFile); file.close(); return null; } catch (IOException e) { throw new InternalError( "I/O exception saving generated file: " + e); }}}); } // return bytecode of proxy class return classFile; }Copy the code

We can try our ProxyGenerator generateProxyClass function.

Public class ProxyGeneratorUtils {/** * write proxy class bytecode to disk * @param path save path */ public static void WriteProxyClassToHardDisk (String path) {/ / get the proxy class bytecode byte [] classFile = ProxyGenerator. GenerateProxyClass (" $Proxy11 ", Student.class.getInterfaces()); FileOutputStream out = null; try { out = new FileOutputStream(path); out.write(classFile); out.flush(); } catch (Exception e) { e.printStackTrace(); } finally { try { out.close(); } catch (IOException e) { e.printStackTrace(); }}}}Copy the code

Call from the main method:

public class Main { public static void main(String[] args) { ProxyGeneratorUtils.writeProxyClassToHardDisk("$Proxy0.class"); }}Copy the code

$proxy0.class = $proxy0.class = $proxy0.class

import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; import proxy.Person; public final class $Proxy0 extends Proxy implements Person { private static Method m1; private static Method m2; private static Method m3; private static Method m0; /** * Call InvocationHandler (); /** * Call InvocationHandler (); /** * Call InvocationHandler (); The InvocationHandler in turn holds an instance of the * proxied object and wonders if it is.... ? Yeah, that's what you think it is. * *super(paramInvocationHandler) is the constructor that calls the superclass Proxy. The parent class holds: protected InvocationHandler h; * Protected Proxy(InvocationHandler h) {* Objects. RequireNonNull (h); * this.h = h; * } * */ public $Proxy0(InvocationHandler paramInvocationHandler) throws { super(paramInvocationHandler); } // This static block is the last one, I bring it to the front to describe static {try {// see what is inside the static block, if you find the giveMoney method. Remember that giveMoney got the name M3 by reflection, M1 = class.forname (" java.lang.object ").getMethod("equals", new Class[] {class.forname (" java.lang.object ")}); m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); m3 = Class.forName("proxy.Person").getMethod("giveMoney", new Class[0]); m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); return; } catch (NoSuchMethodException localNoSuchMethodException) { throw new NoSuchMethodError(localNoSuchMethodException.getMessage()); } catch (ClassNotFoundException localClassNotFoundException) { throw new NoClassDefFoundError(localClassNotFoundException.getMessage()); }} /** ** this calls the giveMoney method of the proxy object, directly invoking the Invoke method of InvocationHandler and passing m3 in. *this.h.invoke(this, m3, null); This is simple and clear. * Now, think again, the proxy object holds an InvocationHandler object, and the InvocationHandler object holds an object that is proestedand refers to the Invoke method in the InvacationHandler. Well, that's it. */ public final void giveMoney() throws { try { this.h.invoke(this, m3, null); return; } catch (Error|RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); }} // Note that toString, hashCode, and equals methods are omitted to save space. The principle is exactly the same as the giveMoney method. }Copy the code

The JDK generates a proxy class for us called $Proxy0 (the 0 after the name is the number, multiple proxy classes are incrementing at once). This class file is stored in memory, and when we create the proxy object, we get the constructor of this class by reflection, and then create the proxy instance. By looking at the source code of this generated proxy class, we can easily see the detailed process of dynamic proxy implementation.

We can think of InvocationHandler as a mediation class. The mediation class holds a propeted object, invokes the propeted object’s corresponding method, and the generated proxy class holds the mediation class. Therefore, when we call the proxy class, The invoke method of the mediation class is then called, which is converted by reflection into a call to the proxied object.

When a proxy class invokes its own method, it invokes the invoke method of the mediation object through the mediation object it holds, so that the proxy can execute the method of the proxied object. That is, dynamic proxies implement specific proxy functionality through mediation classes.

Generated proxy classes: $Proxy0 extends Proxy implements Person; $Proxy0 extends Proxy implements Person; Java’s inheritance mechanism prevents these dynamic proxy classes from implementing dynamic proxies to classes.

 

Reference article:

1, Java dynamic proxy mechanism details

2. Understand JAVA dynamic proxies thoroughly

Java 1.8 dynamic proxy source code analysis

4. Detailed analysis of Java dynamic proxy implementation and principle