samwellwang

samwellwang

coder
twitter

Detailed Explanation of the Proxy Pattern

During the process of writing code, I saw many classes of Proxy, understood that it is the proxy pattern, and knew how the code proceeds.

During the process of writing code, I saw many classes of Proxy, understood that it is the proxy pattern, and knew how the code proceeds. However, I do not quite understand the details and principles of the proxy pattern.
Through online learning, I learned that
The proxy pattern is a common design pattern in Java, divided into static proxy and dynamic proxy based on the timing of creating the proxy class.

Static Proxy#

Static proxy is when the interface, the proxy class, and other components are determined at compile time. Before the program runs, the .class file of the proxy class has already been generated.
The main feature of the static proxy pattern is that there is a common interface, a concrete class, and a proxy class. The proxy class holds an instance of the concrete class and executes the methods of the concrete class instance on its behalf.

Example of Static Proxy#

I created a small example based on my idea. As a star, Jisoo must attend events, and Jisoo is supposed to attend a Chanel event, but due to health reasons, Jennie will attend on her behalf. The effect is assumed to be the same as Jisoo attending.
First, create a Person interface

public interface Person{
    //Attend event
    public void attendance();
}

Then, the implementation class of Idol:

public class Idol implements Person{
    String name;
    int age;
    Idol(String n,int a){
        this.name=n;
        this.age=a;
    }
    //Implement interface
    @Override
    public void attendance() {
        System.out.print(this.name+" attended the Chanel event");
    }
}

Then, the IdolProxy proxy class, which holds an Idol object. It also implements the Person interface like the Idol object.

public class IdolProxy implements Person{
    Idol idol;
    public IdolProxy(Person stu){
        if(stu.getClass()==Idol.class) {
            this.idol=(Idol)stu;
        }
    }
    @Override
    public void attendance() {
        idol.attendance();
    }
}   

Next is the test class

public static void main(String[] args) {
    Person Jisoo=new Idol("Jisoo",18);
    Person Jennie=new IdolProxy(Jisoo);
    Jennie.attendance();
}

The output is:

Jisoo attended the Chanel event

Instead of attending the event through Jisoo, it was done through Jennie's proxy, which is the proxy pattern;
There is a common interface (Person), an implementation class (Idol), and a proxy class (IdolProxy) that all implement the interface,
and the proxy class contains an instance object of the implementation class. The proxy pattern introduces a certain degree of indirectness when accessing the actual object, and this indirectness can add various purposes.
Here, the indirectness refers to not directly calling the actual object's methods, allowing us to add some other purposes during the proxy process, such as some functionalities of AOP.

Dynamic Proxy#

In static proxy, the proxy class is already implemented, which we have written, while dynamic proxy is a proxy class created dynamically at runtime. The proxy class implementation is not written in advance.
The main point is in the method Proxy.newProxyInstance(loader, interfaces, h); this method is a static method under Proxy, and Proxy is a class under java.lang.reflect.
This method is essentially an abstraction of the proxy class in the static proxy above, allowing our code to dictate how to proxy an object. Below is a simple implementation based on the previous example:

Example of Dynamic Proxy#

We still want Jennie to proxy Jisoo, who cannot attend the Chanel event. Most tutorials I found online explain from top to bottom, from creating the interface to implementing the Invocation interface class.
From my understanding, it is easier to comprehend from the bottom up. So let's start directly from the test class:

//Create an instance object, this object is the one being proxied
Person Jisoo = new Idol("Jisoo",18);

//Create an InvocationHandler associated with the proxy object, which is equivalent to the static proxy class idolProxy, but it is more powerful as it contains all class objects as an Object
InvocationHandler idolHandler = new IdolInvocationHandler<Person>(Jisoo);
//Create a proxy object Jennie to proxy Jisoo, every execution method of the proxy object will replace the execution of the invoke method in Invocation
Person Jennie = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, idolHandler);
//Proxy execution method
Jennie.attendance();

The first step Person Jisoo = new Idol(“Jisoo”,18); is straightforward, creating an instantiated object that implements the Person interface.
The key is the second step InvocationHandler idolHandler = new IdolInvocationHandler(Jisoo);
Here, InvocationHandler is java.lang.reflect.InvocationHandler; it is the interface provided by Java to implement dynamic proxy classes, and we need to implement this interface:

public class IdolInvocationHandler<T> implements InvocationHandler{

       //The object being proxied held by the invocationHandler
    T target;

    public IdolInvocationHandler(T target) {
       this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("11111");
        method.invoke(target, args);
        System.out.println("2222");
        return null;
    }
}

This interface defines the invoke method, which means to invoke or call. All methods of the proxied class are called through this method.
How to obtain the methods of the proxied class? It is through Java's reflection mechanism. The invoke method has three parameters: the first is the proxied object, the second is the method object, and the third is the parameters.
Next is:

Person Jennie = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, idolHandler);

This generates a proxy object Jennie. Proxy.newProxyInstance(loader, interfaces, h) contains three parameters: the first is the interface loader, the second is the list of proxy interfaces,
and the third is the object idolHandler of the class that implements the Invocation interface IdolInvocationHandler. Thus, when executing Jennie's attendance method, it will output:

11111
Jisoo attended the Chanel event
2222

This achieves the functionality of dynamic proxy. Note: Dynamic proxies can only proxy interfaces, not classes. The reason is that Java only supports single inheritance, and multiple inheritance relationships can only be achieved through interfaces.
Principle:
Essentially, it is about copying the interface, using these interfaces and the class loader to obtain the proxy class cl. Then, through reflection technology, copy the constructor of the proxy class (this part of the code is in the getConstructor0 method of the Class class), and finally create an object using this constructor while binding this object with InvocationHandler.

PS:

Person idolProxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, (proxy,method,agrs)->{
    if(method.getName()=="attendance") {
        System.out.println("1111");
         method.invoke(Jisoo, args);
         System.out.println("2222");
         return null;
    }
    return null;
});

In the step of creating the dynamic proxy object, it can also be written like this, using Java 8's new feature of Lambda expressions, so there is no need to write the implementation class of Invocation.

Reference Article

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.