No one has ever analyzed the agency model so thoroughly

Keywords: Java JDK cglib

1 from static agent to dynamic agent

For example, some people will be urged to marry by their parents when they reach the marriageable age. Now, under various pressures, many people choose to marry and have children later. So anxious parents began to date their children everywhere, more anxious than their children themselves. Let's look at the code implementation. The code for creating the top-level interface IPerson is as follows.

public interface IPerson {

    void findLove();

}

Son Zhang San wants to find an object and implement the ZhangSan class.

public class ZhangSan implements IPerson {

    public void findLove() {
        System.out.println("Son Zhang San made a request");
    }

}

Father Zhang Laosan wants to help his son Zhang San make a blind date and realize the ZhangLaosan class.

public class ZhangLaosan implements IPerson {

    private ZhangSan zhangsan;

    public ZhangLaosan(ZhangSan zhangsan) {
        this.zhangsan = zhangsan;
    }

    public void findLove() {
        System.out.println("Zhang Laosan began to look for");
        zhangsan.findLove();
        System.out.println("Start dating");
    }

}

Look at the client test code.

public class Test {
    public static void main(String[] args) {
        ZhangLaosan zhangLaosan = new ZhangLaosan(new ZhangSan());
        zhangLaosan.findLove();
    }
}

The operation results are shown in the figure below.

However, there is a disadvantage in the above scene, that is, his father will only help his children find objects, and other people's children will not care. However, in the society, this business has developed into an industry, with matchmakers, marriage agencies, etc. and a variety of customized packages. If you still use static agents, the cost is too high. You need a more general solution to meet the needs of any single person looking for a partner. This upgrades from static agent to dynamic agent. Using dynamic agent, basically as long as it is a person (IPerson), it can provide blind date service. The underlying implementation of dynamic agent generally does not need to be implemented by us. There are many ready-made API s. In the Java ecosystem, the proxy provided by JDK and the class library provided by CGLib are widely used at present. First, upgrade the code based on JDK's dynamic proxy support. First, create the matchmaker (matchmaking agency) class JdkMeipo.

public class JdkMeipo implements InvocationHandler {
    private IPerson target;
    public IPerson getInstance(IPerson target){
        this.target = target;
        Class<?> clazz =  target.getClass();
        return (IPerson) Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object result = method.invoke(this.target,args);
        after();
        return result;
    }

    private void after() {
        System.out.println("The two sides agreed to start exchanges");
    }

    private void before() {
        System.out.println("I'm a matchmaker. I've collected your needs and started looking for them");
    }
}

Then create a class ZhaoLiu.

public class ZhaoLiu implements IPerson {

    public void findLove() {
        System.out.println("Meet the requirements of Zhao Liu");
    }

    public void buyInsure() {

    }

}

Finally, the client test code is as follows.

    public static void main(String[] args) {
        JdkMeipo jdkMeipo = new JdkMeipo();

        IPerson zhaoliu = jdkMeipo.getInstance(new ZhaoLiu());
        zhaoliu.findLove();

    }
		

The operation results are shown in the figure below.

2 static agent in three-tier architecture

Small partners may feel that they still don't know how to apply the agent model to the business scenario. Let's take a look at an actual business scenario. In the distributed business scenario, the database is usually divided into databases and tables. After the database and tables are divided, multiple data sources may need to be configured when using Java operation. We can dynamically switch the data sources by setting the data source path. First create the Order class.

public class Order {
    private Object orderInfo;
    private Long createTime;
    private String id;

    public Object getOrderInfo() {
        return orderInfo;
    }
    public void setOrderInfo(Object orderInfo) {
        this.orderInfo = orderInfo;
    }
    public Long getCreateTime() {
        return createTime;
    }
    public void setCreateTime(Long createTime) {
        this.createTime = createTime;
    }
    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
}

Create an OrderDao persistence layer operation class.

public class OrderDao {
    public int insert(Order order){
        System.out.println("OrderDao establish Order success!");
        return 1;
    }
}


Create the IOrderService interface.

public interface IOrderService {
    int createOrder(Order order);
}

Create an OrderService implementation class.


public class OrderService implements IOrderService {
    private OrderDao orderDao;

    public OrderService(){
        //If Spring is used, it should be injected automatically
        //For ease of use, we directly initialize orderDao in the construction method
        orderDao = new OrderDao();
    }

    @Override
    public int createOrder(Order order) {
        System.out.println("OrderService call orderDao Create order");
        return orderDao.insert(order);
    }
}

Then the static agent is used. The main function is to automatically divide the inventory by year according to the order creation time. According to the opening and closing principle, we modify the original code logic and complete it through the proxy object. Create a data source routing object and use the singleton of ThreadLocal to implement the DynamicDataSourceEntry class.

//Dynamically switch data sources
public class DynamicDataSourceEntry {
   
    //Default data source  
    public final static String DEFAULT_SOURCE = null;  
  
    private final static ThreadLocal<String> local = new ThreadLocal<String>();  

    private DynamicDataSourceEntry(){}

    //Clear data source
    public static void clear() {
        local.remove();
    }  
    
    //Gets the name of the data source currently in use
    public static String get() {
         return local.get();  
    }  

    //Restore the currently switched data source
    public static void restore() {
        local.set(DEFAULT_SOURCE);
    }  
  
    //Set a data source with a known name 
    public static void set(String source) {
        local.set(source); 
    }

    //Dynamically set the data source according to the year
    public static void set(int year) {
        local.set("DB_" + year);
    }
}

Create a proxy class OrderServiceSaticProxy for switching data sources.

public class OrderServiceStaticProxy implements IOrderService {

    private SimpleDateFormat yearFormat = new SimpleDateFormat("yyyy");

    private IOrderService orderService;
    public OrderServiceStaticProxy(IOrderService orderService){
        this.orderService = orderService;
    }

    public int createOrder(Order order) {
        before();
        Long time = order.getCreateTime();
        Integer dbRouter = Integer.valueOf(yearFormat.format(new Date(time)));
        System.out.println("Static proxy classes are automatically assigned to[ DB_" + dbRouter + "]Data source processing data");
        DynamicDataSourceEntry.set(dbRouter);
        orderService.createOrder(order);
        after();
        return 0;
    }

    private void before(){
        System.out.println("Proxy before method.");
    }

    private void after(){
        System.out.println("Proxy after method.");
    }

}

Look at the client test code.

    public static void main(String[] args) {

        try {

            Order order = new Order();
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
            Date date = sdf.parse("2017/02/01");
            order.setCreateTime(date.getTime());

            IOrderService orderService = new OrderServiceStaticProxy(new OrderService());
            orderService.createOrder(order);
        }catch (Exception e){
            e.printStackTrace();;
        }

}

The operation results are shown in the figure below.

It can be seen from the above figure that the results are in line with expectations. Let's review the class diagram to see if it is consistent with the one we drew first, as shown in the following figure.

The basic ideas of dynamic agent and static agent are the same, but the function of dynamic agent is more powerful and more adaptive with the expansion of business.

3 use dynamic agent to switch data sources without perception

After understanding the above case, let's take a look at the data source dynamic routing service to help our partners deepen their impression of the dynamic proxy. Create the dynamic proxy class OrderServiceDynamicProxy, and the code is as follows.

public class OrderServiceDynamicProxy implements InvocationHandler {

    private SimpleDateFormat yearFormat = new SimpleDateFormat("yyyy");
    private Object target;

    public Object getInstance(Object target){
        this.target = target;
        Class<?> clazz = target.getClass();
        return Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);
    }


    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before(args[0]);
        Object object = method.invoke(target,args);
        after();
        return object;
    }

    private void before(Object target){
        try {
            System.out.println("Proxy before method.");
            Long time = (Long) target.getClass().getMethod("getCreateTime").invoke(target);
            Integer dbRouter = Integer.valueOf(yearFormat.format(new Date(time)));
            System.out.println("Static proxy classes are automatically assigned to[ DB_" + dbRouter + "]Data source processing data");
            DynamicDataSourceEntry.set(dbRouter);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    private void after(){
        System.out.println("Proxy after method.");
    }
}

Write the client test code as follows.

public static void main(String[] args) {

    try {

        Order order = new Order();

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
        Date date = sdf.parse("2018/02/01");
        order.setCreateTime(date.getTime());

        IOrderService orderService = (IOrderService)new OrderServiceDynamicProxy(). 
        getInstance(new OrderService());
        orderService.createOrder(order);
    }catch (Exception e){
        e.printStackTrace();
    }

}

It can be seen from the above code that the same operation effect can still be achieved. However, after using the dynamic proxy implementation, it can not only realize the dynamic routing of the data source of Order, but also realize the data source routing of any other class. Of course, there is an important convention that getCreateTime() must be implemented Method, because the routing rules are calculated according to time. The purpose of constraints can be achieved through the interface specification. There is no example here.

4. Core principle of handwritten JDK dynamic agent

We not only know what it is, but also why. Since the function of JDK dynamic agent is so powerful, how is it implemented? Now let's explore the principle and imitate JDK dynamic agent to write a dynamic agent of our own. We all know that JDK dynamic agent uses word reorganization and regenerates objects to replace the original objects, so as to achieve the purpose of dynamic agent The steps to generate the object by the dynamic proxy are as follows. (1) get the reference of the proxy object, and get all its interfaces, and reflect the acquisition. (2) the JDK dynamic proxy class regenerates a new class, and the new class should implement all the interfaces implemented by the proxy class. (3) dynamically generate Java code, and the new business logic method is called by a certain logic code (reflected in the code). (4) compile the newly generated Java code. Class file. (5) reload it into the JVM for operation. The above process is called bytecode reorganization. There is a specification in the JDK that. Class files starting with $under ClassPath are generally automatically generated. Is there any way to see the "true appearance" of the replaced object What? Do such a test, output the object bytecode in memory to a new. Class file through the file stream, and then use the decompile tool to view the source code.

    public static void main(String[] args) {
        try {
            IPerson obj = (IPerson)new JdkMeipo().getInstance(new Zhangsan());
            obj.findLove();

            //View the source code through the decompile tool
            byte [] bytes = ProxyGenerator.generateProxyClass("$Proxy0",new Class[]{IPerson.class});
            FileOutputStream os = new FileOutputStream("E://$Proxy0.class");
            os.write(bytes);
            os.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
		

Run the above code to find a $Proxy0.class file on disk E. decompile with Jad to get the $Proxy0.jad file. Open the file and see the following contents.

import com.tom.pattern.proxy.dynamicproxy.jdkproxy.IPerson;
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 IPerson {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m4;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final void findLove() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void buyInsure() throws  {
        try {
            super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", 
					new Class[]{Class.forName("java.lang.Object")});
            m3 = Class.forName("com.tom.pattern.proxy.dynamicproxy.jdkproxy.IPerson")
					.getMethod("findLove", new Class[0]);
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
            m4 = Class.forName("com.tom.pattern.proxy.dynamicproxy.jdkproxy.IPerson")
					.getMethod("buyInsure", new Class[0]);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

We found that $Proxy0 inherits the Proxy class, implements the Person interface, and overrides findLove() And other methods. In the static code block, we use reflection to find all the methods of the target object, and save the references of all the methods. The rewritten method uses reflection to call the methods of the target object. At this time, we will be curious: where do these codes come from? In fact, they are automatically generated by JDK. Now we don't rely on JDK to dynamically generate the source code and dynamically complete the compilation ourselves Then replace the target object and execute. Create the GPInvocationHandler interface.

public interface GPInvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable;
}

Create the GPProxy class.

/**
 * Tool class used to generate source code
 * Created by Tom.
 */
public class GPProxy {

    public static final String ln = "\r\n";

    public static Object newProxyInstance(GPClassLoader classLoader, Class<?> [] interfaces, 
    GPInvocationHandler h){
       try {
           //1. Dynamically generate source code. java files
           String src = generateSrc(interfaces);
           //2.Java file output disk
           String filePath = GPProxy.class.getResource("").getPath();

           File f = new File(filePath + "$Proxy0.java");
           FileWriter fw = new FileWriter(f);
           fw.write(src);
           fw.flush();
           fw.close();

           //3. Compile the generated. java file into. class file
           JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
           StandardJavaFileManager manage = compiler.getStandardFileManager(null,null,null);
           Iterable iterable = manage.getJavaFileObjects(f);

          JavaCompiler.CompilationTask task = compiler.getTask(null,manage,null,null,null,iterable);
          task.call();
          manage.close();

           //4. Load the compiled. class file into the JVM
          Class proxyClass =  classLoader.findClass("$Proxy0");
          Constructor c = proxyClass.getConstructor(GPInvocationHandler.class);
          f.delete();

           //5. Return the new proxy object after bytecode reorganization
           return c.newInstance(h);
       }catch (Exception e){
           e.printStackTrace();
       }
        return null;
    }

    private static String generateSrc(Class<?>[] interfaces){
            StringBuffer sb = new StringBuffer();
            sb.append(GPProxy.class.getPackage() + ";" + ln);
            sb.append("import " + interfaces[0].getName() + ";" + ln);
            sb.append("import java.lang.reflect.*;" + ln);
            sb.append("public class $Proxy0 implements " + interfaces[0].getName() + "{" + ln);
                sb.append("GPInvocationHandler h;" + ln);
                sb.append("public $Proxy0(GPInvocationHandler h) { " + ln);
                    sb.append("this.h = h;");
                sb.append("}" + ln);
                for (Method m : interfaces[0].getMethods()){
                    Class<?>[] params = m.getParameterTypes();

                    StringBuffer paramNames = new StringBuffer();
                    StringBuffer paramValues = new StringBuffer();
                    StringBuffer paramClasses = new StringBuffer();

                    for (int i = 0; i < params.length; i++) {
                        Class clazz = params[i];
                        String type = clazz.getName();
                        String paramName = toLowerFirstCase(clazz.getSimpleName());
                        paramNames.append(type + " " +  paramName);
                        paramValues.append(paramName);
                        paramClasses.append(clazz.getName() + ".class");
                        if(i > 0 && i < params.length-1){
                            paramNames.append(",");
                            paramClasses.append(",");
                            paramValues.append(",");
                        }
                    }

                    sb.append("public " + m.getReturnType().getName() + " " + m.getName() + "(" 							  + paramNames.toString() + ") {" + ln);
                        sb.append("try{" + ln);
                            sb.append("Method m = " + interfaces[0].getName() + ".class. getMethod(\"" + m.getName() + "\",new Class[]{" + paramClasses.toString() + "});" + ln);
                            sb.append((hasReturnValue(m.getReturnType()) ? "return " : "") + getCaseCode("this.h.invoke(this,m,new Object[]{" + paramValues + "})",m.getReturnType()) + ";" + ln);
                        sb.append("}catch(Error _ex) { }");
                        sb.append("catch(Throwable e){" + ln);
                        sb.append("throw new UndeclaredThrowableException(e);" + ln);
                        sb.append("}");
                        sb.append(getReturnEmptyCode(m.getReturnType()));
                    sb.append("}");
                }
            sb.append("}" + ln);
            return sb.toString();
    }

    private static Map<Class,Class> mappings = new HashMap<Class, Class>();
    static {
        mappings.put(int.class,Integer.class);
    }

    private static String getReturnEmptyCode(Class<?> returnClass){
        if(mappings.containsKey(returnClass)){
            return "return 0;";
        }else if(returnClass == void.class){
            return "";
        }else {
            return "return null;";
        }
    }

    private static String getCaseCode(String code,Class<?> returnClass){
        if(mappings.containsKey(returnClass)){
            return "((" + mappings.get(returnClass).getName() +  ")" + code + ")." + 							returnClass.getSimpleName() + "Value()";
        }
        return code;
    }

    private static boolean hasReturnValue(Class<?> clazz){
        return clazz != void.class;
    }

    private static String toLowerFirstCase(String src){
        char [] chars = src.toCharArray();
        chars[0] += 32;
        return String.valueOf(chars);
    }

}

Create the GPClassLoader class.

public class GPClassLoader extends ClassLoader {

    private File classPathFile;
    public GPClassLoader(){
        String classPath = GPClassLoader.class.getResource("").getPath();
        this.classPathFile = new File(classPath);
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {

        String className = GPClassLoader.class.getPackage().getName() + "." + name;
        if(classPathFile  != null){
            File classFile = new File(classPathFile,name.replaceAll("\\.","/") + ".class");
            if(classFile.exists()){
                FileInputStream in = null;
                ByteArrayOutputStream out = null;
                try{
                    in = new FileInputStream(classFile);
                    out = new ByteArrayOutputStream();
                    byte [] buff = new byte[1024];
                    int len;
                    while ((len = in.read(buff)) != -1){
                        out.write(buff,0,len);
                    }
                    return defineClass(className,out.toByteArray(),0,out.size());
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
        return null;
    }
}

Create the GPMeipo class.

public class GpMeipo implements GPInvocationHandler {
    private IPerson target;
    public IPerson getInstance(IPerson target){
        this.target = target;
        Class<?> clazz =  target.getClass();
        return (IPerson) GPProxy.newProxyInstance(new GPClassLoader(),clazz.getInterfaces(),this);
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object result = method.invoke(this.target,args);
        after();
        return result;
    }

    private void after() {
        System.out.println("The two sides agreed to start exchanges");
    }

    private void before() {
        System.out.println("I'm a matchmaker. I've collected your needs and started looking for them");
    }
}

The client test code is as follows.

    public static void main(String[] args) {
        GpMeipo gpMeipo = new GpMeipo();
        IPerson zhangsan = gpMeipo.getInstance(new Zhangsan());
        zhangsan.findLove();


    }

At this point, the handwritten JDK dynamic agent is completed. Do you have another "trump card" for interview?

5 principle analysis of cglib dynamic agent API

Take a brief look at the use of CGLib dynamic proxy, or take the matchmaker as an example to create the CglibMeipo class.

public class CGlibMeipo implements MethodInterceptor {

    public Object getInstance(Class<?> clazz) throws Exception{
        //Equivalent to the Proxy class in JDK, it is a tool class to complete the Proxy
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        return enhancer.create();
    }

    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) 						     throws Throwable {
        before();
        Object obj = methodProxy.invokeSuper(o,objects);
        after();
        return obj;
    }

    private void before(){
        System.out.println("I'm a matchmaker. I want to find you someone. Now I've confirmed your needs");
        System.out.println("Start looking");
    }

    private void after(){
        System.out.println("The two sides agreed to get married");
    }
}

Create a detail Customer class Customer.

public class Customer {

    public void findLove(){
        System.out.println("Meet the requirements");
    }
}

Here is a small detail. The target object of CGLib dynamic proxy does not need to implement any interface. It implements dynamic proxy by dynamically inheriting the target object. The client test code is as follows.

public static void main(String[] args) {

        try {

            //JDK uses to read the information of the interface
            //CGLib override parent method
            //The purpose is to generate a new class to implement the function of enhancing code logic

            //JDK Proxy must have an interface implementation for users, and the target class is relatively complex
            //CGLib can represent any ordinary class without any requirements

            //The logic of CGLib generation agent is more complex and the calling efficiency is higher. It is no longer necessary to generate a FastClass containing all logic 				 To reflect a call
            //JDK Proxy generates proxy with simple logic and relatively low execution efficiency. Dynamic calls are reflected every time

            //CGLib has a disadvantage. CGLib cannot proxy the method of final

            Customer obj = (Customer) new CGlibMeipo().getInstance(Customer.class);
            System.out.println(obj);
            obj.findLove();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

What is the implementation principle of CGLib dynamic agent? We can add a sentence of code to the client test code, write the. class file after CGLib dynamic agent to the disk, and then decompile it. The code is as follows.

public static void main(String[] args) {
    try {

        //Using the proxy class of CGLib, you can write the. Class file in memory to the local disk
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "E://cglib_proxy_class/");

        Customer obj = (Customer)new CglibMeipo().getInstance(Customer.class);
        obj.findLove();

    } catch (Exception e) {
        e.printStackTrace();
    }
}

Re execute the code and we'll find that E://cglib_ proxy_ There are three. Class files in the class directory, as shown in the following figure.

Through debugging and tracing, it is found that Customer$$EnhancerByCGLIB$feeb52a.class is the agent class generated by CGLib dynamic agent and inherits the Customer class.

package com.tom.pattern.proxy.dynamicproxy.cglibproxy;

import java.lang.reflect.Method;
import net.sf.cglib.core.ReflectUtils;
import net.sf.cglib.core.Signature;
import net.sf.cglib.proxy.*;

public class Customer$$EnhancerByCGLIB$$3feeb52a extends Customer
    implements Factory
{

    ...

    final void CGLIB$findLove$0()
    {
        super.findLove();
    }

    public final void findLove()
    {
        CGLIB$CALLBACK_0;
        if(CGLIB$CALLBACK_0 != null) goto _L2; else goto _L1
_L1:
        JVM INSTR pop ;
        CGLIB$BIND_CALLBACKS(this);
        CGLIB$CALLBACK_0;
_L2:
        JVM INSTR dup ;
        JVM INSTR ifnull 37;
           goto _L3 _L4
_L3:
        break MISSING_BLOCK_LABEL_21;
_L4:
        break MISSING_BLOCK_LABEL_37;
        this;
        CGLIB$findLove$0$Method;
        CGLIB$emptyArgs;
        CGLIB$findLove$0$Proxy;
        intercept();
        return;
        super.findLove();
        return;
    }

    ...
   
}

We have rewritten all the methods of the Customer class. From the source code of the proxy class, we can see that the proxy class will obtain all the methods inherited from the parent class, and there will be MethodProxy corresponding to it. For example, Method CGLIB$findLove method cglib $findlove $0 $method,$Method, MethodProxy CGLIB$findLove MethodProxy cglib $findlove $0 $proxy $Proxy and other methods are called in the findLove() method of the proxy class.


//Proxy method (methodProxy.invokeSuper() method will call)
    final void CGLIB$findLove$0()
    {
        super.findLove();
    }

//The proxy method (methodProxy.invoke() method will be called
//This is why calling methodProxy.invoke in the interceptor will generate a life and death cycle and what is called the interceptor.
    public final void findLove()
    {
        ...
        //Call interceptor
        intercept();
        return;
        super.findLove();
        return;
    }

The calling process is: the proxy object calls this.findLove() method → calls interceptor → methodProxy.invokeSuper() → CGLIB$findLove cglib $findlove $0 → the proxy object findLove() method. At this time, we find that the MethodInterceptor interceptor invokes the proxy method by the invokeSuper() method of MethodProxy. Therefore, the code in the MethodProxy class is very critical. We analyze what it does.

package net.sf.cglib.proxy;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import net.sf.cglib.core.AbstractClassGenerator;
import net.sf.cglib.core.CodeGenerationException;
import net.sf.cglib.core.GeneratorStrategy;
import net.sf.cglib.core.NamingPolicy;
import net.sf.cglib.core.Signature;
import net.sf.cglib.reflect.FastClass;
import net.sf.cglib.reflect.FastClass.Generator;

public class MethodProxy {
    private Signature sig1;
    private Signature sig2;
    private MethodProxy.CreateInfo createInfo;
    private final Object initLock = new Object();
    private volatile MethodProxy.FastClassInfo fastClassInfo;

    public static MethodProxy create(Class c1, Class c2, String desc, String name1, String name2) {
        MethodProxy proxy = new MethodProxy();
        proxy.sig1 = new Signature(name1, desc);
        proxy.sig2 = new Signature(name2, desc);
        proxy.createInfo = new MethodProxy.CreateInfo(c1, c2);
        return proxy;
    }

    ...
    
    private static class CreateInfo {
        Class c1;
        Class c2;
        NamingPolicy namingPolicy;
        GeneratorStrategy strategy;
        boolean attemptLoad;

        public CreateInfo(Class c1, Class c2) {
            this.c1 = c1;
            this.c2 = c2;
            AbstractClassGenerator fromEnhancer = AbstractClassGenerator.getCurrent();
            if(fromEnhancer != null) {
                this.namingPolicy = fromEnhancer.getNamingPolicy();
                this.strategy = fromEnhancer.getStrategy();
                this.attemptLoad = fromEnhancer.getAttemptLoad();
            }

        }
    }
     ...
    
}

Continue to look at the invokeSuper() method.

public Object invokeSuper(Object obj, Object[] args) throws Throwable {
    try {
        this.init();
        MethodProxy.FastClassInfo fci = this.fastClassInfo;
        return fci.f2.invoke(fci.i2, obj, args);
    } catch (InvocationTargetException var4) {
        throw var4.getTargetException();
    }
}

...

private static class FastClassInfo {
    FastClass f1;
    FastClass f2;
    int i1;
    int i2;

    private FastClassInfo() {
    }
}

The above code calls to obtain the FastClass corresponding to the proxy class and execute the proxy method. Remember the three. Class files generated earlier? Customer$$EnhancerByCGLIB$feeb52a$$FastClassByCGLIB$aad62f1.class is the FastClass of the proxy class, and Customer$$FastClassByCGLIB$69574a.class is the FastClass of the proxy class. The reason why CGlib dynamic agent is more efficient than JDK in executing proxy methods is that CGlib adopts the FastClass mechanism. Its principle is to generate a class for the proxy class and the proxy class respectively, and this class will assign an index (int type) to the methods of the proxy class or the proxy class; This index is regarded as an input parameter. FastClass can directly locate the method to be called and call it directly, eliminating the reflection call. Therefore, the calling efficiency is higher than that of JDK agent through reflection call. Let's decompile a FastClass.

public int getIndex(Signature signature)
    {
        String s = signature.toString();
        s;
        s.hashCode();
        JVM INSTR lookupswitch 11: default 223
        ...
        JVM INSTR pop ;
        return -1;
    }

//Partial code omission

    //Direct positioning execution method according to index
    public Object invoke(int i, Object obj, Object aobj[])
        throws InvocationTargetException
    {
        (Customer)obj;
        i;
        JVM INSTR tableswitch 0 10: default 161
           goto _L1 _L2 _L3 _L4 _L5 _L6 _L7 _L8 _L9 _L10 _L11 _L12
_L2:
        eat();
        return null;
_L3:
        findLove();
        return null;
        ...
        throw new IllegalArgumentException("Cannot find matching method/constructor");
    }

FastClass is not generated together with the proxy class, but is generated when the invoke() or invokeSuper() method of MethodProxy is executed for the first time and placed in the cache.

//Both the invoke() or invokeSuper() methods of MethodProxy call the init() method
private void init() {
    if(this.fastClassInfo == null) {
        Object var1 = this.initLock;
        synchronized(this.initLock) {
            if(this.fastClassInfo == null) {
                MethodProxy.CreateInfo ci = this.createInfo;
                MethodProxy.FastClassInfo fci = new MethodProxy.FastClassInfo();
//If it is in the cache, it is fetched; If it is not in the cache, a new FastClass is generated
                fci.f1 = helper(ci, ci.c1);
                fci.f2 = helper(ci, ci.c2);
                fci.i1 = fci.f1.getIndex(this.sig1);//Gets the index of the method
                fci.i2 = fci.f2.getIndex(this.sig2);
                this.fastClassInfo = fci;
            }
        }
    }
}

So far, we have basically understood the principle of CGLib dynamic agent. Small partners interested in code details can study it in depth by themselves.

6 comparative analysis of cglib and JDK Proxy

(1) JDK dynamic proxy implements the interface of the proxy object, and CGLib dynamic proxy inherits the proxy object. (2) Both JDK dynamic agent and CGLib dynamic agent generate bytecode at runtime. JDK dynamic agent directly writes Class bytecode, and CGLib dynamic agent uses ASM framework to write Class bytecode. The implementation of CGLib dynamic agent is more complex, and the efficiency of generating agent is lower than that of JDK dynamic agent. (3) JDK dynamic proxy calls proxy methods through reflection mechanism. CGLib dynamic proxy calls methods directly through FastClass mechanism. CGLib dynamic proxy has higher execution efficiency.

This article is the original of "Tom bomb architecture". Please indicate the source for reprint. Technology lies in sharing, I share my happiness!
If this article is helpful to you, you are welcome to pay attention and praise; If you have any suggestions, you can also leave comments or private letters. Your support is the driving force for me to adhere to my creation. Focus on WeChat official account Tom structure, get more dry cargo!

Posted by phpology on Thu, 28 Oct 2021 08:10:19 -0700