This article is participating in “Java Theme Month – Java Development Practice”, for details: juejin.cn/post/696826…

This is the fifth day of my participation in Gwen Challenge

How to implement JDK dynamic proxy?

JDK dynamic proxy classes are invisible, although you can see the effect, but the bottom line is how to do, why the implementation of the interface?

From the Proxy newProxyInstance of

    public static Object newProxyInstance(ClassLoader loader, Class
       [] interfaces, InvocationHandler h)
        throws IllegalArgumentException{
        // NullPointerException is thrown if the h object is null
        Objects.requireNonNull(h);
        finalClass<? >[] intfs = interfaces.clone();final SecurityManager sm = System.getSecurityManager();
        if(sm ! =null) {// Check the package access and class loader permissions
           checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }
        /* * Look up or generate the designated proxy class. */Class<? > cl = getProxyClass0(loader, intfs);// Omit some code
    }
Copy the code

First, try to get the proxy class, which may be cached, and if not, do the build logic.

java.lang.reflect.Proxy#getProxyClass0

    private staticClass<? > getProxyClass0(ClassLoader loader, Class<? >... interfaces) {// If the number exceeds 65535, throw an exception
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }
        // If the proxy class has already implemented the given interface through the classloader, then a copy of it is returned from the cache
        // Otherwise, it creates the proxy class through 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

java.lang.reflect.Proxy.ProxyClassFactory#apply

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 Defines the 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();
        publicClass<? > 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;
                try{// Get the interface class by reflection
                    interfaceClass = Class.forName(intf.getName(), false, loader);
                } catch(ClassNotFoundException e) { }// The resulting interface class is not the same as the one passed in
                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. */
            longnum = nextUniqueNumber.getAndIncrement();// The name of the production agent class
            String proxyName = proxyPkg + proxyClassNamePrefix + num;
            // Some validation, caching, synchronization operations are not the focus of our research
            /* * 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 newIllegalArgumentException(e.toString()); }}}Copy the code

ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags); ` `

This code is the key to generating the dynamic proxy class, which returns the bytecode array describing the proxy class. The program then reads the bytecode array and converts it into a run-time data structure-class object, which is used as a regular Class.

    public static byte[] generateProxyClass(finalString var0, Class<? >[] var1,int var2) {
        ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
        final byte[] var4 = var3.generateClassFile();
        // Disk write if persistent proxy class is declared.
        if (saveGeneratedFiles) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run(a) {
                    try {
                        int var1 = var0.lastIndexOf(46);
                        Path var2;
                        if (var1 > 0) {
                            Path var3 = Paths.get(var0.substring(0, var1).replace('. ', File.separatorChar));
                            Files.createDirectories(var3);
                            var2 = var3.resolve(var0.substring(var1 + 1, var0.length()) + ".class");
                        } else {
                            var2 = Paths.get(var0 + ".class");
                        }
                        Files.write(var2, var4, new OpenOption[0]);
                        return null;
                    } catch (IOException var4x) {
                        throw new InternalError("I/O exception saving generated file: "+ var4x); }}}); }return var4;
    }
Copy the code

Here we find a key criterion -saveGeneratedFiles, which is whether the proxy class needs to be persisted.

ProxyGenerator.generateProxyClass

public class ProxyGeneratorUtils {
    /** * Write the bytecode of the proxy class to disk *@paramPath Save path */
    public static void writeProxyClassToHardDisk(String path) {
// Get the bytecode of the proxy class
        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

$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 was originally at the end, so I brought it to the front to describe it
   static
  {
    try
    {
      // See what is in the static block here and see if you can find the giveMoney method. Remember that giveMoney got the name M3 from reflection, and forget all else
      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 newNoClassDefFoundError(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, the InvocationHandler object holds an object being proestedand is connected to the Invoke method in the InvocationHandler. Well, that's it. * /
  public final void giveMoney(a)
    throws 
  {
    try
    {
      this.h.invoke(this, m3, null);
      return;
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw newUndeclaredThrowableException(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.