• SpringBoot 之 AOP


    前言:

            Spring 三大核心思想是啥,还记得不?IOC(控制反转),DI(依赖注入),AOP(面向切面编程)。回顾一下这三个东西:

            IOC:不考虑使用 Spring,纯手写 java 程序时,对象之间的使用和依赖完全是由程序员手写代码控制的,何时创建对象完全在代码中标明。IOC 是一种编程思想,它的核心作用就是把对象的创建、管理由人工负责转成 Spring 托管。

            DI:IOC的核心思想很好,那么怎么实现呢?DI就是实现IOC的方案,例如常见的 @Value、@Resource、@Autowired 注解、配置文件都是最具体的使用样例。

            AOP:简单点说就是,把某个核心方法切开看看能不能加点其他处理逻辑。

    一、相关注解

    @Aspect描述这是一个切面类
    @Pointcut定义一个切面,所关注事件入口
    @Before切入方法执行前干什么
    @Around切入方法执行时的增强处理,慎用
    @After切入方法执行后后干什么
    @AfterReturning对切入方法返回数据的增强处理
    @AfterThrowing切入方法抛出异常时的处理

    二、常用注解说明

    常用注解三个:Before、After、Pointcut

    切面类:

    1. @Slf4j
    2. @Aspect
    3. @Component
    4. public class AopTest {
    5. @Pointcut("execution(* quancheng.demo.service..TestService.test(..))")
    6. public void pointFun() {
    7. }
    8. @Before("pointFun()")
    9. public void doB() {
    10. log.info("Before doB...");
    11. }
    12. @After("pointFun()")
    13. public void doA() {
    14. log.info("After doA...");
    15. }
    16. }

    切入的类和方法:

    1. @Slf4j
    2. @Service
    3. public class TestService {
    4. public String test(String str) {
    5. log.info("test service......{}", str);
    6. return str;
    7. }
    8. }

    运行时日志截图:

    PointCut 定义了一个切面,该注解有两个表达式:

    1. execution 表达式

    1. execution(* quancheng.demo.service..TestService.test(..))
    2. * :表示返回值类型,* 表示所有类型;
    3. quancheng.demo.service:这是一个包名,标识需要拦截的包;
    4. 包名后的 ..:表示当前包和所有其子包,在本例中指 quancheng.demo.service 包和其子包下所有类;
    5. TestService :表示具体的类名,如果是 * 表示所有类;
    6. test(..) :test是具体方法名,可以用 * 表示所有的方法;后面括弧里面表示方法的参数,双点表示任何参数;

    2.@annotation 表达式(推荐使用

    1. @annotation(quancheng.demo.aop.anno.ServiceDeal)
    2. @annotation 的参数是一个注解的全类限定名,可以自定义也可以使用现有的注解;
    3. quancheng.demo.aop.anno.ServiceDeal 是自定义的注解;

    切面类样例如下:

    1. @Slf4j
    2. @Aspect
    3. @Component
    4. public class AopTest {
    5. @Pointcut("@annotation(quancheng.demo.aop.anno.ServiceDeal)")
    6. public void pointFan() {
    7. }
    8. @Before("pointFan()")
    9. public void doB() {
    10. log.info("Before doB...");
    11. }
    12. @After("pointFan()")
    13. public void doA() {
    14. log.info("After doA...");
    15. }
    16. }

    切面类的自定义注解:

    1. @Target({ElementType.METHOD, ElementType.PARAMETER})
    2. @Retention(RetentionPolicy.RUNTIME)
    3. public @interface ServiceDeal {
    4. String value() default "";
    5. }

    使用样例:

    1. @Slf4j
    2. @Service
    3. public class TestService {
    4. @ServiceDeal
    5. public String test1(String str) {
    6. log.info("test1 service......{}", str);
    7. return str;
    8. }
    9. }

    三、增强处理注解

    增强处理注解:AfterReturning、AfterThrowing、Around

    1.AfterReturning

    这个只能加些处理逻辑,对修改返回无能为力。

    1. @Slf4j
    2. @Aspect
    3. @Component
    4. public class AopTest {
    5. @Pointcut("@annotation(quancheng.demo.aop.anno.ServiceDeal)")
    6. public void pointFan() {
    7. }
    8. @Before("pointFan()")
    9. public void doB() {
    10. log.info("Before doB...");
    11. }
    12. @After("pointFan()")
    13. public void doA() {
    14. log.info("After doA...");
    15. }
    16. @AfterReturning(pointcut = "pointFan()", returning = "result")
    17. public void dealReturn(String result) {
    18. log.info("打印返回值:{}", result);
    19. }
    20. }

    输出截图:

    注意:returning 的值要跟参数的属性保持一致,要不会报错。

    2.AfterThrowing

    service 加个会抛出异常的方法:

    1. @ServiceDeal
    2. public String test1(String str) {
    3. Integer i = Integer.valueOf(str);
    4. log.info("test1 service......{}", str);
    5. return str;
    6. }

    切面方法处理异常:

    1. @AfterThrowing(pointcut = "pointFan()", throwing = "e")
    2. public void dealException(Throwable e) {
    3. log.info("打印报错:{}", e.getStackTrace());
    4. }

    输出截图:

     3.Around

            当定义一个 Around 增强处理方法时,该方法的第一个形参必须是 ProceedingJoinPoint 类型。在增强处理方法体内,调用 ProceedingJoinPoint 的 proceed 方法才会执行目标方法;调用 ProceedingJoinPoint 的 proceed 方法时,还可以传入一个 Object[] 对象,该数组中的值将被传入目标方法作为实参,这就是 Around 增强处理方法可以改变目标方法参数值的关键,当然传参类型和数量是必须跟实际方法一致。

    这里完全重新写一个注解实例:

    1)定义注解,作用是为了校验age参数是否合法:

    1. @Target({ElementType.METHOD, ElementType.FIELD})
    2. @Retention(RetentionPolicy.RUNTIME)
    3. public @interface AgeCheck {
    4. String message() default "数据格式错误";
    5. int min() default 1;
    6. int max() default 100;
    7. }

    2)使用注解:

    1. @Slf4j
    2. @Service
    3. public class TestService {
    4. @AgeCheck(min = 18, max = 65,message = "年龄范围错误")
    5. public String test(Integer age) {
    6. return age + "";
    7. }
    8. }

    3)定义切面处理逻辑:

    1. @Slf4j
    2. @Aspect
    3. @Component
    4. public class AgeCheckAop {
    5. @Pointcut("@annotation(quancheng.demo.aop.anno.AgeCheck)")
    6. public void pointFan() {
    7. }
    8. @Before("pointFan()")
    9. public void doB() {
    10. log.info("Before doB...");
    11. }
    12. @After("pointFan()")
    13. public void doA() {
    14. log.info("After doA...");
    15. }
    16. @Around(value = "pointFan()")
    17. public Object process(ProceedingJoinPoint joinPoint) {
    18. log.info("进入Around,此时未执行函数方法");
    19. MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
    20. AgeCheck ageCheck = methodSignature.getMethod().getAnnotation(AgeCheck.class);
    21. Integer min = ageCheck.min();
    22. Integer max = ageCheck.max();
    23. String message = ageCheck.message();
    24. log.info("min:{},max:{}", min, max);
    25. Object[] args = joinPoint.getArgs();
    26. log.info("原始传参 args:{}", args);
    27. Integer age = (Integer) args[0];
    28. if (age < min || age > max) {
    29. log.info("不满足条件直接返回,message:{}", message);
    30. return message;
    31. }
    32. Object result = null;
    33. try {
    34. //可以在 proceed() 方法里面修改传参
    35. result = joinPoint.proceed(args);
    36. log.info("在Around,此时已执行完函数方法");
    37. } catch (Throwable throwable) {
    38. throwable.printStackTrace();
    39. }
    40. log.info("数据返回:{},这块可以加些处理逻辑,甚至修改返回", result);
    41. return result;
    42. }
    43. }

    输入age=19时(此时满足校验逻辑,程序会一直执行到最后),截图:

     输入age=17时(此时不满足校验逻辑,程序会提前返回),截图:

    传参和结果都可随意修改,所以这块慎用。

  • 相关阅读:
    ⑲霍兰德ES*如何选专业?高考志愿填报选专业
    [LeetCode308周赛] [前缀和] [栈] [拓扑排序]
    2023年“羊城杯”网络安全大赛 Web方向题解wp 全
    补充:js 制作qq、微信 的表情 缺少的微信表情图片
    【springboot】自动配置原理
    Niantic CEO:AR有望取代二维码,理想的AR眼镜还需3-5年
    【python】scikit-learn包:机器学习
    MySQL创建数据库、创建表操作和用户权限
    多校联测12 树
    HCIP—OSPF虚链路实验
  • 原文地址:https://blog.csdn.net/qingquanyingyue/article/details/128188912