Cglib dynamic agent

Posted by rage123456 on Thu, 20 Jan 2022 06:18:45 +0100

***Both cglib dynamic proxy * * * and * * * jdk dynamic proxy * * * dynamically generate new classes during java running. Cglib uses the newly generated proxy class to inherit the delegate class, because inheritance can have the non private and non final methods of the delegate class in the proxy class, so that the delegate class can be executed;

First, we should introduce dependence

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.2.2</version>
</dependency>
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

1. Create a delegate class

package com.fekertest.cglib.bean;

public class BookServiceBean {
    public void create(){
        System.out.println("create() is running !");
    }
    public void query(){
        System.out.println("query() is running !");
    }
    public void update(){
        System.out.println("update() is running !");
    }
    public void delete(){
        System.out.println("delete() is running !");
    }

}

2. Add interceptor

package com.fekertest.cglib.interceptor;

import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.apache.log4j.Logger;
public class MyCglibProxy implements MethodInterceptor{
    private Logger log=Logger.getLogger(MyCglibProxy.class);
    public Enhancer enhancer = new Enhancer();
    private String name;

    public MyCglibProxy(String name) {
        this.name = name ;
    }
    /**
 * Create a proxy object for the class object
 * 1,Set parent class; 2. Set callback
 * Essence: dynamically creates a subclass of a class object
 *
 * @param cls
 * @return
 */
 public Object getDaoBean(Class cls) {
        enhancer.setSuperclass(cls);
        enhancer.setCallback(this);
        return enhancer.create();
    }

    @Override
 public Object intercept(Object object, Method method, Object[] args,
                            MethodProxy methodProxy) throws Throwable {
        log.info("The method called is:" + method.getName());
        //User judgment
 if(!"boss".equals(name)){
            System.out.println("You have no authority!");
            return null;
        }
        Object result = methodProxy.invokeSuper(object, args);

        return result;
    }
}

The interceptor completes the final call to the target method in intercept. Here, you can write your own interception logic. Here, we write that only boss is allowed to call the target method;

3. Create a factory to generate a delegate class instance

package com.fekertest.cglib.bean;

import com.fekertest.cglib.filter.MyProxyFilter;
import com.fekertest.cglib.interceptor.MyCglibProxy;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.NoOp;

public class BookServiceFactory {
    private static BookServiceBean service = new BookServiceBean();
    private BookServiceFactory() {
    }
    public static BookServiceBean getProxyInstance(MyCglibProxy myProxy){
        Enhancer en = new Enhancer();
        //Acting
  en.setSuperclass(BookServiceBean.class);
        en.setCallback(myProxy);
        //Generate proxy instance
  return (BookServiceBean)en.create();
    }
    public static BookServiceBean getProxyInstanceByFilter(MyCglibProxy myProxy){
        Enhancer en = new Enhancer();
        en.setSuperclass(BookServiceBean.class);
        en.setCallbacks(new Callback[]{myProxy, NoOp.INSTANCE});
        en.setCallbackFilter(new MyProxyFilter());
        return (BookServiceBean)en.create();
    }

    public static void main(String[] args) {
          BookServiceBean service = BookServiceFactory.getProxyInstance(new MyCglibProxy("boss"));
          service.create();
          BookServiceBean service2 = BookServiceFactory.getProxyInstance(new MyCglibProxy("john"));
          service2.create();
          BookServiceBean service3 = BookServiceFactory.getProxyInstanceByFilter(new MyCglibProxy("jhon"));
          service.create();
          BookServiceBean service4 = BookServiceFactory.getProxyInstanceByFilter(new MyCglibProxy("jhon"));
          service2.query();
    }
}

There are two methods: getProxyInstance and getProxyInstanceByFilter. The first method is to directly create an agent, and the second is an agent with a filter;

4. Add filter

package com.fekertest.cglib.filter;

import java.lang.reflect.Method;
import net.sf.cglib.proxy.CallbackFilter;
public class MyProxyFilter implements CallbackFilter {
    @Override
 public int accept(Method arg0) {
        if(!"query".equalsIgnoreCase(arg0.getName()))
            return 0;
        return 1;
    }
}

In this way, you can see the result by calling the method of the above startup class:

create() is running !
You have no authority!
You have no authority!
query() is running !

The first two results are the processing results directly obtained by calling the interceptor, and the last two results are the results obtained by adding a filter. Obviously, the filter filters out the interception of the query method;

Next, let's look at the principle of cglib:
We print the dynamically generated proxy class. We can see that for the Create method, there will be create and CGLIB$create in this proxy class 0 this two individual square method ; his in front person be yes I Guys send use generation reason class Time Wait transfer use of square method , after person yes stay square method Block section implement in noodles transfer use of , change sentence word come say When I Guys generation code transfer use generation reason yes as of c r e a t e square method , however after meeting reach square method Block section implement in transfer use i n t e r c e p t square method , Should square method within be through too p r o x y . i n v o k e S u p e r transfer use C G L I B 0 these two methods; The former is called when we use the proxy class, and the latter is called in the method interceptor. In other words, when our code calls the create method of the proxy object, then we call the intercept method in the method interceptor, and the proxy. is passed through the method. Invokesuper calls CGLIB 0 these two methods; The former is called when we use the proxy class, and the latter is called in the method interceptor. In other words, when our code calls the create method of the proxy object, then we call the intercept method in the method interceptor, and the proxy. is passed through the method. Invokesuper calls the CGLIBcreate cglibcreate $0 method. Don't find it difficult because the method name is too long. In fact, the principle is very simple...

Above we have seen the usage of CGLib dynamic proxy, the actual generated agent class and FastClass mechanism. Let's take the create () method in the first example to see the main calling steps.

Step 1: instantiate the Enhancer object through a series of operations, set the required parameters, and then Enhancer Create() successfully created the proxy object. I won't say more about this

Step 2: call the create () method of the proxy object, and you will enter the intercept () method of the method interceptor. In this method, you will call methodproxy invokeSuper(obj, args); method

Step 3: in invokeSuper, call the method of the target class through the FastClass mechanism

The intercepted object of Jdk dynamic agent calls the intercepted method through the reflection mechanism, which is inefficient. Therefore, cglib adopts the FastClass mechanism to call the intercepted method. FastClass mechanism is to index the methods of a class and directly call the corresponding methods through the index. The following is a small example to illustrate it, which is more intuitive:

public class FastClass{
    public static void main(String[] args){
        Test tt = new Test();
        Test2 fc = new Test2();
        int index = fc.getIndex("f()V");
        fc.invoke(index, tt, null);
    }
}

class Test{
    public void f(){
        System.out.println("f method");
    }

    public void g(){
        System.out.println("g method");
    }
}
class Test2{
    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;
    }

    public int getIndex(String signature){
        switch(signature.hashCode()){
            case 3078479:
                return 1;
            case 3108270:
                return 2;
        }
        return -1;
    }
}

In the above example, Test2 is the Fastclass of Test. In Test2, there are two methods getIndex and invoke. In the getIndex method, index each method of Test, and return the corresponding index according to the input parameter (method name + method descriptor). Invoke calls the method of object O with ol as the input parameter according to the specified index. This avoids reflection calls and improves efficiency.

There is only one intercept () method in the method interceptor. This method has four parameters. obj represents the proxy object, method represents the method in the target class, args represents the method parameters, and proxy represents the MethodProxy object of the proxy method

 public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy)

Proxy is called inside this method invokeSuper (obj, args) method, we enter Look inside the invokeSuper method:

Take a brief look at the init () method:

The internal of FastClassInfo is shown in the following figure, which shows prxy FCI. In invokesuper() method f2. Invoke (fci.i2, obj, args) actually calls CGLIB c r e a t e create create this method

Summary:

Here, draw a simple diagram to see the whole process. When we call method 1, we will first judge whether the method interception interface is implemented in the proxy class. If it is not implemented, we will directly call method 1 of the target class; If it is implemented, it will be intercepted by the method interceptor. In the method interceptor, all the methods in the target class will be indexed. In fact, it is probably to save the reference of each method in the array, so we can call the method directly according to the index of the array instead of using reflection; After the index is created, the invoke method will be called inside the method interceptor (this method is implemented in the generated FastClass), and CGLIB will be called inside the invoke method square method one Method 1 Method 1 this method, that is, calling method 1 of the corresponding target class;

Generally, we need to add our own logic in the method interceptor....

Finally, jdk dynamic agent and cglib dynamic agent are summarized and compared

Dynamic proxy is widely used in Java, such as Spring AOP, Hibernate data query, back-end mock and RPC of test framework, Java annotation object acquisition, etc. The agent relationship of dynamic agent is determined at run time. Next, it mainly expounds the differences between the two dynamic agents.

JDK and CGLib dynamic agent analysis

Since Java 1.3, Java has provided dynamic Proxy technology, which allows developers to create Proxy instances of interfaces at runtime. Later, this technology was used in many places of Spring. JDK dynamic Proxy mainly involves Java There are two classes under the lang.reflect package: Proxy and InvocationHandler. InvocationHandler is an interface that can be used to define crosscutting logic (for example, we print logs before and after method execution. This article is just for demonstration. Practical applications generally do not simply print logs), and call the code of the target class through the reflection mechanism to dynamically weave the crosscutting logic and business logic together.

For JDK dynamic proxy, one limitation is that it can only create proxy instances for interfaces. For classes that do not define business methods through interfaces, how to create dynamic proxy instances? The answer is CGLib.

CGLIB(Code Generation Library) is an ASM based bytecode generation library, which allows us to modify and dynamically generate bytecode at runtime. CGLIB implements the proxy through inheritance, intercepts the calls of all parent methods in the subclass by using the method interception technology, and weaves the crosscutting logic.

Differences between JDK and CGLib dynamic agents

1. Specific implementation principle of JDK dynamic agent:

Create your own calling processor by implementing the InvocationHandler interface;

Create a dynamic Proxy by specifying a ClassLoader object and a set of interface s for the Proxy class;

Get the constructor of the dynamic proxy class through the reflection mechanism, and its only parameter type is the calling processor interface type;

Create a dynamic proxy class instance through the constructor, and call the processor object as a parameter during construction;

JDK dynamic proxy is an interface oriented proxy mode. If the proxy target has no interface, spring can do nothing. Spring produces a new anonymous implementation class of the proxy interface through Java reflection mechanism and rewrites the enhancement method of AOP.

2. CGLib dynamic proxy:

Using ASM open source package, load the class file of proxy object class, and generate subclasses by modifying its bytecode.

3. Comparison between the two:

JDK dynamic proxy is interface oriented.

CGLib dynamic proxy is implemented by inheriting the proxy class at the bottom of bytecode. Therefore, if the proxy class is modified by the final keyword, it will fail.

4. Usage Note:

If the object to be proxied is an implementation class, Spring will use JDK dynamic proxy to complete the operation (Spirng adopts JDK dynamic proxy implementation mechanism by default);

If the object to be proxied is not an implementation class, Spring will force CGLib to implement dynamic proxy.

5. Performance comparison between JDK and CGLib dynamic agent

As for the performance between the two, some people on the Internet have tested the version of JDK that doesn't work. After many tests, the test results are roughly like this. At 1.6 and 1.7, the speed of JDK dynamic agent is slower than that of cglib dynamic agent, but there is no 10 times gap in textbooks. In jdk1 At 8, the speed of JDK dynamic agent was much faster than that of cglib dynamic agent, but the applicable scenarios of JDK dynamic agent and cglib dynamic agent are still different ha!

Finally, share a "universal" class of jdk dynamic proxy and cglib dynamic proxy

package com.fekertest.cglib;

import com.fekertest.cglib.bean.BookServiceBean;
import com.fekertest.jdk.Huochepiao;
import com.fekertest.jdk.Tieluju;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import java.lang.reflect.Proxy;

public class ProxyService {

    /**
 * jdk Dynamic agent
 *
 * @param object Proxied class object
 * @return Proxy instance
 */
 public static Object jdkProxyObject(Object object) {
        //Interceptor
 SimpleInterceptor interceptor = new SimpleInterceptor();
        return Proxy.newProxyInstance(
                object.getClass().getClassLoader(),
                object.getClass().getInterfaces(),
                (proxy, method, args) -> {
                    //Interceptor - preprocessing
 interceptor.before();
                    Object result = method.invoke(object, args);
                    //Interceptor post processing
 interceptor.after();
                    return result;
                });
    }

    /**
 * cglib Dynamic agent
 *
 * @param object Proxied class object
 * @return Proxy instance
 */
 public static Object cglibProxyObject(Object object) {
        //Analog interceptor
 SimpleInterceptor interceptor = new SimpleInterceptor();
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(object.getClass());
        enhancer.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> {
            //Interceptor - preprocessing
 interceptor.before();
            Object result = method.invoke(object, objects);
            //Interceptor post processing
 interceptor.after();
            return result;
        });
        return enhancer.create();
    }

}

class SimpleInterceptor {

    public void before() {
        System.out.println("-----" + this.getClass().getSimpleName() + "do before" + "-----");
    }

    public void after() {
        System.out.println("-----" + this.getClass().getSimpleName() + "do after" + "-----");
    }

    public static void main(String[] args) {
        //Here is the test
        ProxyService proxyService=new ProxyService();
        //cglib dynamic proxy
 BookServiceBean bookServiceBean= (BookServiceBean) proxyService.cglibProxyObject(new BookServiceBean());
        bookServiceBean.create();
        //jdk dynamic agent
 Huochepiao huochepiao= (Huochepiao) proxyService.jdkProxyObject(new Tieluju());
        huochepiao.buyHuochepiao("feker");
    }

}

Topics: Java