preface

  • This article will summarize 5 knowledge points about Spring AOP, mainly around: AOP usage article, AOP principle article, transaction usage article, transaction principle article, transaction synchronizer usage article five topics.

  • The AOP Principles section is divided into two topics:

    How does the source code bind the various advice we define to the target method? 2. What is the execution order of our AOP proxy objectsCopy the code
  • Transaction principles is mainly a transaction propagation mechanism case to explain the implementation of Spring transactions at the bottom

  • The core of this summary is how to use Spring AOP, and how to understand the advice features (I won’t elaborate on the relevant concepts here, you can refer to other articles), so let’s get started!

Test case

1.1 Preview the test case project structure

  • Project launch Entry class entry.java:

    public class Entry {
        public static void main(String[] args) {
            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
            ObjectService objectService = context.getBean(ObjectService.class);
            objectService.list("hi");
        }
    
        // Configure the scan class to enable aop functionality
        @Configuration
        @ComponentScan("com.eugene.sumarry.aop.csdn")
        @EnableAspectJAutoProxy(proxyTargetClass = false, exposeProxy = true)
        public static class AppConfig {}// Methods identified by this annotation will be enhanced
        @Retention(RetentionPolicy.RUNTIME)
        @Target(ElementType.METHOD)
        public @interface AspectAnnotation {
        }
    }
    Copy the code
  • Interface class ObjectService. Java

    public interface ObjectService {
    
        String[] list(String str);
    
        String findOne(a);
    }
    Copy the code
  • The interface implementation class is objectServiceImp.java

    @Service
    public class ObjectServiceImpl implements ObjectService {
    
        @Entry.AspectAnnotation
        @Override
        public String[] list(String str) {
            return new String[] {str, "avenger"."eug"};
        }
    
        @Override
        public String findOne(a) {
            return "avengerEug"; }}Copy the code
  • Define the aspect aspectDefination.java

    @Aspect
    @Component
    public class AspectDefinition {
    
        /** * specifies include@AspectAnnotationAnnotated methods are enhanced */
        @Pointcut("@annotation(com.eugene.sumarry.aop.csdn.Entry.AspectAnnotation)")
        public void pointcutAnnotation(a) {}/** * Pre-notification: fires before the target method is executed
        @Before(value = "pointcutAnnotation()")
        public void methodBefore(JoinPoint joinPoint) {
            String methodName = joinPoint.getSignature().getName();
            System.out.println("Pre-notification: Method name:" + methodName + ", parameter" + Arrays.asList(joinPoint.getArgs()));
        }
    
        /** * Post notification: fires after the target method is executed
        @After(value = "pointcutAnnotation()")
        public void methodAfter(JoinPoint joinPoint) {
            String methodName = joinPoint.getSignature().getName();
            System.out.println("Post notification: Method name:" + methodName + ", parameter" + Arrays.asList(joinPoint.getArgs()));
        }
    
        /** * Return notification: triggered when the target method has finished executing and returns parameters. * /
        @AfterReturning(value = "pointcutAnnotation()", returning = "result")
        public void methodAfterReturning(JoinPoint joinPoint, Object result) {
            String methodName = joinPoint.getSignature().getName();
            System.out.println(Return notification: method name: + methodName + "," +
                    "Parameters" + Arrays.asList(joinPoint.getArgs()) + "," +
                    "Return result:");
            if (result instanceof String[]) {
                Arrays.stream((String[]) result).forEach(System.out::println);
            } else{ System.out.println(result); }}/** * Exception notification: fires when the target method throws an exception
        @AfterThrowing(value = "pointcutAnnotation()", throwing="ex")
        public void methodExceptionOccurred(JoinPoint joinPoint, Exception ex) {
            String methodName = joinPoint.getSignature().getName();
            System.out.println("Exception notification: Method name:" + methodName + ", parameter" + Arrays.asList(joinPoint.getArgs()) + ", exception message:"+ ex.getMessage()); }}Copy the code

1.2 Analysis of test case project structure

  • The entire project structure uses the Java Config method to start Spring. In the AppConfig class, the core functionality of the Spring project is defined:

    AppConfig is a Configuration class

    2, @ ComponentScan specifies the spring scan path is: com. Eugene. Sumarry. Aop. CSDN, it will show in the identification of all the spring under the package annotation will be resolved

    The @enableAspectJAutoProxy annotation indicates that Spring AOP-related components have been imported, indicating that AOP functionality is enabled. The proxyTargetClass attribute specifies which proxy aop will use to enhance the target class. The rules are as follows:

    proxyTargetClass meaning
    The default is false Using JDK dynamic proxies, the end result is that the proxy object and the target object implement the same interface
    Set to true Looking at the structure of the target class, if the target class is an interface, the JDK dynamic proxy is used. Otherwise, use the CGLIb agent.The follow-up will beAOP principle articleIn the proof

    Setting exposeProxy to true means that Spring exposes the proxy object to the thread variable ThreadLocal, which you’ll see below.

    In the AspectDefinition class, we define an aspect: If a spring bean has a method that is marked by the @aspectannotation annotation, the corresponding method will be enhanced, and the enhanced logic will include: It is not easy to describe the four parts of notification in terms of pre-notification, post-notification, return notification, and exception notification. You can think of them as four hook functions. The hook functions are described in the following table (also refer to the AspectDefinition class).

    Notification type (hook function) trigger features
    Pre notice Triggered before the target method is called It will be triggered as long as there are no serious problems with the program and pre-notification is defined
    The rear notice The target method is fired after its logic completes, or an exception is thrown within the target method As long as there are no serious problems with the program and post-notification is defined, it must be triggered
    Return to inform Fired when the return value of the target method is returned withAbnormal noticeOne mountain cannot tolerate two tigers
    Abnormal notice Fired when an exception occurs during the execution of the target method withReturn to informOne mountain cannot tolerate two tigers

    From this introduction, it is easy to see that the list method is enhanced and the findOne method is not enhanced for the ObjectServiceImpl class. This means that when the list method is executed, two things can happen. Case 1: Trigger pre – notification, post – notification, and return notification. Case two: pre – notice, post – notice, abnormal notice. The reason why exception advice and return advice are mutually exclusive will be demonstrated in a subsequent AOP principles article.

1.3 Test the three calls of the list method

1.3.1 No exception is thrown inside the list method of the target object

  • Run the main method of the entry. Java class directly, and you get the following result

  • If the target method list is thrown out without any exception, only pre-notification, post-notification, and return notification are triggered, which conforms to the first case of the above analysis.

1.3.2 An exception is thrown inside the list method of the target object

  • Change the list method to look like this:

    @Entry.AspectAnnotation
    @Override
    public String[] list(String str) {
        int x = 1 / 0;
        return new String[] {str, "avenger"."eug"};
    }
    Copy the code
  • Run the main method of the entry. Java class directly, and you get the following result

  • When exceptions are thrown out of the target method list, only pre-notification, post-notification, and exception notification are triggered, which conforms to the second case of the above analysis. The user also proves that return and exception notifications are mutually exclusive and cannot exist simultaneously. (Proven later in the AOP Principles section)

1.3.3 Call another enhanced method in the list method of the target object

  • Java class and add the findOne method to the @entry.aspectannotation notation. The structure looks like this:

    @Service
    public class ObjectServiceImpl implements ObjectService {
    
        @Entry.AspectAnnotation
        @Override
        public String[] list(String str) {
    	    // How to call the enhanced findOne method here?
            return new String[] {str, "avenger"."eug"};
        }
    
        // Add the @entry.AspectAnnotation annotation here
        @Entry.AspectAnnotation
        @Override
        public String findOne(a) {
            return "avengerEug"; }}Copy the code
  • As noted above: How do I call the enhanced findOne method in the list method? Is it possible to use this.findone directly? It is clear that this. FindOne method cannot be used to call the enhanced findOne method. This involves scenarios where AOP fails, and to understand these scenarios, you need to understand how AOP is implemented. I’ll write a separate AOP failure scenario article later. So what are the ways to implement this requirement?

Method 1: Self dependency injection
  • The following code looks like this:

    @Service
    public class ObjectServiceImpl implements ObjectService {
    
        // In this case, inject a proxy object
        @Autowired
        ObjectService objectService;
    
        @Entry.AspectAnnotation
        @Override
        public String[] list(String str) {
            objectService.findOne();
            return new String[] {str, "avenger"."eug"};
        }
    
        @Entry.AspectAnnotation
        @Override
        public String findOne(a) {
            return "avengerEug"; }}Copy the code
Approach 2: Use the Spring context to retrieve the beans
  • The following code looks like this:

    @Service
    public class ObjectServiceImpl implements ObjectService.ApplicationContextAware {
    
        // Use the Spring context. Or there are a number of SpringContextUtils utility classes available on the web, but they all work in the same way, implementing the ApplicationContextAware interface
        ApplicationContext applicationContext;
    
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            this.applicationContext = applicationContext;
        }
    
        @Entry.AspectAnnotation
        @Override
        public String[] list(String str) {
            applicationContext.getBean(ObjectService.class).findOne();
            return new String[] {str, "avenger"."eug"};
        }
    
        @Entry.AspectAnnotation
        @Override
        public String findOne(a) {
            return "avengerEug"; }}Copy the code
Method 3: Use the exposeProxy attribute annotated by @enableAspectJAutoProxy
  • Set exposeProxy to true and Spring will expose the proxy object to a thread variable. Therefore, with exposeProxy set to true, continue to modify the ObjectServiceImpl class to implement the need to call the enhancement method findOne inside the enhancement method list

  • The code is modified as follows:

    @Service
    public class ObjectServiceImpl implements ObjectService {
    
        @Entry.AspectAnnotation
        @Override
        public String[] list(String str) {
            ((ObjectService) AopContext.currentProxy()).findOne();
            return new String[] {str, "avenger"."eug"};
        }
    
        @Entry.AspectAnnotation
        @Override
        public String findOne(a) {
            return "avengerEug"; }}Copy the code

    Use the aopContext.currentProxy () API to get the proxy objects spring exposes in thread variables. Since the proxy object exists in the thread variable, ThreadLocal, we need to ensure that the entire invocation chain does not switch threads, or we will not get the proxy object exposed by Spring.

Either way, the end result is this:

After the pre-notification of the list method is triggered, the findOne method executes, which indirectly indicates that,Pre-notification is triggered before the target method is executedBecause findOne is executed inside the target method list, the logic of the target method list is not finished. The list method’s post notification, return notification, or exception notification will not be triggered until the findOne method’s logic is finished. The calling process is shown in the figure below:

1.4 Based on 1.3.3 method 3, test the list method throw several cases of exceptions

Throw an exception before calling the findOne method

  • Objectserviceimpl. Java class:

    @Service
    public class ObjectServiceImpl implements ObjectService {
    
        @Entry.AspectAnnotation
        @Override
        public String[] list(String str) {
            // Throw an exception here. The findOne method has not been called yet
            int x = 1 / 0;
            ((ObjectService) AopContext.currentProxy()).findOne();
            return new String[] {str, "avenger"."eug"};
        }
    
        @Entry.AspectAnnotation
        @Override
        public String findOne(a) {
            return "avengerEug"; }}Copy the code
  • The running result is:

    Obviously, throwing an exception before calling the findOne method only affects the list method. It’s also a little easier to understand, because I haven’t called the findOne method yet, and it certainly doesn’t affect the findOne method.

1.4.2 Throw an exception after calling the findOne method

  • Objectserviceimpl. Java class:

    @Service
    public class ObjectServiceImpl implements ObjectService {
    
        @Entry.AspectAnnotation
        @Override
        public String[] list(String str) {
            ((ObjectService) AopContext.currentProxy()).findOne();
            // Throw an exception after calling the findOne method
            int x = 1 / 0;
            return new String[] {str, "avenger"."eug"};
        }
    
        @Entry.AspectAnnotation
        @Override
        public String findOne(a) {
            return "avengerEug"; }}Copy the code
  • The execution result is as follows:

    Obviously, throwing an exception after findOne does not affect the execution of findOne, as shown in the diagram we drew earlier:

    Int x = 1/0; int x = 1/0; int x = 1/0; This code, which only affects the list logic, will not affect the findOne method because the enhanced logic for findOne has already been executed.

  • As an added bonus: If you were familiar with the propagation mechanism of REQUIRES_NEW for Spring transactions, this would be the case as well. Suppose our list is the default propagation mechanism REQUIRED and findOne is the REQUIRES_NEW propagation mechanism. In this case, the findOne transaction will be committed and the list transaction will be rolled back. The principle of this REQUIRES_NEW propagation mechanism is consistent with the above analysis, that is, it creates a new transaction and throws an exception when the remaining logic of the list is executed after the findOne transaction has been committed, which of course does not affect findOne. So the only thing that’s going to affect this is the list method.

1.4.3 Throw an exception inside the findOne method when it is called

  • Objectserviceimpl. Java class:

    @Service
    public class ObjectServiceImpl implements ObjectService {
    
        @Entry.AspectAnnotation
        @Override
        public String[] list(String str) {
            ((ObjectService) AopContext.currentProxy()).findOne();
            return new String[] {str, "avenger"."eug"};
        }
    
        @Entry.AspectAnnotation
        @Override
        public String findOne(a) {
            // Throw an exception here
            int x = 1 / 0;
            return "avengerEug"; }}Copy the code
  • The running result is as follows:

    As a result, it affects the list and findOne methods, meaning that exceptions thrown inside the findOne method affect methods along the call chain, which in turn triggers both the list and findOne exception notifications. This is very much like the chain of responsibility process, if we don’t do extra exception catching for each node on the chain, as soon as one chain throws an exception, it will be thrown up layer by layer. Fortunately, the AOP invocation process in Spring uses the chain of responsibility design pattern. We will explain this in the next AOP Principles article.

  • As a bonus: If you are familiar with the REQUIRED propagation mechanism of Spring transactions, there should also be cases where an internal method throw exception ends up rolling back the entire call chain. This situation is consistent with the use case we are testing now, because the two methods are using the same transaction, and eventually the exception will be thrown up one layer at a time, and when the transaction manager is aware of the exception, the transaction manager will roll back.

Second, the summary

  • Through this study, we understandPre-notification, post-notification, return notification, and exception notificationIs actually a callback to the hook function forReturn notification and exception notification are mutually exclusiveAs well asHow does Spring determine which proxy method to use to generate proxy objectsThe question I will follow up onAOP principle articleProve it.
  • In sections 1.3 and 1.4, tests are done for various aop invocation situations, throw exceptions, and additional information about transactions, which will be explained later in the Transaction Principles section if not clear.
  • Source link: click to view
  • Feel free to like, bookmark and follow my posts if you find them useful. :laughing:
  • I’m a slow walker, but I never walk backwards