2w word to understand the past and present life of Spring AOP

Spring AOP overview

Recently, when looking at the source code of seata, the api of spring aop can be seen everywhere. So while looking at seata, I summarized spring aop

When using the Spring framework, we often need to deal with the two major features of Spring, IOC and AOP. This article will then share the underlying implementation of AOP. The basic content will not be introduced in this article, mainly focusing on the design concept of the underlying api

"Common concepts of AOP design concept are as follows"

"The main application scenarios of AOP are as follows"

"The implementation of Spring AOP has mainly experienced two generations"

The first generation: spring1.x version, which implements the functions of AOP. The second generation: spring2.x version, Spring integrates the implementation of AspectJ

Spring AOP generation

"When we want to add crosscutting logic based on ready-made implementation, we first need to find out where to enhance it. Let's use Pointcut to filter it."

First write a Service to facilitate the later demonstration

public interface EchoService {

    String echo(String message);
}
public class DefaultEchoService implements EchoService {

    @Override
    public String echo(String message) {
        return message;
    }
}

Pointcut

Pointcut interface is defined as follows

public interface Pointcut {

 //  Filter by class
 ClassFilter getClassFilter();

 //  Filter by method
 MethodMatcher getMethodMatcher();

 Pointcut TRUE = TruePointcut.INSTANCE;

}

"When we want to filter out the echo methods of EchoService, we can define the following Pointcut"

public class EchoPointcut implements Pointcut {

    @Override
    public ClassFilter getClassFilter() {
        return new ClassFilter() {
            @Override
            public boolean matches(Class<?> clazz) {
                return EchoService.class.isAssignableFrom(clazz);
            }
        };
    }

    @Override
    public MethodMatcher getMethodMatcher() {
        return new MethodMatcher() {
            @Override
            public boolean matches(Method method, Class<?> targetClass) {
                return "echo".equals(method.getName()) &&
                        method.getParameterTypes().length == 1 &&
                        Objects.equals(String.class, method.getParameterTypes()[0]);
            }

            @Override
            public boolean isRuntime() {
                return false;
            }

            @Override
            public boolean matches(Method method, Class<?> targetClass, Object... args) {
                return false;
            }
        };
    }
}

It still seems troublesome, so Spring has many built-in implementations. Generally, we can use the built-in implementations without defining them ourselves. The above filtering process can be changed to the following

//  The method name is   echo   Will be intercepted
NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
pointcut.setMappedName("echo");

Some pointcuts provided by Spring are implemented as follows

Jointpoint

"The place to add crosscutting logic filtered by Pointcut is Jointpoint."   In the AOP concept, crosscutting logic can be added in many places, such as method execution, field setting, etc. However, "Spring only supports method execution as a Joinpoint", because this type of Joinpoint basically meets 80% of the scenarios

In Joinpoint type   "Method call is better than method execution"

Because Spring only supports method execution, we can get enhanced method information from the Joinpoint implementation class

Advice

When jointpoints are selected, we need to add crosscutting logic on these jointpoints, which is called Advice

There are two ways to implement crosscutting logic in Spring

  1. Implement the Advice interface

  2. Implement the IntroductionInfo interface

The method of implementing the Advice interface is the most commonly used, which will be analyzed in detail later. The way to implement the IntroductionInfo interface is basically useless. Here is a demonstration of the specific usage to facilitate understanding the design concept of the whole AOP API

"IntroductionInfo mainly adds new functions by implementing specific interfaces for the target class"

public interface SayName {

    String getName();
}
public class DefaultSayName implements SayName {

    @Override
    public String getName() {
        return "I am service";
    }
}
public static void main(String[] args) {
    SayName sayName = new DefaultSayName();
    EchoService echoService = new DefaultEchoService();
    //  Built in implementation of IntroductionInfo interface
    DelegatingIntroductionInterceptor interceptor =
            new DelegatingIntroductionInterceptor(sayName);
    Advisor advisor = new DefaultIntroductionAdvisor(interceptor, SayName.class);
    ProxyFactory proxyFactory = new ProxyFactory(echoService);
    proxyFactory.addAdvisor(advisor);
    // hello world
    EchoService proxyService = (EchoService) proxyFactory.getProxy();
    System.out.println(proxyService.echo("hello world"));
    // I am service
    SayName proxySayName = (SayName) proxyFactory.getProxy();
    System.out.println(proxySayName.getName());
}

Maybe you are unfamiliar with Advisor and ProxyFactory in this example. You don't know what role they play. Don't worry. We will analyze the roles of these two classes in detail later

"The way to implement the Advice interface should be the most common way in the Spring AOP generation"

"Add pre execution crosscutting logic to the put method of HashMap" and print the values of key and value put into HashMap

public static void main(String[] args) {
    JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut();
    pointcut.setPattern(".*put.*");
    DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
    advisor.setPointcut(pointcut);
    advisor.setAdvice(new MethodBeforeAdvice() {
        @Override
        public void before(Method method, Object[] args, Object target) throws Throwable {
            System.out.printf("Currently stored key by %s,Value is %s", args[0], args[1]);
        }
    });

    ProxyFactory proxyFactory = new ProxyFactory(new HashMap());
    proxyFactory.addAdvisor(advisor);
    Map<String, String> proxyMap = (Map<String, String>) proxyFactory.getProxy();
    //  The currently stored key is   a. Value is   a
    proxyMap.put("a", "a");
}

Advisor

As we said earlier, in the AOP design concept, we use Aspect to declare aspects, and each Aspect can contain multiple pointcuts and Advice.

"In the Spring AOP generation, the corresponding implementation of Aspect is Advisor.". That is, Advisor is the container of Pointcut and Advice, but an Advisor can only contain one Pointcut and Advice

Because there are two types of implementation methods of Advice, the corresponding Advisor can also be divided into two types

Weave in

"The process of weaving Advice into Jointpoint in Spring is realized through dynamic proxy.". Of course, there are many ways to weave in, not just dynamic agents

Spring uses jdk dynamic proxy and cglib to implement dynamic proxy. Factory mode is used to generate proxy objects. It can be clearly seen from the api

"jdk dynamic agent"

public class CostInvocationHandler implements InvocationHandler {

    private Object target;

    public CostInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        long startTime = System.currentTimeMillis();
        Object result = method.invoke(target, args);
        long cost = System.currentTimeMillis() - startTime;
        System.out.println("cost " + cost);
        return result;
    }
}
public static void main(String[] args) {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    Object proxy = Proxy.newProxyInstance(classLoader,
            new Class[]{EchoService.class},
            new CostInvocationHandler(new DefaultEchoService()));
    EchoService echoService = (EchoService) proxy;
    // cost 0
    // hello world
    System.out.println(echoService.echo("hello world"));
}

「cglib」

public static void main(String[] args) {
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(DefaultEchoService.class);
    enhancer.setInterfaces(new Class[] {EchoService.class});
    enhancer.setCallback(new MethodInterceptor() {
        @Override
        public Object intercept(Object source, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
            long startTime = System.currentTimeMillis();
            Object result = methodProxy.invokeSuper(source, args);
            long cost = System.currentTimeMillis() - startTime;
            System.out.println("cost " + cost);
            return result;
        }
    });
    EchoService echoService = (EchoService) enhancer.create();
    // cost 29
    // hello world
    System.out.println(echoService.echo("hello world"));
}

Automatic dynamic proxy for Spring AOP

We have been demonstrating it in the form of API above. Of course, we can also put these objects into the Spring container, let Spring manage them, and generate proxy objects for beans in the Spring container

The above Demo can be changed to the following form, with little change

"Manual configuration"

public class ProxyConfig {

    //  Create proxy object
    @Bean
    public EchoService echoService() {
        return new DefaultEchoService();
    }

    //  Create advice
    @Bean
    public CostMethodInterceptor costInterceptor() {
        return new CostMethodInterceptor();
    }

    //  Creating an advisor using pointcut and advice
    @Bean
    public Advisor advisor() {
        NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();
        advisor.setMappedName("echo");
        advisor.setAdvice(costInterceptor());
        return advisor;
    }

    //  Create proxy object
    @Bean("echoProxy")
    public ProxyFactoryBean proxyFactoryBean(EchoService echoService) {
        ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
        proxyFactoryBean.setTarget(echoService);
        proxyFactoryBean.setInterceptorNames("advisor");
        return proxyFactoryBean;
    }
}
public static void main(String[] args) {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ProxyConfig.class);
    //  Get proxy object
    EchoService echoService = (EchoService) context.getBean("echoProxy");
    // cost 0
    // hello world
    System.out.println(echoService.echo("hello world"));
}

"You can see that we need to configure the corresponding ProxyFactoryBean for each generated proxy object, and then obtain the proxy object from the container for use.". When there are few proxy objects, they can cope with it. When there are many proxy objects, they must not be tired enough to vomit blood. Is there any simple way?

Spring must have thought of this problem, so it provides the following class defaultadvisor autoproxycreator to implement automatic proxy. We can put this class into the spring container, as shown below

"Auto configuration"

public class AutoProxyConfig {

    //  Create proxy object
    @Bean
    public EchoService echoService() {
        return new DefaultEchoService();
    }

    //  Create advice
    @Bean
    public CostMethodInterceptor costInterceptor() {
        return new CostMethodInterceptor();
    }

    //  Creating an advisor using pointcut and advice
    @Bean
    public Advisor advisor() {
        NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();
        advisor.setMappedName("echo");
        advisor.setAdvice(costInterceptor());
        return advisor;
    }

    @Bean
    public DefaultAdvisorAutoProxyCreator autoProxyCreator() {
        return new DefaultAdvisorAutoProxyCreator();
    }
}
public static void main(String[] args) {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AutoProxyConfig.class);
    EchoService echoService = context.getBean(EchoService.class);
    // cost 0
    // hello world
    System.out.println(echoService.echo("hello world"));
}

The object obtained from the container is directly the object after being proxied, which is very convenient. "Spring AOP provides many classes to implement automatic proxy, but they have a common parent class AbstractAutoProxyCreator. It seems that the secret of automatic proxy lies in this AbstractAutoProxyCreator class."

Implementation of Spring AOP automatic dynamic proxy

What would you do if you were to implement automatic proxy for objects?

Of course, it is smart to intervene in the Bean declaration cycle through BeanPostProcessor! That's what Spring did to test our ideas

Looking at the inheritance relationship of this class basically verifies our idea. We just need to see what methods he has rewritten the BeanPostProcessor?

"AbstractAutoProxyCreator rewrites the following two important methods" postprocessbeforeinstance (executed before Bean instantiation) and postProcessAfterInitialization (executed after Bean initialization)

"postProcessBeforeInstantiation (execution stage before Bean instantiation)"

When the user customizes the implementation of TargetSource, the target object generation agent will be obtained from TargetSource. However, in general, we rarely customize the implementation of TargetSource. So this part is no longer analyzed. Look directly at postProcessAfterInitialization

"postProcessAfterInitialization (Bean post initialization stage execution)"

If it is not proxied, it will enter the wrapIfNecessary method

The idea is very simple, that is, get the corresponding Advisor according to the Bean, then create its proxy object and return it.

"So when the interviewer asks you how Spring AOP and IOC are combined, do you know how to answer?"

In the post initialization stage of the Bean life cycle, if the Bean needs to add crosscutting logic, the corresponding proxy object will be generated at this stage

Spring AOP generation 2 (integrated with AspectJ)

After the release of Spring 2.0, a new way of using spring AOP is added. Spring AOP integrates AspectJ. This version of spring AOP is most commonly used

The main changes are as follows

  1. POJO can be used to define Aspect and Adivce, and provide a series of corresponding annotations, such as @ Aspect and @ Around. Instead of implementing the corresponding interface in version 1.x

  2. We all know how to support the expression of pointcut in aspectj

Demonstrate how aop is used in version 2.0

Define section

@Aspect
public class AspectDefine {

    @Pointcut("execution(* com.javashitang.proxy.EchoService.echo(..))")
    public void pointcutName() {}

    @Around("pointcutName()")
    public Object calCost(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        Object result = joinPoint.proceed();
        long cost = System.currentTimeMillis() - startTime;
        System.out.println("cost " + cost);
        return result;
    }

    @Before("pointcutName()")
    public void beforeMethod() {
        System.out.println("beforeMethod");
    }
}

Add configuration and inject implementation classes

@EnableAspectJAutoProxy
public class AspectJConfig {

    @Bean
    public EchoService echoService() {
        return new DefaultEchoService();
    }
}
public static void main(String[] args) {
    AnnotationConfigApplicationContext context =
            new AnnotationConfigApplicationContext(AspectJConfig.class, AspectDefine.class);
    EchoService echoService = context.getBean(EchoService.class);
    // beforeMethod
    // cost 0
    // hello world
    System.out.println(echoService.echo("hello world"));
    context.close();
}

"Although AspectJ was integrated into spring aop after spring 2.0, it was only used as AspectJ's" fur coat "because the underlying implementation and weaving method was still the original implementation system of 1.x."

@What's the use of EnableAspectJAutoProxy?

"When we want to use aop version 2.0, we must add the @ EnableAspectJAutoProxy annotation on the configuration class. What is the role of this annotation?"

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {

 boolean proxyTargetClass() default false;

 boolean exposeProxy() default false;

}

You can see a very important sentence

@Import(AspectJAutoProxyRegistrar.class)

Inject beans through @ Import. There are three ways to inject beans through @ Import annotation

  1. Based on Configuration Class

  2. Based on ImportSelector interface

  3. Based on ImportBeanDefinitionRegistrar interface

This code mainly does two things

  1. Inject annotation awareaspectjautoproxycreator into the container

  2. When the proxyTargetClass or exposeProxy attribute in the @ EnableAspectJAutoProxy annotation is true, change the proxyTargetClass or exposeProxy attribute in the AnnotationAwareAspectJAutoProxyCreator to true

"proxyTargetClass and exposeProxy are saved in the parent class ProxyConfig of the AnnotationAwareAspectJAutoProxyCreator class. This class stores some configurations to control the generation process of proxy objects."

proxyTargetClass: true use CGLIB to create proxy based on class; false use java interface to create proxy exposeProxy: true save proxy object in AopContext, otherwise it will not be saved

The first attribute is easy to understand, so what is the function of the second attribute? Demonstrate it

@Service
public class SaveSevice {

    public void method1() {
        System.out.println("method1 executed");
        method2();
    }

    public void method2() {
        System.out.println("method2 executed");
    }
}
@Aspect
public class AspectDefine {

    @Pointcut("execution(* com.javashitang.invalid.SaveSevice.method2(..))")
    public void pointcutName() {}

    @Around("pointcutName()")
    public Object calCost(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("Open transaction");
        return joinPoint.proceed();
    }
}
@EnableAspectJAutoProxy
public class InvalidDemo {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext(SaveSevice.class,
                        AspectDefine.class, InvalidDemo.class);
        SaveSevice saveSevice = context.getBean(SaveSevice.class);
        saveSevice.method1();
        System.out.println("--");
        saveSevice.method2();
    }
}

The result is

method1 executed
method2 executed
--
Open transaction
method2 executed

"It can be seen that when method2 is called through method1, aop does not take effect. Only when method2 is called directly, aop takes effect. This is the reason why the transaction method is invalid since the call is not a proxy object's method."

There are many solutions, such as re taking proxy objects from ApplicationContext and calling proxy objects. The other is to get proxy objects through AopContext, and the principle is to put proxy objects in ThreadLocal when the method is called.

@Service
public class SaveSevice {

    public void method1() {
        System.out.println("method1 executed");
        ((SaveSevice) AopContext.currentProxy()).method2();
    }

    public void method2() {
        System.out.println("method2 executed");
    }
}

Change the exposeProxy property to true

@EnableAspectJAutoProxy(exposeProxy = true)
method1 executed
 Open transaction
method2 executed
--
Open transaction
method2 executed

It can be seen that aop takes effect successfully. "When you use @ Transactional annotation and distributed transaction framework, you must pay attention to the problem of sub call, otherwise it is easy to cause transaction failure."

Let's talk about injecting AnnotationAwareAspectJAutoProxyCreator into the container, so what's the role of this class?

See if the inheritance relationship is very similar to the defaultadvisor autoproxycreator class we analyzed above. Isn't this to enable automatic proxy?

Forget about the implementation of automatic proxy? Look back

Tangent expression

"Spring AOP uses AspectJExpressionPointcut to bridge the filtering ability of Aspect. In fact, Aspect has many types of pointcut expressions, but spring AOP only supports the following 10 kinds, because Aspect supports many types of joinpoints, but spring AOP only supports the method execution of this kind of JoinPoint, so the rest of the expressions are unnecessary.

Because the expressions provided by AspectJ are often used in our work, we will demonstrate the specific usage in combination with the Demo

Expression typeexplain
executionMatch method expression, preferred method
withinQualified type
thisThe proxy object is of the specified type, and all methods will be intercepted
targetThe target object is of the specified type, and all methods will be intercepted
argsParameters in matching method
@targetThe target object has the specified annotation, and all methods will be intercepted
@argsThere is a specified annotation on the type of the method parameter
@withinIf there is a specified annotation on the calling object, all methods will be intercepted
@annotationMethod with specified annotation

「execution」

Match method expression, preferred method

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern)
                throws-pattern?)

The pointcut expression for intercepting the perform method of the Performance class is as follows

Put some official demos

// The execution of any public method:
execution(public * *(..))

// The execution of any method with a name that begins with set
execution(* set*(..))

// The execution of any method defined by the AccountService interface
execution(* com.xyz.service.AccountService.*(..))

// The execution of any method defined in the service package:
execution(* com.xyz.service.*.*(..))

"within" qualified type

//  Intercept any method of any class in the service package
within(com.xyz.service.*)

//  Any method of intercepting any class in service package and sub package
within(com.xyz.service..*)

「this」

The proxy object is of the specified type, and all methods will be intercepted

Give an example

@Configuration
@EnableAspectJAutoProxy
public class ThisDemo {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext(ThisDemo.class, AspectDefine.class);
        Name name = context.getBean(Name.class);
        name.getName();
        System.out.println(name instanceof Student);
    }


    @Aspect
    public class AspectDefine {
        @Before("this(com.javashitang.aspectjPointcut.thisDemo.ThisDemo.Student)")
        public void before() {
            System.out.println("before");
        }
    }

    @Bean
    public Student student() {
        return new Student();
    }

    public class Student implements Name {

        @Override
        public String getName() {
            return null;
        }
    }

    public interface Name {
        String getName();
    }
}

Output as

false

jdk dynamic Proxy will be used when there is an interface, so the Proxy object is Proxy and will not be intercepted

When it is set to jdk, the dynamic proxy object is Student, which is intercepted normally

Change the annotation to the following form @ EnableAspectJAutoProxy(proxyTargetClass = true)

Output as

before
true

The "target" target object is the specified type, and all methods will be intercepted

//  If the target object is of type AccountService, it will be delegated
target(com.xyz.service.AccountService)

The difference between this and target is that "this acts on the proxy object and target acts on the target object"

args matches the parameters in the method

//  There is only one parameter matching and the type is com.ms.aop.args.demo1.UserModel
@Pointcut("args(com.ms.aop.args.demo1.UserModel)")

//  Match multiple parameters
args(type1,type2,typeN)

//  Match all methods with the first parameter type com.ms.aop.args.demo1.UserModel,  ..  Represents any parameter
@Pointcut("args(com.ms.aop.args.demo1.UserModel,..)")

The "@ target" target object has the specified annotation. All methods will be blocked

//  The target object contains com.ms.aop.jtarget.Annotation1 annotation. Any method calling the target object will be intercepted
@target(com.ms.aop.jtarget.Annotation1)

There is a specified annotation on the type of the "@ args" method parameter

//  Match one parameter, and the class to which the first parameter belongs has Anno1 annotation
@args(com.ms.aop.jargs.demo1.Anno1)
//  Multiple parameters are matched, and the types of multiple parameters have specified annotations
@args(com.ms.aop.jargs.demo1.Anno1,com.ms.aop.jargs.demo1.Anno2)
//  Multiple parameters are matched, and the class to which the first parameter belongs has Anno1 annotation
@args(com.ms.aop.jargs.demo2.Anno1,...)

「@within」

If there is a specified annotation on the calling object, all methods will be intercepted

//  All methods in classes that declare com.ms.aop.jwithin.Annotation1 annotations will be intercepted
@within(com.ms.aop.jwithin.Annotation1)

"@ target is different from @ within" @ target focuses on the called object, @ within focuses on the called object

"@ annotation" has a method for specifying annotations

//  Annotation1 annotation on the called method
@annotation(com.ms.aop.jannotation.demo2.Annotation1)

Sequential relationship between Adivce

When a method is intercepted by an aspect class, the execution sequence is as follows

@Around - > @ before - > method execution - > @ around - > @ after - > @ afterreturning / @ afterthrowing

When the method ends normally, @ AfterReturning is executed. When the method ends abnormally, @ AfterThrowing is executed. They will not be executed at the same time

When a method is intercepted by multiple aspect classes, the execution sequence is as follows

"The execution Order of multiple aspect s can be controlled through @ Order annotation or implementation of Oreder interface"

"The order of Adivce must be sorted out clearly, otherwise you don't know how many magical behaviors sometimes happen."

Tags: Java Spring Programmer

Posted on Tue, 19 Oct 2021 00:17:03 -0400 by harishkumar09