The proxy pattern is a common design pattern in software development. Its purpose is to let the caller not hold a reference to the specific operator, but to perform specific operations on the specific operator through the proxy. This paper compares and analyzes the implementation principle of static proxy and dynamic proxy respectively.

Static proxy implementation

Proxy interface:

public interface Person {
    String doSomething(int i);
}
Copy the code

Target audience:

public class Worker implements Person {
    @Override
    public String doSomething(int i) {
        System.out.println("I'm doing something by param: "+ i); }}Copy the code

Proxy object:

public class PersonProxy implements Person {
    private Worker worker = null;

    @Override
    public String doSomething(int i) {
        beforeDoSomething();
        if(worker == null) {
            worker = new Worker();
        }
        Stirng result = worker.doSomething();
        afterDoSomething();
        return result;
    }

    private void beforeDoSomething(a) {
        System.out.println("before doing something");
    }

    private void afterDoSomething(a) {
        System.out.println("after doing something"); }}Copy the code

Caller:

public class StaticProxyTest {
    public static void main(String[] args) {
        Person person = new PersonProxy();// instantiate the proxy object
        String result = person.doSomething(Awesome!);
        System.out.println("result: "+ result); }} Before doing something I'm doing something by param: 666
after doing something
result: 666
Copy the code

Limitations of static proxies

As you can see, a static proxy lets the caller no longer hold a reference to the operator directly, leaving all operations to the proxy. But static proxies have their limitations:

  1. If you need to add a method that requires a broker, the broker’s code must also be changed to accommodate the new operation.
  2. If you need an agent to represent another operator, you also need to extend the agent and make it more troublesome.

One might think of a strategy pattern and a factory pattern to solve these two problems separately, but is there a more subtle way? First, let’s take a look at how Java code executes.

Understand the Java code execution process

To get a basic understanding of how dynamic proxies work, start with the Java code execution flow:

The JVM first parses the.class file in binary form through the ClassLoader and generates instances for invocation before running the.class file. Our code execution logic works in the JVM’s runtime system. Can we generate.class files in our own code in the format of.class and then call a custom ClassLoader to load them? The answer is yes, so we can create a class on the fly.

Generate your own.class file

Of course, we don’t need to manually assemble.class files bit by bit. Currently, the most common bytecode generation tools are ASM and Javassist. According to this idea, the process of generating.class files is as follows:

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.CtNewMethod;
 
public class Test {
    public static void main(String[] args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        // Create AutoGenerateClass
        CtClass cc= pool.makeClass("com.guanpj.AutoGenerateClass");
        // Define the show method
        CtMethod method = CtNewMethod.make("public void show(){}", cc);
        // Insert method code
        method.insertBefore("System.out.println(\"I'm just test generate .class file by javassit..... \ ");");
        cc.addMethod(method);
        // Save the generated bytecode
        cc.writeFile("D://temp"); }}Copy the code

The generated.class file looks like this:

Decompile to view content:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.guanpj;

public class AutoGenerateClass {
    public void show(a) {
        System.out.println("I'm just test generate .class file by javassit.....");
    }

    public AutoGenerateClass(a) {}}Copy the code

As you can see, javassit generates a default constructor with no arguments in addition to the show() method.

Custom class loader loading

To enable the custom class to be loaded, we define a class loader to load the specified.class file:

public class CustomClassLoader extends ClassLoader {

    public CustomClassLoader(a) {}protectedClass<? > findClass(String className) { String path ="D://temp//" + className.replace("."."/ /") + ".class";
        byte[] classData = getClassData(path);
        return defineClass(className, classData, 0, classData.length);
    }

    private byte[] getClassData(String path) {
        try {
            InputStream ins = new FileInputStream(path);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int bufferSize = 4096;
            byte[] buffer = new byte[bufferSize];
            int bytesNumRead = 0;
            while((bytesNumRead = ins.read(buffer)) ! = -1) {
                baos.write(buffer, 0, bytesNumRead);
            }
            return baos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null; }}Copy the code

Next, use a ClassLoader to load the.class file you just generated:

public class TestLoadClass {
    public static void main(String[] args) throws Exception {
        CustomClassLoader classLoader = new CustomClassLoader();
        Class clazz = classLoader.findClass("com.guanpj.AutoGenerateClass");

        Object object = clazz.newInstance();
        Method showMethod = clazz.getMethod("show".null);
        showMethod.invoke(object, null); }}Copy the code

The background output is as follows:

The show method was successfully executed!

Use the Proxy class in the JDK for dynamic Proxy

The purpose of using dynamic proxies is to simplify the code, but whether IT’s ASM or Javassist, it’s not easy enough to do dynamic proxies, and that defeats the purpose. Let’s see how InvocationHandler works:

Create InvocationHandler:

public static class InvocationHandlerImpl implements InvocationHandler {
    Person person;

    // Inject the target object
    public InvocationHandlerImpl(Person person) {
        this.person = person;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("before calling method: " + method.getName());
        // reflection calls the target method and gets the return value
        Object result = method.invoke(person, args);
        System.out.println("after calling method: " + method.getName());
        // Return the value as the return value of the invoke method
        returnresult; }}Copy the code

Create proxy objects using dynamic proxies and use:

public class DynamicProxyTest {
    public static void main(String[] args) {
        // Instantiate the target object
        Person person = new Worker();
        // Instantiate InvocationHandler and pass in the target object
        InvocationHandlerImpl handler = new InvocationHandlerImpl(person);
        // Generate the proxy object and pass in InvocationHandler
        Person operationProxy = (Person) 
                Proxy.newProxyInstance(person.getClass().getClassLoader(),
                person.getClass().getInterfaces(), handler);
        // Call the target method
        String result = operationProxy.doSomething(777);
        System.out.println("result: "+ result); }} Before calling method: doSomething I'm doing something by param: 777
after calling method: doSomething
result: 777
Copy the code

Dynamic proxies are essentially the process by which the JVM dynamically creates and loads.class bytecode at run time. It generates a static proxy class at run time, and this static proxy class gets the target method of the proxy object by reflection.

public static class DynamicProxy implements Person {
    InvocationHandler handler;

    public DynamicProxy(InvocationHandler handler) {
        this.handler = handler;
    }

    @Override
    public String doSomething(int i) {
        try {
            return (String) handler.invoke(this,
                    Person.class.getMethod("doSomething".int.class),
                    new Object[] { i });
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return null; }}Copy the code

The process of proxying with this proxy object:

public static void main(String[] args) {
    // Instantiate the target object
    Person person = new Worker();
    // Instantiate InvocationHandler and pass in the target object
    InvocationHandlerImpl handler = new InvocationHandlerImpl(person);
    // Instantiate the proxy object and pass in InvocationHandler
    DynamicProxy dynamicProxy = new DynamicProxy(handler);
    // Call the target method
    String result = dynamicProxy.doSomething(888);
    System.out.println("result: "+ result); } Before calling method: doSomething I'm doing something by param: 888
after calling method: doSomething
result: 888
Copy the code

CGLIB is used for dynamic proxy

NewProxyInstance (ClassLoader loader, Class<? >[] interfaces, InvocationHandler h), interfaces, interfaces, interfaces, interfaces, interfaces, interfaces, interfaces, interfaces, interfaces CGLIB(Code Generation Library) solves this problem.

MethodInterceptorImpl:

public class MethodInterceptorImpl implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("before calling method:" + method.getName());
        proxy.invokeSuper(obj, args);
        System.out.println("after calling method:" + method.getName());
        return null; }}Copy the code

Caller:

public class ProxyTest {
    public static void main(String[] args) {
        Operator operator = new Operator();
        MethodInterceptorImpl methodInterceptorImpl = new MethodInterceptorImpl();

        // Initializes the reinforcer object
        Enhancer enhancer = new Enhancer();
        // Set the proxy class
        enhancer.setSuperclass(operator.getClass());
        // Set the proxy callback
        enhancer.setCallback(methodInterceptorImpl);

        // Create a proxy object
        Operator operationProxy = (Operator) enhancer.create();
        // Call the action methodoperationProxy.doSomething(); }}Copy the code

The process of dynamic proxy using CGLIB is divided into four steps:

  • Implement the MethodInterceptor interface using MethodInterceptorImpl, and do additional operations in the Intercept method
  • Create the enhancer Enhance and set up the proxied action class
  • Generating proxy classes
  • Invoke the action method of the proxy object

conclusion

Both static proxy and dynamic proxy can solve our problems to a certain extent. In the development process, we can choose the appropriate solution according to the actual situation. In conclusion, there are no good solutions, only suitable for their own projects, we should deeply study and understand the principles behind the solution, in order to be able to deal with the development process of the variable.

The code in this article has been uploaded to my Github. If you have different opinions on the content of this article, please leave a comment and we will discuss it together.