preface

The proxy pattern is a design pattern that enables additional extension of the functionality of the source target without modifying the source target. That is, the proxy class accesses the source target, and then the proxy class accesses the source target. In this way, to extend functionality, there is no need to modify the source target’s code. Just add it to the proxy class.

In fact, the core idea of proxy mode is so simple, in Java, the proxy is divided into static proxy and dynamic proxy two kinds, dynamic proxy according to different implementation and distinguish dynamic proxy based on interface and dynamic proxy based on subclass.

Static proxy due to relatively simple, the interview also did not ask what, in the proxy mode, the most asked is dynamic proxy, and dynamic proxy is the core idea of Spring AOP, spring many other functions are also through dynamic proxy to achieve, such as interceptor, transaction control, etc..

Proficient in dynamic proxy technology can make your business code more concise and elegant. Dynamic proxy technology is an essential skill set if you need to write middleware.

This article will take you through all the details of dynamic proxies.

Static agent

Before we talk about dynamic proxies, let’s talk about static proxies.

Static proxies access source objects by declaring an explicit proxy class.

We have two interfaces, Person and Animal. Each interface has two implementation classes, as shown in the UML diagram below:

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

public class Student implements Person{

    private String name;

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

    @Override
    public void wakeup(a) {
        System.out.println(StrUtil.format("The student [{}] wakes up in the morning.",name));
    }

    @Override
    public void sleep(a) {
        System.out.println(StrUtil.format("The students [{}] went to bed for the night.",name)); }}Copy the code

Suppose we now want to do one thing, which is to add a line of output good morning before wakeUp () and a line of output good night before sleep() for all implementation classes. PersonProxy and AnimalProxy:

PersonProxy:

public class PersonProxy implements Person {

    private Person person;

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

    @Override
    public void wakeup(a) {
        System.out.println("Good morning!");
        person.wakeup();
    }

    @Override
    public void sleep(a) {
        System.out.println("Good night!"); person.sleep(); }}Copy the code

AnimalProxy:

public class AnimalProxy implements Animal {

    private Animal animal;

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

    @Override
    public void wakeup(a) {
        System.out.println("Good morning!");
        animal.wakeup();
    }

    @Override
    public void sleep(a) {
        System.out.println("Good night!"); animal.sleep(); }}Copy the code

The final execution code is:

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

    Person doctor = new Doctor("Professor Wang");
    PersonProxy doctorProxy = new PersonProxy(doctor);
    doctorProxy.wakeup();
    doctorProxy.sleep();

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

    Animal cat = new Cat("Mimi");
    AnimalProxy catProxy = new AnimalProxy(cat);
    catProxy.wakeup();
    catProxy.sleep();
}
Copy the code

Output:

Good morning ~ student [Zhang SAN] wake up good night ~ student [Zhang SAN] sleep at night good morning ~ doctor [Professor Wang] wake up good night ~ doctor [Professor Wang] sleep at night good morning ~ dog [wang Wang] wake up good night ~ dog [wang Wang] sleep at night good morning ~ cat [Mimi] wake up in the morning Good night ~~ Kitty [Mimi] goes to bed at nightCopy the code

Conclusion:

The static proxy code is pretty straightforward. There are two proxy classes that represent the Person and Animal interfaces.

While this model is easy to understand, its drawbacks are obvious:

  • There can be a large number of redundant proxy classes, two interfaces are demonstrated here, and if there are 10 interfaces, 10 proxy classes must be defined.
  • Not easy to maintain, and both the proxy class and the target class need to change when the interface changes.

JDK dynamic proxy

Dynamic proxy, in layman’s terms: instead of declaratively creating Java proxy classes, a “virtual” proxy class is generated during runtime and loaded by the ClassLoader. This avoids the need to declare a large number of proxy classes for static proxies.

The JDK has supported dynamic proxy class creation since version 1.3. Only two main core classes: Java lang. Reflect. The Proxy and Java lang. Reflect. InvocationHandler.

Or the previous example, using the JDK dynamic proxy class to achieve the following code:

Create a JdkProxy class for the 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("Good morning ~ ~ ~");
        }else if(methodName.equals("sleep")){
            System.out.println("Good night ~ ~ ~");
        }

        returnmethod.invoke(bean, args); }}Copy the code

Execution code:

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

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

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

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

Explanation:

As you can see, as opposed to static proxy classes, no matter how many interfaces there are, only one proxy class is required. The core code is also simple. The only points to note are the following:

  • JDK dynamic proxies need to declare interfaces, and creating a dynamic proxy class must give the “virtual” class an interface. As you can see, each bean created by the dynamic proxy class is no longer the same object.

  • Why does the JdkProxy need to construct and pass in the original bean? This is because, in addition to handling the additional functionality, the methods of the original bean need to be executed to fulfill the responsibilities of the agent.

    The core method of JdkProxy here is

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

    Where proxy is the object after being propped (not the original object), method is the propped method, and args is the parameter of the method.

    If you use Method.invoke (proxy, args) instead of passing the original bean, you end up in an endless loop.

What can be represented

The JDK dynamic proxy is also the most commonly used proxy method. Also called interface proxy. A few days ago, one of my friends asked me in the group whether dynamic proxy can represent one class at a time, or whether multiple classes can be represented.

The JDK dynamic proxy simply generates classes “out of thin air” from the interface, and the actual execution is delegated to the implementation class of InvocationHandler. In the above example, I need to continue to execute the logic of the original bean to construct the original bean. You can construct any object you want into the proxy implementation class. That is, you can pass in multiple objects, or you don’t delegate any classes. Multiple proxy instances are simply generated “out of thin air” for an interface, and all of these instances end up in the implementation class of InvocationHandler to execute a common piece of code.

So, a practical scenario in previous projects would be if I had multiple yamL-defined rule files and generated a dynamic proxy class for each yamL rule file by scanning the yamL file. To do this, ALL I need to do is define an interface and an implementation class for InvocationHandler, and pass in the objects that YAML parses. Eventually these dynamic proxy classes all enter the Invoke method to execute some common logic.

Cglib dynamic proxy

Spring’s default dynamic proxy implementation prior to 5.x was always JDK dynamic proxy. But since 5.x, Spring has used Cglib as a dynamic proxy implementation by default. And Springboot has also moved to the Cglib dynamic proxy implementation since 2.x.

What caused the spring architecture to move to Cglib as a whole, and what are the disadvantages of JDK dynamic proxies?

So let’s talk about dynamic proxies for Cglib.

Cglib is an open source project with ASM as its underlying bytecode processing framework. Cglib provides more powerful dynamic proxies than the JDK. The main advantages over JDK dynamic proxies are:

  • JDK dynamic proxies can only be based on interfaces, and proxy-generated objects can only be assigned to interface variables. Cglib does not have this problem. Cglib is implemented by subclass generation, and proxy objects can be assigned to both implementation classes and interfaces.
  • Cglib is faster and performs better than JDK dynamic proxies.

So what does that mean by subclassing?

Again, in the previous example, we want to achieve the same effect. The following code

Create the CglibProxy class for the 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(a){
        // Set the class to subclass
        enhancer.setSuperclass(bean.getClass());
        enhancer.setCallback(this);
        // Create subclass instances dynamically using bytecode technology
        return enhancer.create();
    }
    // Implement the MethodInterceptor interface method
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        String methodName = method.getName();
        if (methodName.equals("wakeup")){
            System.out.println("Good morning ~ ~ ~");
        }else if(methodName.equals("sleep")){
            System.out.println("Good night ~ ~ ~");
        }

        // Call the method of the original bean
        returnmethod.invoke(bean,args); }}Copy the code

Execution code:

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

    proxy = new CglibProxy(new Doctor("Professor Wang"));
    Doctor doctor = (Doctor) proxy.getProxy();
    doctor.wakeup();
    doctor.sleep();

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

    proxy = new CglibProxy(new Cat("Mimi"));
    Cat cat = (Cat) proxy.getProxy();
    cat.wakeup();
    cat.sleep();
}
Copy the code

Explanation:

Cglib is used as a proxy here in much the same way as JDK dynamic proxies. You also need to pass in the original bean construct. The reasons mentioned above are not repeated here.

The key code is here

// Set the class to subclass
enhancer.setSuperclass(bean.getClass());
enhancer.setCallback(this);
// Create subclass instances dynamically using bytecode technology
return enhancer.create();
Copy the code

As you can see, Cglib creates a subclass of the original bean “out of thin air” and refers Callback to this, the current object, which is the proxy object. The Intercept method is called. In the Intercept method, additional functionality is performed and the corresponding method of the original bean is called.

When we debug the generated proxy object, we can also see that Cglib subclasses the original bean out of thin air:

Javassist dynamic proxy

Javassist is an open source library for analyzing, editing, and creating Java bytecodes that can be directly edited and generated by Java. In contrast to bcEL, ASM, and other tools, developers can dynamically change the structure of a class or dynamically generate a class without having to understand virtual machine instructions.

In everyday use, Javassit is often used to dynamically modify bytecode. It can also be used to implement dynamic proxy functions.

Without further ado, it’s the same example, and I’ll use the Javassist dynamic proxy to implement it again

Create JavassitProxy to be used as a unified proxy:

public class JavassitProxy {

    private Object bean;

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

    public Object getProxy(a) 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("Good morning ~ ~ ~");
            }else if(methodName.equals("sleep")){
                System.out.println("Good night ~ ~ ~");
            }
            return method.invoke(bean, args);
        };
        Object proxy = c.newInstance();
        ((Proxy)proxy).setHandler(mi);
        returnproxy; }}Copy the code

Execution code:

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

    proxy = new JavassitProxy(new Doctor("Professor Wang"));
    Doctor doctor = (Doctor) proxy.getProxy();
    doctor.wakeup();
    doctor.sleep();

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

    proxy = new JavassitProxy(new Cat("Mimi"));
    Cat cat = (Cat) proxy.getProxy();
    cat.wakeup();
    cat.sleep();
}
Copy the code

Explanation:

Familiar recipes, familiar flavors, the general idea is similar. Pass in the original bean construct as well. As you can see, JavAssist also solves this problem by subclassing “out of thin air,” and the end of the code also completes the proxy by calling the target method of the original bean.

The javaassit feature is that you can set the method of the proxy you want with a filter, which can be constructed like the Criteria constructor. The rest of the code should be easy to follow if you look closely at the previous code demo.

ByteBuddy dynamic proxy

ByteBuddy, that sounds awesome. No.

ByteBuddy is also a well-known open source library that, like Cglib, is based on an ASM implementation. There is also a better known library called Mockito, which many of you have used to write test cases. The core of the library is based on ByteBuddy, which can dynamically generate mock classes, which is very convenient. Another big application of ByteBuddy is the Java Agent, which intercepts a class and inserts its own code before it is loaded.

ByteBuddy is a powerful artifact. Can be used in many scenarios. But I’m only going to introduce ByteBuddy as a dynamic proxy, and I’ll probably write a whole article about other ways to use it, but I’ll dig a hole for myself.

So, again, familiar examples, familiar recipes. Let’s implement the previous example again with ByteBuddy

Create ByteBuddyProxy as a unified proxy:

public class ByteBuddyProxy {

    private Object bean;

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

    public Object getProxy(a) 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("Good morning ~ ~ ~");
            }else if(methodName.equals("sleep")){
                System.out.println("Good night ~ ~ ~");
            }
            returnmethod.invoke(bean, args); }}}Copy the code

Execution code:

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

    proxy = new ByteBuddyProxy(new Doctor("Professor Wang"));
    Doctor doctor = (Doctor) proxy.getProxy();
    doctor.wakeup();
    doctor.sleep();

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

    proxy = new ByteBuddyProxy(new Cat("Mimi"));
    Cat cat = (Cat) proxy.getProxy();
    cat.wakeup();
    cat.sleep();
}
Copy the code

Explanation:

Again, by looking at the code carefully, ByteBuddy also uses subclasses to implement dynamic proxies.

What is the performance of the various dynamic proxies

Four dynamic proxies were introduced for the same example. There are two modes of proxy:

  • JDK dynamic proxies adopt the interface proxy model, proxy objects can only be assigned to interfaces, allowing multiple interfaces
  • Cglib, Javassist, and ByteBuddy all use the subclass proxy model, which can be assigned to an interface or copied to a concrete implementation class

Spring5.x and SpringBoot2. X only use Cglib as a dynamic proxy implementation, is Cglib the best performance?

I have done a simple and crude experiment here, directly put the above four sections of execution code on a single line with the same step cycle multiple times, with time to determine the performance of their four. I think I can tell you something.

10000 JDK PROXY cycles :0.714970125 seconds Cglib cycles :0.434937833 seconds Javassist cycles :1.294194708 seconds ByteBuddy 10000 times :9.731999042 secondsCopy the code

The result is as follows

Indeed, it was CGLIb that performed best. As for why ByteBuddy is so slow, it’s not necessarily because of poor ByteBuddy performance, but also because I didn’t write the test code the right way. So this is only a rough guide.

Spring’s choice of Cglib makes sense.

The last

Dynamic proxy technology is a magic touch for someone who writes open source or middleware. This feature offers a new solution. This makes the code more elegant and simple. Dynamic proxies are also a great help in understanding the core ideas of Spring, and it is hoped that those interested in dynamic proxy technology will try running through the code in the sample to strengthen their understanding.

Finally, I have attached all the code used in this article, which I have uploaded to Gitee:

Gitee.com/bryan31/pro…

If you have seen this and think this article can help you, please give it a thumbs up and share, also hope to follow my public account. I am an open source writer who will share technology and life in my spare time and grow with you.