Java Dynamic Proxy Analysis and a simple example

  • 2020-06-03 06:25:26
  • OfStack

Java dynamic proxy

If you want to understand Java dynamic proxy, you must first understand what is called proxy. Those familiar with design patterns must know that among the 23 design patterns summarized by Gof, there is one object structure pattern called proxy (Proxy). The proxy in dynamic proxy refers to this design pattern.

It seems to me that the so-called proxy pattern and the "decorator pattern" of the 23 design patterns are one thing. There are also some articles on the Internet about the similarities and differences between the two modes. From the perspective of details, it is possible to distinguish the two modes artificially. However, when the abstraction reaches a certain height, I think the two modes are completely identical. Therefore, if you learn the proxy mode, you will also master the decorative mode.

The proxy pattern

The proxy pattern is simply a wrapping of an object that generates an object with a list of methods similar to the original object 1, but each method can be wrapped.

Static agent

Let's start with a snippet of code:


package common;

public class Test {
  static interface Subject{
    void sayHi();
    void sayHello();
  }
  
  static class SubjectImpl implements Subject{

    @Override
    public void sayHi() {
      System.out.println("hi");
    }

    @Override
    public void sayHello() {
      System.out.println("hello");
    }
  }
  
  static class SubjectImplProxy implements Subject{
    private Subject target;
    
    public SubjectImplProxy(Subject target) {
      this.target=target;
    }
    
    @Override
    public void sayHi() {
      System.out.print("say:");
      target.sayHi();
    }

    @Override
    public void sayHello() {
      System.out.print("say:");
      target.sayHello();
    }
  }
  
  public static void main(String[] args) {
    Subject subject=new SubjectImpl();
    Subject subjectProxy=new SubjectImplProxy(subject);
    subjectProxy.sayHi();
    subjectProxy.sayHello();
  }
}

This code first defines an Subject interface with two methods.

The SubjectImpl class is then defined to implement the Subject interface and implement two of its methods, which is certainly fine here.

Now define another SubjuectImplProxy class that also implements the Subject interface. The SubjectImplProxy class wraps an instance of the SubjectImpl class and internally defines a variable, target, to hold an instance of SubjectImpl. SubjectImplProxy also implements the two methods specified by the interface, and in its implementation version, it calls the implementation of SubjectImpl, but adds its own processing logic.

It's not hard to see how this code can be prefixed by wrapping SubjectImpl around the output. This proxy approach is called static proxy.

A dynamic proxy

From the demo above we can see that the shortcoming of static agent: we have two methods of SubjectImpl, is of the same package, but want to be in SubjectImplProxy wrap the same logic to write two times, and later if Subject interface to add new method, SubjectImplProxy must also add the new implementation, although SubjectImplProxy packaging may be 1 sample of all methods.

Let me change the static proxy in the above example to dynamic proxy. Let's see the difference in 1:


package common;

import java.lang.invoke.MethodHandle;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class Test {
  static interface Subject{
    void sayHi();
    void sayHello();
  }
  
  static class SubjectImpl implements Subject{

    @Override
    public void sayHi() {
      System.out.println("hi");
    }

    @Override
    public void sayHello() {
      System.out.println("hello");
    }
  }
  
  static class ProxyInvocationHandler implements InvocationHandler{
    private Subject target;
    public ProxyInvocationHandler(Subject target) {
      this.target=target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      System.out.print("say:");
      return method.invoke(target, args);
    }
    
  }
  
  public static void main(String[] args) {
    Subject subject=new SubjectImpl();
    Subject subjectProxy=(Subject) Proxy.newProxyInstance(subject.getClass().getClassLoader(), subject.getClass().getInterfaces(), new ProxyInvocationHandler(subject));
    subjectProxy.sayHi();
    subjectProxy.sayHello();
    
  }
}

Just looking at the main method, only line 2 is different from the previous static proxy. The same subjectProxy proxy object is generated, but the generated code is different. Static agent is directly new 1 SubjectImplProxy instance, while the dynamic proxy invokes the java. lang. reflect. Proxy. newProxyInstance () method, we have a look at the source of this method under 1:


 public static Object newProxyInstance(ClassLoader loader,
                     Class<?>[] interfaces,
                     InvocationHandler h)
    throws IllegalArgumentException
  {
    if (h == null) {
      throw new NullPointerException();
    }

    /*
     * Look up or generate the designated proxy class.
     */
    Class<?> cl = getProxyClass(loader, interfaces);  // Gets the proxy class Class

    /*
     * Invoke its constructor with the designated invocation handler.
     */
    try {
      Constructor cons = cl.getConstructor(constructorParams);  //constructorParams It was written dead: { InvocationHandler.class } , the proxy class returned above Class1 It is extends Proxy , and Proxy There are 1 The parameters for InvocationHandler Constructor of 
      return cons.newInstance(new Object[] { h });  // Here we define ourselves through the constructor InvocationHandler When we call any method of the proxy class, we actually call the one we defined InvocationHandler Subclassed overridden invoke() function 
    } catch (NoSuchMethodException e) {
      throw new InternalError(e.toString());
    } catch (IllegalAccessException e) {
      throw new InternalError(e.toString());
    } catch (InstantiationException e) {
      throw new InternalError(e.toString());
    } catch (InvocationTargetException e) {
      throw new InternalError(e.toString());
    }
  }

The above Class < ? > cl = getProxyClass (loader interfaces); Call the getProxyClass method:


public static Class<?> getProxyClass(ClassLoader loader,
                     Class<?>... interfaces)
    throws IllegalArgumentException
  {
    if (interfaces.length > 65535) {  // Because in the class File, 1 The number of interfaces saved by each class is used 2 In terms of bytes, so java In the 1 The maximum number of classes can be implemented 65535 An interface 
      throw new IllegalArgumentException("interface limit exceeded");
    }

    Class<?> proxyClass = null;

    /* collect interface names to use as key for proxy class cache */
    String[] interfaceNames = new String[interfaces.length];

    // for detecting duplicates
    Set<Class<?>> interfaceSet = new HashSet<>();
     // validation interfaces Whether the interface can be loaded by the classloader, whether it is an interface, and whether there are duplicates 
    for (int i = 0; i < interfaces.length; i++) {
      /*
       * Verify that the class loader resolves the name of this
       * interface to the same Class object.
       */
      String interfaceName = interfaces[i].getName();
      Class<?> interfaceClass = null;
      try {
        interfaceClass = Class.forName(interfaceName, false, loader);
      } catch (ClassNotFoundException e) {
      }
      if (interfaceClass != interfaces[i]) {
        throw new IllegalArgumentException(
          interfaces[i] + " 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.contains(interfaceClass)) {
        throw new IllegalArgumentException(
          "repeated interface: " + interfaceClass.getName());
      }
      interfaceSet.add(interfaceClass);

      interfaceNames[i] = interfaceName;
    }

    /*
     * Using string representations of the proxy interfaces as
     * keys in the proxy class cache (instead of their Class
     * objects) is sufficient because we require the proxy
     * interfaces to be resolvable by name through the supplied
     * class loader, and it has the advantage that using a string
     * representation of a class makes for an implicit weak
     * reference to the class.
     */
    List<String> key = Arrays.asList(interfaceNames);  // use interfaces List as key cached cache In, that is to achieve the same interfaces The proxy class will only create the load 1 time 

    /*
     * Find or create the proxy class cache for the class loader.
     */
    Map<List<String>, Object> cache;
    synchronized (loaderToCache) {
      cache = loaderToCache.get(loader);
      if (cache == null) {
        cache = new HashMap<>();
        loaderToCache.put(loader, cache);
      }
      /*
       * This mapping will remain valid for the duration of this
       * method, without further synchronization, because the mapping
       * will only be removed if the class loader becomes unreachable.
       */
    }

    /*
     * Look up the list of interfaces in the proxy class cache using
     * the key. This lookup will result in one of three possible
     * kinds of values:
     *   null, if there is currently no proxy class for the list of
     *     interfaces in the class loader,
     *   the pendingGenerationMarker object, if a proxy class for the
     *     list of interfaces is currently being generated,
     *   or a weak reference to a Class object, if a proxy class for
     *     the list of interfaces has already been generated.
     */
     // See if it's in the cache, if it's in the cache, just take it out and then return Otherwise judge according to pendingGenerationMarker Determines if any other threads are generating the current proxy class, if any cache.wait() Wait and create if none exists. 

    synchronized (cache) {
      /*
       * Note that we need not worry about reaping the cache for
       * entries with cleared weak references because if a proxy class
       * has been garbage collected, its class loader will have been
       * garbage collected as well, so the entire cache will be reaped
       * from the loaderToCache map.
       */
      do {
        Object value = cache.get(key);
        if (value instanceof Reference) {
          proxyClass = (Class<?>) ((Reference) value).get();
        }
        if (proxyClass != null) {
          // proxy class already generated: return it
          return proxyClass;
        } else if (value == pendingGenerationMarker) {
          // proxy class being generated: wait for it
          try {
            cache.wait();
          } catch (InterruptedException e) {
            /*
             * The class generation that we are waiting for should
             * take a small, bounded time, so we can safely ignore
             * thread interrupts here.
             */
          }
          continue;
        } else {
          /*
           * No proxy class for this list of interfaces has been
           * generated or is being generated, so we will go and
           * generate it now. Mark it as pending generation.
           */
          cache.put(key, pendingGenerationMarker);
          break;
        }
      } while (true);
    }
     // Confirm the package to which the agent class to be generated belongs, if interfaces All interfaces are public , the agent class belongs to the package is the default package; If you have interface not public Then all are not public the interface Must be in 1 A bag or an error. 
    try {
      String proxyPkg = null;   // package to define proxy class in

      /*
       * 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 (int i = 0; i < interfaces.length; i++) {
        int flags = interfaces[i].getModifiers();
        if (!Modifier.isPublic(flags)) {
          String name = interfaces[i].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,
        proxyPkg = "";     // use the unnamed package
      }

      {
        /*
         * Choose a name for the proxy class to generate.
         */
        long num;
        synchronized (nextUniqueNumberLock) {
          num = nextUniqueNumber++;
        }
        String proxyName = proxyPkg + proxyClassNamePrefix + num;  // The name of the generated proxy class, proxyPkg Is the package name of the agent class identified above, proxyClassNamePrefix It's a string written dead." $Proxy ", num is 1 A global" 1 the long Type number, from 0 Start the accumulation every time a new proxy class is generated +1 , you can also see that the number of dynamic proxy classes generated cannot be exceeded Long.maxValue
        /*
         * Verify that the class loader hasn't already
         * defined a class with the chosen name.
         */

        /*
         * Generate the specified proxy class.
         */
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
          proxyName, interfaces);  // generate 1 In a proxyName For the class name Interfaces Class bytecode for all interfaces in 
        try {
          proxyClass = defineClass0(loader, proxyName,
            proxyClassFile, 0, proxyClassFile.length);  // Load the generated classes 
        } 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());
        }
      }
      // add to set of all generated proxy classes, for isProxyClass
      proxyClasses.put(proxyClass, null);

    } finally {
      /*
       * We must clean up the "pending generation" state of the proxy
       * class cache entry somehow. If a proxy class was successfully
       * generated, store it in the cache (with a weak reference);
       * otherwise, remove the reserved entry. In all cases, notify
       * all waiters on reserved entries in this cache.
       */
       // Creates successfully, will cache In the key the pendingGenerationMarker Replace the weak reference to the actual proxy class, otherwise clear pendingGenerationMarker Mark; Whether it succeeds or not, it has to be executed cache.notifyAll() , let others create the same proxy class and execute it cache.wait() Thread resume execution. 
      synchronized (cache) {
        if (proxyClass != null) {
          cache.put(key, new WeakReference<Class<?>>(proxyClass));
        } else {
          cache.remove(key);
        }
        cache.notifyAll();
      }
    }
    return proxyClass; // Finally, the proxy class is returned Class
  }

Here, we have parsed the java source code of dynamic proxy, and now the idea is clear:

Proxy.newProxyInstance(ClassLoader loader,Class < ? > The [] interfaces,InvocationHandler h) method simply does the following:

1. Generate the bytecode of a proxy class that implements all interfaces in the parameter interfaces and inherits from Proxy, and then load the proxy class with classLoader in the parameter.

2. Use the constructor Proxy(InvocationHandler h) of the proxy parent class to create an instance of the proxy class, passing in a subclass of our custom InvocationHandler.

3. Return this proxy class instance, because the proxy class we constructed implements all the interfaces in interfaces (that is, subject.getClass ().getInterfaces ()) passed in our program, the returned proxy class can be strongly converted to the Subject type to invoke the methods defined in the interface.

Now that we know that subjectProxy returned from ES106en.newProxyInstance () can be strongly converted to the Subject type to call the method defined in the interface, how to handle the proxy class instance after the method is called, we need to look at the source code of the proxy class. But proxy classes are loaded by programs that dynamically generate bytecode. What about the source code? It doesn't matter, can add System in main method. The getProperties () put (" sun. misc. ProxyGenerator. saveGeneratedFiles ", "true"), will combine the generated proxy class Class files stored on the local disk, and then decompiled can get proxy class source code:


package common;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy
 implements Test.Subject
{
 private static Method m4;
 private static Method m1;
 private static Method m3;
 private static Method m0;
 private static Method m2;
 
 static
 {
   try {
     m4 = Class.forName("Test$Subject").getMethod("sayHello", new Class[0]);
     m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
     m3 = Class.forName("Test$Subject").getMethod("sayHi", new Class[0]);
     m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
     m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
  } catch (Exception e) {
    throw new RuntimeException(e);
  }
 }

 public $Proxy0(InvocationHandler paramInvocationHandler)
 {
  super(paramInvocationHandler);
 }

 public final void sayHello()
 {
  try
  {
   this.h.invoke(this, m4, null);
   return;
  }
  catch (RuntimeException localRuntimeException)
  {
   throw localRuntimeException;
  }
  catch (Throwable localThrowable)
  {
    throw new UndeclaredThrowableException(localThrowable);
  }
 }

 public final boolean equals(Object paramObject)
 {
  try
  {
   return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
  }
  catch (RuntimeException localRuntimeException)
  {
   throw localRuntimeException;
  }
  catch (Throwable localThrowable)
  {
    throw new UndeclaredThrowableException(localThrowable);
  }
 }

 public final void sayHi()
 {
  try
  {
   this.h.invoke(this, m3, null);
   return;
  }
  catch (RuntimeException localRuntimeException)
  {
   throw localRuntimeException;
  }
  catch (Throwable localThrowable)
  {
    throw new UndeclaredThrowableException(localThrowable);
  }
 }

 public final int hashCode()
 {
  try
  {
   return ((Integer)this.h.invoke(this, m0, null)).intValue();
  }
  catch (RuntimeException localRuntimeException)
  {
   throw localRuntimeException;
  }
  catch (Throwable localThrowable)
  {
    throw new UndeclaredThrowableException(localThrowable);
  }
 }

 public final String toString()
 {
  try
  {
   return (String)this.h.invoke(this, m2, null);
  }
  catch (RuntimeException localRuntimeException)
  {
   throw localRuntimeException;
  }
  catch (Throwable localThrowable)
  {
    throw new UndeclaredThrowableException(localThrowable);
  }
 }
}

We can see the proxy class internal implementation is simpler, when call each proxy class each method USES reflection to adjust h invoke method (that is, our custom InvocationHandler subclasses override invoke method), with the parameter passed the proxy class instance, interface method and the parameters of the call list, so that we can be achieved in the rewrite invoke method for all series 1 packing method.

Conclusion:

The main advantage of dynamic proxy in use compared with static proxy is that it can package all methods of an object in one package, and the dynamic proxy class does not need to be changed when methods are added by the proxy class later.

The disadvantage is that the proxy class must implement the interface, because the dynamic proxy class inherits the Proxy class at the time of implementation. java does not support multiple inheritance, so the dynamic proxy class can only define methods according to the interface.

Finally, dynamic proxy is called dynamic proxy because when java implements dynamic proxy, dynamic proxy classes are dynamically generated and loaded at runtime. In contrast, static proxy classes and other common classes 1 are loaded at the classloading stage.

Thank you for reading, I hope to help you, thank you for your support to this site!


Related articles: