Using AspectJ to implement AOP function in Spring 5 framework

Posted by jerryroy on Sun, 14 Jun 2020 07:54:54 +0200

1. Preface

The most common way in development is to use Aspect to realize AOP function. Next, we will simply use @ Aspect to realize AOP Aspect oriented programming. For the usage of Spring native AOP, please refer to the previous blog post AOP proxyfactory underlying implementation of spring 5 framework (5)

Before using AspectJ, let me review some important concepts as follows:

  1. Before: executes program logic before a connection point.
  2. after returning: the program logic executed after the connection point is normal. Note that if the program throws an exception, the notification will not be executed.
  3. after throwing: the program logic executed when the program is abnormal.
  4. after: the program logic when the connection point ends execution (it will be executed whether there is an exception or not)
  5. around: the most powerful notification function in spring, which can complete and implement the above four functions.

2. Use Aspect to realize AOP function

If you use @ Aspect annotation, you need to support the following Aspect dependencies in your project:

        <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.8.10</version>
        </dependency>

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.10</version>
        </dependency>

Next, we still use Calculator to demonstrate the use of AspectJ method as follows:

  • New interface and its implementation

First, AspectJ provides us with the following notice annotations:

|Notice name|||

public interface Calculator {

    int add(int a, int b);

    int sub(int a, int b);

    double divide(int a, int b);
}


@Service
public class CalculatorImpl implements Calculator {
    @Override
    public int add(int a, int b) {
        return a + b;
    }

    @Override
    @AdviceRequired
    public int sub(int a, int b) {
        return a - b;
    }

    @Override
    public double divide(int a, int b) {
        return a / b;
    }
}

  • Define tangent class
@Aspect
@Component
public class LogAspect {

    /**
     * Defines the pointcut, where execution defines the pointcut expression
     */
    @Pointcut(value = "execution(* *..aop..*(..))")
    public void logPoint() {

    }
    /**
     * Pre notification is executed before method execution
     */
    @Before(value = "execution(public * com.codegeek.aop.day1.CalculatorImpl.*(..))")
    public static void logStart(JoinPoint joinPoint) {
        Object[] args = joinPoint.getArgs();
        // Get the source object of the execution method -- the place where the expression actually cuts in and runs
        System.out.println(joinPoint.getTarget().getClass());
        //  System.out.println(joinPoint.getStaticPart()); print detailed pointcut expression

        System.out.println("LogAspect-General notice method@before: " + joinPoint.getSignature().getName() + "The log is on....Method parameters:" + Arrays.asList(args));
    }

    /**
     * The notification is executed when the result is returned after the method is executed
     * @param result
     */
    @AfterReturning(value = "execution(public * com.codegeek.aop.day1.CalculatorImpl.*(..))", returning = "result")
    public static void logRun(Object result) {
        System.out.println("LogAspect-General notice@AfterReturning" + "The operation result is:" + result);
    }

    /**
     *
     * This notification will be executed if there is an exception in method execution
     */
    @AfterThrowing(value = "execution(public  * com.codegeek.aop.day1.Calculator.*(..))", throwing = "e")
    public static void logException(Exception e) {
        System.out.println("LogAspect-General notice@AfterThrowing Something's wrong:" + e);
    }

    /**
     * Post notification pointcut after execution
     */
    @After(value = "execution(public  * com.codegeek.aop.day1.Calculator.*(..))")
    public void logEnd() {
        System.out.println("LogAspect-General notice@After The log is over");
    }

    @Around(value = "logPoint()")
    public Object logAround(ProceedingJoinPoint proceedingJoinPoint) {
        Object proceed = null;
        try {
            // @Before
            System.out.println("Notice before surround.....Currently executed method:" + proceedingJoinPoint.getSignature().getName());
            proceed = proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
            // @AfterReturn
            System.out.println("Post orbit notification.....");
        } catch (Throwable throwable) {
            // @AfterThrowing
            System.out.println("Surround exception notification.......");
            // If the exception is not thrown, the exception catch will be dropped, and the normal notification exception will not be able to sense the exception object, so it is considered to be executed normally
           throw new RuntimeException(throwable);

        } finally {
            // @After
            System.out.println("Wrap end notification.....");
        }
        return proceed;
    }
}

After we define the facet class, we need to pay attention to several important points:

  • The @ Component and @ Aspect annotations are used to declare a tangent class object, which can be scanned by Spring IOC container.
  • The @ Pointcut annotation is used to define the pointcuts of methods. The so-called pointcuts are used to determine whether notifications can be executed in the target methods of those target classes.
  • @Before, @ Around, @ After, @ AfterReturning, @ AfterThrowing can introduce the value of pointcuts, or customize the value of pointcuts

Next we need to define the configuration class so that Spring can scan to the tangent class and successfully execute the notification.

@Configuration
@ComponentScan(basePackages = {"com.codegeek.aop.day1"})
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AOPConfig {

}
  • Test method:

It should be noted that this configuration class can be introduced on the test class as follows:

[external link image transfer failed. The source station may have anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-nijFEgSr-1592113317231)(Spring-AOP uses @ Aspect annotation. assets/image-20200614122427720.png))

    @Test
    public void testAspect() {
        // Configure the aspect class IOC to generate the proxy class of the interface, otherwise it is the basic class
        System.out.println();
        Calculator bean = applicationContext.getBean(Calculator.class);
        System.out.println(bean.add(1, 5));
        System.out.println("-------------------");
    }

Here we use the normal method to test the running results as follows:

Notify before wrapping..... Current execution method: add
class com.codegeek.aop.day1.CalculatorImpl
 LogAspect - normal notification method @ before: add log started.... method parameter: [1, 5]
Notification after orbit
 Wrap end notification
 LogAspect - general notification @ After log is over
 LogAspect general notice @ AfterReturning: 6
6
-------------------

Next, we test an exception as follows:

    @Test
    public void testAspect() {
        // Configure the aspect class IOC to generate the proxy class of the interface, otherwise it is the basic class
        System.out.println();
        Calculator bean = applicationContext.getBean(Calculator.class);
        System.out.println(bean.add(1, 5));
        System.out.println("-------------------");
        System.out.println(bean.divide(5, 0));
        System.out.println(bean);
        System.out.println(bean.getClass());
    }

The output after running the test method is as follows:

Notify before wrapping..... Current execution method: add
class com.codegeek.aop.day1.CalculatorImpl
 LogAspect - normal notification method @ before: add log started.... method parameter: [1, 5]
Notification after orbit
 Wrap end notification
 LogAspect - general notification @ After log is over
 LogAspect general notice @ AfterReturning: 6
6
-------------------
Notify before wrapping..... Current execution method: divide
class com.codegeek.aop.day1.CalculatorImpl
 LogAspect - normal notification method @ before: divide log started.... method parameter: [5, 0]
Surround exception notification
 Wrap end notification
 LogAspect - general notification @ After log is over
 LogAspect - general notice @ AfterThrowing has an exception: java.lang.RuntimeException: java.lang.ArithmeticException: / by zero

java.lang.RuntimeException: java.lang.ArithmeticException: / by zero

We found that the exception notification has been executed, but the exception notification will not be executed when the program is running normally. At the same time, we can find that the @ AfterReturning annotation notification has not been executed, and it will only execute the notification when the program is running normally. The order of notice execution can be expressed as follows:

[normal front]
try {
  Before surround notification
  Orbit execution
  Orbit return
} catch(Exception e) {
  Surround exception notification
} finally {
  Surround post notification
}
[normal post]
[normal method return / normal exception notification]

3. Using xml to realize AOP

First, we annotate the @ Aspect annotation on the LogAspect class as follows:

[failed to save the image in the external link. The source station may have anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-Dhct8PCr-1592113317233)(Spring-AOP uses @ Aspect annotation. Assets / image-202006123137800. PNG))

Then create a new aspect class OrderAspect as follows:

@Component
//@Aspect
@Order(1)
public class OrderAspect {
    
   // @Before(value = "execution(* *..aop..*(..))")
    public static void logStart(JoinPoint joinPoint) {
        Object[] args = joinPoint.getArgs();
        //  System.out.println(joinPoint.getStaticPart()); print detailed pointcut expression

        System.out.println("OrderAspect-General notice method@before: " + joinPoint.getSignature().getName() + "The log is on....Method parameters:" + Arrays.asList(args));
    }
   // @Pointcut(value = "execution(* *..aop..*(..))")
    public void logPoint() {

    }

    // returning tells the value returned after the method is executed
   // @AfterReturning(value = "execution(public * com.codegeek.aop.day1.CalculatorImpl.*(..))", returning = "result")
    public static void logRun(Object result) {
        System.out.println("OrderAspect-General notice@AfterReturning" + "The operation result is:" + result);
    }

    //@AfterThrowing(value = "execution(public  * com.codegeek.aop.day1.Calculator.*(..))", throwing = "e")
    public static void logException(Exception e) {
        System.out.println("OrderAspect-General notice@AfterThrowing Something's wrong:" + e);
    }

  //  @After(value = "execution(public  * com.codegeek.aop.day1.Calculator.*(..))")
    public void logEnd() {
        System.out.println("OrderAspect-General notice@After The log is over");
    }
}

Then configure the following in xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--  false use jdk Agent otherwise use CGlib-->
<aop:aspectj-autoproxy proxy-target-class="true"/>
<context:component-scan base-package="com.codegeek.aop.day1"/>

  <aop:config>
    <aop:pointcut id="logPoint" expression="execution(* *..aop..*(..))"/>
    <aop:aspect id="myAspect" ref="orderAspect">
      <aop:before method="logStart" pointcut-ref="logPoint" arg-names="joinPoint"/>
      <aop:after method="logEnd" pointcut-ref="logPoint" />
      <aop:after-returning method="logRun" pointcut-ref="logPoint" returning="result"/>
      <aop:after-throwing method="logException" pointcut-ref="logPoint" throwing="e"/>
    </aop:aspect>
  </aop:config>
  <bean id="orderAspect" class="com.codegeek.aop.day1.OrderAspect"/>
</beans>

We run the test again as follows:

    @Test
    public void testAspect() {
        // Configure the aspect class IOC to generate the proxy class of the interface, otherwise it is the basic class
        System.out.println();
        Calculator bean = applicationContext.getBean(Calculator.class);
        System.out.println(bean.add(1, 5));
        System.out.println("-------------------");
        System.out.println(bean.divide(5, 0));
        System.out.println(bean);

The operation results are as follows:

OrderAspect-General notice method@before: add The log is on....Method parameters:[1, 5]
OrderAspect-General notice@AfterThe log is over
OrderAspect-General notice@AfterReturningThe operation result is:6
6
-------------------
OrderAspect-General notice method@before: divide The log is on....Method parameters:[5, 0]
OrderAspect-General notice@AfterThe log is over
OrderAspect-General notice@AfterThrowingSomething's wrong:java.lang.ArithmeticException: / by zero

java.lang.ArithmeticException: / by zero

4. How to select AOP type

In the above, we demonstrated an AOP implementation based on Aspect style annotation, and also saw an AOP implementation based on XML configuration. These are affected by a variety of factors, such as program requirements, development tools, the level of familiarity of the development team with AOP, and so on. Since both Spring AOP and AspectJ use the same Aspect style, if there are additional functional requirements for AspectJ, you can migrate the existing Aspect style annotation code to Aspect.

Topics: calculator Spring Java xml