Simple use of AOP in Spring

Although the road is endless and faraway, I still want to pursue the truth in the world.

AOP

Aspect Oriented Programming means Aspect Oriented Programming, which may be because java's object-oriented programming is used to reduce duplicate code and coupling when dealing with log, authorization and other operations Therefore, a concept of aspect programming is introduced Popular aspect programming can be understood as an enhanced operation of multiple methods

Basic knowledge

This article doesn't explain the basics much. The following learning addresses are relatively comprehensive, but it is strongly recommended that you read the official website documents when learning Spirng related knowledge. Generally, blogs are the knowledge that some program apes understand and sort out by themselves. Many have a big problem because they don't understand clearly, so you must learn through the official website documents, Then assist the blog to understand some difficulties

dataaddress
Spring official websitehttps://docs.spring.io/spring-framework/docs/current/reference/html/core.html
AOP Foundationhttps://blog.csdn.net/qq_41701956/article/details/84427891

Introduce dependency

        <!-- AOP support -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
            <scope>test</scope>
        </dependency>
        <!--aspectJ Weave in -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
        </dependency>

I Turn on @ AspectJ support

Official documents:
To enable @AspectJ support with Java @Configuration, add the @EnableAspectJAutoProxy annotation, as the following example shows:

@Configuration
@EnableAspectJAutoProxy
public class AppConfig {

}

This means that when configuring using java annotation, add @ EnableAspectJAutoProxy to the configuration class to enable the default support of spring for AspectJ

Define section

Two tangent points are defined in the tangent plane, and the connection points within the tangent point overlap. In order to verify the priority of the notification

/**
 * @description: Define a slice
 *     be careful:
 *     1. Highest priority to lowest priority: @ Around, @ Before, @ After, @ AfterReturning, @ aftercrossing.
 *    Note, however, that the @ after advice method will be effectively called after any @ afterreturn or @ aftercrossing advice methods in the same aspect
 *    2. When two overlapping tangent points are defined in a tangent plane, if notifications are defined separately at this time, the order cannot be determined. The official website recommends that we merge the two tangent points
 *    Form a tangent point, or define another tangent plane, and specify the priority through @ Order (the smaller the data, the higher the priority)
 * @author: wangml
 * @date: 2021/8/11 09:00
 */
@Aspect
@Order(100)
@Component
public class NotVeryUsefulAspect {

    private static final Logger logger = LoggerFactory.getLogger(NotVeryUsefulAspect.class);

    @Pointcut("execution(* com.huntkey.rx..service.*.*(..))")
    public void pointMed1() {};

    @Pointcut("execution(* com.huntkey.rx..service.UserService.delUser(..))")
    public void pointMed2() {};

    @Before("pointMed1()")
    public void beforeMedExecute() {
        logger.info("----Section method 1 is called before execution.----");
    }

    @AfterReturning("pointMed1()")
    public void afterReturnMedExecute() {
        logger.info("----Section method 1 is executed after successful execution.----");
    }


    @After("pointMed1()")
    public void afterMedExecute() {
        logger.info("----Section method 1 is called after execution.--------");
    }

    @Around("pointMed2()")
    public Object aroundMed2Execute(ProceedingJoinPoint pjp) throws Throwable {
        logger.info("******Facet method 2 is called********");
        // Modify input parameters
        Object[] args = pjp.getArgs();
        logger.info("Initial parameters: {}", args);

        // Get method signature
        MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
        // Gets the parameter list of the method
        Class[] parameterTypes = methodSignature.getParameterTypes();
        if (String.class.isAssignableFrom(parameterTypes[0])) {
            // The first parameter type of the method parameter is String
            args[0] = "88888";
        }
        logger.info("Modified parameters: {}", args);
        logger.info("########surround, Method execution preprocessing#########");

        Object result = pjp.proceed(args);

        logger.info("########surround, Method execution post-processing#########");
        logger.info("Returned data: {}", result);
        return result;
    }
}

Define a UserService

/**
 * @description: UserService
 * @author: wangml
 * @date: 2021/8/11 09:25
 */
@Service
public class UserService {

    public void addUser() {
        System.out.println("insert a user into user table");
    }

    public String delUser(String id) {
        System.out.println("delete a user from user table where id = " + id);
        return "delete user success!";
    }
}

New test class

/**
 * @description:AOP Using test classes
 * @author: wangml
 * @date: 2021/8/11 09:32
 */
@RunWith(SpringRunner.class)
@SpringBootTest
public class AopTest {

    @Autowired
    private UserService userService;

//    @Autowired
//    private EmployeeService employeeService;

    @Test
    public void addUser() {
        userService.addUser();
    }

    @Test
    public void delUser() {
        userService.delUser("1111");
    }

//    @Test
//    public void addEmployee() {
//        employeeService.addEmployee();
//    }
//
//    @Test
//    public void delEmployee() {
//        employeeService.delEmployee("111");
//    }
}

Analysis: when addUser is executed, because the method is within the connection point expression range of pointMed1, the notification of pointMed1 will be executed. The contents of beforemedexecute, afterreturnmedexecute and aftermedexecute will be executed. The execution results are as follows, which meet the expectations

2021-08-11 17:38:03.200  INFO 13388 --- [           main] c.h.rx.sceo.aop.NotVeryUsefulAspect      : ----Section method 1 is called before execution.----
insert a user into user table
2021-08-11 17:38:03.216  INFO 13388 --- [           main] c.h.rx.sceo.aop.NotVeryUsefulAspect      : ----Section method 1 is executed after successful execution.----
2021-08-11 17:38:03.217  INFO 13388 --- [           main] c.h.rx.sceo.aop.NotVeryUsefulAspect      : ----Section method 1 is called after execution.--------

When delUser is executed, the join point expressions of the two pointcuts are satisfied and will be executed. However, because @ around > @ before > @ afterreturning > @ after, pointpointmed2 will be executed first. The results are as follows, which meet the expectations

2021-08-11 17:39:28.614  INFO 14100 --- [           main] c.h.rx.sceo.aop.NotVeryUsefulAspect      : ******Facet method 2 is called********
2021-08-11 17:39:28.614  INFO 14100 --- [           main] c.h.rx.sceo.aop.NotVeryUsefulAspect      : Initial parameters: 1111
2021-08-11 17:39:28.617  INFO 14100 --- [           main] c.h.rx.sceo.aop.NotVeryUsefulAspect      : Modified parameters: 88888
2021-08-11 17:39:28.617  INFO 14100 --- [           main] c.h.rx.sceo.aop.NotVeryUsefulAspect      : ########surround, Method execution preprocessing#########
2021-08-11 17:39:28.617  INFO 14100 --- [           main] c.h.rx.sceo.aop.NotVeryUsefulAspect      : ----Section method 1 is called before execution.----
delete a user from user table where id = 88888
2021-08-11 17:39:28.632  INFO 14100 --- [           main] c.h.rx.sceo.aop.NotVeryUsefulAspect      : ----Section method 1 is executed after successful execution.----
2021-08-11 17:39:28.632  INFO 14100 --- [           main] c.h.rx.sceo.aop.NotVeryUsefulAspect      : ----Section method 1 is called after execution.--------
2021-08-11 17:39:28.632  INFO 14100 --- [           main] c.h.rx.sceo.aop.NotVeryUsefulAspect      : ########surround, Method execution post-processing#########
2021-08-11 17:39:28.632  INFO 14100 --- [           main] c.h.rx.sceo.aop.NotVeryUsefulAspect      : Returned data: delete user success!

Special attention:

  1. Surround can control whether the actual method is executed. It is implemented through processed. If it is not written, the method will not be executed, and the calling process is a chain call, which is similar to the processing of filter FilterChain
  2. Wrapping can be used to pass parameters in the processed method before proceed ing. The processed method returns data. If the method is of void type, null is returned by default
  3. Generally, surround is preferred (official website)

Define another slice

It is mainly used to verify the priority of different cut points and to define the use of cut points with user-defined annotations

/**
 * @description: Slice intercepts all methods that use annotations
 * @author: wangml
 * @date: 2021/8/11 14:16
 */
@Aspect
@Order(10)
@Component
public class VeryUsefulAspect {

    public static final Logger logger = LoggerFactory.getLogger(VeryUsefulAspect.class);

    @Pointcut("@annotation(com.huntkey.rx.sceo.anno.Idempotent)")
    public void pointCutMed(){};

    @Around("pointCutMed()")
    public Object aroundAnn(ProceedingJoinPoint joinPoint) throws Throwable {
        // Gets the data passed in the annotation
        Object[] args = joinPoint.getArgs();
        logger.info("Method parameters are not processed, The parameter value is: {}", args);

        MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature();
        Method method = methodSignature.getMethod();

        Idempotent annotation = method.getAnnotation(Idempotent.class);
        String value = annotation.value();
        String name = annotation.name();
        if (!"".equals(value) || !"".equals(name)) {
            // When the return value of the method in the annotation is not the default value, there is a re assignment
            logger.info("*****Annotation data operation********");
            logger.info("Idempotent Annotation data, value: {} name: {}", value, name);
        }

        // Specific implementation method
        Object proceed = joinPoint.proceed();

        // Processed is the data returned after the method is executed. If the method is void, it will be null by default
        logger.info("***Method returns data after execution: {}", proceed);
        return proceed;
    }
}

Custom annotation @ Idempotent

/**
 * @description: Method annotation
 * @author: wangml
 * @date: 2021/8/11 14:12
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {

    @AliasFor("name")
    String value() default "";

    @AliasFor("value")
    String name() default "";
}

Define an EmployeeService

/**
 * @description: Employee service (test AOP annotation usage)
 * @author: wangml
 * @date: 2021/8/11 14:48
 */
@Service
public class EmployeeService {

    private static final Logger logger = LoggerFactory.getLogger(EmployeeService.class);

    public void addEmployee() {
        logger.info("insert a employee into employee table");
    }

    @Idempotent(name = "wangml")
    public String delEmployee(String id) {
        logger.info("delete a employee from employee table where id = {}", id);
        return "delete employee success!";
    }
}

Analysis: addEmployee doesn't add @ Idempotent, so only pointMed1 in NotVeryUsefulAspect
The connection point expression in is satisfied, the execution method is executed, and the output is as follows, which meets the expectation

2021-08-11 18:04:37.884  INFO 5228 --- [           main] c.h.rx.sceo.aop.NotVeryUsefulAspect      : ----Section method 1 is called before execution.----
2021-08-11 18:04:37.899  INFO 5228 --- [           main] c.h.rx.sceo.service.EmployeeService      : insert a employee into employee table
2021-08-11 18:04:37.900  INFO 5228 --- [           main] c.h.rx.sceo.aop.NotVeryUsefulAspect      : ----Section method 1 is executed after successful execution.----
2021-08-11 18:04:37.900  INFO 5228 --- [           main] c.h.rx.sceo.aop.NotVeryUsefulAspect      : ----Section method 1 is called after execution.--------

delEmployee adds @ Idempotent, so the join point expression of pointCutMed in the newly defined facet VeryUsefulAspect satisfies the join point expression in pointMed1 in NotVeryUsefulAspect, but the facet priority of VeryUsefulAspect is higher than that of NotVeryUsefulAspect, so the notification in VeryUsefulAspect is executed before NotVeryUsefulAspect The execution method is entered as follows, which meets the expectation

2021-08-11 18:09:41.215  INFO 7796 --- [           main] c.huntkey.rx.sceo.aop.VeryUsefulAspect   : Method parameters are not processed, The parameter value is: 111
2021-08-11 18:09:41.219  INFO 7796 --- [           main] c.huntkey.rx.sceo.aop.VeryUsefulAspect   : *****Annotation data operation********
2021-08-11 18:09:41.219  INFO 7796 --- [           main] c.huntkey.rx.sceo.aop.VeryUsefulAspect   : Idempotent Annotation data, value:  name: wangml
2021-08-11 18:09:41.219  INFO 7796 --- [           main] c.h.rx.sceo.aop.NotVeryUsefulAspect      : ----Section method 1 is called before execution.----
2021-08-11 18:09:41.236  INFO 7796 --- [           main] c.h.rx.sceo.service.EmployeeService      : delete a employee from employee table where id = 111
2021-08-11 18:09:41.236  INFO 7796 --- [           main] c.h.rx.sceo.aop.NotVeryUsefulAspect      : ----Section method 1 is executed after successful execution.----
2021-08-11 18:09:41.236  INFO 7796 --- [           main] c.h.rx.sceo.aop.NotVeryUsefulAspect      : ----Section method 1 is called after execution.--------
2021-08-11 18:09:41.236  INFO 7796 --- [           main] c.huntkey.rx.sceo.aop.VeryUsefulAspect   : ***Method returns data after execution: delete employee success!

summary

  • aop is powerful, but it's best not to define too many, or you will faint when checking the problem, because there may be layers of nesting, and you can process the data every time
  • It is applicable to reduce repetitive code. It can be used when many business classes need some processing logic that is not available before

Keywords: Spring AOP

Added by ego0 on Fri, 24 Dec 2021 09:29:16 +0200