Spring AOP——面向切面编程

一、AOP——另一种编程思想

1.1什么是AOP

AOP (Aspect Oriented Programming),翻译过来就是 面向切面编程。AOP是一种编程思想,基于OOP基础之上的新的编程思想;指在程序运行期间,将某段代码动态的切入到指定方法的指定位置进行运行的这种编程方式。

在学习AOP前我们需要了解什么是动态代理

场景:我们使用计算器运行计算器方法的时候进行日志记录,传统的我们有以下几种方法

1.1.1直接写在方法内部

方式一

定义计算器接口

public interface Calculator {
    int add(int i, int k);

    int sub(int i, int k);
}

实现计算器接口 并写上日志记录

public class CalculatorImpl implements Calculator {
    public int add(int i, int k) {
        System.out.println("【add】方法开始了,它是用的参数是【" + i + "," + k + "】");
        int result = i + k;
        System.out.println("【add】方法结束了,它是结果是【" + result + "】");
        return result;
    }

    public int sub(int i, int k) {
        System.out.println("【sub】方法开始了,它是用的参数是【" + i + "," + k + "】");
        int result = i - k;
        System.out.println("【sub】方法结束了,它是结果是【" + result + "】");
        return result;
    }

}

测试方法

 @org.junit.Test public void calTest() {
        Calculator calculator=new CalculatorImpl() ;
        calculator.add(1,3);

    }

输出:

【add】方法开始了,它是用的参数是【1,3】
【add】方法结束了,它是结果是【4

方式二

定义一个LogUtils 用来作为日志类 减少耦合

LogUtils

public class LogUtils {

    public static void logStart(Object... args) {
        System.out.println("【xxx】方法开始执行了,参数列表【" + Arrays.asList(args) + "】");
    }

    public static void logEnd(Object result) {
        System.out.println("【xxx】执行结果是---" + result);
    }

}

修改CalculatorImpl 以add方法为例

public class CalculatorImpl implements Calculator {
    public int add(int i, int k) {
        LogUtils.logStart(i, k);
        int result = i + k;
        LogUtils.logEnd(result);
        return result;
    }
    }

测试方法不变输出结果:

【xxx】方法开始执行了,参数列表【[1, 3]】
【xxx】执行结果是---4

总结

通过以上代码的输出结果和编码,我们可以发现

方式一方式二: 直接将日志写在方法内部;不推荐,维护麻烦

  • 日志记录功能:可有可无,系统的辅助功能
  • 业务逻辑:这才是核心功能
  • 造成了高耦合

我们希望的是:

  • 业务逻辑(核心功能)不变
  • 日志模块:在我们核心功能运行的期间自己动态的加上;
   //核心方法
   public int add(int i, int k) {
        int result = i + k;
        return result;
    }
  • 我们期望:运行的时候日志功能可以在以上代码上加上,低耦合,在指定的方法加上日志或者过滤功能

这时我们就希望有一张机制:动态代理

什么是动态代理呢,就拿jdk中自带的动态代理来说吧

JDK自带的动态代理

代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样的好是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能.
这里使用到编程中的一个思想:不要随意去修改别人已经写好的代码或者方法,如果需改修改,可以通过代理的方式来扩展该方法

举个例子来说明代理的作用:假设我们想邀请一位明星,那么并不是直接连接明星,而是联系明星的经纪人,来达到同样的目的.明星就是一个目标对象,他只要负责活动中的节目,而其他琐碎的事情就交给他的代理人(经纪人)来解决.这就是代理思想在现实中的一个例子

在Spirng当中动态代理的使用

如果目标对象实现了接口,默认情况下会采用JDK的动态代理来实现AOP,废话不多说上代码

CalculatorImpl实现类

public class CalculatorImpl implements Calculator {
    public int add(int i, int k) {
        int result = i + k;
        return result;
    }

    public int sub(int i, int k) {
        int result = i - k;
        return result;
    }

}

此时我们方法的执行就呢,就不要自己去创建对象去调用了,而是交给我们的代理对象

二、动态代理

1.首先定义logUtils类

public class LogUtils {

    public static void logStart(Method method, Object... args) {
        System.out.println("【" + method.getName() + "】方法开始执行了,参数列表【" + Arrays.asList(args) + "】");
    }

    public static void logReturn(Method method, Object result) {
        System.out.println("【" + method.getName() + "】执行结果是---" + result);
    }

    public static void logEnd(Method method) {
        System.out.println("【" + method.getName() + "】执行结束");
    }

    public static void logException(Method method, Exception e) {
        System.err.println("【" + method.getName() + "】出现异常,异常信息:" + e);
    }

}

2.定义CalcutorProxy类

public class CalculatorProxy {

    public static Calculator getProxy(final Calculator calculator) {
        /**
         *调用处理器
         * @param proxy 代理对象 给jdk对象使用  任何时候不要使用
         * @param method  当前要执行的目标对象方法
         * @param args  方法调用时外界传入的参数值
         * @return
         * @throws Throwable
         */
        InvocationHandler h = new InvocationHandler() {
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object result = null;
                try {
                    //目标方法执行前
                    LogUtils.logStart(method, args);
                    //利用反射执行目标方法
                    result = method.invoke(calculator, args);
                    //目标方法执行后
                    LogUtils.logReturn(method, result);

                } catch (Exception e) {
                    //异常
                    LogUtils.logException(method, e);
                } finally {
                    //结束
                    LogUtils.logEnd(method);
                }     //反正值必须返回 外界才能拿到
                return result;
            }
        };

        //定义了代理类的ClassLoder
        ClassLoader loader = calculator.getClass().getClassLoader();
        //代理类实现的接口列表
        Class<?>[] interfaces = calculator.getClass().getInterfaces();
        //Proxy为目标对象创建代理对象
        Object proxy = Proxy.newProxyInstance(loader, interfaces, h);
        return (Calculator)proxy;
    }
}

3.测试方法

  @org.junit.Test public void calTest() {
        //计算器对象
        Calculator calculator = new CalculatorImpl();
        //如果拿到了这个对象的代理对象,代理对象执行加减乘除
        Calculator proxy = CalculatorProxy.getProxy(calculator);
        proxy.sub(1, 2);
        System.out.println("calculator的接口" + Arrays.asList(calculator.getClass().getInterfaces()));
        //代理对象和被代理对象唯一产生的关联就是实现了同一个接口
        System.out.println("代理对象的接口" + Arrays.asList(proxy.getClass().getInterfaces()));
    }

输出结果:

【sub】方法开始执行了,参数列表【[1, 2]】
【sub】执行结果是----1
【sub】执行结束
calculator的接口[interface com.xiamu.cal.Calculator]
代理对象的接口[interface com.xiamu.cal.Calculator]

通过上面案例,我们发现动态代理已经面向切面了,但它对于我们的Spring来说还是有缺点的

  • 实现了Calculator接口
  • 如果目标对象没有实现接口,是无法为目标对象创建代理对象
  • 如果我只在个别方法执行前,或者执行后添加日志,动态代理是很难做到的

所以呢,我们的spring就实现了AOP,底层动态代理

  • 可以用spring一句话代码不写的去创建动态代理
  • 没有强制要求实现接口
  • 将某段代码(日志)动态的切入(不把日志代码写死在业务逻辑方法中)到指定方法(逻辑方法)的指定位置(方法运行前后、异常、等)这种编程方式 我们把这种编程方式叫做面向切面编程,
    (spring简化了面向切面编程)
  • 有了我们的spring AOP后就可以和动态代理说再见拉

三、AOP

image

AOP术语

通知(Advice): AOP 框架中的增强处理。通知描述了切面何时执行以及如何执行增强处理。
连接点(join point): 连接点表示应用执行过程中能够插入切面的一个点,这个点可以是方法的调用、异常的抛出。在 Spring AOP 中,连接点总是方法的调用。
切点(PointCut): 可以插入增强处理的连接点。
切面(Aspect): 切面是通知和切点的结合。
引入(Introduction):引入允许我们向现有的类添加新的方法或者属性。
织入(Weaving): 将增强处理添加到目标对象中,并创建一个被增强的对象,这个过程就是织入。

3.1.1

导入jar包

3.1.2写配置文件

  1. 将目标类和切面类(封装了通知方法)放入ioc容器中
  2. 告诉spring哪个是切面类 注解@Aspect
  3. 告诉spring切面类的每一个方法何时何地运行

spring aop通知(advice)分成五类:

**前置通知[Before advice]:**在连接点前面执行,前置通知不会影响连接点的执行,除非此处抛出异常。
**正常返回通知[After returning advice]:**在连接点正常执行完成后执行,如果连接点抛出异常,则不会执行。
**异常返回通知[After throwing advice]:**在连接点抛出异常后执行。
**返回通知[After (finally) advice]:**在连接点执行完成后执行,不管是正常执行完成,还是抛出异常,都会执行返回通知中的内容。
**环绕通知[Around advice]:**环绕通知围绕在连接点前后,比如一个方法调用的前后。这是最强大的通知类型,能在方法调用前后自定义

切入点表达式

我们使用execution指示器选择Instrument的play方法,方法表达式以 * 号开始,标识我们不关心方法的返回值类型。然后我们指定了全限定类名和方法名。对于方法参数列表,我们使用 … 标识切点选择任意的play方法,无论该方法的入参是什么。
多个匹配之间我们可以使用链接符 &&、||、!来表示 “且”、“或”、“非”的关系。但是在使用 XML 文件配置时,这些符号有特殊的含义,所以我们使用 “and”、“or”、“not”来表示。

举例:
限定该切点仅匹配的包是 com.xiamu.cal.impl.CalculatorImpl,可以使用
execution(* com.xiamu.cal.impl.CalculatorImpl.add(…)) && within(com.xiamu.cal.impl.CalculatorImpl.)
在切点中选择 bean,可以使用
execution(
com.xiamu.cal.impl.CalculatorImpl.add(…)) && bean(calculatorImpl)

我们先上代码,围着代码来分析

先定义切面类 要注意个细节 必须给切面类加上@Aspect注解 否则是不生效的
Component
@Aspect
public class AspectLogUtils {

    //在方法运行前执行 想在执行目标方法之前运行,写入切入点表达式
    //execution (访问权限符 返回值类型 方法签名)
    //joinPoint 封装了当前目标方法的详细信息
    @Before("execution(* com.xiamu.cal.impl.CalculatorImpl.*(int,int))") public void logStart(JoinPoint joinPoint) {

        System.out.println("xxx方法开始执行了,参数列表【" +Arrays.asList(joinPoint.getArgs())+ "】");
    }

    //通知方法会在目标方法返回后调用
    @AfterReturning("execution(* com.xiamu.cal.impl.CalculatorImpl.*(int,int))")
    public void logReturn(){
        System.out.println("xxx方法开始执行完成,计算结果是");
    }
    //通知方法会在目标方法抛出异常后调用
    @AfterThrowing("execution(public int com.xiamu.cal.impl.CalculatorImpl.*(int,int))") public void logException() {
        System.out.println("xxx抛出了异常---");
    }

    //方法结束时候执行
    @After("execution(* com.xiamu.cal.impl.CalculatorImpl.*(int,int))") public void logAfter() {
        System.out.println("xxx执行结束");
    }

   //环绕通知围绕在连接点前后,比如一个方法调用的前后。这是最强大的通知类型,能在方法调用前后自定义

}


当然加了注解后spring是不知道的 这时就由我们来告诉Spring 开启aop功能了

在配置文件上加上

    <context:component-scan base-package="com.xiamu"></context:component-scan>
    <!--开启注解的aop功能,aop名称空间-->
    <aop:aspectj-autoproxy />
测试类

当然这里也需要注解一个小细节:从ioc容器中获取目标对象,如果想要用类型,一定要用它的接口类型,不要用本类

  ApplicationContext ioc = new ClassPathXmlApplicationContext("application.xml");
        Calculator calculator = (Calculator)ioc.getBean(Calculator.class);
        calculator.add(1, 2);
        calculator.sub(1,2);
输出
xxx方法开始执行了,参数列表【[1, 2]】
xxx执行结束
xxx方法开始执行完成,计算结果是
xxx方法开始执行了,参数列表【[1, 2]】
xxx执行结束
xxx方法开始执行完成,计算结果是

如你看到的,上面我们写的多个通知使用了相同的切点表达式,对于像这样频繁出现的相同的表达式,比如我们只切add方法 sub()方法不切,这时候我们就要考虑一个问题了,修改起来是不是很麻烦,我们可以使用 @Pointcut注解声明切点表达式,然后使用表达式,

修改代码如下:

@Component
@Aspect
public class AspectLogUtils {

    /**
     * 抽取可重用的切入点表达式
     * 1.随便生命一个没有实现的返回void的空方法
     * 2.给切入点表达式加上@Pointcut注解
     */
    @Pointcut("execution(* com.xiamu.cal.impl.CalculatorImpl.*(int,int))")
    public void point(){

    }

    //在方法运行前执行 想在执行目标方法之前运行,写入切入点表达式
    //execution (访问权限符 返回值类型 方法签名)
    //joinPoint 封装了当前目标方法的详细信息
    @Before("point()") public void logStart(JoinPoint joinPoint) {

        System.out.println("xxx方法开始执行了,参数列表【" +Arrays.asList(joinPoint.getArgs())+ "】");
    }

    //通知方法会在目标方法返回后调用
    @AfterReturning("point()")
    public void logReturn(){
        System.out.println("xxx方法开始执行完成,计算结果是");
    }
    //通知方法会在目标方法抛出异常后调用
    @AfterThrowing("point()") public void logException() {
        System.out.println("xxx抛出了异常---");
    }

    //方法结束时候执行
    @After("point()") public void logAfter() {
        System.out.println("xxx执行结束");
    }

}





程序运行结果没有变化。
这里,我们使用

@Pointcut("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..))")
public void point(){}

声明了一个切点表达式,该方法 point 的内容并不重要,方法名也不重要,实际上它只是作为一个标识,供通知使用。

此时细心的你肯定发现了把 通知方法没有输出方法的参数 和计算的结果,这时我们就需要注解来处理参数了

通过注解处理通知中的参数

我们给通知方法的输出加上方法名和计算结果的值

上代码:

@Component
@Aspect
public class AspectLogUtils {

    /**
     * 抽取可重用的切入点表达式
     * 1.随便生命一个没有实现的返回void的空方法
     * 2.给切入点表达式加上@Pointcut注解
     */
    @Pointcut("execution(* com.xiamu.cal.impl.CalculatorImpl.*(int,int))")
    public void point(){

    }

    @Before("point()") public void logStart(JoinPoint joinPoint) {
        System.out.println(
            "【" + joinPoint.getSignature().getName() + "】方法开始执行了,参数列表【" + Arrays.asList(joinPoint.getArgs()) + "】");
    }

    //通知方法会在目标方法返回后调用
    @AfterReturning(value = "point()",returning = "result") public void logReturn(
        JoinPoint joinPoint,Object result) {
        System.out.println("【" + joinPoint.getSignature().getName() + "】方法开始执行完成,计算结果是"+result);
    }

    //通知方法会在目标方法抛出异常后调用
    @AfterThrowing("point()") public void logException(
        JoinPoint joinPoint) {
        System.out.println("【" + joinPoint.getSignature().getName() + "】抛出了异常---");
    }

    //方法结束时候执行
    @After("point()") public void logAfter(JoinPoint joinPoint) {
        System.out.println("【" + joinPoint.getSignature().getName() + "】执行结束");
    }
    //环绕通知围绕在连接点前后,比如一个方法调用的前后。这是最强大的通知类型,能在方法调用前后自定义

}

输出结果:

【add】方法开始执行了,参数列表【[1, 2]】
【add】执行结束
【add】方法开始执行完成,计算结果是3
【sub】方法开始执行了,参数列表【[1, 2]】
【sub】执行结束--
【sub】方法开始执行完成,计算结果是-1

看到了这里你是否发现还有一个问题,对,我们想的一样,那就是通知方法的执行顺序:

通知方法执行顺序

从输出结果可以看出:

  • 正常执行:1.@Before(前置)===2.@After(后置)===3.@AfterReturning(正常返回)
  • 异常执行:1.@Before(前置)===2.@After(后置)===3.@AfterThrowing(方法异常)

不是5种通知吗 我们只写了4种?别急环绕通知这就来了它是spring通知中强大的通知

@Around: 环绕:是psring中强大的通知
@Around:环绕:动态代理

try{
    //前置通知
    method.invoke(obj,args);
    //返回通知
}catch(e){
    //异常通知
}finally{
    //后置通知
}

四合一通知就是环绕通知

修改源码增加环绕通知

@Component
@Aspect
public class AspectLogUtils {

    /**
     * 抽取可重用的切入点表达式
     * 1.随便生命一个没有实现的返回void的空方法
     * 2.给切入点表达式加上@Pointcut注解
     */
    @Pointcut("execution(* com.xiamu.cal.impl.CalculatorImpl.*(int,int))")
    public void point(){

    }

    @Before("point()") public void logStart(JoinPoint joinPoint) {
        System.out.println(
            "【" + joinPoint.getSignature().getName() + "】方法开始执行了,参数列表【" + Arrays.asList(joinPoint.getArgs()) + "】");
    }

    //通知方法会在目标方法返回后调用
    @AfterReturning(value = "point()",returning = "result") public void logReturn(
        JoinPoint joinPoint,Object result) {
        System.out.println("【" + joinPoint.getSignature().getName() + "】方法开始执行完成,计算结果是"+result);
    }

    //通知方法会在目标方法抛出异常后调用
    @AfterThrowing("point()") public void logException(
        JoinPoint joinPoint) {
        System.out.println("【" + joinPoint.getSignature().getName() + "】抛出了异常---");
    }

    //方法结束时候执行
    @After("point()") public void logAfter(JoinPoint joinPoint) {
        System.out.println("【" + joinPoint.getSignature().getName() + "】执行结束");
    }
    //环绕通知围绕在连接点前后,比如一个方法调用的前后。这是最强大的通知类型,能在方法调用前后自定义
    @Around("point()")public void logAround(){
        System.out.println("环绕哈哈");
    }
}

输出:

环绕哈哈
【add】执行结束
【add】方法开始执行完成,计算结果是null

我们发现我们的目标方法被阻塞了,这时我们就需要在使用时,我们传入了 ProceedingJoinPoint 类型的参数,这个对象是必须要有的,并且需要调用 ProceedingJoinPoint 的 proceed() 方法。

修改源码

  @Around("point()") public Object logAround(ProceedingJoinPoint pjp) throws Throwable {
        Object proceed = null;
        //方法名
        String name = pjp.getSignature().getName();
        try {
            //参数
            Object[] args = pjp.getArgs();

            //@Before
            System.out.println("【环绕通知】" + name + "方法开始");
            //利用反射调用目标方法,等同于动态代理的method.invoke()
            //推进目标方法的执行
            proceed = pjp.proceed(args);
            //@AfterReturning
            System.out.println("【环绕通知】" + name + "方法返回,返回值:" + proceed);

        } catch (Exception e) {
            //@AfterThrowing 
            System.out.println("【环绕通知】" + name + "异常,异常信息:" + e);
        }
        finally {
            //@After
            System.out.println("【环绕通知】" + name + "方法结束:" );
        }
        //反射后调用的值也一定返回出去
        return proceed;
    }

输出:

【环绕通知】add方法开始
【环绕通知】add方法返回,返回值:3
【环绕通知】add方法结束:
【环绕通知】sub方法开始
【环绕通知】sub方法返回,返回值:-1
【环绕通知】sub方法结束:

Object proceed= pjp.proceed(args); 动态代理的method.invoke() 帮我们执行目标方法

好处:

  1. 顺序执行正常,正常返回
  2. 优先于普通通知执行顺序
  3. 跟目标方法运行有关
  4. 影响目标方法用环绕通知,只是做记录跟踪用普通通知

在来看一组多切面运行

AspectLogUtils

@Component @Aspect public class AspectLogUtils {

    /**
     * 抽取可重用的切入点表达式
     * 1.随便生命一个没有实现的返回void的空方法
     * 2.给切入点表达式加上@Pointcut注解
     */
    @Pointcut("execution(* com.xiamu.cal.impl.CalculatorImpl.*(int,int))") public void point() {

    }

    @Before("point()") public void logStart(JoinPoint joinPoint) {
        System.out.println(
            "[AspectLogUtils-前置]【" + joinPoint.getSignature().getName() + "】方法开始执行了,参数列表【" + Arrays.asList(joinPoint.getArgs()) + "】");
    }

    //通知方法会在目标方法返回后调用
    @AfterReturning(value = "point()", returning = "result") public void logReturn(JoinPoint joinPoint, Object result) {
        System.out.println("[AspectLogUtils-返回]【" + joinPoint.getSignature().getName() + "】方法开始执行完成,计算结果是" + result);
    }

    //通知方法会在目标方法抛出异常后调用
    @AfterThrowing("point()") public void logException(JoinPoint joinPoint) {
        System.out.println("[AspectLogUtils-异常]【" + joinPoint.getSignature().getName() + "】抛出了异常---");
    }

    //方法结束时候执行
    @After("point()") public void logAfter(JoinPoint joinPoint) {
        System.out.println("[AspectLogUtils-结束]【" + joinPoint.getSignature().getName() + "】执行结束");
    }

}

LogUtils

@Component @Aspect public class LogUtils {

    /**
     * 抽取可重用的切入点表达式
     * 1.随便生命一个没有实现的返回void的空方法
     * 2.给切入点表达式加上@Pointcut注解
     */
    @Pointcut("execution(* com.xiamu.cal.impl.CalculatorImpl.*(int,int))") public void point() {

    }

    @Before("point()") public void logStart(JoinPoint joinPoint) {
        System.out.println(
            "[LogUtils-前置]【" + joinPoint.getSignature().getName() + "】方法开始执行了,参数列表【" + Arrays.asList(joinPoint.getArgs()) + "】");
    }

    //通知方法会在目标方法返回后调用
    @AfterReturning(value = "point()", returning = "result") public void logReturn(JoinPoint joinPoint, Object result) {
        System.out.println("[LogUtils-返回]【" + joinPoint.getSignature().getName() + "】方法开始执行完成,计算结果是" + result);
    }

    //通知方法会在目标方法抛出异常后调用
    @AfterThrowing("point()") public void logException(JoinPoint joinPoint) {
        System.out.println("[LogUtils-异常]【" + joinPoint.getSignature().getName() + "】抛出了异常---");
    }

    //方法结束时候执行
    @After("point()") public void logAfter(JoinPoint joinPoint) {
        System.out.println("[LogUtils-结束]【" + joinPoint.getSignature().getName() + "】执行结束");
    }
    
}

输出:

[AspectLogUtils-前置]【add】方法开始执行了,参数列表【[1, 2][LogUtils-前置]【add】方法开始执行了,参数列表【[1, 2][LogUtils-结束]【add】执行结束
[LogUtils-返回]【add】方法开始执行完成,计算结果是3
[AspectLogUtils-结束]【add】执行结束
[AspectLogUtils-返回]【add】方法开始执行完成,计算结果是3

咦~我们发现了什么 AspectLogUtils前置通知先执行,却最后出去,后进来的LogUtils却先出去这是为什么?来我们画个图

image
总结:

  1. 先进来的最后出去
  2. 执行顺序,按大写字母顺序
  3. @Order改变切面顺序 数值越小 优先级越高

我们再把多切面运行加上LogUtils环绕通知看看运行结果

我们在以上代码添加上:

    @Around("point()") public Object logAround(ProceedingJoinPoint pjp) throws Throwable {
        Object proceed = null;
        //方法名
        String name = pjp.getSignature().getName();
        try {
            //参数
            Object[] args = pjp.getArgs();

            //@Before
            System.out.println("【环绕通知】" + name + "方法开始");
            //利用反射调用目标方法,等同于动态代理的method.invoke()
            //推进目标方法的执行
            proceed = pjp.proceed(args);
            //@AfterReturning
            System.out.println("【环绕通知】" + name + "方法返回,返回值:" + proceed);

        } catch (Exception e) {
            //@AfterThrowing
            System.out.println("【环绕通知】" + name + "异常,异常信息:" + e);
        }
        finally {
            //@After
            System.out.println("【环绕通知】" + name + "方法结束:" );
        }
        //反射后调用的值也一定返回出去
        return proceed;
    }

输出:

【环绕通知】add方法开始
[LogUtils-前置]【add】方法开始执行了,参数列表【[1, 2][AspectLogUtils-前置]【add】方法开始执行了,参数列表【[1, 2][AspectLogUtils-结束]【add】执行结束
[AspectLogUtils-返回]【add】方法开始执行完成,计算结果是3
【环绕通知】add方法返回,返回值:3
【环绕通知】add方法结束:
[LogUtils-结束]【add】执行结束
[LogUtils-返回]【add】方法开始执行完成,计算结果是3

分析:

LogUtils前置通知进入目标方法,接着AspectLogUtils进入目标方法,目标方法要先执行,LogUtils有环绕通知,我们的环绕通知要放行,以前的思路,在目标方法执行后切到环绕通知去执行,但是环绕通知在切目标方法之前,AspectLogUtils已经进来了,跟LogUtils已经没关系了,所以AspectLogUtils已经在执行目标方法了,目标方法才能出去,LogUtils才能再出去

  • 环绕只影响了当前切面,围绕着当前切面

还有基于配置的AOP这里就不一一细述了

Aop应用场景

  1. AOP加日志保存到数据库
  2. AOP做做权限验证:(filter能做的我们都能做)如果权限不够,目标方法不运行,环绕通知
  3. AOP做安全检查
  4. AOP做事务控制

总结

本文简单记录了 AOP 的编程思想,然后介绍了 Spring 中 AOP 的相关概念 相比于 AspectJ 的面向切面编程,Spring AOP 也有一些局限性,但是已经可以解决开发中的绝大多数问题了,如果确实遇到了 Spring AOP 解决不了的场景,如有不足,请指出

猜你喜欢

转载自blog.csdn.net/weixin_47815882/article/details/106226484
今日推荐