Dynamic agent combing

JDK dynamic agent

 

*JDK dynamic proxy creates proxy instances for interfaces (similar to creating implementation classes for interfaces)

1: It mainly involves the two classes Proxy and InvocationHandler in the java.lang.reflect package. InvocationHandler is an interface, which can be implemented by a user-defined class to define enhanced logic, and then call the code of the target class through the reflection mechanism to dynamically weave the crosscutting (enhanced) logic and business logic together.

Code example:

1: The target class implements the JDKProxyInterface interface

package springAop;

/**
 * Target class
 */
public class JDKProxyImp implements JDKProxyInterface {
    @Override
    public void say() {
        System.out.println("Hello JdkDynamicAopProxy");
    }
}

2: Enhanced Logic: InvocationHandler implementation class

package springAop;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class MyInvocationHandler implements InvocationHandler {
    private Object object;
    //In invoke(), the method of the target class needs to be executed through reflection, so the target instance needs to be passed in. Here, the target instance needs to be passed in through the constructor
    public MyInvocationHandler(Object o){
        this.object = o;
    }
    /**
     *
     * @param proxy  The final generated proxy instance is generally not used
     * @param method A specific method of the proxy target instance, which can initiate a reflection call of the target instance method,
     * @param args   The input parameter of a method of the proxy instance is used when reflecting the call
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //Here, you can write the crosscutting logic before the target method is executed, that is, the method before advice in spring Aop
        System.out.println("Pre enhancement, similar MethodBeforeAdvice");
        Object result = method.invoke(object,args);
        //Post enhancement
        System.out.println("Post enhancement, similar AfterReturningAdvice");
        return result;
    }
}

  3: Call logic: create a proxy object for the target class through the Proxy.newProxyInstance() method

    @Test
    public void JDK Dynamic agent(){
        JDKProxyInterface jDKProxyInterface = new JDKProxyImp();
        MyInvocationHandler myInvocationHandler = new MyInvocationHandler(jDKProxyInterface);
        JDKProxyInterface proxy = (JDKProxyInterface) Proxy.newProxyInstance(
                jDKProxyInterface.getClass().getClassLoader(),
                jDKProxyInterface.getClass().getInterfaces(),
                myInvocationHandler);
        proxy.say();
    }
}

4: Operation results:

 

CGLib dynamic proxy

*Create proxy objects for classes that do not implement interfaces. In fact, the bottom layer adopts bytecode technology, which can create subclasses for a class. The subclass adopts method interception technology to intercept the calls of all parent class methods and weave them into crosscutting logic.

Code example:

1: Generate proxy subclasses for the target class through Enhancer,

2: Implement the MethodInterceptor interface under the org.springframework.cglib.proxy package and implement its interceptor() method

package springAop;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
import java.util.Arrays;

public class CglibProxy implements MethodInterceptor {
    /**
     * Using Enhancer to generate proxy objects for target objects
     * @param clazz
     * @return
     */
    public Object getProxy(Class clazz){
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(clazz); //Set the class that needs to be subclassed
        enhancer.setCallback(this);
        return enhancer.create(); //Using bytecode technology to dynamically create subclass instances
    }

    /**
     *
     * @param o  Target class instance
     * @param method   Reflection object of target class method
     * @param objects  Dynamic input parameters of target class methods
     * @param proxy Proxy class instance
     * @return
     * @throws Throwable
     */
    @Override
    //All methods of the parent class will be intercepted, and the parent class here is the object corresponding to clazz
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy proxy) throws Throwable {
        System.out.println("Target class instance:"+o.getClass().getName()+",Target class method reflection object"+method.getName()
                +",Target method parameter list:"+ Arrays.toString(objects)+",Proxy class instance:"+proxy);
        System.out.println("Enhanced logic");
        Object result = proxy.invokeSuper(o,objects);  //Call the method of the parent class through the proxy method
        //Returns the execution result of the target method
        System.out.println("Output result of target method:"+result);
        return result;
    }
}

Call logic:

    @Test
    public void CGlib Dynamic agent(){
        CglibTest target = new CglibTest();
        CglibProxy cglibProxy = new CglibProxy();
        CglibTest proxy = (CglibTest) cglibProxy.getProxy(target.getClass());
        proxy.add(1,3);
    }

result:

Summary:

Question 1: JDK dynamic agent and CGLib dynamic agent add enhancement logic to all methods of the target class, but do not enhance the specified methods of the specified class.

Question 2: Code coupling, the weaving point of enhanced logic is formulated by hard coding, that is, before the beginning or end of the target method.

Problem 3: in the process of creating agent instances, when creating agents for different classes, you need to write corresponding creation codes respectively, which can not be universal.

 Spring AOP

1: Spring Aop introduces pointcut (pointcut) classes for us, specifying that crosscutting logic is woven into those methods of those classes.

2: Introduction of advice (enhancement): it describes the enhancement logic and the specific weaving point of the method (before, after, both ends of the method, etc., after the method throws an exception).

3: In addition, Spring Aop also introduces Advisor (aspect) to assemble Pointcut and Advice. With the information of advisor, the spring bottom layer can use JDK or CGLib dynamic proxy technology to create proxy objects woven into the aspect for the target Bean in the same way. Spring Aop solves the above three problems through these three points.

Spring AOP enhancement type: 5 classes

1: The org.aopalliance.aop.BeforeAdvice interface is pre enhanced. Spring only supports the enhancement of methods, so the MethodBeforeAdvice interface is generally used. Implement enhancement before target method call

2: Post enhancement: the org.springframework.aop.AfterReturningAdvice interface represents the fact enhancement after the target method is executed

3: Surround enhancement   org.aopalliance.intercept.MethodInterceptor interface, which represents the implementation of enhancements before and after the execution of the target method

4: Exception throwing enhancement: org.springframework.aop.ThrowsAdvice   Means to implement enhancement (e.g. transaction rollback mechanism) after the target method throws an exception,

5: Introduction enhancement: org.springframework.aop.IntroductionInterceptor means to add some new methods and properties to the target class.

Note: the ThrowsAdvice interface is a label interface and does not specify a method. During operation, Spring uses the reflection mechanism to judge by itself. The enhanced method of exception throwing must be defined in the following signature form:

void afterThrowing([Method method,Object[] args,Object target],Throwable)

1: Method name must be afterThrowing

2:   Method input parameters: either 4 or 1. The first three are optional parameters. The last parameter must be of type Throwable or its subclass.

The reasons can be referred to,

org.springframework.aop.framework.adapter.ThrowsAdviceInterceptor class, because the aspect of ThrowsAdvice type will be encapsulated into ThrowsAdviceInterceptor by ThrowsAdviceAdapter:

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

package org.springframework.aop.framework.adapter;

import java.io.Serializable;
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInterceptor;
import org.springframework.aop.Advisor;
import org.springframework.aop.ThrowsAdvice;

class ThrowsAdviceAdapter implements AdvisorAdapter, Serializable {
    ThrowsAdviceAdapter() {
    }

    public boolean supportsAdvice(Advice advice) {
        return advice instanceof ThrowsAdvice;
    }

    public MethodInterceptor getInterceptor(Advisor advisor) {
        return new ThrowsAdviceInterceptor(advisor.getAdvice());
    }
}

ThrowsAdviceInterceptor part code:

It will judge whether the method name is: afterThrowing, the method parameters are 1 or 4, and whether the last parameter type is Throwable or its subclass.

public class ThrowsAdviceInterceptor implements MethodInterceptor, AfterAdvice {
    private static final String AFTER_THROWING = "afterThrowing";
    private static final Log logger = LogFactory.getLog(ThrowsAdviceInterceptor.class);
    private final Object throwsAdvice;
    private final Map<Class<?>, Method> exceptionHandlerMap = new HashMap();

    public ThrowsAdviceInterceptor(Object throwsAdvice) {
        Assert.notNull(throwsAdvice, "Advice must not be null");
        this.throwsAdvice = throwsAdvice;
        Method[] methods = throwsAdvice.getClass().getMethods();
        Method[] var3 = methods;
        int var4 = methods.length;

        for(int var5 = 0; var5 < var4; ++var5) {
            Method method = var3[var5];
            if (method.getName().equals("afterThrowing") && (method.getParameterTypes().length == 1 || method.getParameterTypes().length == 4) && Throwable.class.isAssignableFrom(method.getParameterTypes()[method.getParameterTypes().length - 1])) {
                this.exceptionHandlerMap.put(method.getParameterTypes()[method.getParameterTypes().length - 1], method);
                if (logger.isDebugEnabled()) {
                    logger.debug("Found exception handler method: " + method);
                }
            }
        }

        if (this.exceptionHandlerMap.isEmpty()) {
            throw new IllegalArgumentException("At least one handler method must be found in class [" + throwsAdvice.getClass() + "]");
        }
    }

Example of exception notification: ThrowsAdvice

package springAop;

import org.springframework.aop.ThrowsAdvice;

import java.lang.reflect.Method;

/**
 * Multiple methods can be defined in exception notification. When the target method throws an exception, call the enhancement method principle:
 * On the class inheritance tree, the closer the distance between the two classes, the higher the similarity between the two classes. After the target method throws an exception,
 * After throwing method with the highest similarity between exception input and exception throw will be preferentially selected
 */
public class MyThrowsAdvice implements ThrowsAdvice {
    public void afterThrowing(Exception e){
        System.out.println("Exception notification a parameter");
    };

    public void afterThrowing(Method method,Object[] args, Object target, ArithmeticException e){
        System.out.println("Exception notification 4 parameters");
    };
}

Target class:

package springAop;

public class CglibTest {

    public void testThrowsAdvice(){
        System.out.println("There are two methods in exception notification. The exception enhancement method will be selected according to the similar exception types");
        System.out.println(10/0);
    }

    public void testThrowsAdvice2(){
        throw  new RuntimeException("For exception notification, the selection method shall follow the principle of exception proximity");
    }
}

call

    @Test
    public  void test Exception notification(){
        ProxyFactory pf = new ProxyFactory();
        pf.addAdvice(new MyThrowsAdvice());
        pf.setTarget(new CglibTest());
        CglibTest proxy = (CglibTest)pf.getProxy();
       // proxy.testThrowsAdvice();
        proxy.testThrowsAdvice2();
    }

testThrowsAdvice2() running result: the RuntimeException exception is thrown, so

public void afterThrowing(Exception e) method.

  Running result of testThrowsAdvice(): the method whose parameter is ArithmeticException is called because the ArithmeticException exception is thrown

 

Keywords: Java Spring Dynamic Proxy

Added by mr_zhang on Sun, 12 Sep 2021 07:56:08 +0300