相关推荐recommended
spring之面向切面:AOP(2)
作者:mmseoamin日期:2023-12-20

学习的最大理由是想摆脱平庸,早一天就多一份人生的精彩;迟一天就多一天平庸的困扰。各位小伙伴,如果您:

想系统/深入学习某技术知识点…

一个人摸索学习很难坚持,想组团高效学习…

想写博客但无从下手,急需写作干货注入能量…

热爱写作,愿意让自己成为更好的人…

文章目录

  • 前言
  • 一、基于注解的AOP
    • 1、技术说明
    • 2、准备工作
    • 3、创建切面类并配置
    • 4、各种通知
    • 5、切入点表达式语法
    • 6、重用切入点表达式
    • 7、获取通知的相关信息
    • 8、环绕通知
    • 9、切面的优先级
    • 二、基于XML的AOP
      • 1、准备工作
      • 2、实现
      • 总结

        前言

        一、基于注解的AOP

        1、技术说明

        2、准备工作

        3、创建切面类并配置

        4、各种通知

        5、切入点表达式语法

        6、重用切入点表达式

        7、获取通知的相关信息

        8、环绕通知

        9、切面的优先级

        二、基于XML的AOP

        1、准备工作

        2、实现


        一、基于注解的AOP

        1、技术说明

        spring之面向切面:AOP(2),在这里插入图片描述,第1张

        spring之面向切面:AOP(2),在这里插入图片描述,第2张

        • 动态代理分为JDK动态代理和cglib动态代理
        • 当目标类有接口的情况使用JDK动态代理和cglib动态代理,没有接口时只能使用cglib动态代理
        • JDK动态代理动态生成的代理类会在com.sun.proxy包下,类名为$proxy1,和目标类实现相同的接口
        • cglib动态代理动态生成的代理类会和目标在在相同的包下,会继承目标类
        • 动态代理(InvocationHandler):JDK原生的实现方式,需要被代理的目标类必须实现接口。因为这个技术要求代理对象和目标对象实现同样的接口(兄弟两个拜把子模式)。
        • cglib:通过继承被代理的目标类(认干爹模式)实现代理,所以不需要目标类实现接口。
        • AspectJ:是AOP思想的一种实现。本质上是静态代理,将代理逻辑“织入”被代理的目标类编译得到的字节码文件,所以最终效果是动态的。weaver就是织入器。Spring只是借用了AspectJ中的注解。

          2、准备工作

          ①添加依赖

          在IOC所需依赖基础上再加入下面依赖即可:

          
              
              
              
                  org.springframework
                  spring-context
                  6.0.2
              
              
              
                  org.springframework
                  spring-aop
                  6.0.2
              
              
              
                  org.springframework
                  spring-aspects
                  6.0.2
              
              
              
                  org.junit.jupiter
                  junit-jupiter-api
                  5.3.1
              
              
              
                  org.apache.logging.log4j
                  log4j-core
                  2.19.0
              
              
                  org.apache.logging.log4j
                  log4j-slf4j2-impl
                  2.19.0
              
          
          

          ②准备被代理的目标资源

          接口:

          public interface Calculator {
              
              int add(int i, int j);
              
              int sub(int i, int j);
              
              int mul(int i, int j);
              
              int div(int i, int j);
              
          }
          

          实现类:

          @Component
          public class CalculatorImpl implements Calculator {
              
              @Override
              public int add(int i, int j) {
              
                  int result = i + j;
              
                  System.out.println("方法内部 result = " + result);
              
                  return result;
              }
              
              @Override
              public int sub(int i, int j) {
              
                  int result = i - j;
              
                  System.out.println("方法内部 result = " + result);
              
                  return result;
              }
              
              @Override
              public int mul(int i, int j) {
              
                  int result = i * j;
              
                  System.out.println("方法内部 result = " + result);
              
                  return result;
              }
              
              @Override
              public int div(int i, int j) {
              
                  int result = i / j;
              
                  System.out.println("方法内部 result = " + result);
              
                  return result;
              }
          }
          

          3、创建切面类并配置

          // @Aspect表示这个类是一个切面类
          @Aspect
          // @Component注解保证这个切面类能够放入IOC容器
          @Component
          public class LogAspect {
              
              @Before("execution(public int com.atguigu.aop.annotation.CalculatorImpl.*(..))")
              public void beforeMethod(JoinPoint joinPoint){
                  String methodName = joinPoint.getSignature().getName();
                  String args = Arrays.toString(joinPoint.getArgs());
                  System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
              }
              @After("execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))")
              public void afterMethod(JoinPoint joinPoint){
                  String methodName = joinPoint.getSignature().getName();
                  System.out.println("Logger-->后置通知,方法名:"+methodName);
              }
              @AfterReturning(value = "execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))", returning = "result")
              public void afterReturningMethod(JoinPoint joinPoint, Object result){
                  String methodName = joinPoint.getSignature().getName();
                  System.out.println("Logger-->返回通知,方法名:"+methodName+",结果:"+result);
              }
              @AfterThrowing(value = "execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))", throwing = "ex")
              public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex){
                  String methodName = joinPoint.getSignature().getName();
                  System.out.println("Logger-->异常通知,方法名:"+methodName+",异常:"+ex);
              }
              
              @Around("execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))")
              public Object aroundMethod(ProceedingJoinPoint joinPoint){
                  String methodName = joinPoint.getSignature().getName();
                  String args = Arrays.toString(joinPoint.getArgs());
                  Object result = null;
                  try {
                      System.out.println("环绕通知-->目标对象方法执行之前");
                      //目标对象(连接点)方法的执行
                      result = joinPoint.proceed();
                      System.out.println("环绕通知-->目标对象方法返回值之后");
                  } catch (Throwable throwable) {
                      throwable.printStackTrace();
                      System.out.println("环绕通知-->目标对象方法出现异常时");
                  } finally {
                      System.out.println("环绕通知-->目标对象方法执行完毕");
                  }
                  return result;
              }
              
          }
          

          在Spring的配置文件中配置:

          
          
              
              
              
          
          

          执行测试:

          public class CalculatorTest {
              private Logger logger = LoggerFactory.getLogger(CalculatorTest.class);
              @Test
              public void testAdd(){
                  ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");
                  Calculator calculator = ac.getBean( Calculator.class);
                  int add = calculator.add(1, 1);
                  logger.info("执行成功:"+add);
              }
          }
          

          4、各种通知

          • 前置通知:使用@Before注解标识,在被代理的目标方法前执行
          • 返回通知:使用@AfterReturning注解标识,在被代理的目标方法成功结束后执行(寿终正寝)
          • 异常通知:使用@AfterThrowing注解标识,在被代理的目标方法异常结束后执行(死于非命)
          • 后置通知:使用@After注解标识,在被代理的目标方法最终结束后执行(盖棺定论)
          • 环绕通知:使用@Around注解标识,使用try…catch…finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置

            各种通知的执行顺序:

            • Spring版本5.3.x以前:
              • 前置通知
              • 目标操作
              • 后置通知
              • 返回通知或异常通知
              • Spring版本5.3.x以后:
                • 前置通知
                • 目标操作
                • 返回通知或异常通知
                • 后置通知

            5、切入点表达式语法

            ①作用

            spring之面向切面:AOP(2),在这里插入图片描述,第3张

            ②语法细节

            • 用*号代替“权限修饰符”和“返回值”部分表示“权限修饰符”和“返回值”不限

            • 在包名的部分,一个“*”号只能代表包的层次结构中的一层,表示这一层是任意的。

              • 例如:*.Hello匹配com.Hello,不匹配com.atguigu.Hello
              • 在包名的部分,使用“*…”表示包名任意、包的层次深度任意

              • 在类名的部分,类名部分整体用*号代替,表示类名任意

              • 在类名的部分,可以使用*号代替类名的一部分

                • 例如:*Service匹配所有名称以Service结尾的类或接口
                • 在方法名部分,可以使用*号表示方法名任意

                • 在方法名部分,可以使用*号代替方法名的一部分

                  • 例如:*Operation匹配所有方法名以Operation结尾的方法
                  • 在方法参数列表部分,使用(…)表示参数列表任意

                  • 在方法参数列表部分,使用(int,…)表示参数列表以一个int类型的参数开头

                  • 在方法参数列表部分,基本数据类型和对应的包装类型是不一样的

                    • 切入点表达式中使用 int 和实际方法中 Integer 是不匹配的
                    • 在方法返回值部分,如果想要明确指定一个返回值类型,那么必须同时写明权限修饰符

                      • 例如:execution(public int Service.(…, int)) 正确

                        例如:execution( int *…Service.(…, int)) 错误

                        spring之面向切面:AOP(2),在这里插入图片描述,第4张

                        6、重用切入点表达式

                        ①声明

                        @Pointcut("execution(* com.atguigu.aop.annotation.*.*(..))")
                        public void pointCut(){}
                        

                        ②在同一个切面中使用

                        @Before("pointCut()")
                        public void beforeMethod(JoinPoint joinPoint){
                            String methodName = joinPoint.getSignature().getName();
                            String args = Arrays.toString(joinPoint.getArgs());
                            System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
                        }
                        

                        ③在不同切面中使用

                        @Before("com.atguigu.aop.CommonPointCut.pointCut()")
                        public void beforeMethod(JoinPoint joinPoint){
                            String methodName = joinPoint.getSignature().getName();
                            String args = Arrays.toString(joinPoint.getArgs());
                            System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
                        }
                        

                        7、获取通知的相关信息

                        ①获取连接点信息

                        获取连接点信息可以在通知方法的参数位置设置JoinPoint类型的形参

                        @Before("execution(public int com.atguigu.aop.annotation.CalculatorImpl.*(..))")
                        public void beforeMethod(JoinPoint joinPoint){
                            //获取连接点的签名信息
                            String methodName = joinPoint.getSignature().getName();
                            //获取目标方法到的实参信息
                            String args = Arrays.toString(joinPoint.getArgs());
                            System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
                        }
                        

                        ②获取目标方法的返回值

                        @AfterReturning中的属性returning,用来将通知方法的某个形参,接收目标方法的返回值

                        @AfterReturning(value = "execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))", returning = "result")
                        public void afterReturningMethod(JoinPoint joinPoint, Object result){
                            String methodName = joinPoint.getSignature().getName();
                            System.out.println("Logger-->返回通知,方法名:"+methodName+",结果:"+result);
                        }
                        

                        ③获取目标方法的异常

                        @AfterThrowing中的属性throwing,用来将通知方法的某个形参,接收目标方法的异常

                        @AfterThrowing(value = "execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))", throwing = "ex")
                        public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex){
                            String methodName = joinPoint.getSignature().getName();
                            System.out.println("Logger-->异常通知,方法名:"+methodName+",异常:"+ex);
                        }
                        

                        8、环绕通知

                        @Around("execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))")
                        public Object aroundMethod(ProceedingJoinPoint joinPoint){
                            String methodName = joinPoint.getSignature().getName();
                            String args = Arrays.toString(joinPoint.getArgs());
                            Object result = null;
                            try {
                                System.out.println("环绕通知-->目标对象方法执行之前");
                                //目标方法的执行,目标方法的返回值一定要返回给外界调用者
                                result = joinPoint.proceed();
                                System.out.println("环绕通知-->目标对象方法返回值之后");
                            } catch (Throwable throwable) {
                                throwable.printStackTrace();
                                System.out.println("环绕通知-->目标对象方法出现异常时");
                            } finally {
                                System.out.println("环绕通知-->目标对象方法执行完毕");
                            }
                            return result;
                        }
                        

                        9、切面的优先级

                        相同目标方法上同时存在多个切面时,切面的优先级控制切面的内外嵌套顺序。