[spring] how to use dynamic proxy in AOP

Posted by hex on Sun, 06 Mar 2022 18:14:05 +0100

Ideas of this paper

  • How to implement dynamic proxy in jdk, cglib and spring.
  • spring implements ProxyFactory details of dynamic agents.
  • Understand the general idea of AOP implementation from the use of ProxyFactory.

What is an agent

  • Provide a proxy for other objects to control access to this object, enhance a method in a class, and extend the program.
  • There are three common implementation methods: jdk, cglib and ProxyFactory of spring.

How to create a dynamic proxy JDK

  • The dynamic proxy of jdk must have an interface!
UserService target = new UserService();

// Proxy object for UserInterface interface
// The first parameter is the class loader, the second parameter is the interface of the agent, and the third parameter is the specific logic of the agent
Object proxy = Proxy.newProxyInstance(UserService.class.getClassLoader(), new Class[]{UserInterface.class}, new InvocationHandler() {
    /**
     * proxy: Proxy object
     * method: Method of execution
     * args: Method parameters
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("before...");
        Object result = method.invoke(target, args);
        System.out.println("after...");
        return result;
    }
});

UserInterface userService = (UserInterface) proxy;
userService.test();

How to create dynamic proxy cglib

UserService target = new UserService();

// Through cglib Technology
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserService.class);

// Define additional logic, that is, proxy logic
enhancer.setCallbacks(new Callback[]{new MethodInterceptor() {
    /**
     * o:Proxy object
     * method: Method of proxy object
     * objects: Parameters of function call
     * methodProxy: Proxy for method
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("before...");
        Object result = methodProxy.invoke(target, objects);
        System.out.println("after...");
        return result;
    }
}});

// UserService object created by dynamic agent
UserService userService = (UserService) enhancer.create();

// When the test method of this userService is executed, some additional logic will be executed
userService.test();

How to create a dynamic proxy: ProxyFactory of spring

// Get the original object
UserService target = new UserService();

// Create agent factory
ProxyFactory proxyFactory = new ProxyFactory();
// Specifies that the interface uses the dynamic proxy of jdk, and does not specify that the interface uses the dynamic proxy of cglib
// proxyFactory.setInterfaces(UserInterface.class);
// Set original object
proxyFactory.setTarget(target);
// Add an agent logic and various Advice
proxyFactory.addAdvice(new MethodInterceptor() {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("before...");
        // Execute the proxy method
        Object result = invocation.proceed();
        System.out.println("after...");
        return result;
    }
});

// Get the proxy object and execute
UserInterface userService = (UserInterface) proxyFactory.getProxy();
userService.test();

What is Advice

  • Method before advice: the method is executed before. You can override the before method with new MethodBeforeAdvice.
  • AfterReturningAdvice: the method is executed after return. You can override the afterReturning method with new AfterReturningAdvice.
  • ThrowsAdvice: the method executes after throwing an exception. You can override the afterThrowing method with new ThrowsAdvice. The fourth parameter of afterThrowing is a specific exception, which will be matched.
  • After (finally) advice: the method is executed after it is finally executed. This is the last, which is later than return.
  • MethodInterceptor: surround execution. This is the most powerful Advice. You can customize the execution order. You can override the invoke method with the new MethodInterceptor.

Advisor: judgment is added on the basis of Advice!

  • Advisor is composed of a Pointcut and an Advice.
  • Pointcut allows you to specify the logic to be represented. For example, you can specify the method of that class to proxy.
UserService target = new UserService();

ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(target);
proxyFactory.addAdvisor(new PointcutAdvisor() {
    // Make logical matching
    @Override
    public Pointcut getPointcut() {
        return new StaticMethodMatcherPointcut() {
            @Override
            public boolean matches(Method method, Class<?> targetClass) {
                return method.getName().equals("testAbc");
            }
        };
    }

    // Specific agent logic
    @Override
    public Advice getAdvice() {
        return new MethodInterceptor() {
            @Override
            public Object invoke(MethodInvocation invocation) throws Throwable {
                System.out.println("before...");
                Object result = invocation.proceed();
                System.out.println("after...");
                return result;
            }
        };
    }

    // Useless methods. At present, there are only a few test method calls in the source code
    @Override
    public boolean isPerInstance() {
        return false;
    }
});

UserInterface userService = (UserInterface) proxyFactory.getProxy();
userService.test();

Make the object of dynamic proxy Bean: ProxyFactoryBean

/**
 * Bean of specific agent
 */
@Bean
public ProxyFactoryBean userService(){
    // Define original object
    UserService userService = new UserService();
    // Get ProxyFactoryBean object
    ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
    // Sets the object to proxy
    proxyFactoryBean.setTarget(userService);
    // Set which BeanName proxy logic to execute
    proxyFactoryBean.setInterceptorNames("zhangweiAroundAdvise");
    // Return proxy object
    return proxyFactoryBean;
}

/**
 * Agent logic
 */
@Bean
public MethodInterceptor zhangweiAroundAdvise(){
    return new MethodInterceptor() {
        @Override
        public Object invoke(MethodInvocation invocation) throws Throwable {
            System.out.println("before...");
            Object result = invocation.proceed();
            System.out.println("after...");
            return result;
        }
    };
}

Solve the problem that beans do not need to specify their own proxy logic and reduce the coupling degree: BeanNameAutoProxyCreator

/**
 * BeanNameAutoProxyCreator It is a BeanPostProcessor, which will execute before instantiation!
 */
@Bean
public BeanNameAutoProxyCreator beanNameAutoProxyCreator() {
    BeanNameAutoProxyCreator beanNameAutoProxyCreator = new BeanNameAutoProxyCreator();

    // All beannames beginning with userSe will execute the proxy logic of Zhangwei around advise
    beanNameAutoProxyCreator.setBeanNames("userSe*");
    beanNameAutoProxyCreator.setInterceptorNames("zhangweiAroundAdvise");
    beanNameAutoProxyCreator.setProxyTargetClass(true);

    return beanNameAutoProxyCreator;
}

Solve the problem that BeanNameAutoProxyCreator can only match according to BeanName: defaultadvisor autoproxycreator that can match methods

/**
 * An Advisor defined.
 * The current logic indicates that as long as the method is test, the ZhangweiAfterReturningAdvise agent will be executed
 */
@Bean
public DefaultPointcutAdvisor defaultPointcutAdvisor(){
    NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
    pointcut.addMethodName("test");

    DefaultPointcutAdvisor defaultPointcutAdvisor = new DefaultPointcutAdvisor();
    defaultPointcutAdvisor.setPointcut(pointcut);
    defaultPointcutAdvisor.setAdvice(new ZhangweiAfterReturningAdvise());

    return defaultPointcutAdvisor;
}

/**
 * DefaultAdvisorAutoProxyCreator It is a BeanPostProcessor, which will be executed during initialization!
 * He will look for which beans in the spring container are of type Advisor. Match whether the names in the container match
 * The code of this Bean can be written as @ import (defaultadvisor autoproxycreator. Class)
 */
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
 
    DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();

    return defaultAdvisorAutoProxyCreator;
}

Simplify again: @ Aspect

@Aspect
@Component
public class ZhangweiAspect {

    @Before("execution(public void com.zhangwei.service.UserService.test())")
    public void zhangweiBefore(JoinPoint joinPoint) {
        System.out.println("zhangweiBefore");
    }

}

Concluding remarks

  • Get more pre knowledge articles of this article and new valuable articles. Let's become architects together!
  • Paying attention to the official account enables you to have an in-depth understanding of MySQL, concurrent programming and spring source code.
  • Pay attention to the official account and follow the continuous and efficient learning of JVM!
  • This official account is not advertising!!! Update daily!!!

Topics: Spring