The Springboot project quickly realizes the Aop function

foreword

There are several key points in this article. First, some basic theoretical knowledge about AOP needs to be understood before formal use of AOP; second, how to quickly integrate Aop functions in the Springboot project; third, some small uses of AOP Tips and dos and don'ts.

Dependency import

After Springboot introduces the AOP dependency package, generally speaking, there is no need to do other configurations. In the lower version or other configurations affect the related functions of AOP, resulting in the aop function not taking effect, you can try adding @ to the startup class EnableAspectJAutoProxy to enable;

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

Code

1. Custom annotation @TestAop

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAop {
}

2、ExampleAop .java

@Component
@Aspect
@Slf4j
public class ExampleAop {

    //切入点:增强标有@TestAop注解的方法
    @Pointcut(value = "@annotation(TestAop)")
    //切入点签名
    public void pointcut() {
        System.out.println("pointCut签名。。。");
    }

    //前置通知
    @Before("pointcut()")
    public void deBefore(JoinPoint joinPoint) throws Throwable {
        log.info("前置通知被执行");
        //可以joinpoint中得到命中方法的相关信息,利用这些信息可以做一些额外的业务操作;
    }

    //返回通知
    @AfterReturning(returning = "ret", pointcut = "pointcut()")
    public void doAfterReturning(Object ret) throws Throwable {
        log.info("返回通知被执行");
        //可以joinpoint中得到命中方法的相关信息,利用这些信息可以做一些额外的业务操作;
    }

    //异常通知
    @AfterThrowing(throwing = "ex", pointcut = "pointcut()")
    public void throwss(JoinPoint jp, Exception ex) {
        log.info("异常通知被执行");
        //可以joinpoint中得到命中方法的相关信息,利用这些信息可以做一些额外的业务操作;
        //可以从ex中获取到具体的异常信息
    }

    //后置通知
    @After("pointcut()")
    public void after(JoinPoint jp) {
        log.info("后置通知被执行");
        //可以joinpoint中得到命中方法的相关信息,利用这些信息可以做一些额外的业务操作;
    }

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        log.info("------环绕通知 start");
        String methodName = proceedingJoinPoint.getSignature().getName();
        String className = proceedingJoinPoint.getTarget().getClass().getName();
        Object[] args = proceedingJoinPoint.getArgs();
        String argsName = null;
        StringBuilder sb = new StringBuilder();
        if (args != null && args.length > 0) {
            for (int i = 0; i < args.length; i++) {
                if (args[i] != null) {
                    sb.append(";").append(args[i].getClass().getName());
                }
            }
            if (sb.toString().length() > 0) {
                argsName = sb.toString().substring(1);
            }
        }
        log.info("命中类:{},方法{},参数{};", className, methodName, argsName);
        Object proceed = proceedingJoinPoint.proceed();
        log.info("------环绕通知 end");
        return proceed;
    }

}

Core Annotations and Classes

1. Aspect, which means that the current class is an aspect class. A simple understanding is the abstract encapsulation of the entry point and the notification, expressing the correspondence between the entry point and the notification method;

2. @Before, pre-notification, used on the method, the method marked by the @Before annotation will be executed before the cut-in method is executed;

3. @After, post-notification, used on the method, the method marked by the @After annotation will be executed after the cut-in method is executed;

4. @AfterReturning, return notification, used on the method, the method marked by the @AfterReturning annotation will be executed after the returned result of the cut-in method;

6. @AfterThrowing: Exception notification, used on methods, the method marked by @AfterThrowing annotation will be executed after the exception is thrown by the cut-in method, generally used to obtain exception information purposefully;

7. @Aroud: surround notification, used on methods, the method marked by @Around annotation will be executed before and after the cut-in method is executed;

8. @Pointcut, the entry point, marked on the method, is used to define the entry point. The so-called pointcut refers to the definition of which connection points are cut and enhanced. In Spring, it specifically refers to the definition of which methods are cut and enhanced. ; There are many kinds of expressions that are annotated by @Pointcut to indicate the pointcut, the most commonly used are two, execution expression and annotation;

9. Jointpoint, the connection point, the so-called connection point refers to the position point cut into by the aop aspect, and in Spring, it specifically refers to the method cut into;

10、PointCut,

11. Advice, notification, the so-called notification refers to the definition of specific operations to be done after intercepting the defined entry point; in Spring, it refers to being annotated by @Before, @After, @AfterReturning, @AfterThrowing, @Around the method of marking;

Common ways to mark pointcuts

1. Execution expression

Expression method: access modifier return value package name. package name... class name. method (parameter list)

Example 1: It means to match all methods starting with add in all com.fanfu packages and all classes under subpackages, and the return value and parameters are not limited;

@Pointcut("execution(* com.fanfu..*.*.add*(..))")

Example 2: Indicates matching all com.fanfu packages and all classes under subpackages that start with add, the parameter type is String, and the return value is not limited;

@Pointcut("execution(* com.fanfu..*.*.add*(String))")

Example 3: Indicates matching any class, any method, and any parameter under the com.fanfu package;

@Pointcut("execution(* com.fanfu..*.*.*(String))")
execution() is the body of the expression;
the first * indicates that the return value type is arbitrary, that is, there is no limit to the return value type; the
* after the package indicates the current package, and two consecutive .. after the package indicate the current package and sub-packages;
( ..) means any parameter;
the last *.*(..) means match any class, any method, any parameter;

2. Notes

Annotation syntax: @annotation (custom annotation)

Example: Indicates methods that match all @TestAop annotations;

@Pointcut("@annotation(com.fanfu.config.TestAop)")

Tips for Spring Aop

Each @Pointcut can use execution or annotations to define pointcuts, and logical operators can also be used between multiple pointcuts, such as &&, ||, !Operation;

1. point1()&&point2() means the intersection of all entry points that hit point1 and point2; for example: in all classes of the com.fanfu package and all its subpackages, the method name starts with add, and the parameter type is String. The method is the intersection with all methods in the com.fanfu.service package and all subpackages, regardless of method name and parameter type, that is, in all classes in the com.fanfu.service package and all subpackages, the method or It is the method of add1 or add2, and the logic in the surround notification around() method will be executed before and after the call;

@Component
@Aspect
@Slf4j
public class ExampleAop {
    @Pointcut("execution(* com.fanfu..*.*.add*(String))")
    public void point1() {
    }
    @Pointcut("execution(* com.fanfu.service..*.*(..))")
    public void point2() {
    }
    @Pointcut("point1()&&point2()")
    public void point() {
    }
    @Around("point()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        log.info("------around start");
        Object proceed = proceedingJoinPoint.proceed();
        log.info("------around end");
        return proceed;
    }
}

2. point1()&&point2() represents the union of all entry points that hit point1 and point2; for example: in all classes of the com.fanfu.service package and all its subpackages, the method name is add1, and the parameter type is String All methods, and all classes in the com.fanfu.controller package and all subpackages, the method name is add2, and the parameter type is the union of all methods of String, that is, the package of com.fanfu.service or com.fanfu.controller And in all classes of all subordinate subpackages, the method or the method of add1 or add2 will execute the logic in the surround notification around() method before and after the call;

@Component
@Aspect
@Slf4j
public class ExampleAop {
    @Pointcut("execution(* com.fanfu.service..*.add*(String))")
    public void point1() {
    }
    @Pointcut("execution(* com.fanfu.controller..*.*.add*(String))")
    public void point2() {
    }
    @Pointcut("point1()||point2()")
    public void point() {
    }
    @Around("point()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        log.info("------around start");
        Object proceed = proceedingJoinPoint.proceed();
        log.info("------around end");
        return proceed;
    }
}

3. !point() indicates that all entry points that hit point() are reversed. For example: in all classes of the com.fanfu.service package and all its subpackages, methods that do not start with add will be executed before and after the call Notify the logic inside the around() method;

@Component
@Aspect
@Slf4j
public class ExampleAop {
    @Pointcut("execution(* com.fanfu.service..*.add*(String))")
    public void point() {
    }
    @Around("!point()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        log.info("------around start");
        Object proceed = proceedingJoinPoint.proceed();
        log.info("------around end");
        return proceed;
    }
}

Notes on Spring Aop

1. Match the method with the defined cut point. If the method matches the current class for the first time in the current call chain, it will hit, that is, execute the relevant notification. If the current call chain is not over, the current method is called in the current method If the method of the class matches other pointcuts, it will not be hit again, that is, when other methods matching the pointcut are executed, relevant notifications will not be triggered again;

For example:

When http://localhost:8080/example is requested, the example method in ExampleController is triggered, and ExampleController#example() calls ExampleService#test(), and inside ExampleService#test(), ExampleService#test1 is called sequentially () and ExampleService#test2(); in ExampleAop, according to the configuration in execution, it can match test(), test1(), test2(), and the actual hit method is only test();

@Component
@Aspect
@Slf4j
public class ExampleAop {
    @Pointcut("execution(* com.fanfu.service.impl.ExampleServiceImpl.test*(..))")
    public void point2() {
        log.info("切入点匹配");
    }
    @Around("point()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        log.info("------around start");
        String methodName = proceedingJoinPoint.getSignature().getName();
        String className = proceedingJoinPoint.getTarget().getClass().getName();
        Object[] args = proceedingJoinPoint.getArgs();
        String argsName=null;
        StringBuilder sb=new StringBuilder();
        if (args!=null&&args.length>0) {
            for (int i = 0; i < args.length; i++) {
                if (args[i] != null) {
                    sb.append(";").append(args[i].getClass().getName());
                }
            }
            if (sb.toString().length()>0) {
                argsName=sb.toString().substring(1);
            }
        }
        log.info("命中类:{},方法{},参数{};",className,methodName,argsName);
        Object proceed = proceedingJoinPoint.proceed();
        log.info("------around end");
        return proceed;
    }
}
@Service
@Slf4j
public class ExampleServiceImpl implements IExampleService {

    @Override
    public String test(String msg) {
        log.info("test方法被执行");
        this.test1(msg);
        this.test2(msg);
        return msg;
    }

    public String test1(String msg) {
        log.info("test1方法被执行");
        return "msg1";
    }

    public String test2(String msg) {
        log.info("test2方法被执行");
        return "msg2";
    }
}
public interface IExampleService {
    public String test(String msg);
    public String test1(String msg);
    public String test2(String msg);
}
@RestController
@Slf4j
public class ExampleController {
    @Autowired
    private IExampleService exampleService;
    @GetMapping("/example")
    public String example() {
        log.info("example start");
        exampleService.test(null);
        log.info("example end");
        return String.valueOf(System.currentTimeMillis());
    }
}

2. For the above question, if the execution expression is replaced with a comment, will the result be different? Then change @Pointcut in ExampleAop into annotation form, and then add annotation @TestAop in ExampleService#test1(), ExampleService#test2(), ExampleService#test(), the verification result is still the same, only test() will hit, Others will not! So pay attention.

@Component
@Aspect
@Slf4j
public class ExampleAop {
    @Pointcut("@annotation(TestAop)")
    public void point() {
    }
    @Around("point()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        log.info("------around start");
        String methodName = proceedingJoinPoint.getSignature().getName();
        String className = proceedingJoinPoint.getTarget().getClass().getName();
        Object[] args = proceedingJoinPoint.getArgs();
        String argsName = null;
        StringBuilder sb = new StringBuilder();
        if (args != null && args.length > 0) {
            for (int i = 0; i < args.length; i++) {
                if (args[i] != null) {
                    sb.append(";").append(args[i].getClass().getName());
                }
            }
            if (sb.toString().length() > 0) {
                argsName = sb.toString().substring(1);
            }
        }
        log.info("命中类:{},方法{},参数{};", className, methodName, argsName);
        Object proceed = proceedingJoinPoint.proceed();
        log.info("------around end");
        return proceed;
    }
}
@Service
@Slf4j
public class ExampleServiceImpl implements IExampleService {

    @Override
    @TestAop
    public String test(String msg) {
        log.info("test方法被执行");
        this.test1(msg);
        this.test2(msg);
        return msg;
    }
    @Override
    @TestAop
    public String test1(String msg) {
        log.info("test1方法被执行");
        return "msg1";
    }
    @Override
    @TestAop
    public String test2(String msg) {
        log.info("test2方法被执行");
        return "msg2";
    }
   
}

3. Under what circumstances will ExampleService#test1(), ExampleService#test2(), and ExampleService#test() hit at the same time? Let ExampleController#example() to ExampleService#test1(), ExampleService#test2(), and ExampleService#test() be on different call chains, so they can be hit at the same time;

@RestController
@Slf4j
public class ExampleController {
    @Autowired
    private IExampleService exampleService;
    @GetMapping("/example")
    public String example() {
        log.info("example start");
        exampleService.test(null);
        exampleService.test1(null);
        exampleService.test2(null);
        log.info("example end");
        return String.valueOf(System.currentTimeMillis());
    }
}

Summarize

It is not difficult to integrate Aop with Springboot, the difficulty is how to use it well in actual business. If you want to make good use of AOP, it is very necessary to understand the working principle of Spring AOP. For example, the specific entry point has been matched according to the definition in @Pointcut, but in fact it may not necessarily hit, that is, trigger the relevant notification. The working principle of Spring AOP, this pit will definitely be filled later.

Guess you like

Origin blog.csdn.net/fox9916/article/details/129654377