当前位置: 首页 > article >正文

Spring 6 第5章——面向切面:AOP

搭建子模块——spring6-aop 

一、场景模拟

(1)声明接口

  1. 接口代码:
    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);
    }
  2. 如图:

(2)创建实现类

  1. 实现类代码:
    package com.atguigu.spring6.aop.example;
    
    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;
        }
    }
  2. 如图:

(3)创建带日志功能的实现类

  1. 实现类代码:
    package com.atguigu.spring6.aop.example;
    
    public class CalculatorLogImpl implements Calculator{
        @Override
        public int add(int i, int j) {
            System.out.println("[日志] add 方法开始了,参数是:" + i + "," + j);
            int result = i + j;
            System.out.println("方法内部 result = " + result);
            System.out.println("[日志] add 方法结束了,结果是:" + result);
            return result;
        }
    
        @Override
        public int sub(int i, int j) {
            System.out.println("[日志] sub 方法开始了,参数是:" + i + "," + j);
            int result = i - j;
            System.out.println("方法内部 result = " + result);
            System.out.println("[日志] sub 方法结束了,结果是:" + result);
            return result;
        }
    
        @Override
        public int mul(int i, int j) {
            System.out.println("[日志] mul 方法开始了,参数是:" + i + "," + j);
            int result = i * j;
            System.out.println("方法内部 result = " + result);
            System.out.println("[日志] mul 方法结束了,结果是:" + result);
            return result;
        }
    
        @Override
        public int div(int i, int j) {
            System.out.println("[日志] div 方法开始了,参数是:" + i + "," + j);
            int result = i / j;
            System.out.println("方法内部 result = " + result);
            System.out.println("[日志] div 方法结束了,结果是:" + result);
            return result;
        }
    }
  2. 可以发现,在这个类的方法中,我们自己写的日志功能和核心代码混合在一起,这样很不好。我们要把日志功能和核心代码分离开来

(4)提出问题

  1. 现有代码缺陷:针对带有日志功能的实现类,我们发现有如下缺陷:
    1. 对核心业务功能有干扰,导致程序员在开发核心业务功能时分散了精力
    2. 附加的日志功能分散在各个业务功能方法中,不利于统一维护
  2. 解决思路:解决这两个问题的核心就是解耦。我们需要把附加功能从业务功能的代码中抽取出来
  3. 困难:要抽取的代码在方法内部,靠以前把子类中的重复代码抽取到父类的方式没法解决,所以需要引入新的技术

二、代理模式

(1)概念

  1. 介绍:代理模式是二十三种设计模式中的一种,属于结构型模式。它的作用就是提供一个代理类,让我们在调用目标方法的时候,不再是对目标方法进行直接调用,而是通过代理类来间接调用。让不属于目标方法核心逻辑的代码从目标方法中剥离出来——解耦。调用目标方法时先调用代理对象的方法,减少对目标方法的调用和打扰,同时让附加功能能够集中在一起,也有利于统一维护
  2. 使用代理前:
  3. 使用代理后:
  4. 相关术语:
    1. 将非核心逻辑剥离出来以后,封装这些非核心逻辑的类、对象、方法
    2. 目标:被代理“套用”了非核心逻辑代码的类、对象、方法 

(2)静态代理

  1. 创建静态代理类:
    package com.atguigu.spring6.aop.example;
    
    public class CalculatorStaticProxy implements Calculator{
        private Calculator calculator;
    
        public CalculatorStaticProxy(Calculator calculator) {
            this.calculator = calculator;
        }
    
        @Override
        public int add(int i, int j) {
            //输出日志
            System.out.println("[日志] add 方法开始了,参数是:" + i + "," + j);
            int addResult = calculator.add(i, j);
            //输出日志
            System.out.println("[日志] add 方法结束了,结果是:" + addResult);
            return addResult;
        }
    
        @Override
        public int sub(int i, int j) {
            //输出日志
            System.out.println("[日志] sub 方法开始了,参数是:" + i + "," + j);
            int subResult = calculator.sub(i, j);
            //输出日志
            System.out.println("[日志] sub 方法结束了,结果是:" + subResult);
            return subResult;
        }
    
        @Override
        public int mul(int i, int j) {
            //输出日志
            System.out.println("[日志] mul 方法开始了,参数是:" + i + "," + j);
            int mulResult = calculator.mul(i, j);
            //输出日志
            System.out.println("[日志] mul 方法结束了,结果是:" + mulResult);
            return mulResult;
        }
    
        @Override
        public int div(int i, int j) {
            //输出日志
            System.out.println("[日志] div 方法开始了,参数是:" + i + "," + j);
            int divResult = calculator.div(i, j);
            //输出日志
            System.out.println("[日志] div 方法结束了,结果是:" + divResult);
            return divResult;
        }
    }
  2. 静态代理确实实现了解耦,但是由于代码都写死了,完全不具备任何的灵活性。就拿日志功能来说,将来其它地方也需要附加日志,那还得再声明多个静态代理类,那就产生了大量重复的代码。日志功能还是分散的,没有统一管理
  3. 再提出进一步的需求:将日志功能都集中到一个代理类,将来有任何日志需求,都通过这一个代理类来实现。这就需要使用动态代理技术了

(3)动态代理

  1. 如图:
  2. 首先我们创建了一个代理类ProxyFactory,(1)代理类里有一个私有属性:目标对象(2)代理类里有一个有参构造器(代码可以理解成即将根据目标对象生成代理)(3)代理类里有一个方法getProxy用于生成代理
  3. getProxy方法的返回类型是Object,因为Object是祖宗类,所以我们可以强制类型转换,把代理的类型转为目标对象的类型,然后代理就可以调用目标对象所属类中的方法了
  4. 当我们用代理去调用目标对象所属类中的方法时,实际上是调用了我们在getProxy方法中重写的invoke方法(个人理解)
  5. invoke方法中的三个参数(Object proxy, Method method, Object[] args)。proxy应该就是我们的代理,method就是代理调用的目标对象所属类中的方法,args是给该方法传入的参数

(4)测试

  1. 代理类的代码:
    package com.atguigu.spring6.aop.example;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.util.Arrays;
    
    public class ProxyFactory {
        //目标对象
        private Object target;
    
        public ProxyFactory(Object target) {
            this.target = target;
        }
    
        //返回代理对象
        public Object getProxy(){
            //有三个形参
            //第一个形参ClassLoader是类加载器
            //第二个形参Class[] interfaces是目标对象实现的所有接口
            //第三个形参InvocationHandler是设置代理对象实现目标对象方法的过程
            ClassLoader classLoader = target.getClass().getClassLoader();
            Class<?>[] interfaces = target.getClass().getInterfaces();
            InvocationHandler invocationHandler = new InvocationHandler(){
                //第一个参数:代理对象
                //第二个参数:需要重写目标对象的方法
                //第三个参数:method方法里面的参数
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    System.out.println("[动态代理][日志] "+method.getName()+",参数:"+ Arrays.toString(args));
                    //调用目标的方法
                    Object result = method.invoke(target, args);
                    System.out.println("[动态代理][日志] "+method.getName()+",结果:"+ result);
                    return result;
                }
            };
            return Proxy.newProxyInstance(classLoader,interfaces,invocationHandler);
        }
    }
  2. 测试类的代码:
    package com.atguigu.spring6.aop.example;
    
    public class TestCal {
        public static void main(String[] args) {
            ProxyFactory proxyFactory = new ProxyFactory(new CalculatorImpl());
            //创建代理对象(动态)
            Calculator proxy = (Calculator)proxyFactory.getProxy();
            proxy.add(1,2);
        }
    }
  3. 测试效果:

三、AOP概念及相关术语

(1)概念

  1. AOP(Aspect Oriented Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程的一种补充和完善
  2. 它以预编译的方式和运行期动态代理方式实现。在不修改源码的情况下,给程序动态统一添加额外功能的一种技术
  3. 利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率

(2)相关术语

3.2.1横切关注点

  1. 分散在各个模块中解决同一样的问题,如用户验证、日志管理、事务处理、数据缓存都属于横切关注点
  2. 从每个方法中抽取出来的同一类非核心业务。在同一个项目中,我们可以使用多个横切关注点对相关方法进行多个不同方面的增强
  3. 这个概念不是语法层面的,而是根据附加功能的逻辑上的需要:有十个附加功能,就有十个横切关注点

3.2.2通知(增强)

  1. 通知(增强):通俗地说,就是你想要增强的功能(即增加要执行的方法),比如:安全、事务、日志等
  2. 每一个横切关注点上要做的事情都需要写一个方法来实现,这样的方法就叫做通知
    1. 前置通知:在被代理的目标方法之前执行的方法
    2. 返回通知:在被代理的目标方法成功结束后执行(寿终正寝)
    3. 异常通知:在被代理的目标方法异常结束后执行(死于非命)
    4. 后置通知:在被代理的目标方法最终结束后执行(盖棺定论)
    5. 环绕通知:使用try...catch...finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置(怎么围绕整个被代理的目标方法呢?通过ProceedingJoinPoint类型的连接点,调用proceed方法)
  3. 图示:
  4. 注意:后置通知不管被代理的目标方法有没有成功执行,都会执行!!! 

3.2.3切面

  1. 切面类:封装通知(即方法)的类
  2. 图示:

3.2.4目标

  1. 被代理的目标对象

3.2.5代理

  1. 向目标对象应用通知之后创建的代理对象

3.2.6连接点

  1. 把方法排成一排,每一个横切位置看成x轴方向,把方法从上到下执行的顺序看成y轴,x轴和y轴的交叉点就是连接点
  2. 通俗说,就是Spring允许你用通知的地方
  3. 图示:

3.2.7切入点

  1. 定位连接点的方式
  2. 每个类的方法中都包含多个连接点,所以连接点是类中客观存在的事物(从逻辑上来说)
  3. 如果把连接点看作数据库中的记录,那么切入点就是查询记录的 SQL 语句
  4. Spring 的 AOP 技术可以通过切入点定位到特定的连接点。通俗说,要实际去增强的方法
  5. 切点通过 org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件

(3)作用

  1. 简化代码:把方法中固定位置的重复的代码抽取出来,让被抽取的方法更专注于自己的核心功能,提高内聚性
  2. 代码增强:把特定的功能封装到切面类中,看哪里有需要,就往上套,被套用了切面逻辑的方法就被切面给增强了

四、基于注解的AOP

(1)技术说明

  1. 图示:
  2. 动态代理分为JDK动态代理和cglib动态代理
  3. 当目标类有接口的情况使用JDK动态代理和cglib动态代理,没有接口时只能使用cglib动态代理
  4. JDK动态代理动态生成的代理类会在com.sun.proxy包下,类名为$proxy1,和目标类实现相同的接口

  5. cglib动态代理动态生成的代理类会和目标在在相同的包下,会继承目标类

  6. 动态代理(InvocationHandler):JDK原生的实现方式,需要被代理的目标类必须实现接口。因为这个技术要求代理对象和目标对象实现同样的接口(兄弟两个拜把子模式)

  7. cglib:通过继承被代理的目标类(认干爹模式)实现代理,所以不需要目标类实现接口

  8. AspectJ:是AOP思想的一种实现。本质上是静态代理,将代理逻辑“织入”被代理的目标类编译得到的字节码文件,所以最终效果是动态的。weaver就是织入器。Spring只是借用了AspectJ中的注解

(2)准备工作

  1. 添加依赖(在IoC所需依赖的基础之上再加入下面的依赖即可):
    <dependencies>
        <!--spring context依赖-->
        <!--当你引入Spring Context依赖之后,表示将Spring的基础依赖引入了-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>6.0.2</version>
        </dependency>
    
        <!--spring aop依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>6.0.2</version>
        </dependency>
        <!--spring aspects依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>6.0.2</version>
        </dependency>
    
        <!--junit5测试-->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>5.3.1</version>
        </dependency>
    
        <!--log4j2的依赖-->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.19.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j2-impl</artifactId>
            <version>2.19.0</version>
        </dependency>
    </dependencies>
  2. 准备被代理的目标资源(接口和实现类):
    package com.atguigu.spring6.aop.example;
    
    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);
    }
    package com.atguigu.spring6.aop.example;
    
    import org.springframework.stereotype.Component;
    
    @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)创建切面类并配置

  1. 在Spring的配置文件中配置:
    <?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:context="http://www.springframework.org/schema/context"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                               http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                               http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
                              ">
    
        <context:component-scan base-package="com.atguigu.spring6.aop"></context:component-scan>
        <aop:aspectj-autoproxy />
    </beans>

  2. 接口代码和实现类代码:
    package com.atguigu.spring6.aop.annoaop;
    
    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);
    }
    package com.atguigu.spring6.aop.annoaop;
    
    import org.springframework.stereotype.Component;
    
    @Component
    public class CalculatorImpl implements Calculator {
        @Override
        public int add(int i, int j) {
            int result = i+j;
            System.out.println("方法内部 result = " + result);
            //int a = 1/0;
            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. 切面类代码:
    package com.atguigu.spring6.aop.annoaop;
    
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.*;
    import org.springframework.stereotype.Component;
    
    import java.util.Arrays;
    
    //切面类
    @Aspect //切面类
    @Component
    public class LogAspect {
        //设置切入点和通知类型
        //通知类型:前置通知/返回通知/异常通知/后置通知/环绕通知
        //前置通知@Before(value="切入点表达式配置切入点")
        @Before(value="execution(public int com.atguigu.spring6.aop.annoaop.CalculatorImpl.*(..))")
        public void beforeMethod(JoinPoint joinPoint){
            String methodName = joinPoint.getSignature().getName();
            Object[] args = joinPoint.getArgs();
            System.out.println("Logger-->前置通知,方法名称:"+methodName+",参数"+ Arrays.toString(args));
        }
        //后置通知@After
        @After(value = "execution(* com.atguigu.spring6.aop.annoaop.CalculatorImpl.*(..))")
        public void afterMethod(JoinPoint joinPoint){
            String methodName = joinPoint.getSignature().getName();
            Object[] args = joinPoint.getArgs();
            System.out.println("Logger-->后置通知,方法名称:"+methodName+",参数"+ Arrays.toString(args));
        }
        //返回通知@afterReturningMethod
        @AfterReturning(value = "execution(* com.atguigu.spring6.aop.annoaop.CalculatorImpl.*(..))",returning = "result")
        public void afterReturningMethod(JoinPoint joinPoint,Object result){
            String methodName = joinPoint.getSignature().getName();
            Object[] args = joinPoint.getArgs();
            System.out.println("Logger-->返回通知,方法名称:"+methodName+",参数"+ Arrays.toString(args)+",返回值"+result);
        }
        //异常通知@AfterThrowing    获取到目标方法的异常信息
        //目标方法出现异常,这个通知执行
        @AfterThrowing(value = "execution(* com.atguigu.spring6.aop.annoaop.CalculatorImpl.*(..))",throwing = "ex")
        public void afterThrowingMethod(JoinPoint joinPoint,Throwable ex){
            String methodName = joinPoint.getSignature().getName();
            Object[] args = joinPoint.getArgs();
            System.out.println("Logger-->异常通知,方法名称:"+methodName+",参数"+ Arrays.toString(args)+",异常信息"+ex);
        }
        //环绕通知@Around()
        @Around(value = "execution(* com.atguigu.spring6.aop.annoaop.CalculatorImpl.*(..))")
        public Object aroundMethod(ProceedingJoinPoint joinPoint){
            String methodName = joinPoint.getSignature().getName();
            Object[] args = joinPoint.getArgs();
            String argString = Arrays.toString(args);
            Object result = null;
            try{
                System.out.println("环绕通知==目标方法之前执行");
                result = joinPoint.proceed();
                System.out.println("环绕通知==目标方法返回值之后");
            }catch (Throwable throwable){
                System.out.println("环绕通知==目标方法出现异常之后执行");
            }finally {
                System.out.println("环绕通知==目标方法执行完毕执行");
            }
            return result;
        }
    }
  4. 测试代码:
    package com.atguigu.spring6.aop.annoaop;
    
    import org.junit.jupiter.api.Test;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class TestAop {
        @Test
        public void testAdd(){
            ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
            Calculator calculator = context.getBean(Calculator.class);
            //我们虽然是在CalculatorImpl上加的@Component注解,但是该类有接口
            calculator.add(2,3);
        }
    }
  5.  也就是说,我们写了一个接口Calculator,写了一个接口的实现类CalculatorImpl,然后在这个实现类上面加了@Component注解,用于生成该类的一个对象。我们还写了一个切面类LogAspect,并在该切面类上用了@Component注解。当我们在测试类中使用CalculatorImpl的bean,并调用这个bean的方法时,还会执行通知
  6. XML中对通知的配置顺序可以决定通知的执行顺序!!!

(4)各种通知

  1. 前置通知:使用@Before注解标识,在被代理的目标方法执行
  2. 返回通知:使用@AfterReturning注解标识,在被代理的目标方法成功结束后执行(寿终正寝
  3. 异常通知:使用@AfterThrowing注解标识,在被代理的目标方法异常结束后执行(死于非命
  4. 后置通知:使用@After注解标识,在被代理的目标方法最终结束后执行(盖棺定论
  5. 环绕通知:使用@Around注解标识,使用try...catch...finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置
  6. 各种通知的执行顺序:
  7. 这些通知可以用一个JoinPoint类型的参数。joinPoint.getSignature().getName();可以获得切入点的方法签名的方法名。joinPoint.getArgs();可以获得我们执行被代理的目标方法(切入点)时,给它传的参数,以一个数组形式返回。为什么在前置通知里也可以获得这些呢?因为我们先调用切入点,然后才会执行它。执行顺序:前置通知→目标操作→返回通知/异常通知→后置通知
  8. 在@AfterReturning()的括号里面,除了可以写value,还可以写returning。因为返回通知是在目标操作成功执行完以后才执行的,因此可以用returning获得目标操作的返回值。但是如果在后置通知的形参里要用到这个返回值,那么给返回值取的形参名和returing后面跟的要保持一致
  9. 在@AfterThrowing()的括号里面,除了可以写value,还可以写throwing。因为异常通知是在目标操作执行后才执行的,因此可以用throwing获取目标操作的异常信息。但是如果在异常通知的形参里要用到这个异常信息,那么给异常信息取的形参名和throwing后面跟的要保持一致
  10. 切入点我们可以理解成要被加上前置通知/返回通知/异常通知/后置通知/环绕通知的方法

(5)切入点表达式语法

  1. 图示:
  2. 用*号代替“权限修饰符”和“返回值”部分表示“权限修饰符”和“返回值”不限(*同时覆盖权限修饰符和方法返回值
  3. 在包名的部分,一个“*”号只能代表包的层次结构中的一层,表示这一层是任意的(例如:*.Hello匹配com.Hello,不匹配com.atguigu.Hello )
  4. 在包名的部分,使用“*..”表示包名任意、包的层次深度任意
  5. 在类名的部分,类名部分整体用*号代替,表示类名任意
  6. 在类名的部分,可以使用*号代替类名的一部分(例如:例如:*Service匹配所有名称以Service结尾的类或接口 )
  7. 在方法名部分,可以使用*号表示方法名任意
  8. 在方法名部分,可以使用*号代替方法名的一部分(例如:*Operation匹配所有方法名以Operation结尾的方法)
  9. 在方法参数列表部分,使用(..)表示参数列表任意
  10. 在方法参数列表部分,使用(int,..)表示参数列表以一个int类型的参数开头
  11. 在方法参数列表部分,基本数据类型和对应的包装类型是不一样的(切入点表达式中使用 int 和实际方法中 Integer 是不匹配的)
  12. 在方法返回值部分,如果想要明确指定一个返回值类型,那么必须同时写明权限修饰符(权限修饰符和返回值类型必须同时出现,要么同时被代替) 
  13. 其实就是,execution(方法签名),只是这个方法签名的方法名部分,必须是包名+类名+方法名 

(6)重用切入点表达式

  1. 可以发现,在上述代码中,这几个通知的切入点表达式有重复。其实我们可以把切入点表达式只写一次,然后重用
  2. 怎么重用切入点表达式?
    //重用切入点表达式
        @Pointcut(value = "execution(* com.atguigu.spring6.aop.annoaop.CalculatorImpl.*(..))")
        public void pointCut(){}
  3. 然后把这几个通知的注解的value="execution(...)"换成value="pointCut()"。这个方法不一定要叫pointCut
  4. 如果有多个不同的切面(切面类也叫切面),那就要这么写。就是在pointCut()前面加上包名+切面类的类名

(7)获取通知的相关信息

  1. 获取连接点(JoinPoint)信息可以在通知的参数位置设置JoinPoint类型的形参
  2. 连接点可以理解为是通知和目标操作的连接。可以通过连接点,在通知中得到目标操作的相关信息
  3. @AfterReturning中的属性returning,用来将通知的某个形参,接收目标方法的返回值
  4. @AfterThrowing中的属性throwing,用来将通知的某个形参,接收目标方法的异常

(8)环绕通知

  1. 环绕通知的代码:
    //环绕通知@Around()
        @Around(value = "execution(* com.atguigu.spring6.aop.annoaop.CalculatorImpl.*(..))")
        public Object aroundMethod(ProceedingJoinPoint joinPoint){
            String methodName = joinPoint.getSignature().getName();
            Object[] args = joinPoint.getArgs();
            String argString = Arrays.toString(args);
            Object result = null;
            try{
                System.out.println("环绕通知==目标方法之前执行");
                result = joinPoint.proceed();
                System.out.println("环绕通知==目标方法返回值之后");
            }catch (Throwable throwable){
                System.out.println("环绕通知==目标方法出现异常之后执行");
            }finally {
                System.out.println("环绕通知==目标方法执行完毕执行");
            }
            return result;
        }
  2. 注意:在前置通知、返回通知、异常通知、后置通知中使用的连接点都是JoinPoint类型。但是在环绕通知中,如果想调用目标操作,那么连接点就必须是ProceedingJoinPoint

(9)切面的优先级

  1. 相同目标方法上同时存在多个切面时,切面的优先级控制切面的内外嵌套顺序
    1. 优先级高的切面:外面
    2. 优先级低的切面:里面
  2. 使用@Order注解可以控制切面的优先级
    1. @Order(较小的数):优先级高
    2. @Order(较大的数):优先级低
  3. 图示:

五、基于XML的AOP

(1)准备工作

  1. 接口代码:
    package com.atguigu.spring6.aop.xmlaop;
    
    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);
    }
  2. 实现类代码:
    package com.atguigu.spring6.aop.xmlaop;
    
    import org.springframework.stereotype.Component;
    
    @Component
    public class CalculatorImpl implements Calculator {
        @Override
        public int add(int i, int j) {
            int result = i+j;
            System.out.println("方法内部 result = " + result);
            //int a = 1/0;
            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. 配置文件代码:
    <?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:context="http://www.springframework.org/schema/context"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context.xsd
           http://www.springframework.org/schema/aop
           http://www.springframework.org/schema/aop/spring-aop.xsd">
    
        <!--开启组件扫描-->
        <context:component-scan base-package="com.atguigu.spring6.aop.xmlaop"></context:component-scan>
    
        <!--配置aop五种通知类型-->
        <aop:config>
            <!--配置切面类-->
            <aop:aspect ref="logAspect">
                <!--配置切入点-->
                <aop:pointcut id="pointcut" expression="execution(* com.atguigu.spring6.aop.xmlaop.CalculatorImpl.*(..))"/>
                <!--配置五种通知类型-->
                <!--前置通知-->
                <aop:before method="beforeMethod" pointcut-ref="pointcut"></aop:before>
    
                <!--后置通知-->
                <aop:after method="afterMethod" pointcut-ref="pointcut"></aop:after>
    
                <!--返回通知-->
                <aop:after-returning method="afterReturningMethod" returning="result" pointcut-ref="pointcut"></aop:after-returning>
    
                <!--异常通知-->
                <aop:after-throwing method="afterThrowingMethod" throwing="ex" pointcut-ref="pointcut"></aop:after-throwing>
    
                <!--环绕通知-->
                <aop:around method="aroundMethod" pointcut-ref="pointcut"></aop:around>
            </aop:aspect>
        </aop:config>
    </beans>
  4. 测试类代码:
    package com.atguigu.spring6.aop.xmlaop;
    
    import org.junit.jupiter.api.Test;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class TestAop {
        @Test
        public void testAdd(){
            ApplicationContext context = new ClassPathXmlApplicationContext("beanaop.xml");
            Calculator calculator = context.getBean(Calculator.class);
            //我们虽然是在CalculatorImpl上加的@Component注解,但是该类有接口
            calculator.add(2,3);
        }
    }

(2)实现

  1. 测试效果:

http://www.kler.cn/a/513458.html

相关文章:

  • STM32 FreeROTS Tickless低功耗模式
  • 基于tldextract提取URL里的子域名、主域名、顶级域
  • 【Vim Masterclass 笔记25】S10L45:Vim 多窗口的常用操作方法及相关注意事项
  • 2024年度总结-CSDN
  • Golang 中强大的重试机制,解决瞬态错误
  • Python绘制数据地图-MovingPandas
  • Spring Boot框架下的上海特产销售商城网站开发之旅
  • Java复习第四天
  • 如何写出优秀的提示词?ChatGPT官方的六种方法
  • 【科研建模】Pycaret自动机器学习框架使用流程及多分类项目实战案例详解
  • 【蓝桥杯】43694.正则问题
  • Axios发起HTTP请求时的先后执行顺序
  • 1/20赛后总结
  • 22. C语言 输入与输出详解
  • 云计算、AI与国产化浪潮下DBA职业之路风云变幻,如何谋破局启新途?
  • docker load报错(unexpected EOF)
  • 深入解析 Spring 框架中的事务传播行为
  • 视频修复最强算法 部署笔记2025
  • Java面试专题——面向对象
  • JavaScript中的数据类型以及存储上的差别
  • Python----Python高级(文件操作open,os模块对于文件操作,shutil模块 )
  • 深入探究分布式日志系统 Graylog:架构、部署与优化
  • 自动化标注平台开源,基于 yolov8标注平台可本地部署
  • AI 赋能:开启人类 “长生不老” 新纪元?
  • 2025发文新方向:AI+量化 人工智能与金融完美融合!
  • #HarmonyOs篇: 管理应用拥有的状态LocalStorage AppStorage