Detailed Explanation of Spring Series Proxy (Java Dynamic Proxy & cglib Proxy)

Why use an agent

 

Let's look at a case first.

There is an interface IService, as follows:

package com.javacode2018.lesson001.demo15;
 
public interface IService {
    void m1();
    void m2();
    void m3();
}

The interface has two implementation classes ServiceA and ServiceB, as follows:

package com.javacode2018.lesson001.demo15;
 
public class ServiceA implements IService {
    @Override
    public void m1() {
        System.out.println("我是ServiceA中的m1方法!");
    }
 
    @Override
    public void m2() {
        System.out.println("我是ServiceA中的m2方法!");
    }
 
    @Override
    public void m3() {
        System.out.println("我是ServiceA中的m3方法!");
    }
}
package com.javacode2018.lesson001.demo15;
 
public class ServiceB implements IService {
    @Override
    public void m1() {
        System.out.println("我是ServiceB中的m1方法!");
    }
 
    @Override
    public void m2() {
        System.out.println("我是ServiceB中的m2方法!");
    }
 
    @Override
    public void m3() {
        System.out.println("我是ServiceB中的m3方法!");
    }
}

Come to a test case to call the method of the above class, as follows:

package com.javacode2018.lesson001.demo15;
 
import org.junit.Test;
 
public class ProxyTest {
    @Test
    public void m1() {
        IService serviceA = new ServiceA();
        IService serviceB = new ServiceB();
        serviceA.m1();
        serviceA.m2();
        serviceA.m3();
 
        serviceB.m1();
        serviceB.m2();
        serviceB.m3();
    }
}


The above code is very simple, so I won’t explain it. Let’s run the m1() method and output:

I am m1 method in ServiceA!
I am m2 method in ServiceA!
I am m3 method in ServiceA! I am m1 method in ServiceA
!
I am m2 method in ServiceA!
I am m3 method in ServiceA!

The above is our original program. Suddenly, the leader has a requirement: when calling any method in the IService interface, the time-consuming method needs to be recorded.

What would you do at this point?

The IService interface has two implementation classes ServiceA and ServiceB. We can add statistical time-consuming codes to all methods of these two classes. If there are dozens of implementations of the IService interface, do we need to modify a lot of codes? Method needs to be retested? Is it very painful, but the above method of modifying the code can solve the problem, but it adds a lot of work (coding & testing).

Suddenly one day, the leader said that these time-consuming statistics should be sent to the monitoring system for monitoring and alarming.

At this time, is it necessary to modify the above code again? Going to test again? At this point the system is difficult to maintain.

Also, if the above classes are provided to us by a third party in the form of jar packages, these classes are all class files at this time, and we cannot modify the source code at this time.

Static proxy mode

A better way: you can create a proxy class for the IService interface, and use this proxy class to indirectly access the implementation class of the IService interface. In this proxy class, do time-consuming and send to the monitoring code. The code is as follows:

package com.javacode2018.lesson001.demo15;
 
// IService的代理类
public class ServiceProxy implements IService {
    //目标对象,被代理的对象
    private IService target;
 
    public ServiceProxy(IService target) {
        this.target = target;
    }
 
    @Override
    public void m1() {
        long starTime = System.nanoTime();
        this.target.m1();
        long endTime = System.nanoTime();
        System.out.println(this.target.getClass() + ".m1()方法耗时(纳秒):" + (endTime - starTime));
    }
 
    @Override
    public void m2() {
        long starTime = System.nanoTime();
        this.target.m1();
        long endTime = System.nanoTime();
        System.out.println(this.target.getClass() + ".m1()方法耗时(纳秒):" + (endTime - starTime));
    }
 
    @Override
    public void m3() {
        long starTime = System.nanoTime();
        this.target.m1();
        long endTime = System.nanoTime();
        System.out.println(this.target.getClass() + ".m1()方法耗时(纳秒):" + (endTime - starTime));
    }
}

ServiceProxy is the proxy class of the IService interface. The target is the proxied object, that is, the object that actually needs to be accessed. It also implements the IService interface. The above three methods add statistical time-consuming code. When we need to access other implementations of IService class, it can be accessed indirectly through ServiceProxy, the usage is as follows:

@Test
public void serviceProxy() {
    IService serviceA = new ServiceProxy(new ServiceA());//@1
    IService serviceB = new ServiceProxy(new ServiceB()); //@2
    serviceA.m1();
    serviceA.m2();
    serviceA.m3();
 
    serviceB.m1();
    serviceB.m2();
    serviceB.m3();
}


The above code focuses on @1 and @2. The proxy object ServiceProxy is created. The object accessed by the proxy is passed in the ServiceProxy construction method. Now when we access ServiceA or ServiceB, we need to go through ServiceProxy. The output of the operation is:

I am the m1 method in ServiceA!
class com.javacode2018.lesson001.demo15.ServiceA.m1() method time-consuming (nanoseconds): 90100
I am the m1 method in ServiceA!
class com.javacode2018.lesson001.demo15.ServiceA. m1() method time-consuming (nanoseconds): 31600
I am the m1 method in ServiceA!
class com.javacode2018.lesson001.demo15.ServiceA.m1() method time-consuming (nanoseconds): 25800
I am the m1 method in ServiceB !
class com.javacode2018.lesson001.demo15.ServiceB.m1() method time-consuming (nanoseconds): 142100
I am the m1 method in ServiceB!
class com.javacode2018.lesson001.demo15.ServiceB.m1() method time-consuming ( nanoseconds): 35000
I am the m1 method in ServiceB!
class com.javacode2018.lesson001.demo15.ServiceB.m1() method time-consuming (nanoseconds): 32900

In the above implementation, we did not modify the methods in ServiceA and ServiceB, but created a proxy class for the IService interface, through which to access the target object, some common functions that need to be added are placed in the proxy, when the leader has other needs , we only need to modify the code of ServiceProxy to facilitate system expansion and testing.

If now we need to add time-consuming statistical functions to all interfaces in the system, if we follow the above method, we need to create a proxy class for each interface. At this time, the amount of code and testing workload is also huge, then we Can you write a general proxy class to meet the above functions?

2 implementations of generic proxy:

jdk dynamic proxy

cglib proxy

jdk dynamic agent detailed
jdk provides support for implementing agents, mainly using two classes:

java.lang.reflect.Proxy
java.lang.reflect.InvocationHandler

There is a limitation on the use of the proxy that comes with jdk. You can only create a proxy class for the interface . If you need to create a proxy class for a specific class, you need to use the cglib that will be mentioned later.

java.lang.reflect.Proxy
This is the main class in the jdk dynamic proxy. There are some static methods that are often used. Let's get familiar with it:

getProxyClass method

Create a proxy class for the specified interface and return the Class object of the proxy class

public static Class<?> getProxyClass(ClassLoader loader,
                                         Class<?>... interfaces)

Parameter Description:

loader : the class loader that defines the proxy class

interfaces : Specify the list of interfaces that need to be implemented, and the created proxy will implement the interfaces specified by interfaces in order by default

newProxyInstance method

Create an instance object of the proxy class

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)

This method first creates a proxy class for the specified interface, and then generates an instance of the proxy class. The last parameter is special and is of type InvocationHandler. This is an excuse as follows:

public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;

the above method will return a proxy object, when calling any method of the proxy object, it will be processed by the invoke method of the InvocationHandler interface, so the main code needs In the uninstall invoke method, there will be a case in detail later.

isProxy method

Determines whether the specified class is a proxy class

public static boolean isProxyClass(Class<?> cl)

getInvocationHandler method

Get the InvocationHandler object of the proxy object

public static InvocationHandler getInvocationHandler(Object proxy)
        throws IllegalArgumentException


Let’s familiarize ourselves with the above methods. Next, let’s look at the two specific ways to create an agent .

Creating a Proxy: One
Step Method

1. Call the Proxy.getProxyClass method to obtain the Class object of the proxy class
2. Use the InvocationHandler interface to create a proxy class processor
3. Create a proxy object through the proxy class and InvocationHandler
4. The proxy object has been created above, and then we can use the proxy target

the case

First come an interface IService

package com.javacode2018.lesson001.demo16;
 
public interface IService {
    void m1();
    void m2();
    void m3();
}


Create a proxy object for the IService interface

@Test
public void m1() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
    // 1. 获取接口对应的代理类
    Class<IService> proxyClass = (Class<IService>) Proxy.getProxyClass(IService.class.getClassLoader(), IService.class);
    // 2. 创建代理类的处理器
    InvocationHandler invocationHandler = new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("我是InvocationHandler,被调用的方法是:" + method.getName());
            return null;
        }
    };
    // 3. 创建代理实例
    IService proxyService = proxyClass.getConstructor(InvocationHandler.class).newInstance(invocationHandler);
    // 4. 调用代理的方法
    proxyService.m1();
    proxyService.m2();
    proxyService.m3();
}

run output

I am InvocationHandler, the called method is: m1
I am InvocationHandler, the called method is: m2
I am InvocationHandler, the called method is: m3


Create proxy: Method 2


There is an easier way to create proxy objects.

step

1. Use the InvocationHandler interface to create a proxy class processor
2. Use the static method newProxyInstance of the Proxy class to directly create a proxy object
3. Use the proxy object

the case

@Test
public void m2() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
    // 1. 创建代理类的处理器
    InvocationHandler invocationHandler = new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("我是InvocationHandler,被调用的方法是:" + method.getName());
            return null;
        }
    };
    // 2. 创建代理实例
    IService proxyService = (IService) Proxy.newProxyInstance(IService.class.getClassLoader(), new Class[]{IService.class}, invocationHandler);
    // 3. 调用代理的方法
    proxyService.m1();
    proxyService.m2();
    proxyService.m3();
}

Run output:

I am InvocationHandler, the called method is: m1
I am InvocationHandler, the called method is: m2
I am InvocationHandler, the called method is: m3

Case: Time-consuming statistics of methods in any interface

Next, we implement a general proxy through the jdk dynamic proxy to solve the time-consuming problem of counting all interface methods.

The main code is implemented on the agent processor InvocationHandler, as follows:

package com.javacode2018.lesson001.demo16;
 
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
 
public class CostTimeInvocationHandler implements InvocationHandler {
 
    private Object target;
 
    public CostTimeInvocationHandler(Object target) {
        this.target = target;
    }
 
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        long starTime = System.nanoTime();
        Object result = method.invoke(this.target, args);//@1
        long endTime = System.nanoTime();
        System.out.println(this.target.getClass() + ".m1()方法耗时(纳秒):" + (endTime - starTime));
        return result;
    }
 
    /**
     * 用来创建targetInterface接口的代理对象
     *
     * @param target          需要被代理的对象
     * @param targetInterface 被代理的接口
     * @param <T>
     * @return
     */
    public static <T> T createProxy(Object target, Class<T> targetInterface) {
        if (!targetInterface.isInterface()) {
            throw new IllegalStateException("targetInterface必须是接口类型!");
        } else if (!targetInterface.isAssignableFrom(target.getClass())) {
            throw new IllegalStateException("target必须是targetInterface接口的实现类!");
        }
        return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new CostTimeInvocationHandler(target));
    }
}


The above is mainly the createProxy method used to create proxy objects, 2 parameters:

target: the target object, which needs to implement the targetInterface interface

targetInterface: the interface that needs to create a proxy

In the invoke method, the target method is invoked through method.invoke(this.target, args), and then the time-consuming of the method is counted.

test case

@Test
public void costTimeProxy() {
    IService serviceA = CostTimeInvocationHandler.createProxy(new ServiceA(), IService.class);
    IService serviceB = CostTimeInvocationHandler.createProxy(new ServiceB(), IService.class);
    serviceA.m1();
    serviceA.m2();
    serviceA.m3();
 
    serviceB.m1();
    serviceB.m2();
    serviceB.m3();
}


run output

I am the m1 method in ServiceA!
class com.javacode2018.lesson001.demo16.ServiceA.m1() method time-consuming (nanoseconds): 61300
I am the m2 method in ServiceA!
class com.javacode2018.lesson001.demo16.ServiceA. m1() method time-consuming (nanoseconds): 22300
I am the m3 method in ServiceA!
class com.javacode2018.lesson001.demo16.ServiceA.m1() method time-consuming (nanoseconds): 18700
I am the m1 method in ServiceB !
class com.javacode2018.lesson001.demo16.ServiceB.m1() method time-consuming (nanoseconds): 54700
I am the m2 method in ServiceB!
class com.javacode2018.lesson001.demo16.ServiceB.m1() method time-consuming ( nanoseconds): 27200
I am the m3 method in ServiceB!
class com.javacode2018.lesson001.demo16.ServiceB.m1() method time-consuming (nanoseconds): 19800


Our next interface also needs to count time-consuming functions. At this time, we can achieve the same function without creating a new proxy class, as follows:

IUserService interface

package com.javacode2018.lesson001.demo16;
 
public interface IUserService {
    /**
     * 插入用户信息
     * @param name
     */
    void insert(String name);
}
IUserService接口实现类:

package com.javacode2018.lesson001.demo16;
 
public class UserService implements IUserService {
    @Override
    public void insert(String name) {
        System.out.println(String.format("用户[name:%s]插入成功!", name));
    }
}


test case

@Test
public void userService() {
    IUserService userService = CostTimeInvocationHandler.createProxy(new UserService(), IUserService.class);
    userService.insert("路人甲Java");
}


Run output:

User [name: PasserbyJava] was inserted successfully!
class com.javacode2018.lesson001.demo16.UserService.m1() method time-consuming (nanoseconds): 193000


When we create a new interface above, we don’t need to create a new proxy class. We just need to use CostTimeInvocationHandler.createProxy to create a new proxy object, which is much more convenient.

Proxy use Note
1. Proxy in jdk can only generate proxy classes for interfaces. If you want to create proxy classes for a certain class, Proxy is powerless. At this time, we need to use cglib to be mentioned below.

2. You need to master several commonly used static methods provided in the Proxy class

3. Create a proxy object through Proxy. When calling any method of the proxy object, it will be processed by the invoke method in the InvocationHandler interface. The content of this interface is the key

Detailed explanation of cglib proxy


what is cglib


The jdk dynamic proxy can only create a proxy for the interface, and there are limitations in its use. In the actual scene, our class does not necessarily have an interface. At this time, if we want to implement the proxy function for ordinary classes, we need to use cglib to achieve it.

cglib is a powerful, high-performance bytecode generation library, which is used to extend Java classes and implement interfaces at runtime; in essence, it dynamically generates a subclass to cover the class to be proxied (non-final modified class and methods). Enhancer may be the most commonly used class in CGLIB. Unlike Proxy in jdk, Enhancer can represent both ordinary classes and interfaces. Enhancer creates a subclass of the proxy object and intercepts all method calls (including the toString and hashCode methods inherited from Object). Enhancer cannot intercept final methods, such as the Object.getClass() method, which is determined by the semantics of Java final methods. Based on the same reason, Enhancer cannot perform proxy operations on final classes.

As an open source project, CGLIB's code is hosted on github at the address:

https://github.com/cglib/cglib


cglib composition structure


The bottom layer of CGLIB uses ASM (a short and concise bytecode manipulation framework) to manipulate bytecodes to generate new classes. In addition to the CGLIB library, scripting languages ​​such as Groovy and BeanShell also use ASM to generate bytecode. ASM uses a SAX-like parser to achieve high performance. We discourage direct use of ASM, as it requires sufficient knowledge of the format of Java bytecode.

Spring has integrated all the classes in the third-party cglib jar package into spring's own jar package. The content of this series is related to spring. For convenience, we directly use the integrated ones in spring to explain

5 cases to demonstrate the common usage of cglib


Case 1: Intercept all methods (MethodInterceptor)

Create a concrete class as follows:

package com.javacode2018.lesson001.demo17;
 
public class Service1 {
    public void m1() {
        System.out.println("我是m1方法");
    }
 
    public void m2() {
        System.out.println("我是m2方法");
    }
}


Next, we create a proxy for this class, which prints the call log of each method.

package com.javacode2018.lesson001.demo17;
 
import org.junit.Test;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
 
import java.lang.reflect.Method;
 
public class CglibTest {
 
    @Test
    public void test1() {
        //使用Enhancer来给某个类创建代理类,步骤
        //1.创建Enhancer对象
        Enhancer enhancer = new Enhancer();
        //2.通过setSuperclass来设置父类型,即需要给哪个类创建代理类
        enhancer.setSuperclass(Service1.class);
        /*3.设置回调,需实现org.springframework.cglib.proxy.Callback接口,
        此处我们使用的是org.springframework.cglib.proxy.MethodInterceptor,也是一个接口,实现了Callback接口,
        当调用代理对象的任何方法的时候,都会被MethodInterceptor接口的invoke方法处理*/
        enhancer.setCallback(new MethodInterceptor() {
            /**
             * 代理对象方法拦截器
             * @param o 代理对象
             * @param method 被代理的类的方法,即Service1中的方法
             * @param objects 调用方法传递的参数
             * @param methodProxy 方法代理对象
             * @return
             * @throws Throwable
             */
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                System.out.println("调用方法:" + method);
                //可以调用MethodProxy的invokeSuper调用被代理类的方法
                Object result = methodProxy.invokeSuper(o, objects);
                return result;
            }
        });
        //4.获取代理对象,调用enhancer.create方法获取代理对象,这个方法返回的是Object类型的,所以需要强转一下
        Service1 proxy = (Service1) enhancer.create();
        //5.调用代理对象的方法
        proxy.m1();
        proxy.m2();
    }
}


The comments in the above code are very detailed, listing the specific steps to create a proxy for the specified class. The Enhancer class and the MethodInterceptor interface are mainly used in the whole process.

enhancer.setSuperclass is used to set the parent class of the proxy class, that is, which class needs to create a proxy class, here is Service1

enhancer.setCallback passes parameters of the MethodInterceptor interface type, and the MethodInterceptor interface has an intercept method, which intercepts all method calls of the proxy object.

Another important point is that Object result = methodProxy.invokeSuper(o, objects); can call the proxy class, that is, the specific method in the Service1 class. From the meaning of the method name, it can be seen that the call to the parent class is actually for a certain The class creates a proxy, and the bottom layer of cglib creates a subclass for the Service1 class by modifying the bytecode.

Run output:

Calling method: public void com.javacode2018.lesson001.demo17.Service1.m1()
I am the m1 method
Calling method: public void com.javacode2018.lesson001.demo17.Service1.m2()
I am the m2 method
It can be seen from the output Both methods in Service1 are intercepted by invoke in MethodInterceptor.

Case 2: Intercept all methods (MethodInterceptor)

Create a class as follows:

package com.javacode2018.lesson001.demo17;
 
 
public class Service2 {
    public void m1() {
        System.out.println("我是m1方法");
        this.m2(); //@1
    }
 
    public void m2() {
        System.out.println("我是m2方法");
    }
}


This class is similar to Service1 above, the difference is that @1 calls the m2 method in the m1 method.

Let's use the same method in Case 1 to create a proxy for Service2, as follows:

@Test
public void test2() {
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(Service2.class);
    enhancer.setCallback(new MethodInterceptor() {
        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            System.out.println("调用方法:" + method);
            Object result = methodProxy.invokeSuper(o, objects);
            return result;
        }
    });
    Service2 proxy = (Service2) enhancer.create();
    proxy.m1(); //@1
}


Pay attention to the code of @1 above, only the m1 method is called, and look at the output effect:

Call method: public void com.javacode2018.lesson001.demo17.Service2.m1()
I am the m1 method
Call method: public void com.javacode2018.lesson001.demo17.Service2.m2()
I am the m2 method


It can be seen from the output that the m1 and m2 methods are both processed by the interceptor, and the m2 method is called in the m1 method of Service1 and is also intercepted.

The @configuration annotation in spring is implemented in this way. Let me give you a familiar @configuration case:

package com.javacode2018.lesson001.demo17;
 
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
 
@Configuration
public class Config {
 
    @Bean
    public C1 c1(){
        return new C1();
    }
    @Bean
    public C2 c2(){
        C1 c1 = this.c1(); //@1
        return new C2(c1);
    }
    @Bean
    public C3 c3(){
        C1 c1 = this.c1(); //@2
        return new C3(c1);
    }
 
    public static class C1{}
 
    public static class C2{
        private C1 c1;
 
        public C2(C1 c1) {
            this.c1 = c1;
        }
    }
    public static class C3{
        private C1 c1;
 
        public C3(C1 c1) {
            this.c1 = c1;
        }
    }
 
}


In the above code, C2 and C3 depend on C1, and they are both injected into C1 through the constructor. Note that @1 and @2 in the code both call the c1 method to obtain C1 in the container. How to ensure that the C1 obtained multiple times is one? This place is implemented by using the cglib proxy to intercept the @Bean annotation.

Case 3: Intercept all methods and return a fixed value (FixedValue)

When calling any method of a certain class, you want to return a fixed value. At this time, you can use the FixedValue interface, as follows:

enhancer.setCallback(new FixedValue() {
            @Override
            public Object loadObject() throws Exception {
                return "路人甲";
            }
        });


For the proxy object created above, calling any of its methods will return "Passerby A".

The case code is as follows:

Create a class Service3, as follows:

package com.javacode2018.lesson001.demo17;
 
public class Service3 {
    public String m1() {
        System.out.println("我是m1方法");
        return "hello:m1";
    }
 
    public String m2() {
        System.out.println("我是m2方法");
        return "hello:m2";
    }
}


Applicable test cases:

@Test
public void test3() {
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(Service3.class);
    enhancer.setCallback(new FixedValue() {
        @Override
        public Object loadObject() throws Exception {
            return "路人甲";
        }
    });
    Service3 proxy = (Service3) enhancer.create();
    System.out.println(proxy.m1());//@1
    System.out.println(proxy.m2()); //@2
    System.out.println(proxy.toString());//@3
}


@1, @2, @3 invoked 3 methods of the proxy object, and the running output:

Run output:

A passerby A
passerby A
passerby

It can be seen that the output is the value of a vault.

Case 4: Release directly without any operation (NoOp.INSTANCE)

There is a sub-interface org.springframework.cglib.proxy.NoOp under the Callback interface. When this is used as a Callback, the called method will be released directly, as if there is no proxy, feel the effect:

@Test
public void test6() {
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(Service3.class);
    enhancer.setCallback(NoOp.INSTANCE);
    Service3 proxy = (Service3) enhancer.create();
    System.out.println(proxy.m1());
    System.out.println(proxy.m2());
}


Run output:

I am m1 method
hello:m1
I am m2 method
hello:m2


It can be seen from the output that the called method is not processed by the proxy, and directly enters the method of the target class Service3.

Case 5: Different methods use different interceptors (CallbackFilter)

There is a class as follows:

package com.javacode2018.lesson001.demo17;
 
public class Service4 {
    public void insert1() {
        System.out.println("我是insert1");
    }
 
    public void insert2() {
        System.out.println("我是insert2");
    }
 
    public String get1() {
        System.out.println("我是get1");
        return "get1";
    }
 
    public String get2() {
        System.out.println("我是get2");
        return "get2";
    }
}


Requirements, creating a proxy for this class needs to implement the following functions:

The method starting with insert requires time-consuming statistical methods

The method starting with get directly returns a fixed string `Welcome to learn spring with [passerby java]! `

Let's look at the code and then explain:

@Test
public void test4() {
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(Service4.class);
    //创建2个Callback
    Callback[] callbacks = {
            //这个用来拦截所有insert开头的方法
            new MethodInterceptor() {
                @Override
                public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                    long starTime = System.nanoTime();
                    Object result = methodProxy.invokeSuper(o, objects);
                    long endTime = System.nanoTime();
                    System.out.println(method + ",耗时(纳秒):" + (endTime - starTime));
                    return result;
                }
            },
            //下面这个用来拦截所有get开头的方法,返回固定值的
            new FixedValue() {
                @Override
                public Object loadObject() throws Exception {
                    return "路人甲Java";
                }
            }
    };
    enhancer.setCallbackFilter(new CallbackFilter() {
        @Override
        public int accept(Method method) {
            return 0;
        }
    });
    //调用enhancer的setCallbacks传递Callback数组
    enhancer.setCallbacks(callbacks);
    /**
     * 设置过滤器CallbackFilter
     * CallbackFilter用来判断调用方法的时候使用callbacks数组中的哪个Callback来处理当前方法
     * 返回的是callbacks数组的下标
     */
    enhancer.setCallbackFilter(new CallbackFilter() {
        @Override
        public int accept(Method method) {
            //获取当前调用的方法的名称
            String methodName = method.getName();
            /**
             * 方法名称以insert开头,
             * 返回callbacks中的第1个Callback对象来处理当前方法,
             * 否则使用第二个Callback处理被调用的方法
             */
            return methodName.startsWith("insert") ? 0 : 1;
        }
    });
    Service4 proxy = (Service4) enhancer.create();
    System.out.println("---------------");
    proxy.insert1();
    System.out.println("---------------");
    proxy.insert2();
    System.out.println("---------------");
    System.out.println(proxy.get1());
    System.out.println("---------------");
    System.out.println(proxy.get2());
 
}


Run output:

---------------
I am insert1
public void com.javacode2018.lesson001.demo17.Service4.insert1(), time-consuming (nanoseconds): 15396100
-------- -------
I am insert2
public void com.javacode2018.lesson001.demo17.Service4.insert2(), time-consuming (nanoseconds): 66200
---------------
passers-by A Java
---------------
Passerby A Java


Code description:

Since different methods need to be processed differently in the requirements, there are two Callback objects. When the method of the proxy object is called, which Callback will be used? At this time, it will be judged by the accept in the CallbackFilter. This method Returns the index into the callbacks array.

There is also a simple implementation of the above case, see case 6

Case 6: Optimization of Case 5 (CallbackHelper)

There is a CallbackHelper class in cglib, which can loop the code of Case 5. The CallbackHelper class is equivalent to encapsulating some codes to facilitate the realization of the requirements of Case 5. The implementation is as follows:

@Test
public void test5() {
    Enhancer enhancer = new Enhancer();
    //创建2个Callback
    Callback costTimeCallback = (MethodInterceptor) (Object o, Method method, Object[] objects, MethodProxy methodProxy) -> {
        long starTime = System.nanoTime();
        Object result = methodProxy.invokeSuper(o, objects);
        long endTime = System.nanoTime();
        System.out.println(method + ",耗时(纳秒):" + (endTime - starTime));
        return result;
    };
    //下面这个用来拦截所有get开头的方法,返回固定值的
    Callback fixdValueCallback = (FixedValue) () -> "路人甲Java";
    CallbackHelper callbackHelper = new CallbackHelper(Service4.class, null) {
        @Override
        protected Object getCallback(Method method) {
            return method.getName().startsWith("insert") ? costTimeCallback : fixdValueCallback;
        }
    };
    enhancer.setSuperclass(Service4.class);
    //调用enhancer的setCallbacks传递Callback数组
    enhancer.setCallbacks(callbackHelper.getCallbacks());
    /**
     * 设置CallbackFilter,用来判断某个方法具体走哪个Callback
     */
    enhancer.setCallbackFilter(callbackHelper);
    Service4 proxy = (Service4) enhancer.create();
    System.out.println("---------------");
    proxy.insert1();
    System.out.println("---------------");
    proxy.insert2();
    System.out.println("---------------");
    System.out.println(proxy.get1());
    System.out.println("---------------");
    System.out.println(proxy.get2());
 
}


Run output:

---------------
I am insert1
public void com.javacode2018.lesson001.demo17.Service4.insert1(), time-consuming (nanoseconds): 9777500
-------- -------
I am insert2
public void com.javacode2018.lesson001.demo17.Service4.insert2(), time-consuming (nanoseconds): 50600
---------------
passers-by A Java
---------------
Passerby A Java


The output effect is exactly the same as Case 4. The above focus is on CallbackHelper, which has some packaging. If you are interested, you can take a look at the source code, which is relatively simple.

Case 7: Time-consuming proxy class to implement general statistical arbitrary class methods

It is relatively simple to directly upload the code, as follows:

package com.javacode2018.lesson001.demo17;
 
 
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
 
import java.lang.reflect.Method;
 
public class CostTimeProxy implements MethodInterceptor {
    //目标对象
    private Object target;
 
    public CostTimeProxy(Object target) {
        this.target = target;
    }
 
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        long starTime = System.nanoTime();
        //调用被代理对象(即target)的方法,获取结果
        Object result = method.invoke(target, objects); //@1
        long endTime = System.nanoTime();
        System.out.println(method + ",耗时(纳秒):" + (endTime - starTime));
        return result;
    }
 
    /**
     * 创建任意类的代理对象
     *
     * @param target
     * @param <T>
     * @return
     */
    public static <T> T createProxy(T target) {
        CostTimeProxy costTimeProxy = new CostTimeProxy(target);
        Enhancer enhancer = new Enhancer();
        enhancer.setCallback(costTimeProxy);
        enhancer.setSuperclass(target.getClass());
        return (T) enhancer.create();
    }
}


We can directly use the above static method createProxy to create a proxy object for the target object target, and the proxied object automatically realizes the time-consuming statistics of method calls.

@1: Call the method of the proxy object to get the real result.

It is very simple to use, here is a test case, as follows:

@Test
public void test7() {
    //创建Service1代理
    Service1 service1 = CostTimeProxy.createProxy(new Service1());
    service1.m1();
 
    //创建Service3代理
    Service3 service3 = CostTimeProxy.createProxy(new Service3());
    System.out.println(service3.m1());
}


Run output:

I am m1 method
public void com.javacode2018.lesson001.demo17.Service1.m1(), time-consuming (nanoseconds): 53200
I am m1 method
public java.lang.String com.javacode2018.lesson001.demo17.Service3.m1( ), time-consuming (nanoseconds): 49200
hello:m1

 Case 7: Proxy Interface

First define an interface:

package example.proxy;
 
public interface Animal {
    String say();
 
    String food();
}


Another implementation class:

package example.proxy;
 
public class Dog implements Animal{
    @Override
    public String say() {
        System.out.println("汪");
        return "汪";
    }
 
    @Override
    public String food() {
        System.out.println("骨头");
        return "骨头";
    }
}


Test, jdk dynamic proxy and cglib proxy comparison:

package example.proxy;
 
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
 
import java.lang.reflect.Proxy;
 
public class ProxyMain {
    public static void main(String[] args) {
        cglibProxy();
    }
 
    public static void cglibProxy() {
        Dog dog = new Dog();
        Enhancer enhancer = new Enhancer();
//        enhancer.setSuperclass(Dog.class);
        enhancer.setInterfaces(new Class[]{Animal.class}); //注意这里需要设置代理的接口class数组
        enhancer.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> {
            System.out.println("start");
            Object invoke = methodProxy.invoke(dog, objects);
            System.out.println("end");
            return invoke;
        });
//        enhancer.setUseCache(false);
        Animal o = (Animal) enhancer.create();
        o.food();
    }
    public static void jdkProxy() {
        Dog dog = new Dog();
        Animal instance = (Animal) Proxy.newProxyInstance(Dog.class.getClassLoader(), Dog.class.getInterfaces(), (proxy, method, args1) -> {
            System.out.println("start");
            Object invoke = method.invoke(dog, args1);
            System.out.println("end");
            return invoke;
        });
        instance.food();
        instance.say();
    }
}


Results of the:

start
bone
end

In fact, cglib and jdk's dynamic proxy are very similar. In the end, the original method is executed through the proxy class, but jdk uses reflection, and cglib uses the fastcalss mechanism. 

The difference between CGLIB and Java dynamic proxy


1. Java dynamic proxy can only proxy interfaces, not common classes (because the parent class of all generated proxy classes is Proxy, and the Java class inheritance mechanism does not allow multiple inheritance); CGLIB can proxy common classes;

2. The Java dynamic agent uses the native Java reflection API to operate, which is more efficient in generating classes; CGLIB uses the ASM framework to directly operate on bytecodes, which is more efficient in the execution of classes

difference:


1. The java dynamic agent can only act as an agent for the implementation interface of the class, and cannot directly act as an agent for the class.

2. The cglib dynamic proxy can not only proxy the class directly but also proxy the interface.

3. The jdk dynamic proxy implements the InvocationHandler interface and the invoke() method

4. The cglib dynamic proxy implements the MethodInterceptor method interceptor intercept method;

5. Use the ASM framework to load the class file generated by the proxy object class, and modify its bytecode through the java reflection mechanism to generate a subclass for processing

The same points:
1. They are all done using the java reflection mechanism.

Think about usage scenarios
1. When to use jdk dynamic proxy and cglib dynamic proxy?

1. The target object generates an interface and uses JDK dynamic proxy by default

2. If the target object uses the interface, you can force the use of cglib

3. If the target object does not implement the interface, the cglib library must be used, and Spring will automatically convert between the JDK dynamic proxy and cglib

2. Is Cglib faster than JDK?

1. The bottom layer of cglib is the ASM bytecode generation framework, but the proxy class generated by bytecode technology is more efficient than using java reflection before JDL1.6

2. After jdk6, JDK dynamic proxy is gradually optimized, and the efficiency is higher than that of cglib proxy when the number of calls is relatively small.

3. The efficiency of cglib is high only when a large number of calls are made, but the efficiency of JDK is higher than that of cglib in 1.8

4. Cglib cannot proxy the method declared final, because cglib dynamically generates proxy objects, and the class modified by the final keyword is immutable and can only be referenced and cannot be modified;

3. How does Spring choose whether to use JDK or cglib?

1. When the bean implements the interface, the JDK proxy mode will be used

2. When the bean does not implement the interface, implement it with cglib

3. You can force the use of cglib (add <aop:aspectj-autoproxy proxyt-target-class="true"/> to the spring configuration)

Notice:

There is such a paragraph in the official documentation of the jar package of cglib

note

for this dynamic subclassing to work, the class that the spring container will subclass cannot be final, and the method to be overridden cannot be final either. also, testing a class that has an abstract method requires you to subclass the class yourself and to supply a stub implementation of the abstract method. finally, objects that have been the target of method injection cannot be serialized. as of spring 3.2 it is no longer necessary to add cglib to your classpath, because cglib classes are repackaged under org.springframework and distributed within the spring-core jar. this is done both for convenience as well as to avoid potential conflicts with other projects that use differing versions of cglib

Since spring3.2, the spring framework itself no longer needs the cglib jar package, because cjlib.jar has been integrated into the jar package of the spring project. In order to prevent other conflicts in the project that depend on the cglib version.

Attach a cglib proxy class

Use the following settings to generate the corresponding class file

System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\code\\cglib");​​​​​​​

import java.lang.reflect.Method;
import net.sf.cglib.core.ReflectUtils;
import net.sf.cglib.core.Signature;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Factory;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class LandlordSerivceImpl$$EnhancerByCGLIB$$f0007ca2 extends LandlordSerivceImpl implements Factory {
    private MethodInterceptor CGLIB$CALLBACK_0; // 拦截器(增强类)
    private static Object CGLIB$CALLBACK_FILTER;
    private static final Method CGLIB$rent$0$Method;
    private static final MethodProxy CGLIB$rent$0$Proxy;
    private static final Object[] CGLIB$emptyArgs; // 参数
    private static final Method CGLIB$without$1$Method; // 被代理方法
    private static final MethodProxy CGLIB$without$1$Proxy; // 代理方法
    private static final Method CGLIB$equals$2$Method; // 被代理方法
    private static final MethodProxy CGLIB$equals$2$Proxy; // 代理方法

    static void CGLIB$STATICHOOK1() {
        CGLIB$THREAD_CALLBACKS = new ThreadLocal();
        CGLIB$emptyArgs = new Object[0];
        // 代理类
        Class var0 = Class.forName("com.myproxy.landlord.LandlordSerivceImpl$$EnhancerByCGLIB$$f0007ca2");
        // 被代理类
        Class var1;
        // 方法集合
        Method[] var10000 = ReflectUtils.findMethods(new String[]{"rent", "()V", "without", "()V"}, (var1 = Class.forName("com.myproxy.landlord.LandlordSerivceImpl")).getDeclaredMethods());
        // 方法实例赋值给字段(被代理方法)
        CGLIB$rent$0$Method = var10000[0];
        // 方法实例赋值给字段(代理方法)
        CGLIB$rent$0$Proxy = MethodProxy.create(var1, var0, "()V", "rent", "CGLIB$rent$0");
    }

    final void CGLIB$rent$0() {
        super.rent();
    }
    //收组
    public final void rent() {
    	 // 获取拦截器(增强类)
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if (var10000 != null) {
        	// 拦截器(增强类)
            var10000.intercept(this, CGLIB$rent$0$Method, CGLIB$emptyArgs, CGLIB$rent$0$Proxy);
        } else {
            super.rent();
        }
    }

    final void CGLIB$without$1() {
        super.without();
    }

    //退租
    public final void without() { 
    	// 获取拦截器(增强类)
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if (var10000 != null) {
        	// 拦截器(增强类)
            var10000.intercept(this, CGLIB$without$1$Method, CGLIB$emptyArgs, CGLIB$without$1$Proxy);
        } else {
            super.without();
        }
    }

}

reference:

Cglib dynamic proxy in spring_spring opens cglib_唂雨云's Blog

Detailed Explanation of Spring Series Proxy (Java Dynamic Proxy & cglib Proxy)

Guess you like

Origin blog.csdn.net/qq_30436011/article/details/129659068