preface

In Java, dynamic proxies fall into two categories:

  • Interface based JDK dynamic proxy
  • CGlib dynamic proxy based on class

Today I’m going to talk about the former, interface-based dynamic proxy. Dynamic proxy in the framework of the application is very extensive, understand the dynamic proxy, spring, Mybatis and other commonly used framework source code reading is also very helpful. The origin of this article is also due to pneumonia (you must not run around, go out also remember to wear masks, this is really important!!) Sqlsession.getmapper (Class Class) method, because the source code used JDK dynamic proxy, so decided to dig the IMPLEMENTATION of JDK dynamic proxy.

What you can learn from this article:

  • Why can JDK dynamic proxies only be based on interfaces?
  • How are proxy classes generated and created?
  • How do I decompile a proxy class?
  • Why are proxy objects automatically executedinvoke()Methods?
  • Proxy objectinvoke(Object proxy, Method method, Object[] args)How does the method parameter in the.

use

All subsequent parsing will be based on this example

Because JDK dynamic proxies are interface-based, we need to create an interface first

public interface Hello {
    void sayHello(a);
}
Copy the code

Then we give this interface an implementation class, this implementation class is not necessary, Mybatis is directly using the interface, and there is no implementation class, here is to make it easier to understand.

public class Amy implements Hello{

    private String name;

    public Amy(String name) {
        this.name = name;
    }

    public void sayHello(a) {
        System.out.println(name + " say hello"); }}Copy the code

We then create a proxy class (which is not the same as the JDK generated proxy class in the source code analysis below), and the JDK dynamic proxy specifies that the proxy class must implement the InvocationHandler interface

public class ProxyHello implements InvocationHandler {

    private Hello hello;

    public ProxyHello(Hello hello) {
        this.hello = hello;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("before say hello");
        method.invoke(hello, args);
        System.out.println("after say hello");
        return null; }}Copy the code

Finally, we write a test class, create a proxy object, execute the sayHello() method, and see the output

public class Test {
    public static void main(String[] args) {

        Amy amy = new Amy("Amy");

        ProxyHello proxyHello = new ProxyHello(amy);

        Hello helloProxy = (Hello) Proxy.newProxyInstance(
                Thread.currentThread().getContextClassLoader(),
                newClass[]{Hello.class}, proxyHello); helloProxy.sayHello(); }}Copy the code

The final output

As you can see from the output, the invoke() method inside the proxy object is actually executed.

Source code analysis

NewProxyInstance (ClassLoader loader, Class
[] interfaces, InvocationHandler h) accept three parameters

  • Class loader, we use the current thread’s class loader directly, usuallyAppClassLoader
  • The interface to be proxied, in this exampleHello.class, which is the interface we created
  • Proxy objects, in this case objects of the ProxyHello class

newProxyInstance()

There is no comment written statement does not affect the understanding of the source code, are some of the operation permission check, so can be temporarily ignored.

public static Object newProxyInstance(ClassLoader loader, Class
       [] interfaces, InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);

        finalClass<? >[] intfs = interfaces.clone();final SecurityManager sm = System.getSecurityManager();
        if(sm ! =null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        /* * create a proxy class for the method, note that this is only the creation of a class, * is the code to create the.class file, is not a proxy object * is mainly used to analysis this method!! * /Class<? > cl = getProxyClass0(loader, intfs);try {
            if(sm ! =null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }
            // Get the constructor of the created proxy class, after which we will decompile to see the implementation of the proxy class
            finalConstructor<? > cons = cl.getConstructor(constructorParams);final InvocationHandler ih = h;
            if(! Modifier.isPublic(cl.getModifiers())) { AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run(a) {
                        cons.setAccessible(true);
                        return null; }}); }// Create an instance object of the proxy class through the constructor of the proxy class and return the proxy object
            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 newInternalError(t.toString(), t); }}catch (NoSuchMethodException e) {
            throw newInternalError(e.toString(), e); }}Copy the code

getProxyClass0()

private staticClass<? > getProxyClass0(ClassLoader loader, Class<? >... interfaces) {// The number of interfaces cannot be larger than 65535
    if (interfaces.length > 65535) {
        throw new IllegalArgumentException("interface limit exceeded");
    }
    // Get the proxy class from the buffer. If it doesn't exist in the cache, create it using ProxyClassFactory
    // The cache is cached by weak references, which are good for caching
    return proxyClassCache.get(loader, interfaces);
}
Copy the code

proxyClassCache.get()

public V get(K key, P parameter) {
    Objects.requireNonNull(parameter);

    expungeStaleEntries();
    // Create a cache key referenced by classLoader
    Object cacheKey = CacheKey.valueOf(key, refQueue);

    // There is a level 2 cache in the map
    // Level 1 cache: the cache key created above is used to obtain the corresponding cache loaded through the class loader
    // All proxy classes, i.e. the Map of the second level cache
    // Level 2 cache: the virtual reference created by the interface is the key, and the vendor of the proxy class corresponding to the interface is the value
    // This statement is to fetch the second level cache
    ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
    if (valuesMap == null) {
        ConcurrentMap<Object, Supplier<V>> oldValuesMap
            = map.putIfAbsent(cacheKey,
                              valuesMap = new ConcurrentHashMap<>());
        if(oldValuesMap ! =null) { valuesMap = oldValuesMap; }}// Create a key for level 2 cache based on the interface
    Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
    // Get the vendor that created the proxy class from the secondary cache
    Supplier<V> supplier = valuesMap.get(subKey);
    
    // Factory is a Factory class that implements the Supplier interface
    Factory factory = null;

    while (true) {
        if(supplier ! =null) {
            // If the vendor exists, get the proxy class directly from the vendor
            V value = supplier.get();
            if(value ! =null) {
                // Get the proxy class and return it directly
                returnvalue; }}// Create a new vendor
        if (factory == null) {
            factory = new Factory(key, parameter, subKey, valuesMap);
        }
        
        // If the vendor does not exist, store the vendor created above in the cache,
        // Then start the loop again
        if (supplier == null) {
            supplier = valuesMap.putIfAbsent(subKey, factory);
            if (supplier == null) { supplier = factory; }}else {
            // If a vendor exists, but no proxy class is obtained
            // The newly created vendor replaces the old vendor
            if (valuesMap.replace(subKey, supplier, factory)) {
                // The replacement succeeds, and the loop is repeated
                supplier = factory;
            } else {
                // Replace failed, use the old vendor to continue trying to get the proxy classsupplier = valuesMap.get(subKey); }}}}Copy the code

The way we actually create and get the proxy class is supplent.get ().

Typically, the first loop does not fetch the proxy class, the first loop creates a vendor and stores it in the cache, and the second loop retrieves the vendor from the cache and then retrieves the proxy class from the vendor.

supplier.get()

Since the supplier variable is an object that references the Factory class, what we really want to look at is the Factory class’s get() method

The Factory class is WeakCache’s private inner class

private final class Factory implements Supplier<V> {
    private final K key;
    private final P parameter;
    private final Object subKey;
    private final ConcurrentMap<Object, Supplier<V>> valuesMap;

    Factory(K key, P parameter, Object subKey,
            ConcurrentMap<Object, Supplier<V>> valuesMap) {
        this.key = key;
        this.parameter = parameter;
        this.subKey = subKey;
        this.valuesMap = valuesMap;
    }

    @Override
    public synchronized V get(a) { // serialize access
        // Re-check whether the corresponding proxy class vendor exists
        Supplier<V> supplier = valuesMap.get(subKey);
        if(supplier ! =this) {
            return null;
        }
        // else still us (supplier == this)

        // create new value
        V value = null;
        try {
            ValueFactory is the factory that creates the proxy class
            // Create a real proxy class from the factory
            // It is an instance object of the ProxyClassFactory class
            value = Objects.requireNonNull(valueFactory.apply(key, parameter));
        } finally {
            if (value == null) {
                valuesMap.remove(subKey, this); }}assertvalue ! =null;

        // Create a virtual reference for the proxy class
        CacheValue<V> cacheValue = new CacheValue<>(value);

        reverseMap.put(cacheValue, Boolean.TRUE);

        // Try to replace the old vendor with the cache vendor for the newly created proxy class
        // This method creates an object of the Factory class
        // This method execution must succeed
        if(! valuesMap.replace(subKey,this, cacheValue)) {
            throw new AssertionError("Should not reach here");
        }
        // Return the proxy class
        returnvalue; }}Copy the code

valueFactory.apply()

Method to create a proxy class

private static final class ProxyClassFactory implements BiFunction<ClassLoader, Class<? >[], Class<? >> { // prefix for all proxy class names 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; try { 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 is an interface if (! interfaceClass.isInterface()) { throw new IllegalArgumentException( interfaceClass.getName() + " is not an interface"); } /* * Verify that the interface is not duplicated */ if (interfaceset. put(interfaceClass, boil.true)! = null) { throw new IllegalArgumentException( "repeated interface: " + interfaceClass.getName()); String proxyPkg = null; 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 proxy class, the name of the actual is a increasing number of * / long num = nextUniqueNumber. GetAndIncrement (); Proxy.sun.proxy.$Proxy0, com.sun.proxy.$Proxy1 String proxyName = proxyPkg + proxyClassNamePrefix + num; /* * Generate a class file for the proxy class, which is the class we normally create * this method is not open source. * If we go into this method, we can see a property called saveGeneratedFiles * this property is used to determine whether to export the created class file locally. * We can set this property at run time to export the generated class file. And then we compile the proxy class file * / byte [] proxyClassFile = ProxyGenerator. GenerateProxyClass (proxyName, interfaces, accessFlags); // Create a Class object from a Class file, which is a native method (C/C++ implementation), 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

Decompile the proxy class file

Output the proxy class file

To output the proxy class file, we have two methods:

1, before we run the Test class, first to a configuration, add the red box on the part of the property – Dsun. Misc. ProxyGenerator. SaveGeneratedFiles = true

2. Before running the code, set the global properties of the system and add the code in the red box

After executing the code, you will see that a com.sun.proxy package has been created in the parent directory of the main() class with a class file like $proxy0.class. This is the generated proxy class.

Decompile the proxy class

I didn’t find a suitable plug-in on IDEA. I didn’t know why it couldn’t be used when I saw several plug-ins on the Internet, and the only one that worked didn’t have a good effect. So I found another JD-GUI on the Internet, which didn’t need installation and was very easy to use.

Attach download link (ladder required) :JD-GUI

Jd-gui was decompilating a class file with a void method when it was decompilating a cglib proxy class. I now use the default decompiler for IDEA (open the class file that needs to be decompiled directly in IDEA). Although the variable names are ugly, they are at least correct.

This site can also decomcompile class files, the effect is similar to IDEA, so far I have not found any problems in the process of using: javare.cn/

We directly use software to open our generated proxy class (this code is still a JD_GUI decompression, because there is no error, so there is no replacement, does not impact the reading), as follows:

package com.sun.proxy;

import com.lhd.proxy.Hello;
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 Hello {
  private static Method m1;
  
  private static Method m3;
  
  private static Method m2;
  
  private static Method m0;
  
  public $Proxy0(InvocationHandler paramInvocationHandler) {
    super(paramInvocationHandler);
  }
  
  public final boolean equals(Object paramObject) {
    try {
      return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
    } catch (Error|RuntimeException error) {
      throw null;
    } catch (Throwable throwable) {
      throw newUndeclaredThrowableException(throwable); }}public final void sayHello(a) {
    try {
      this.h.invoke(this, m3, null);
      return;
    } catch (Error|RuntimeException error) {
      throw null;
    } catch (Throwable throwable) {
      throw newUndeclaredThrowableException(throwable); }}public final String toString(a) {
    try {
      return (String)this.h.invoke(this, m2, null);
    } catch (Error|RuntimeException error) {
      throw null;
    } catch (Throwable throwable) {
      throw newUndeclaredThrowableException(throwable); }}public final int hashCode(a) {
    try {
      return ((Integer)this.h.invoke(this, m0, null)).intValue();
    } catch (Error|RuntimeException error) {
      throw null;
    } catch (Throwable throwable) {
      throw newUndeclaredThrowableException(throwable); }}static {
    try {
      m1 = Class.forName("java.lang.Object").getMethod("equals".new Class[] { Class.forName("java.lang.Object")}); m3 = Class.forName("com.lhd.proxy.Hello").getMethod("sayHello".new Class[0]);
      m2 = Class.forName("java.lang.Object").getMethod("toString".new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode".new Class[0]);
      return;
    } catch (NoSuchMethodException noSuchMethodException) {
      throw new NoSuchMethodError(noSuchMethodException.getMessage());
    } catch (ClassNotFoundException classNotFoundException) {
      throw newNoClassDefFoundError(classNotFoundException.getMessage()); }}}Copy the code

Using this Proxy class we can see why the JDK dynamic Proxy can only be based on the Proxy class, because the Proxy class inherits from Proxy class, and Java does not support multiple inheritance, so the Proxy can only be implemented by implementing the interface.

We can see that the proxy class implements the interface Hello we passed in, so the sayHello() method in the proxy class is very simple to implement. In fact, all the methods in the proxy class are very simple to implement, which is this.h.invoke().

Let’s examine this statement:

  • This: refers to the proxy object
  • H: h is inherited from Proxy class, is the InvocationHandler type, that is, we callnewProxyInstance()The ProxyHello passed in, one of the proxy’s constructors also takes arguments of this type, which we use innewProxyInstanceMethod gets the constructor of the proxy class, which takes the type argument, and then creates the proxy class object through that constructor
  • Invoke: is ProxyHelloinvoke()methods

When a proxy class object executes the sayHello() method, it actually executes the this.h.invoke(this, m3, null) method, which is the invoke() method of the object executing ProxyHello and whose method argument is fetched at the bottom:

m3 = Class.forName("com.lhd.proxy.Hello").getMethod("sayHello", new Class[0]);

When we call method.invoke(Hello, args), we invoke the m3 method and specify an object and argument to execute the method we need to execute.

The end of the

So far, all five of the above questions have been answered. However, the details of how to write the class file of the proxy class are not explored at present, because there is no open source, relatively obscure, and the amount of code, so I will not go into the details. If you have any questions, feel free to discuss them in the comments section. If there are any mistakes, please forgive and correct them.