Proxy class decryption What does the generated proxy class look like for JDK dynamic proxies? Why is the Invoke method always called when any method of a proxy class is called? Let’s decrypt it further. Because dynamic proxy generates bytecode dynamically at runtime, the corresponding class file cannot be seen at compile time, so it is not intuitive to see the proxy class content. Let’s start with the newProxyInstance method (code analysis based on JDK7), which is used to create proxy class objects, focusing only on important code segments,

Class<? > cl = getProxyClass0(loader, intfs); /* * Invoke its constructor with the designated invocation handler. */ try { final Constructor<? > cons = cl.getConstructor(constructorParams); final InvocationHandler ih = h;if(sm ! = null && ProxyAccessHelper.needsNewInstanceCheck(cl)) { // create proxy instance withdoPrivilege as the proxy class may
                // implement non-public interfaces that requires a special permission
                return AccessController.doPrivileged(new PrivilegedAction<Object>() {
                    public Object run() {
                        returnnewInstance(cons, ih); }}); }else {
                return newInstance(cons, ih);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString());
        }
Copy the code

In the code segment

final Constructor<? > cons = cl.getConstructor(constructorParams);Copy the code

Used to get the constructor for the proxy class, the constructorParams parameter is actually an InvocationHandler, so guess from here that the proxy class has a property of type InvocationHandler as an argument to the constructor. So where is this proxy class created? Note the code snippet above

Class<? > cl = getProxyClass0(loader, intfs);Copy the code

This is where the proxy class is dynamically created, further digging into the getProxyClass0 method,

private static Class<? > getProxyClass0(ClassLoader loader, Class<? >... interfaces) {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

To continue tracing the code, go to proxyClassCache. Get (loader, interfaces), which focuses on the following code

Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
Copy the code

To continue tracing the code, go to subkeyFactory.apply (key, parameter), enter the apply method, which has a lot of important information, such as the package name of the generated proxy class, find the important code,

long num = nextUniqueNumber.getAndIncrement();
String proxyName = proxyPkg + proxyClassNamePrefix + num;
Copy the code

The above code is used to generate the proxy class name, nextUniqueNumber AtomicLong type, is a global variable, so nextUniqueNumber getAndIncrement () will get the new value using the current value plus one. ProxyClassNamePrefix is declared as follows,

private static final String proxyClassNamePrefix = "$Proxy";
Copy the code

Therefore, the generated Proxy class name format is: package name +$Proxy+num, such as jdkProxy.$Proxy12. Now that the name of the proxy class is constructed, it’s time to start creating the proxy class, go ahead and look at the code,

byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces);
Copy the code

So this is where you actually create the proxy class, so I’m going to go into the generateProxyClass method,

public static byte[] generateProxyClass(final String var0, Class[] var1) {
        ProxyGenerator var2 = new ProxyGenerator(var0, var1);
        final byte[] var3 = var2.generateClassFile();
        if(saveGeneratedFiles) {
            AccessController.doPrivileged(new PrivilegedAction() {
                public Void run() {
                    try {
                        FileOutputStream var1 = new FileOutputStream(ProxyGenerator.dotToSlash(var0) + ".class");
                        var1.write(var3);
                        var1.close();
                        return null;
                    } catch (IOException var2) {
                        throw new InternalError("I/O exception saving generated file: "+ var2); }}}); }return var3;
    }
Copy the code

GenerateProxyClass methods can be reused. GenerateProxyClass methods can be called outside the generateProxyClass method. Prints the generated bytecode file to the specified location. The createProxyClassFile method outputs the bytecode file of the proxy class to the /Users directory. Go directly to the bytecode file and use the decompile tool to view it. The resulting code is as follows:

public final class LoginServiceProxy extends Proxy
  implements LoginService
{
  private static Method m1;
  private static Method m3;
  private static Method m0;
  private static Method m2;
  public LoginServiceProxy(InvocationHandler paramInvocationHandler)
    throws 
  {
    super(paramInvocationHandler);
  }
  public final boolean equals(Object paramObject)
    throws 
  {
    try
    {
      return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  public final void login()
    throws 
  {
    try
    {
      this.h.invoke(this, m3, null);
      return;
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  public final int hashCode()
    throws 
  {
    try
    {
      return ((Integer)this.h.invoke(this, m0, null)).intValue();
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  public final String toString()
    throws 
  {
    try
    {
      return (String)this.h.invoke(this, m2, null);
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  static
  {
    try
    {
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object")}); m3 = Class.forName("jdkproxy.LoginService").getMethod("login", 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]);
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    {
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }
    catch (ClassNotFoundException localClassNotFoundException)
    {
      throw new NoClassDefFoundError(localClassNotFoundException.getMessage()); }}}Copy the code

As you can see from the code above, when the proxy class calls the target method, the Invoke method of the implementation class is called by the InvocationHandler interface, which clearly explains why the invoke method must be called when the target method is called.

Conclusion: 1. The static proxy method is simple, but there will be repeated code; 2. Dynamic proxy is relatively complex to use, but you can create proxy classes dynamically, avoiding manual writing repetitive code; 3. Because of Java’s single-inheritance nature, JDK dynamic proxies can only create proxy classes for interfaces, not implementation classes.