Java based proxy model

Proxy pattern is one of the common design patterns. It is intended to provide a proxy for a specified object to control access to the object. Agents in Java are divided into dynamic agents and static agents. Dynamic agents are widely used in Java, such as AOP implementation of Spring, remote RPC calls, etc. The biggest difference between static proxy and dynamic proxy is whether the proxy class is generated before or after the JVM is started. This paper will introduce the static agent and dynamic agent of Java, and the comparison between them. The focus is to introduce the principle and implementation of dynamic agent.

proxy pattern

Definition of Proxy Pattern: provide a proxy for other objects to control access to this object. In some cases, one object is not suitable or cannot directly reference another object, and the proxy object can act as an intermediary between the client and the target object. For example, the object to be accessed is on a remote machine. In an object-oriented system, direct access to some objects will bring a lot of trouble to users or system structure for some reasons (such as high object creation cost, or some operations need security control, or need out of process access). We can add an access layer to this object when accessing this object.

Composition of agents

The agent consists of the following three roles:

  • Abstract roles: business methods implemented by real roles are declared through interfaces or abstract classes.
  • Agent role: an abstract role is an agent of a real role. The abstract method is implemented through the business logic method of the real role, and its own operations can be attached.
  • Real role: implement Abstract roles and define the business logic to be implemented by real roles for proxy roles to call.

Advantages of agency

  1. Clear responsibilities: the real role is to realize the logic of the actual business, not related to non business logic (such as transaction management).
  2. Isolation: the proxy object can act as an intermediary between the client and the target object. The target object is not directly exposed to the client, so as to isolate the target object
  3. High scalability: the proxy object can flexibly extend the target object.

Examples of agents

We use an example of loading and displaying pictures to explain the working principle of the agent. Pictures are stored on the disk, and each IO will take more events. If we need to display pictures frequently, it will take a long time to read them from the disk every time. We use an agent to cache pictures. We only read pictures from the disk when we read them for the first time, and then read them from the cache. The source code example is as follows:

import java.util.*;
 
interface Image {
    public void displayImage();
}

//on System A 
class RealImage implements Image {
    private String filename;
    public RealImage(String filename) { 
        this.filename = filename;
        loadImageFromDisk();
    }

    private void loadImageFromDisk() {
        System.out.println("Loading   " + filename);
    }

    public void displayImage() { 
        System.out.println("Displaying " + filename); 
    }
}

//on System B 
class ProxyImage implements Image {
    private String filename;
    private Image image;
 
    public ProxyImage(String filename) { 
        this.filename = filename; 
    }
    public void displayImage() {
        if(image == null)
              image = new RealImage(filename);
        image.displayImage();
    }
}
 
class ProxyExample {
    public static void main(String[] args) {
        Image image1 = new ProxyImage("HiRes_10MB_Photo1");
        Image image2 = new ProxyImage("HiRes_10MB_Photo2");     
        
        image1.displayImage(); // loading necessary
        image2.displayImage(); // loading necessary
    }
}

Static proxy

Static proxy needs to define two classes in the program: target object class and proxy object class. In order to ensure the consistency of their behavior, the target object and proxy object implement the same interface. The proxy class information has been determined before the program runs, and the proxy object will contain the reference of the target object.

Give an example to illustrate the use of static agent: suppose we have an interface method for calculating employee wages, and an implementation class implements specific logic. What should we do if we need to add logs to the logic for calculating employee wages? Adding directly to the implementation logic of salary calculation will lead to the introduction of non business logic, which does not comply with the specification. At this time, we can introduce a log agent to output relevant log information before and after salary calculation.

  • The interface for calculating employee salary is defined as follows:
public interface Employee {
    double calculateSalary(int id);
}
  • The implementation classes for calculating employee wages are as follows:
public class EmployeeImpl {
    public double calculateSalary(int id){
        return 100;
    }
}
  • The proxy class with log is implemented as follows:
public class EmployeeLogProxy implements Employee {

    //The proxy class needs to contain an object reference to the target class
    private EmployeeImpl employee;

    //It also provides a construction method with parameters to specify which object to proxy
    public EmployeeProxyImpl(EmployeeImpl employee){
        this.employee = employee;
    }

    public double calculateSalary(int id) {

        //Log before calling the calculateSalary method of the target class
        System.out.println("Employee is currently being calculated: " + id + "After tax salary");
        double salary = employee.calculateSalary(id);
        System.out.println("Calculate employee: " + id + "End of after tax salary");
        // Log after calling the target class method
        return salary;
    }
}

Dynamic agent

The proxy object class of dynamic proxy is created when the program runs, while the static proxy object class is determined at the compilation time of the program, which is the biggest difference between the two. The advantage of dynamic proxy is that it no longer requires developers to write many proxy classes manually. For example, in the above example, if another Manager class needs logs for salary calculation logic, we need to create a new ManagerLogProxy to proxy objects. If there are many proxy objects, there will be many proxy classes to write.

Using dynamic proxy does not have this problem. A type of proxy only needs to be written once and can be applied to all proxy objects. For example, Employee and Manager mentioned above only need to abstract an interface related to salary calculation, and they can use the same set of dynamic agent logic to implement the agent.

Dynamic proxy example

Next, we use the logic of Employee and Manager to calculate salary in the above to show the usage of dynamic agent.

Interface abstraction

We know that both Employee and Manager have the logic of calculating salary, and the logic of calculating salary needs to be logged, so we need to abstract an interface for calculating salary:

public interface SalaryCalculator {
    double calculateSalary(int id);
}

Implementation of interface

public class EmployeeSalaryCalculator implements SalaryCalculator{
    public double calculateSalary(int id){
        return 100;
    }
}
public class ManagerSalaryCalculator implements SalaryCalculator{
    public double calculateSalary(int id){
        return 1000000;
    }
}

Create InvocationHandler for dynamic proxy

public class SalaryLogProxy implements InvocationHandler {
    private SalaryCalculator calculator;

    public SalaryLogProxy(SalaryCalculator calculator) {
        this.calculator = calculator;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("--------------begin-------------");
        Object invoke = method.invoke(subject, args);
        System.out.println("--------------end-------------");
        return invoke;
    }
}

Create proxy object

public class Main {

    public static void main(String[] args) {
        SalaryCalculator calculator = new ManagerSalaryCalculator();
        InvocationHandler calculatorProxy = new SalaryLogProxy(subject);
        SalaryCalculator proxyInstance = (SalaryCalculator) Proxy.newProxyInstance(calculatorProxy.getClass().getClassLoader(), subject.getClass().getInterfaces(), calculatorProxy);
        proxyInstance.calculateSalary(1);
    }

}

Dynamic agent source code analysis

The process of dynamic agent is shown in the following figure. You can see that the dynamic agent includes the following contents:

  • Target object: the object we need to proxy, corresponding to new ManagerSalaryCalculator() above.
  • Interface: the method that the target object and proxy object need to provide together, corresponding to the SalaryCalculator above.
  • Proxy proxy: used to generate proxy object classes.
  • Proxy object class: the proxy object obtained by proxy and corresponding parameters.
  • Class loader: the class loader used to load the proxy object class, corresponding to calculatorProxy.getClass().getClassLoader() above.

Proxy.newProxyInstance

The key code of dynamic proxy is Proxy.newProxyInstance(classLoader, interfaces, handler)

  • You can see that Proxy.newProxyInstance does two things: 1. Get the constructor of the proxy object class; 2. Instantiate the proxy object according to the constructor.
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
                Class<?>[] interfaces, InvocationHandler h) {
    Objects.requireNonNull(h);

    final Class<?> caller = System.getSecurityManager() == null
                                    ? null : Reflection.getCallerClass();

    /*
     * Look up or generate the designated proxy class and its constructor.
     */
    // Get the constructor of the proxy object class, which contains the construction and loading of the proxy object class
    Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);
 
    // Generate a proxy instance according to the constructor
    return newProxyInstance(caller, cons, h);
}

Proxy object class

By looking at the source code, we can find that the Proxy object classes extend the Proxy class and implement the methods in the specified interface. Because java cannot inherit more, Proxy classes have been inherited here, and other classes cannot be inherited. Therefore, the dynamic Proxy of JDK does not support the Proxy of implementation class, but only the Proxy of interface.

Tags: Java Spring

Posted on Sun, 19 Sep 2021 23:42:37 -0400 by Hard Styler