Implementation of proxy mode in Java

Posted by llama on Mon, 07 Feb 2022 12:31:59 +0100

Chapter 3: detailed explanation of the above book https://qlygmwcx.blog.csdn.net/article/details/117279470 Today, let's take a look at what kind of requirements promote the upgrading and evolution of code, so as to describe how to use AOP in Spring in the next blog.

First, let's talk about the proxy mode. There are two proxy modes: one is static proxy and the other is dynamic proxy. Through the basic implementation of proxy mode in Java, we can access AOP of Spring framework from here.

From the perspective of virtual machine loading classes, the two agents are essentially the same. They add some extra behaviors based on the behavior of the original class, or even completely replace the original behavior.

The way of static proxy is that we manually replace these behaviors, and then let the compiler compile them for us. At the same time, we add some other things to the bytecode on the basis of the original class or replace the original things to produce a new type with the same but different behavior as the original class interface.

1. Basic function calculator

1.1 version 1, simple calculation

    MyCalculator calc=new MyCalculator();
    System.out.println(calc.add(10,10));;

package calc;

public interface ICalculator {
    /**
     * Add up
     * @param i
     * @param j
     * @return
     */
    public Integer add(Integer i,Integer j);

    /**
     * subtract 
     * @param i
     * @param j
     * @return
     */
    public Integer sub(Integer i,Integer j);

    /**
     * Multiply
     * @param i
     * @param j
     * @return
     */
    public Integer mul(Integer i,Integer j);

    /**
     * be divided by
     * @param i
     * @param j
     * @return
     */
    public Integer div(Integer i,Integer j);
}

public class MyCalculator implements ICalculator {

    public MyCalculator() {
        System.out.println("MyCalculator initialization");
    }

    @Override
    public Integer add(Integer i, Integer j) {
        Integer result = i + j;
        return result;
    }

    @Override
    public Integer sub(Integer i, Integer j) {
        Integer result = i - j;
        return result;
    }

    @Override
    public Integer mul(Integer i, Integer j) {
        Integer result = i * j;
        return result;
    }

    @Override
    public Integer div(Integer i, Integer j) {
        Integer result = i / j;
        return result;
    }
}

1.2 for all calculations, we need to add log information, including [before calculation], [after calculation], [calculation exception information] and [return information after calculation]

    MyCalculator calc=new MyCalculator();
    System.out.println(calc.add(10,10));
    System.out.println(calc.sub(10,10));
    System.out.println(calc.mul(10,10));
    System.out.println(calc.div(10,10));
    System.out.println(calc.div(10,0));


public class MyCalculator implements ICalculator {

    public MyCalculator() {
        System.out.println("MyCalculator initialization");
    }

    @Override
    public Integer add(Integer i, Integer j) {
        Method method = null;
        Integer result = 0;
        try {
            method = MyCalculator.class.getMethod("add", Integer.class, Integer.class);
            //Thread.currentThread().getStackTrace()[2].getClass().getEnclosingMethod()

            LogUtil.start(method, i, j);
            result = i + j;
        } catch (Exception e) {
            LogUtil.logException(method, e);
        } finally {
            LogUtil.logFinally(method);
        }

        LogUtil.stop(method, result);
        return result;
    }

    @Override
    public Integer sub(Integer i, Integer j) {

        Method method = null;
        Integer result = 0;
        try {
            method = MyCalculator.class.getMethod("sub", Integer.class, Integer.class);
            //Thread.currentThread().getStackTrace()[2].getClass().getEnclosingMethod()

            LogUtil.start(method, i, j);
            result = i - j;
        } catch (Exception e) {
            LogUtil.logException(method, e);
        } finally {
            LogUtil.logFinally(method);
        }

        LogUtil.stop(method, result);
        return result;
    }

    @Override
    public Integer mul(Integer i, Integer j) {

        Method method = null;
        Integer result = 0;
        try {
            method = MyCalculator.class.getMethod("mul", Integer.class, Integer.class);
            //Thread.currentThread().getStackTrace()[2].getClass().getEnclosingMethod()

            LogUtil.start(method, i, j);
            result = i * j;
        } catch (Exception e) {
            LogUtil.logException(method, e);
        } finally {
            LogUtil.logFinally(method);
        }

        LogUtil.stop(method, result);
        return result;
    }

    @Override
    public Integer div(Integer i, Integer j) {

        Method method = null;
        Integer result = 0;
        try {
            method = MyCalculator.class.getMethod("div", Integer.class, Integer.class);
            //Thread.currentThread().getStackTrace()[2].getClass().getEnclosingMethod()

            LogUtil.start(method, i, j);
            result = i / j;
        } catch (Exception e) {
            LogUtil.logException(method, e);
        } finally {
            LogUtil.logFinally(method);
        }

        LogUtil.stop(method, result);
        return result;
    }
}


public class LogUtil {

    /**
     * Function start execution information
     * @param method
     * @param pars
     */
    public static void start(Method method, Object... pars){
        System.out.println(String.format("function[%s]Start execution. The parameters are:%s",method.getName(), Arrays.asList(pars)));
    }

    /**
     * Function end execution information
     * @param method
     * @param pars
     */
    public static void stop(Method method, Object objReturn){
        System.out.println(String.format("function[%s]After starting, the return value is:%s",method.getName(),objReturn));
    }

    /**
     * Abnormal function information
     * @param method
     * @param pars
     */
    public static void logException(Method method,Exception e){
        System.out.println(String.format("function[%s]Execution exception. The exception information is:%s",method.getName(),e.getMessage()));
    }

    /**
     * Function completion information
     * @param method
     * @param pars
     */
    public static void logFinally(Method method){
        System.out.println(String.format("function[%s]Start over",method.getName()));
    }
}
MyCalculator initialization
 function[add]Start execution. The parameters are:[10, 10]
function[add]Start over
 function[add]After starting, the return value is: 20
20
 function[sub]Start execution. The parameters are:[10, 10]
function[sub]Start over
 function[sub]After starting, the return value is: 0
0
 function[mul]Start execution. The parameters are:[10, 10]
function[mul]Start over
 function[mul]After starting, the return value is: 100
100
 function[div]Start execution. The parameters are:[10, 10]
function[div]Start over
 function[div]After starting, the return value is: 1
1
 function[div]Start execution. The parameters are:[10, 0]
function[div]Execution exception. The exception information is:/ by zero
 function[div]Start over
 function[div]After starting, the return value is: 0
0

1.3 static proxy mode

    MyCalculator calc=new MyCalculator();
    System.out.println(calc.add(10,10));
    System.out.println(calc.sub(10,10));
    System.out.println(calc.mul(10,10));
    System.out.println(calc.div(10,10));
    try{
        System.out.println(calc.div(10,0));
    }
    catch (Exception e)
    {
        System.out.println(e.getMessage());
    }

    MyCalculatorStaticProxy staticProxy=newMyCalculatorStaticProxy(calc);
    System.out.println(staticProxy.add(10,10));
    System.out.println(staticProxy.sub(10,10));
    System.out.println(staticProxy.mul(10,10));
    System.out.println(staticProxy.div(10,10));
    System.out.println(staticProxy.div(10,0));

public class MyCalculator implements ICalculator {
    public MyCalculator() {
        System.out.println("MyCalculator initialization");
    }

    @Override
    public Integer add(Integer i, Integer j) {
        return i + j;
    }

    @Override
    public Integer sub(Integer i, Integer j) {
        return i - j;
    }

    @Override
    public Integer mul(Integer i, Integer j) {
        return i * j;
    }

    @Override
    public Integer div(Integer i, Integer j) {
        return i / j;
    }
}

package calc;

import java.lang.reflect.Method;

/**
 * @Classname MyCalculatorStaticProxy
 * @Description MyCalculator Static proxy mode
 * @Date 2021/5/31 13:00
 * @Created by xiaocai
 */
public class MyCalculatorStaticProxy implements ICalculator {

    MyCalculator myCalculator=null;

    public MyCalculatorStaticProxy(MyCalculator myCalculator)
    {
        this.myCalculator=myCalculator;
    }

    @Override
    public Integer add(Integer i, Integer j) {
        Method method = null;
        Integer result = 0;
        try {
            method = MyCalculator.class.getMethod("add", Integer.class, Integer.class);
            //Thread.currentThread().getStackTrace()[2].getClass().getEnclosingMethod()

            LogUtil.start(method, i, j);
            result = myCalculator.add(i,j);
        } catch (Exception e) {
            LogUtil.logException(method, e);
        } finally {
            LogUtil.logFinally(method);
        }

        LogUtil.stop(method, result);
        return result;
    }

    @Override
    public Integer sub(Integer i, Integer j) {
        Method method = null;
        Integer result = 0;
        try {
            method = MyCalculator.class.getMethod("sub", Integer.class, Integer.class);
            //Thread.currentThread().getStackTrace()[2].getClass().getEnclosingMethod()

            LogUtil.start(method, i, j);
            result = myCalculator.sub(i,j);
        } catch (Exception e) {
            LogUtil.logException(method, e);
        } finally {
            LogUtil.logFinally(method);
        }

        LogUtil.stop(method, result);
        return result;
    }

    @Override
    public Integer mul(Integer i, Integer j) {
        Method method = null;
        Integer result = 0;
        try {
            method = MyCalculator.class.getMethod("mul", Integer.class, Integer.class);
            //Thread.currentThread().getStackTrace()[2].getClass().getEnclosingMethod()

            LogUtil.start(method, i, j);
            result = myCalculator.sub(i,j);
        } catch (Exception e) {
            LogUtil.logException(method, e);
        } finally {
            LogUtil.logFinally(method);
        }

        LogUtil.stop(method, result);
        return result;
    }

    @Override
    public Integer div(Integer i, Integer j) {

        Method method = null;
        Integer result = 0;
        try {
            method = MyCalculator.class.getMethod("div", Integer.class, Integer.class);
            //Thread.currentThread().getStackTrace()[2].getClass().getEnclosingMethod()

            LogUtil.start(method, i, j);
            result = myCalculator.div(i,j);
        } catch (Exception e) {
            LogUtil.logException(method, e);
        } finally {
            LogUtil.logFinally(method);
        }

        LogUtil.stop(method, result);
        return result;
    }
}

MyCalculator initialization
20
0
100
1
/ by zero
 function[add]Start execution. The parameters are:[10, 10]
function[add]Start over
 function[add]After starting, the return value is: 20
20
 function[sub]Start execution. The parameters are:[10, 10]
function[sub]Start over
 function[sub]After starting, the return value is: 0
0
 function[mul]Start execution. The parameters are:[10, 10]
function[mul]Start over
 function[mul]After starting, the return value is: 0
0
 function[div]Start execution. The parameters are:[10, 10]
function[div]Start over
 function[div]After starting, the return value is: 1
1
 function[div]Start execution. The parameters are:[10, 0]
function[div]Execution exception. The exception information is:/ by zero
 function[div]Start over
 function[div]After starting, the return value is: 0
0

Static proxy mode:

  1. You can extend the target function without modifying the function of the target object
  2. Disadvantages:

Because the proxy object needs to implement the same interface as the target object, there will be many proxy classes, and there are too many classes At the same time, once the method is added to the interface, both the target object and the proxy object must be maintained
How to solve the shortcomings of static proxy? The answer is that you can use dynamic proxy

1.4 dynamic agent mode

    public static void main(String[] args) {
        dynamicProxy();
    }

    private static void dynamicProxy() {
        MyCalculator calc=new MyCalculator();
        ICalculator dynamicProxyCalc= CalculatorProxy.getCalculator(calc);
        System.out.println(dynamicProxyCalc.add(10,10));
        System.out.println(dynamicProxyCalc.sub(10,10));
        System.out.println(dynamicProxyCalc.mul(10,10));
        System.out.println(dynamicProxyCalc.div(10,10));
        System.out.println(dynamicProxyCalc.div(10,0));
    }


public class MyCalculator implements ICalculator {

    public MyCalculator() {
        System.out.println("MyCalculator initialization");
    }

    @Override
    public Integer add(Integer i, Integer j) {
        return i + j;
    }

    @Override
    public Integer sub(Integer i, Integer j) {
        return i - j;
    }

    @Override
    public Integer mul(Integer i, Integer j) {
        return i * j;
    }

    @Override
    public Integer div(Integer i, Integer j) {
        return i / j;
    }
}

package calc;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @Classname CalculatorProxy
 * @Description Calculator Dynamic agent mode
 * @Date 2021/5/31 13:51
 * @Created by xiaocai
 */
public class CalculatorProxy {

    public static ICalculator getCalculator(final ICalculator calculator){
        //Gets the class loader of the proxied object
        ClassLoader loader =calculator.getClass().getClassLoader();

        //All interfaces of the proxied object
        Class<?>[] interfaces=calculator.getClass().getInterfaces();

        //Used to execute the methods required by the proxy class
        InvocationHandler hander=new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object result = 0;
                try {
                    LogUtil.start(method, args);
                    result=method.invoke(args);
                } catch (Exception e) {
                    LogUtil.logException(method, e);
                } finally {
                    LogUtil.logFinally(method);
                }

                LogUtil.stop(method, result);
                return result;
            }
        };

        Object objReturn= Proxy.newProxyInstance(loader,interfaces,hander);

        return (ICalculator)objReturn;
    }
}
MyCalculator initialization
 function[add]Start execution. The parameters are:[10, 10]
function[add]Execution exception. The exception information is: object is not an instance of declaring class
 function[add]Start over
 function[add]After starting, the return value is: 0
0
 function[sub]Start execution. The parameters are:[10, 10]
function[sub]Execution exception. The exception information is: object is not an instance of declaring class
 function[sub]Start over
 function[sub]After starting, the return value is: 0
0
 function[mul]Start execution. The parameters are:[10, 10]
function[mul]Execution exception. The exception information is: object is not an instance of declaring class
 function[mul]Start over
 function[mul]After starting, the return value is: 0
0
 function[div]Start execution. The parameters are:[10, 10]
function[div]Execution exception. The exception information is: object is not an instance of declaring class
 function[div]Start over
 function[div]After starting, the return value is: 0
0
 function[div]Start execution. The parameters are:[10, 0]
function[div]Execution exception. The exception information is: object is not an instance of declaring class
 function[div]Start over
 function[div]After starting, the return value is: 0
0

Process finished with exit code 0

2. Explanation of agency model

We use the agent mode to solve the above problems. From the use of static agents, we generally do so.

  1. Proxy classes generally hold a reference to the object being proxied.

  2. For the methods we don't care about, all of them are entrusted to the proxy object.

  3. The way we deal with our concerns.

This kind of proxy is dead and will not be created dynamically at runtime, because we are equivalent to generating a proxy class that cannot be dynamically changed for the proxy object at compile time, that is, the moment you press CTRL+S.

A mandatory requirement of dynamic Proxy based on Java Proxy implementation is that the Proxy class must implement an interface, or itself is an interface, just like our Connection.

  • ASM: https://zhuanlan.zhihu.com/p/94498015?utm_source=wechat_timeline ASM is a Java bytecode manipulation framework, which can be used to dynamically generate classes or enhance the functions of existing classes. ASM can directly generate binary class files, or dynamically change the class behavior before the class is loaded into the Java virtual machine. Java class es are stored in class files with strict format definitions.

  • The Proxy must have an interface. If there is no interface, it cannot be used. In this way, the classes under the reflect package provided by JDK are used. However, in the production environment, we cannot guarantee that every class has an implemented interface.

  • CGLib has no Proxy constraint, that is, it does not require the class to implement the interface.

  • There is logic in Spring, that is, when the class always has inheritance, Proxy is directly used. If there is no inheritance, CGLib is used to implement it.

That's what I did.

  1. Proxy classes generally hold a reference to the object being proxied.

  2. For the methods we don't care about, all of them are entrusted to the proxy object.

  3. The way we deal with our concerns.

This kind of proxy is dead and will not be created dynamically at runtime, because we are equivalent to generating a proxy class that cannot be dynamically changed for the proxy object at compile time, that is, the moment you press CTRL+S.

A mandatory requirement of dynamic Proxy based on Java Proxy implementation is that the Proxy class must implement an interface, or itself is an interface, just like our Connection.

  • ASM: https://zhuanlan.zhihu.com/p/94498015?utm_source=wechat_timeline ASM is a Java bytecode manipulation framework, which can be used to dynamically generate classes or enhance the functions of existing classes. ASM can directly generate binary class files, or dynamically change the class behavior before the class is loaded into the Java virtual machine. Java class es are stored in class files with strict format definitions.

  • The Proxy must have an interface. If there is no interface, it cannot be used. In this way, the classes under the reflect package provided by JDK are used. However, in the production environment, we cannot guarantee that every class has an implemented interface.

  • CGLib has no Proxy constraint, that is, it does not require the class to implement the interface.

  • There is logic in Spring, that is, when the class always has inheritance, Proxy is directly used. If there is no inheritance, CGLib is used to implement it.

  • In the early days, the efficiency of CGLib was higher than that of Proxy. With the continuous upgrading of JDK, the efficiency gap gradually narrowed.