头图

Secret of dynamic agent, let you figure out dynamic agent thoroughly!

铂赛东
中文

Preface

The proxy mode is a design mode that can additionally extend the functions of the source target without modifying the source target. That is, by accessing the proxy class of the source target, the proxy class then visits the source target. In this way, to extend the functionality, there is no need to modify the source and target code. Just add to the proxy class.

1.png

In fact, the core idea of the proxy model is that simple. In Java, there are two types of proxy: static proxy and dynamic proxy. Dynamic proxy distinguishes between interface-based dynamic proxy and subclass-based dynamic proxy according to different implementations.

The static agent is relatively simple, and there is nothing to ask in the interview. In the agent mode, the most asked is the dynamic agent, and the dynamic agent is also the core idea of spring aop. Many other functions of spring are also realized through dynamic agents, such as Interceptor, transaction control, etc.

Proficiency in dynamic agent technology can make your business code more streamlined and elegant. If you need to write some middleware, then dynamic agent technology is an indispensable skill pack.

Then this article will show you all the details of the dynamic agent.

Static proxy

Before talking about dynamic proxy, let's talk about static proxy first.

The so-called static proxy is to access the source object by declaring a clear proxy class.

We have 2 interfaces, Person and Animal. Each interface has two implementation classes, UML is as follows:

2.png

The code in each implementation class is almost the same, use Student as an example (other classes are almost identical to this one)

public class Student implements Person{

    private String name;

    public Student() {
    }

    public Student(String name) {
        this.name = name;
    }

    @Override
    public void wakeup() {
        System.out.println(StrUtil.format("学生[{}]早晨醒来啦",name));
    }

    @Override
    public void sleep() {
        System.out.println(StrUtil.format("学生[{}]晚上睡觉啦",name));
    }
}

Suppose we have one thing to do now, which is to add a line of output Good morning~ wakeup() all implementation classes, and add a line of output Good night~ sleep() . Then we only need to write two proxy classes PersonProxy and AnimalProxy :

PersonProxy:

public class PersonProxy implements Person {

    private Person person;

    public PersonProxy(Person person) {
        this.person = person;
    }

    @Override
    public void wakeup() {
        System.out.println("早安~");
        person.wakeup();
    }

    @Override
    public void sleep() {
        System.out.println("晚安~");
        person.sleep();
    }
}

AnimalProxy:

public class AnimalProxy implements Animal {

    private Animal animal;

    public AnimalProxy(Animal animal) {
        this.animal = animal;
    }

    @Override
    public void wakeup() {
        System.out.println("早安~");
        animal.wakeup();
    }

    @Override
    public void sleep() {
        System.out.println("晚安~");
        animal.sleep();
    }
}

final execution code of

public static void main(String[] args) {
    Person student = new Student("张三");
    PersonProxy studentProxy = new PersonProxy(student);
    studentProxy.wakeup();
    studentProxy.sleep();

    Person doctor = new Doctor("王教授");
    PersonProxy doctorProxy = new PersonProxy(doctor);
    doctorProxy.wakeup();
    doctorProxy.sleep();

    Animal dog = new Dog("旺旺");
    AnimalProxy dogProxy = new AnimalProxy(dog);
    dogProxy.wakeup();
    dogProxy.sleep();

    Animal cat = new Cat("咪咪");
    AnimalProxy catProxy = new AnimalProxy(cat);
    catProxy.wakeup();
    catProxy.sleep();
}

Output:

早安~
学生[张三]早晨醒来啦
晚安~
学生[张三]晚上睡觉啦
早安~
医生[王教授]早晨醒来啦
晚安~
医生[王教授]晚上睡觉啦
早安~~
小狗[旺旺]早晨醒来啦
晚安~~
小狗[旺旺]晚上睡觉啦
早安~~
小猫[咪咪]早晨醒来啦
晚安~~
小猫[咪咪]晚上睡觉啦

Conclusion:

I believe that the code of the static proxy has no more to say, the code is very simple and easy to understand. Two proxy classes are used here, proxying the Person and Animal interfaces respectively.

Although this model is easy to understand, its shortcomings are also obvious:

  • There will be a large number of redundant proxy classes. Two interfaces are demonstrated here. If there are 10 interfaces, 10 proxy classes must be defined.
  • It is not easy to maintain. Once the interface changes, both the proxy class and the target class need to be changed.

JDK dynamic proxy

Dynamic proxy, in layman's terms: There is no need to create a java proxy class declaratively, but a "virtual" proxy class is generated during the running process and loaded by the ClassLoader. This avoids the need to declare a large number of proxy classes like static proxies.

JDK has supported the creation of dynamic proxy classes since version 1.3. There are only two main core classes: java.lang.reflect.Proxy and java.lang.reflect.InvocationHandler .

Still the previous example, the code implemented with the JDK dynamic proxy class is as follows:

Create a JdkProxy class for unified proxy:

public class JdkProxy implements InvocationHandler {

    private Object bean;

    public JdkProxy(Object bean) {
        this.bean = bean;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
        if (methodName.equals("wakeup")){
            System.out.println("早安~~~");
        }else if(methodName.equals("sleep")){
            System.out.println("晚安~~~");
        }

        return method.invoke(bean, args);
    }
}

execution code:

public static void main(String[] args) {
    JdkProxy proxy = new JdkProxy(new Student("张三"));
    Person student = (Person) Proxy.newProxyInstance(proxy.getClass().getClassLoader(), new Class[]{Person.class}, proxy);
    student.wakeup();
    student.sleep();

    proxy = new JdkProxy(new Doctor("王教授"));
    Person doctor = (Person) Proxy.newProxyInstance(proxy.getClass().getClassLoader(), new Class[]{Person.class}, proxy);
    doctor.wakeup();
    doctor.sleep();

    proxy = new JdkProxy(new Dog("旺旺"));
    Animal dog = (Animal) Proxy.newProxyInstance(proxy.getClass().getClassLoader(), new Class[]{Animal.class}, proxy);
    dog.wakeup();
    dog.sleep();

    proxy = new JdkProxy(new Cat("咪咪"));
    Animal cat = (Animal) Proxy.newProxyInstance(proxy.getClass().getClassLoader(), new Class[]{Animal.class}, proxy);
    cat.wakeup();
    cat.sleep();
}

explanation:

As you can see, compared to static proxy classes, no matter how many interfaces there are, only one proxy class is needed here. The core code is also very simple. The only points to note are the following 2 points:

  • JDK dynamic proxy needs to declare an interface. To create a dynamic proxy class, you must give this "virtual" class an interface. As you can see, at this time, each bean after the creation of the dynamic proxy class is no longer the original object.

    3.png

  • Why does JdkProxy still need to construct the original bean? Because after processing the additional functions, you need to execute the original bean method to complete the responsibilities of the agent.

    The core method of JdkProxy here is

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

Among them, proxy is the object after proxy (not the original object), method is the method to be proxied, and args is the parameter of the method.

If you don't pass the original bean and use method.invoke(proxy, args) directly, then you will fall into an endless loop.

can 16140174c85bc4 represent?

The dynamic proxy of JDK is also the most commonly used proxy method. Also called interface proxy. A few days ago, a small partner asked me in the group whether dynamic agents can act for one class at a time, and can multiple classes be used.

To put it bluntly, JDK dynamic proxy only generates classes based on the interface "out of thin air". As for the specific execution, they are all InvocationHandler the implementation class of 06140174c85c0c. In the above example, I need to continue to execute the logic of the original bean before constructing the original bean. As long as you need, you can construct any object into this proxy implementation class. In other words, you can pass in multiple objects, or you don't proxy any classes. Just generate multiple proxy instances "out of thin air" for a certain interface, and these multiple proxy instances will eventually enter InvocationHandler to execute a certain piece of common code.

Therefore, a practical scenario in previous projects is that I have multiple rule files defined in yaml, and by scanning the yaml files, a dynamic proxy class is generated for each yaml rule file. To achieve this, I only need to define an interface in advance and define InvocationHandler , and at the same time pass in the yaml parsed object. Eventually these dynamic proxy classes will enter the invoke method to execute a common logic.

Cglib dynamic proxy

Spring's default dynamic proxy implementation before 5.X has always been jdk dynamic proxy. But starting from 5.X, spring began to use Cglib as a dynamic proxy implementation by default. And springboot has also turned to Cglib dynamic proxy implementation from 2.X.

What caused the spring system to switch to Cglib as a whole, and what are the disadvantages of jdk dynamic proxy?

Then we will now talk about the dynamic proxy of Cglib.

Cglib is an open source project. Its bottom layer is the bytecode processing framework ASM. Cglib provides a more powerful dynamic proxy than jdk. The main advantages compared to jdk dynamic agent are:

  • The jdk dynamic proxy can only be based on the interface, and the object generated by the proxy can only be assigned to the interface variable. Cglib does not have this problem. Cglib is implemented by generating subclasses. The proxy object can be assigned to the implementation class or to the interface.
  • Cglib is faster than jdk dynamic proxy and has better performance.

So what is meant by subclassing?

Still the previous example, we want to achieve the same effect. code show as below

creates the CglibProxy class for unified proxy:

public class CglibProxy implements MethodInterceptor {

    private Enhancer enhancer = new Enhancer();

    private Object bean;

    public CglibProxy(Object bean) {
        this.bean = bean;
    }

    public Object getProxy(){
        //设置需要创建子类的类
        enhancer.setSuperclass(bean.getClass());
        enhancer.setCallback(this);
        //通过字节码技术动态创建子类实例
        return enhancer.create();
    }
    //实现MethodInterceptor接口方法
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        String methodName = method.getName();
        if (methodName.equals("wakeup")){
            System.out.println("早安~~~");
        }else if(methodName.equals("sleep")){
            System.out.println("晚安~~~");
        }

        //调用原bean的方法
        return method.invoke(bean,args);
    }
}

execution code:

public static void main(String[] args) {
    CglibProxy proxy = new CglibProxy(new Student("张三"));
    Student student = (Student) proxy.getProxy();
    student.wakeup();
    student.sleep();

    proxy = new CglibProxy(new Doctor("王教授"));
    Doctor doctor = (Doctor) proxy.getProxy();
    doctor.wakeup();
    doctor.sleep();

    proxy = new CglibProxy(new Dog("旺旺"));
    Dog dog = (Dog) proxy.getProxy();
    dog.wakeup();
    dog.sleep();

    proxy = new CglibProxy(new Cat("咪咪"));
    Cat cat = (Cat) proxy.getProxy();
    cat.wakeup();
    cat.sleep();
}

explanation:

Cglib is used as an agent here, and the idea is similar to that of jdk dynamic agent. You also need to pass in the original bean structure. The reasons are mentioned above, so I won't repeat them here.

The key code is here

//设置需要创建子类的类
enhancer.setSuperclass(bean.getClass());
enhancer.setCallback(this);
//通过字节码技术动态创建子类实例
return enhancer.create();

It can be seen that Cglib created a subclass of the original bean "out of thin air", and pointed the Callback to this, which is the current object, which is the proxy object. This will call the intercept method. In the intercept method, the additional function is executed, and finally the corresponding method of the original bean is called.

When debugging the generated proxy object, we can also see that Cglib generated a subclass of the original bean out of thin air:

4.png

javassist dynamic proxy

Javassist is an open source library for analyzing, editing and creating Java bytecodes, which can directly edit and generate bytecodes generated by Java. Compared with these tools such as bcel and asm, developers can dynamically change the structure of the class or generate the class dynamically without knowing the virtual machine instructions.

In daily use, javassit is usually used to dynamically modify the bytecode. It can also be used to implement dynamic proxy functions.

Not much to say, still the same example, I use javassist dynamic proxy to achieve it again

creates JavassitProxy to be used as a unified proxy:

public class JavassitProxy {

    private Object bean;

    public JavassitProxy(Object bean) {
        this.bean = bean;
    }

    public Object getProxy() throws IllegalAccessException, InstantiationException {
        ProxyFactory f = new ProxyFactory();
        f.setSuperclass(bean.getClass());
        f.setFilter(m -> ListUtil.toList("wakeup","sleep").contains(m.getName()));

        Class c = f.createClass();
        MethodHandler mi = (self, method, proceed, args) -> {
            String methodName = method.getName();
            if (methodName.equals("wakeup")){
                System.out.println("早安~~~");
            }else if(methodName.equals("sleep")){
                System.out.println("晚安~~~");
            }
            return method.invoke(bean, args);
        };
        Object proxy = c.newInstance();
        ((Proxy)proxy).setHandler(mi);
        return proxy;
    }
}

execution code:

public static void main(String[] args) throws Exception{
    JavassitProxy proxy = new JavassitProxy(new Student("张三"));
    Student student = (Student) proxy.getProxy();
    student.wakeup();
    student.sleep();

    proxy = new JavassitProxy(new Doctor("王教授"));
    Doctor doctor = (Doctor) proxy.getProxy();
    doctor.wakeup();
    doctor.sleep();

    proxy = new JavassitProxy(new Dog("旺旺"));
    Dog dog = (Dog) proxy.getProxy();
    dog.wakeup();
    dog.sleep();

    proxy = new JavassitProxy(new Cat("咪咪"));
    Cat cat = (Cat) proxy.getProxy();
    cat.wakeup();
    cat.sleep();
}

explanation:

The familiar formula, the familiar taste, and the general idea are similar. The original bean structure is also passed in. It can be seen that javassist also uses the "out of thin air" method of generating subclasses to solve the problem. At the end of the code, the target method of the original bean is also called to complete the proxy.

The more characteristic of javaassit is that it can use filter to set the required proxy method, which can be constructed like the Criteria constructor. The other code, if you read the previous code demonstration carefully, you should be able to easily understand it.

5.png

ByteBuddy dynamic proxy

ByteBuddy, bytecode buddy, it's pretty awesome when you hear it.

ByteBuddy is also a famous open source library, and like Cglib, it is also implemented based on ASM. There is also a more famous library called Mockito. I believe many people have used this stuff to write test cases. Its core is implemented based on ByteBuddy, which can dynamically generate mock classes, which is very convenient. In addition, another big application of ByteBuddy is java agent. Its main function is to intercept the class before it is loaded and insert its own code.

ByteBuddy is very powerful and an artifact. It can be applied in many scenarios. But here, I only introduce the use of ByteBuddy as a dynamic proxy. Regarding other usage methods, you may have to write a special article to describe it. Here, I will first dig a hole for myself.

Come, it's still a familiar example, a familiar formula. Let's implement the previous example again with ByteBuddy

creates ByteBuddyProxy and acts as a unified proxy:

public class ByteBuddyProxy {

    private Object bean;

    public ByteBuddyProxy(Object bean) {
        this.bean = bean;
    }

    public Object getProxy() throws Exception{
        Object object = new ByteBuddy().subclass(bean.getClass())
                .method(ElementMatchers.namedOneOf("wakeup","sleep"))
                .intercept(InvocationHandlerAdapter.of(new AopInvocationHandler(bean)))
                .make()
                .load(ByteBuddyProxy.class.getClassLoader())
                .getLoaded()
                .newInstance();
        return object;
    }

    public class AopInvocationHandler implements InvocationHandler {

        private Object bean;

        public AopInvocationHandler(Object bean) {
            this.bean = bean;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            String methodName = method.getName();
            if (methodName.equals("wakeup")){
                System.out.println("早安~~~");
            }else if(methodName.equals("sleep")){
                System.out.println("晚安~~~");
            }
            return method.invoke(bean, args);
        }
    }
}

execution code:

public static void main(String[] args) throws Exception{
    ByteBuddyProxy proxy = new ByteBuddyProxy(new Student("张三"));
    Student student = (Student) proxy.getProxy();
    student.wakeup();
    student.sleep();

    proxy = new ByteBuddyProxy(new Doctor("王教授"));
    Doctor doctor = (Doctor) proxy.getProxy();
    doctor.wakeup();
    doctor.sleep();

    proxy = new ByteBuddyProxy(new Dog("旺旺"));
    Dog dog = (Dog) proxy.getProxy();
    dog.wakeup();
    dog.sleep();

    proxy = new ByteBuddyProxy(new Cat("咪咪"));
    Cat cat = (Cat) proxy.getProxy();
    cat.wakeup();
    cat.sleep();
}

explanation:

The idea is still the same as before. By carefully observing the code, ByteBuddy also uses the method of creating subclasses to implement dynamic agents.

6.png

How is the performance of various dynamic agents

The implementation of the same example of 4 kinds of dynamic agents was introduced above. There are two types of proxy modes:

  • JDK dynamic proxy adopts the mode of interface proxy, proxy objects can only be assigned to interfaces, allowing multiple interfaces
  • Cglib, Javassist, ByteBuddy all adopt the mode of subclass proxy. The proxy object can be assigned to the interface or copied to the specific implementation class.

Spring5.X and Springboot2.X only use Cglib as a dynamic proxy implementation. Is cglib the best performance?

I did a simple and rude experiment here. I directly performed the above 4 pieces of execution code through a single-threaded synchronization loop multiple times, and used time to determine the performance of the 4 of them. You should be able to see some clues.

JDK PROXY循环10000遍所耗时:0.714970125秒
Cglib循环10000遍所耗时:0.434937833秒
Javassist循环10000遍所耗时:1.294194708秒
ByteBuddy循环10000遍所耗时:9.731999042秒

The results of the implementation are as above

Judging from the execution results, cglib is indeed the best. As for why the execution of ByteBuddy is so slow, it may not be due to the poor performance of ByteBuddy. It may also be that there is a problem with the writing of my test code and I have not found the correct way. So this can only be used as a rough reference.

It seems that Spring's choice of Cglib is reasonable.

finally

Dynamic proxy technology is an artifact for someone who often writes open source or middleware. This feature provides a new solution. This makes the code more elegant and simple. Dynamic agents are also of great help to understanding the core ideas of spring. I hope that children's shoes who are interested in dynamic agent technology can try to run the code in the example to strengthen their understanding.

Finally, attach all the code used in this article, I have uploaded it to Gitee:

https://gitee.com/bryan31/proxy-demo

If you have seen this and think this article can help you, please like and share the article, and I hope to pay attention to my public account. I am an open source author, and I will share my technology and life in my free time and grow up with you.

image.png

阅读 1.2k

开源作者&内容创作者,专注于架构,开源,微服务,分布式等领域的技术研究和原创分享

835 声望
8k 粉丝
0 条评论
你知道吗?

开源作者&内容创作者,专注于架构,开源,微服务,分布式等领域的技术研究和原创分享

835 声望
8k 粉丝
宣传栏