JDK dynamic proxy is widely used, Spring AOP, MyBatis mapper are used in the JDK dynamic proxy.

Use of JDK dynamic proxies

1. Create the proxy class interface and proxy class. Create a class that implements the InvocationHandler interface and implement the invoke method of that interface. 3. Create a Proxy object through the Proxy newProxyInstance(ClassLoaderloader, Class[] interfaces, InvocationHandler H) method. Example: Create a Hello interface

public interface Hello {

    void sayHello(a);
}
Copy the code

Create an implementation class that implements the Hello interface.

public class HelloImpl implements Hello {

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

Create a class that implements the InvocationHandler interface.

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

public class HelloInvocationHandler implements InvocationHandler {

    private Object hello;

    public HelloInvocationHandler(Object hello) {
        this.hello = hello;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("start");
        Object invoke = method.invoke(hello, args);
        System.out.println("end");
        returninvoke; }}Copy the code

Create a Proxy object using Proxy

Hello hello = new HelloImpl();
HelloInvocationHandler helloInvocationHandler = newHelloInvocationHandler(hello); ClassLoader classLoader = hello.getClass().getClassLoader(); Class<? >[] interfaces = hello.getClass().getInterfaces(); Hello helloProxy = (Hello)Proxy.newProxyInstance(classLoader, interfaces, helloInvocationHandler); helloProxy.sayHello();Copy the code

Take a look at the output

start
hello
end
Copy the code

The source code

Take a look at the Proxy’s newProxyInstance method

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

	/ / to omit

        /* * Generate the proxy class */Class<? > cl = getProxyClass0(loader, intfs);/* * Invoke its constructor with the designated invocation handler. */
        try {
	    / / to omit
            
	    // Get the proxy class constructor
            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; }}); }// Through the constructor, set InvocationHandler to the proxy object and create 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

The main function of this code is to generate the proxy class. The constructor of the proxy class creates a proxy object. How to generate the proxy class

 private staticClass<? > getProxyClass0(ClassLoader loader, Class<? >... interfaces) {if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }

        return proxyClassCache.get(loader, interfaces);
    }

Copy the code

GetProxyClass0 determines the number of interfaces, and then gets the proxy class from proxyClassCache. Take a look at the definition of proxyClassCache

private static finalWeakCache<ClassLoader, Class<? >[], Class<? >> proxyClassCache =new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
Copy the code

By definition, proxyClassCache is actually a cache class, and the generated class is placed in the cache. Let’s look at a constructor for this class

 public WeakCache(BiFunction
       
         subKeyFactory, BiFunction
        
          valueFactory)
        ,>
       ,> {
        this.subKeyFactory = Objects.requireNonNull(subKeyFactory);
        this.valueFactory = Objects.requireNonNull(valueFactory);
    }
Copy the code

You can see that subKeyFactory is actually KeyFactory and valueFactory is actually ProxyClassFactory

Okay, now back to the getProxyClass0 method, which analyzed the proxyClassCache, let’s move on to its get method

public V get(K key, P parameter) { // key is ClassLoader interface
    / / to omit

    // Obtain cacheKey according to ClassLoader
    Object cacheKey = CacheKey.valueOf(key, refQueue);

    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; }}// Generate subkeys based on keys and interfaces
    Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
    Supplier<V> supplier = valuesMap.get(subKey);
    Factory factory = null;

    while (true) {
        if(supplier ! =null) {
            V value = supplier.get();
            if(value ! =null) {
                returnvalue; }}if (factory == null) {
            factory = new Factory(key, parameter, subKey, valuesMap);
        }

        if (supplier == null) {
            supplier = valuesMap.putIfAbsent(subKey, factory);
            if (supplier == null) { supplier = factory; }}else {
            if (valuesMap.replace(subKey, supplier, factory)) {
                supplier = factory;
            } else{ supplier = valuesMap.get(subKey); }}}}Copy the code

Retrun value returns a proxy class. Retrun value returns a proxy class. Retrun value returns a proxy class. Supplier is Factory; Factory is generated by key, paramter, subkey, valuesMap. Key and paramter are method inputs classLoader and interfaces. Subkeys are derived from subKeyFactory and paramter keys, i.e. from classLoaders and interfaces; A vlauesMap is obtained by a map based on a cacheKey, which is obtained by a key based on a classloader. Finally, this method is used to obtain the supplier from the map, but the map structure is complicated. It is a two-tier map structure: The first level key is a cacheKey object generated based on the ClassLoader, and the second level key is a Subkey object generated by the classLoader and interfaces.

Next, go to supplene.get () to see how it generates the proxy object. Supplier is a Factory, so look at the Factory get method

 @Override
 public synchronized V get(a) { // serialize access
      Supplier<V> supplier = valuesMap.get(subKey);
      if(supplier ! =this) {
          return null;
      }

      V value = null;
      try {
          // Get the proxy class
          value = Objects.requireNonNull(valueFactory.apply(key, parameter));
      } finally {
          if (value == null) { // remove us on failure
              valuesMap.remove(subKey, this); }}assertvalue ! =null;

      CacheValue<V> cacheValue = new CacheValue<>(value);

      reverseMap.put(cacheValue, Boolean.TRUE);

      if(! valuesMap.replace(subKey,this, cacheValue)) {
          throw new AssertionError("Should not reach here");
      }

      returnvalue; }}Copy the code

Valuefactory. apply(key,paramter) is a ProxyClassFactory

@Override
publicClass<? > apply(ClassLoader loader, Class<? >[] interfaces) {// Interface validation is omitted


    String proxyPkg = null;     // package to define proxy class in
    int accessFlags = Modifier.PUBLIC | Modifier.FINAL;

    // Verify the interface is omitted

    if (proxyPkg == null) {
        // if no non-public proxy interfaces, use com.sun.proxy package
        proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
    }

    // Assemble the generated class name
    long num = nextUniqueNumber.getAndIncrement();
    String proxyName = proxyPkg + proxyClassNamePrefix + num;
    
   // Get the class's byte array
    byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
            proxyName, interfaces, accessFlags);
    try {
        return defineClass0(loader, proxyName,
                            proxyClassFile, 0, proxyClassFile.length);
    } catch (ClassFormatError e) {
            throw newIllegalArgumentException(e.toString()); }}}Copy the code

Finally, the proxy class is generated by ProxyGenerator, the specific generation details will not be analyzed, here involves a lot of Java class bytecode knowledge, in ProxyGenerator also reflected a lot of bytecode things, interested can study, Google.

Now that you have the byte array, you can save it as a class and decompile to see the generated proxy class code.

 // The proxy class name
 String className = "HelloProxy";
 int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
 // Get the proxy class byte array
 byte[] data = ProxyGenerator.generateProxyClass(className, new Class[]{Hello.class},accessFlags);
 FileOutputStream out;
 out = null;
 try {
     // Save the proxy class to a file
     out = new FileOutputStream(className + ".class");
     System.out.println((new File("HelloProxy")).getAbsolutePath());
     out.write(data);
 } catch (FileNotFoundException e) {
     e.printStackTrace();
 } catch (IOException e) {
     e.printStackTrace();
 } finally {
     if (null! = out) {try {
             out.close();
         } catch(IOException e) { e.printStackTrace(); }}}Copy the code

Decompile-generated proxy classes:

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

public final class HelloProxy extends Proxy implements Hello {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;

    public Hello(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw newUndeclaredThrowableException(var4); }}public final void sayHello(a) throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw newUndeclaredThrowableException(var3); }}public final String toString(a) throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw newUndeclaredThrowableException(var3); }}public final int hashCode(a) throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw newUndeclaredThrowableException(var3); }}static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("Bolg.Hello").getMethod("sayHello");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw newNoClassDefFoundError(var3.getMessage()); }}}Copy the code

The proxy class gets all the propped object’s methods reflectively. When calling a method, it actually calls the Invoke method of the InvocationHandler passed in from the constructor and passes in the propped object’s methods.