Dynamic proxy parsing

Posted by BoxingKing on Thu, 01 Aug 2019 05:52:27 +0200

I. What is the Agency Model

Definition of proxy pattern: The proxy pattern provides a proxy object for an object, and the proxy object controls the reference of the original object. Generally speaking, the agency model is a common intermediary in our life.

Classification of Agent Patterns: Agent Patterns are divided into Static Agents and Dynamic Agents

II. Static Agent

Take simple transaction processing as an example

interface
public interface UserDao{
     void save();
}
//Implementation class
public class UserDaoImpl implements UserDao{
      public void save(){//Preservation}
}
//Transaction Agent Class
public class TransactionHandler implements UserDao{
     private UserDaoImpl userDao;//Target proxy object
     public TransactionHandler(UserDao userDao){
         this.useDao = userDao;
     }
    
     public void save(){
          //Open a transaction
          userDao.save();
         //Closing the transaction
     }
}

Static proxy is very simple, but it has one disadvantage. If you want to proxy many classes and methods, you can only write more than one proxy class one by one, so you can not achieve the purpose of code reuse.

III. Dynamic Agent

Take the implementation of JDK dynamic proxy as an example

//Transaction Agent Class
public class TransactionHandler implements InvocationHandler{
     private Object target;//Target proxy object
     public TransactionHandler(Object target){
         this.target= target;
     }
     
     @Override
     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
         //Open a transaction
         Object result = method.invoke(target,args);
         //Closing the transaction
         return result
     }
}
//Call method
public class Main {
    public static void main(String[] args) {
        Object target = new UserDaoImpl();
        TransactionHandler handler = new TransactionHandler(target);
        UserDao userDao = (UserDao)Proxy.newProxyInstance(
           target.getClass().getClassLoader(),
           target.getClass().getInterfaces(),
           handler);
        userDao.save();
    }

Here are two questions. We'll slowly unveil them later.

1. How to generate proxy classes? 2. How to execute the proxy method?

IV. Principle of JDK Dynamic Agent

Let's first look at the Proxy.newProxyInstance method source code

public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,
                                                               InvocationHandler h) throws IllegalArgumentException {
.....
Class<?> cl = getProxyClass0(loader, intfs);//Get or generate proxy classes
....
final Constructor<?> cons = cl.getConstructor(constructorParams);//Obtain that the parameter type is the InvocationHandler.class constructor
....
return cons.newInstance(new Object[]{h});//Generate proxy instances
....
}

This method mainly does three things.

1. Getting or generating proxy classes

2. Obtain that the parameter type is InvocationHandler.class constructor

3. Generating proxy instances

Now let's see how it generates proxy classes. Look at the getProxyClass0 source code

private static Class<?> getProxyClass0(ClassLoader loader,Class<?>... interfaces) {
....
   return proxyClassCache.get(loader, interfaces);//proxyClassCache->WeakCache
}
public V get(K key, P parameter) {
....
    Object cacheKey = CacheKey.valueOf(key, refQueue);//Wrap ClassLoader as a CacheKey as a first-level cached key
    ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);//Get the classload cache
    if (valuesMap == null) {
         ConcurrentMap<Object, Supplier<V>> oldValuesMap = map.putIfAbsent(cacheKey, 
                 valuesMap = new ConcurrentHashMap<>());//Place it in CAS mode, put it if it does not exist, or return the old value
         //Question: Why use secondary caching? Why map.putIfAbsent?
    }
    Supplier<V> supplier = valuesMap.get(subKey);
    while (true) {
        if (supplier != null) {
            V value = supplier.get();//Get the proxy class
            if (value != null) {
                 return value;//Get it straight back
            }
        }
        //If null, generate a proxy factory
        if (factory == null) {
            factory = new Factory(key, parameter, subKey, valuesMap);
        }
        if (supplier == null) {
            supplier = valuesMap.putIfAbsent(subKey, factory);
            if (supplier == null) {
                supplier = factory;//If there is no old value, assign the new value to the reference object and exit iteratively
            }//If the replacement fails, it indicates that the value is already there, and it is recycled.
        }else{//If other threads change the value, they replace the original value.
             if (valuesMap.replace(subKey, supplier, factory)) {
                   supplier = factory;
             } else {
                   supplier = valuesMap.get(subKey);//Replacement failed, continue to use the original value
             }
        } 
    }
}

This method mainly depends on whether there is a proxy factory class in the cache. If there is a direct call to get() to return, the cache uses WeakCache, which will be reclaimed when the new generation reclaims and will not occupy memory.

If not in the cache, a proxy factory is generated through the new Factory. Here are some threading security aspects.

Here's Supperlier.get(), and now let's see what's done in this method.

private final class Factory implements Supplier<V> {
....
    public synchronized V get() {
        Supplier<V> supplier = valuesMap.get(subKey);
        if (supplier != this) {//Verify here that supplier is the Factory instance itself, because it may have been modified by other threads.
            return null;
        }
        V value = null;
        try{
            value = Objects.requireNonNull(valueFactory.apply(key, parameter));
            //Give ProxyClassFactory to generate proxy classes
        }finally{
            if (value == null) {//If the generation of the proxy class fails, delete the secondary cache
                 valuesMap.remove(subKey, this);
            }
        }
       ....
       return value;
    }
}

This method is relatively simple, mainly to the valueFactory.apply generated return, valueFactory is the ProxyClassFactory class.

Let's see what's going on in this method.

public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
.... 
    int accessFlags = Modifier.PUBLIC | Modifier.FINAL;//Generating proxy classes defaults to public final
    String proxyName = proxyPkg + proxyClassNamePrefix + num;//Package name + prefix + serial number
    //Generating bytecode with ProxyGenerator
    byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName,interfaces, accessFlags);
    try{
        //Generating proxy classes from bytecodes
        return defineClass0(loader, proxyName, proxyClassFile,0, proxyClassFile.length);
    } catch (ClassFormatError e) {....}
}

Mainly some specification definitions, and then generate bytecode according to ProxyGenerator.generateProxyClass

What has been done in this method?

public static byte[] generateProxyClass(String var0, Class<?>[] var1) {
   return generateProxyClass(var0, var1, 49);
}

public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) {
   ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
   final byte[] var4 = var3.generateClassFile();
   ....
   return var4;
}

private byte[] generateClassFile() {
    //First, toString, hashCode, equals and other proxy methods are generated for the proxy class.
    addProxyMethod(hashCodeMethod, Object.class);
    addProxyMethod(equalsMethod, Object.class);
    addProxyMethod(toStringMethod, Object.class);
    //Traversing through each method of each interface and generating ProxyMethod objects for it
    for (int i = 0; i < interfaces.length; i++) {
        Method[] methods = interfaces[i].getMethods();
        for (int j = 0; j < methods.length; j++) {
             addProxyMethod(methods[j], interfaces[i]);
        }
     }
     ....//Write class attributes into the bout stream
     return bout.toByteArray();
}

It can be seen that the method attributes of the proxy class are written to the byte stream in several steps.

The generated proxy class is as follows

public class Proxy0 extends Proxy implements UserDao {
    //The first step is to generate the constructor
    protected Proxy0(InvocationHandler h) {
        super(h);
    }
    //The second step is to generate static fields
    private static Method m1;   //hashCode method
    private static Method m2;   //equals method
    private static Method m3;   //toString method
    private static Method m4;   //...
    
    //The third step is to generate the proxy method
    @Override
    public int hashCode() {
        try {
            return (int) h.invoke(this, m1, null);
        } catch (Throwable e) {
            throw new UndeclaredThrowableException(e);
        }
    }
    
    @Override
    public boolean equals(Object obj) {
    }
    
    @Override
    public String toString() {
    }

    @Override
    public void save(User user) {
        try {
            //Construct an array of parameters, if you have more than one parameter to add later
            Object[] args = new Object[] {user};
            h.invoke(this, m4, args);//Enhancement of Agent Classes
        } catch (Throwable e) {
            throw new UndeclaredThrowableException(e);
        }
    }
    
    //The fourth step is to generate a static initialization method
    static {
        try {
            Class c1 = Class.forName(Object.class.getName());
            Class c2 = Class.forName(UserDao.class.getName());    
            m1 = c1.getMethod("hashCode", null);
            m2 = c1.getMethod("equals", new Class[]{Object.class});
            m3 = c1.getMethod("toString", null);
            m4 = c2.getMethod("save", new Class[]{User.class});
            //...
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

V. CGLIB Dynamic Agent

First look at how pure CGLIB is used

//Interception class
public class Interceptor implements MethodInterceptor {
    public Object intercept(Object obj, Method method, Object[] args,    MethodProxy proxy) throws Throwable {
        //invokeSuper calls the original method, invoke takes effect on the instance of the original class, and invokes the proxy method.
        //pre-processing
        Object retVal =  proxy.invokeSuper(obj, args);
        //Postprocessing
        return retVal;
    }
}

public static void main(String[] args) {
    //Instantiate an enhancer, a class generator in cglib
    Enhancer eh = new Enhancer();
    //Setting target classes
    eh.setSuperclass(Target.class);
    // Setting Interceptor Objects
    eh.setCallback(new Interceptor());
    // Generate the proxy class and return an instance
    Target t = (Target) eh.create();
    t.method();
}

How to generate the source code of the proxy class is not expanded. The general idea is the same as JDK. Finally, the way to generate the proxy class is different.

Look at the generated proxy class first

public class Target$$EnhancerByCGLIB$$788444a0 extends Target implements Factory
{
    ....//equal tostring is not written. Take a typical example.    
    final void CGLIB$g$0()//invokeSuper calls this method
    {
      super.g();
    }
    
    public final void g()//invoke calls this method
    {
      MethodInterceptor tmp4_1 = this.CGLIB$CALLBACK_0;
      if (tmp4_1 == null)
      {
          CGLIB$BIND_CALLBACKS(this);
          tmp4_1 = this.CGLIB$CALLBACK_0;
      }
      if (this.CGLIB$CALLBACK_0 != null) {
          tmp4_1.intercept(this, CGLIB$g$0$Method, CGLIB$emptyArgs, CGLIB$g$0$Proxy);
      }
      else{
          super.g();
      }
    }
}

It can be seen that unlike JDK, JDK is the original interface of implements, CGLIB is the original class of extends, and two corresponding methods are generated for different calls.

The difference between CGLIB and JDK dynamic proxy 1. CGLIB proxy class does not need to implement interfaces, but it can not be final type 2. CGLIB callback method can no longer be processed by method.invoke reflection. FastClass mechanism with higher efficiency is to index a class's method and call the corresponding method directly by index. Method

For example:

//Get the method index first
    public int getIndex(String signature){
        switch(signature.hashCode()){
        case 3078479:
            return 1;
        case 3108270:
            return 2;
        }
        return -1;
    }

    //Then the method is obtained by indexing.
    public Object invoke(int index, Object o, Object[] ol){
        Test t = (Test) o;
        switch(index){
        case 1:
            t.f();
            return null;
        case 2:
            t.g();
            return null;
        }
        return null;
    }

SPRING AOP

Spring Aop uses JDK dynamic proxy by default, unless CGLIB dynamic proxy is specified, but only JDK dynamic proxy code can be used for interface classes in DefaultAopProxyFactory.

public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
    if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
        Class targetClass = config.getTargetClass();
        if (targetClass == null) {
            throw new AopConfigException("TargetSource cannot determine target class: " +
                    "Either an interface or a target is required for proxy creation.");
        }
        if (targetClass.isInterface()) {
            return new JdkDynamicAopProxy(config);
        }
        if (!cglibAvailable) {
            throw new AopConfigException(
                    "Cannot proxy target class because CGLIB2 is not available. " +
                    "Add CGLIB to the class path or specify proxy interfaces.");
        }
        return CglibProxyFactory.createCglibProxy(config);
    }
    else {
        return new JdkDynamicAopProxy(config);
    }
}

Take Jdk DynamicAopProxy as an example to see how spring selectively enhances methods

1. Generating proxy classes

@Override
public Object getProxy(ClassLoader classLoader) {
	if (logger.isDebugEnabled()) {
		logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());
	}
	Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised);
	findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
	return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);//hanler introduced himself
}

It can be seen that the method of enhancement is not the logic defined by ourselves, but JdkDynamicAopProxy. Then JdkDynamicAopProxy must have an invoke method, and see how this method is handled.

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
.....
     //Acquisition of Interception Chain
     List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
     if (chain.isEmpty()) {
         //Direct tuning method return
         retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);
    }else{
        invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
        //Execution Interception Chain
        retVal = invocation.proceed();
    }
....
}

Appendix

JDK source: https://www.cnblogs.com/liuyun1995/p/8144628.html

CGLIB source: https://www.cnblogs.com/cruze/p/3843996.html

Class loading problem: http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-5.html#jvms-5.3

Topics: Programming JDK Spring Oracle