SpringAOP

一、理解AOP思想

我们知道在面向对象OOP编程存在一些弊端,当需要为多个不具有继承关系的对象引入同一个公共行为时,例如日志、安全监测等,我们只有在每个对象里引入公共行为,这样程序中就产生了大量的重复代码,所以有了面向对象编程的补充,面向切面编程(AOP)

使用AOP技术,可以将一些系统性相关的编程工作或重复代码,独立提取出来独立实现,然后通过切面切入程序中,可以让开发者更专注于业务逻辑的实现,减少了大量重复代码,提高了效率和可维护性。

AOP(Aspect Oriented Programming),它是面向对象编程的一种补充,主要应用于处理一些具有横切性质的系统级服务,如日志收集、事务管理、安全检查、缓存、对象池管理等。

轻松理解AOP思想

AOP实现的关键就在于AOP框架自动创建的AOP代理,AOP代理则可分为 静态代理动态代理 两大类,其中静态代理是指使用AOP框架提供的命令进行编译,从而在编译阶段就可生成 AOP 代理类,因此也称为 编译时增强;而动态代理则在运行时借助于JDK动态代理、CGLIB等在内存中 “临时” 生成AOP动态代理类,因此也被称为 运行时增强

二、AspectJ和SpringAOP的区别与联系

在提到AOP时我们总会想到SpringAOP,而在提到SpringAOP时又总会跟AspectJ扯上关系,那么这两者之间究竟有什么关系呢?

1.AspectJ框架

AspectJ是一个面向切面的框架,它扩展了Java语言,定义了AOP语法,能够在编译器提供代码的织入,所以它有一个专门的编译器用来生成遵守字节码规范的Class文件。

AspectJ属于静态织入,通过修改代码来实现,有如下几个织入的时机:

​1.编译期织入(Compile-time weaving)
如类 A 使用 AspectJ 添加了一个属性,类 B 引用了它,这个场景就需要编译期的时候就进行织入,否则没法编译类 B。

2.编译后织入(Post-compile weaving)
也就是已经生成了.class 文件,或已经打成 jar 包了,这种情况我们需要增强处理的话,就要用到编译后织入。

3.类加载后织入(Load-time weaving)
指的是在加载类的时候进行织入,要实现这个时期的织入,有几种常见的方法。

  • 1)自定义类加载器来干这个,这个应该是最容易想到的办法,在被织入类加载到 JVM 前去对它进行加载,这样就可以在加载的时候定义行为了。

  • 2)在 JVM 启动的时候指定 AspectJ 提供的 agent:
    -javaagent:xxx/xxx/aspectjweaver.jar。

2.SpringAOP

众所周知,Spring两大特色或者说是核心即 IoCAOP,AspectJ框架提供了一套完整的AOP解决方案,而SpringAOP的目的并不是为了提供最完整的AOP实现 (虽然SpringAOP具有相当的能力),而是要帮助解决企业应用中的常见问题,提供一个AOP实现与Spring IOC之间的紧密集成,能够处理业务中的横切关注点。

1.SpringAOP 是基于动态代理来实现横切的,代理的方式提供了两种:

  • 基于JDK的动态代理
    必须是面向接口的,只有实现了具体接口的类才能生成代理对象。

  • 基于CGLIB动态代理
    对于没有实现了接口的类,也可以产生代理,产生这个类的子类的方式。

2.SpringAop需要依赖IOC容器来管理,并且只能作用于Spring容器,使用纯Java代码实现。

3.在性能上,由于SpringAop是基于动态代理来实现的,在容器启动时需要生成代理实例,在方法调用上也会增加栈的深度,使得性能不如AspectJ那么好。

在这里插入图片描述

3.总结

总的来说,两者都是AOP思想的一种实现,但是在面向切面编程关注的点不同:AspectJ 可以做 Spring AOP 干不了的事情,它是AOP编程的完全解决方案;Spring AOP则致力于解决企业级开发中最普遍的AOP(方法织入),而不是成为像 AspectJ 一样的AOP方案。因为AspectJ 在实际运行之前就完成了织入,所以说它生成的类是没有额外运行时开销的。

相同点和区别我们都知道了,而两者之间有什么联系呢,为什么在使用Spring AOP的时候还需要(非必须)导入AspectJ的包呢?

曾经以为 AspectJ 是 Spring AOP 一部分,但其实是Spring通过集成AspectJ实现了以注解的方式定义切面,这样大大减少了配置文件的工作量,所以使用注解方式需要导入包。

<dependency>
	<groupId>org.aspectj</groupId>
	<artifactId>aspectjweaver</artifactId>
	<version>1.9.5</version>
</dependency>

使用了 @Aspect 来定义切面,使用 @Pointcut 来定义切入点,使用Advice来定义增强处理:@Before@AfterReturning@AfterThrowing@After@Around,这几个注解都是在org.aspectj.lang.annotation包中。虽然使用了Aspect的Annotation,但是并没有使用它的编译器和织入器。

此外,因为Java的反射机制无法获取方法参数名,Spring还需要利用轻量级的字节码处理框架asm (已集成在Spring Core模块中) 处理@AspectJ中所描述的方法参数名。

三、AOP操作术语

  • 1.通知(Advice):就是你想要的功能(切面要完成的功能),也就是上文说的日志、事务、安全等。先定义好,然后在想用的地方使用。

    Spring切面可以应用五种通知类型:

  1. 前置通知(Before):在目标方法被调用之前调用通知功能。
  2. 后置通知(After)(最终通知):在方法返回前执行通知,就算目标方法抛出异常,后置通知也会执行,即目标方法无论执行成功与否最终都会执行。
  3. 后置返回通知(After-Returning):在方法执行return后执行的,这个是不可能可以修改方法的返回值的,这里需要注意和后置通知不同,目标方法抛出异常,后置返回通知不会执行,即目标方法成功执行完毕并return后才会执行。
  4. 异常通知(After-throwing):在目标方法抛出异常后调用通知。
  5. 环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。
  • 2.连接点(JoinPoint):程序执行的某个特定位置:如类开始初始化前、类初始化后、类某个方法调用前、调用后、方法抛出异常后。一个类或一段程序代码拥有一些具有边界性质的特定点,这些特定点就称为连接点。

    简单来说,就是Spring允许你使用通知的地方,基本每个方法的前后(两者都有也行),或抛出异常时都可以是连接点,Spring只支持方法连接点,其它如AspectJ还可以让你在构造器或属性注入时都行,只要记住,和方法有关的前前后后(抛出异常),都是连接点。

  • 3.切入点(PointCut):上面说的连接点的基础上,来定义切入点,你的一个类里有15个方法,那就有几十个连接点了,但是你并不想在所有方法附近都使用通知(使用称为“织入”,下文有详细说明),你只想让其中的几个,在调用这几个方法之前/之后/抛出异常时干点什么,那么就用切点来定义这几个方法,让切点来筛选连接点,选中那几个你想要增强的方法。

  • 4.切面(Aspect):切面是通知和切入点的结合。其实没连接点什么事,连接点就是为了让我们好理解切点,明白这个概念就可以。通知说明了干什么和什么时候干(什么时候通过方法名中的before/after/around就能知道),而切入点说明了在哪干(指定到底是哪个方法),这就是一个完整切面定义。

  • 5.目标对象(Target):通知逻辑的织入目标类,如果没有AOP,目标业务类需要自己实现所有业务逻辑,而在AOP的帮助下,目标业务类只实现那些非横切逻辑的程序逻辑,而性能监视和事务管理等这些横切逻辑则可以使用AOP动态织入到特定的连接点上。

  • 6.引介(Introduction):是一种特殊的通知,在不修改源代码的前提下,它可以在运行期为类动态地添加一些属性和方法。

  • 7.织入(weaving):织入是将通知添加到目标类具体连接点上的过程,AOP像一台织布机,将目标类、通知或引介通过AOP这台织布机
    天衣无缝的编制到一起,AOP有三种织入的方式:
    1)编译器织入,这要求使用特殊的Java编译器。
    2)类装载期织入,这要求使用特殊的类装载器。
    3)动态代理织入,在运行期为目标类添加通知生成子类的方式。

    把切面应用到目标对象来创建新的代理对象的过程,Spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。

  • 8.代理(proxy):一个类被织入通知后,就产出了一个结果类,它是融合了原类和通知逻辑的代理类。根据不同的代理方式,代理类既可能是和原类具有相同接口的类,也可能是原类的子类,所以我们可以采用调用原类相同的方式调用代理类。

四、Spring AOP的使用

1.XML方式
1.创建用于拦截的Bean
public class TestBean {

    private String message="test bean";

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public void test(){
        System.out.println(this.getMessage());
    }
}
2.编写通知类
public class TestAdvisor {

    /**
     * 前置通知
     */
    public void beforeAdvisor(){
        System.out.println("beforeAdvisor");
    }

    /**
     * 后置通知
     */
    public void afterAdvisor(){
        System.out.println("afterAdvisor");
    }

    /**
     * 环绕通知
     * @param joinPoint
     */
    public void aroundAdvisor(ProceedingJoinPoint joinPoint){
        System.out.println("aroundAdvisor.....before");
        Object o = null;
        try{
            //执行proceed方法的作用就是让目标方法执行,环绕通知=前置+目标方法执行+后置通知
            o = joinPoint.proceed();
        }catch(Throwable e){
            e.printStackTrace();
        }
        System.out.println("aroundAdvisor.....after");
    }
}

3.编写XML配置文件AOP
<?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">

    <bean id="test" class="com.xmlaop.TestBean">
        <property name="message" value="测试XML配置方式实现AOP"/>
    </bean>

    <!-- 声明增强方法所在的bean -->
    <bean id="theAdvisor" class="com.xmlaop.TestAdvisor"/>
    
    <!-- 配置切面 -->
    <aop:config>
        <!--定义切入点  -->
        <aop:pointcut id="pointcut" expression="execution(* *.test(..))"/>
        <!--引用包含增强方法的Bean  -->
        <aop:aspect ref="theAdvisor">
        	 <!--前置通知-->
            <aop:before method="beforeAdvisor" pointcut-ref="pointcut"/>
             <!--后置通知-->
            <aop:after method="afterAdvisor" pointcut-ref="pointcut"/>
            <!--环绕通知-->
            <aop:around method="aroundAdvisor" pointcut-ref="pointcut" arg-names="joinPoint"/>
        </aop:aspect>
    </aop:config>
</beans>
4.测试类
public class TestAOP {

    public static void main(String[] args) {
        ApplicationContext context=new ClassPathXmlApplicationContext("aspectTest.xml");
        TestBean testBean=(TestBean)context.getBean("test");
        testBean.test();
    }
}
5.执行结果

在这里插入图片描述

2.注解方式
1.引入AspectJ包
<!-- aspectjweaver -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>${aspectj.version}</version>
</dependency>
2.创建用于拦截的Bean
public class TestBean {
    private String message = "test bean";

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public void test(){
        System.out.println(this.message);
    }
}

3.创建Advisor

在AspectJTest类中,我们要做的就是在所有类的test方法执行前在控制台beforeTest。而在所有类的test方法执行后打印afterTest,同时又使用环绕的方式在所有类的方法执行前后在此分别打印before1和after1,以下是AspectJTest的代码:

@Aspect
public class AspectJTest {
    @Pointcut("execution(* *.test(..))")
    public void test(){
    }
    
    @Before("test()")
    public void beforeTest(){
        System.out.println("beforeTest");
    }
    
    @Around("test()")
    public Object aroundTest(ProceedingJoinPoint p){
        System.out.println("around.....before");
        Object o = null;
        try{
            o = p.proceed();
        }catch(Throwable e){
            e.printStackTrace();
        }
        System.out.println("around.....after");
        return o;
    }
    
    @After("test()")
    public void afterTest()
    {
        System.out.println("afterTest");
    }
 }
4.创建配置文件

要在Spring中开启AOP功能,,还需要在配置文件中作如下声明:

<?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">

    <aop:aspectj-autoproxy/>
    <bean id="test" class="com.yhl.myspring.demo.aop.TestBean">
        <property name="message" value="这是一个苦逼的程序员"/>
    </bean>
    <bean id="aspect" class="com.yhl.myspring.demo.aop.AspectJTest"/>
</beans>
5.测试
public class Test {
    public static void main(String[] args) {
        ApplicationContext bf = new ClassPathXmlApplicationContext("aspectTest.xml");
        TestBean bean = (TestBean)bf.getBean("test");
        bean.test();
    }
}
6.执行结果

在这里插入图片描述

看到这里细心的你可能注意到了两种方式同样都使用了前置、后置和环绕,但是通知的执行顺序却不一样,下文中我们再探讨。

这篇文章也很不错,可以做个参考。
SpringAOP的两种配置方式详细示例

3.小结

如果项目采用JDK5.0以上版本,可以考虑使用 @AspectJ 注解方式,减少配置的工作量;如果不愿意使用注解或项目采用的JDK版本较低而无法使用注解,则可以选择使用<aop:aspect>配合普通JavaBean的形式。

4.AOP execution表达式

先来看一个表达式

execution(* com.sample.service.impl..*.*(..))

解释如下

符号含义
execution()表达式的主体
第一个 * 号表示返回值可以是任意类型
com.sample.service.implAOP所切的服务的包名,即,我们的业务部分
包名后面的两个点表示当前包及子包
**第二个 ***表示类名,即所有类
.*(点点)表示 任何方法名,括号表示参数,两个点表示任何参数类型

AspectJ中的exection表达式基本语法格式为:

在这里插入图片描述
除了返回类型模式、方法名模式和参数模式外,其它项都是可选的。

下面,我们给出各种使用execution()函数实例。

1)通过方法签名定义切点

 execution(public * *(..))

匹配所有目标类的public方法。第一个*代表返回类型,第二个*代表方法名,而..代表任意入参的方法;

 execution(* *To(..))

匹配目标类所有以To为后缀的方法。第一个*代表返回类型,而*To代表任意以To为后缀的方法;

2)通过类定义切点

 execution(* com.test.Waiter.*(..))

匹配Waiter的所有方法。第一个*代表返回任意类型,com.test.Waiter.* 代表Waiter类中的所有方法。

 execution(*com.test.Waiter+.*(..))

匹配Waiter类及其所有实现类的方法。

3)通过类包定义切点

在类名模式串中,*表示包下的所有类,而..表示包、子孙包下的所有类。

 execution(* com.test.*(..))

匹配com.test包下所有类的所有方法;

 execution(* com.test..*(..))

匹配com.test包、子孙包下所有类的所有方法,如com.test.daocom.test.service包下的所有类的所有方法都匹配。..出现在类名中时,后面必须跟*,表示包、子孙包下的所有类;

 execution(* com..*.*Dao.find*(..))

匹配包名前缀为com的任何包下类名后缀为Dao的方法,方法名必须以find为前缀。如com.test.UserDao.findByUserId()com.test.dao.TestDao#findAll)的方法都匹配切点。

4)通过方法入参定义切点

切点表达式中方法入参部分比较复杂,可以使用 * .. 通配符,其中 * 表示任意类型的参数,而 .. 表示任意类型参数且参数个数不限。

 execution(* joke(String,int))

匹配 joke(String,int) 方法,且joke()方法的第一个入参是String,第二个入参是int。如果方法中的入参类型是Java.lang包下的类,可以直接使用类名,否则必须使用全限定类名,如joke(java.util.List,int)。

 execution(* joke(String,*))

匹配目标类中的joke()方法,该方法第一个入参为String,第二个入参可以是任意类型,如joke(String s1,String s2)joke(String s1,double d2)都匹配,但joke(String s1,double d2,String s3)则不匹配;

 execution(* joke(String,..))

匹配目标类中的joke()方法,该方法第一个入参为String,.. 表示后面可以有任意个入参且入参类型不限,如 joke(String s1)joke(String s1,String s2)joke(String s1,double d2,Strings3)都匹配。

 execution(* joke(Object+))

匹配目标类中的joke()方法,方法拥有一个入参,且入参是Object类型或该类的子类。它匹配joke(String s1)和joke(Client c)。如果我们定义的切点是execution(*joke(Object)),则只匹配joke(Object object)而不匹配joke(String str)joke(Client c)

五、Spring AOP五种通知的执行顺序

这里分别用XML配置方式和注解方式对SpringAOP五种通知:

  • @Before前置通知
  • @After 后置通知
  • @Around 环绕通知
  • @AfterReturning 后置返回通知
  • @AfterThrowing 异常通知

在代码有无异常和目标方法有无返回值的情况做了测试,这里贴出执行结果并做一个总结。

1.XML配置方式
(1)测试前置、后置、环绕通知、后置返回、异常–未抛出异常执行结果–目标方法有返回值

执行结果

beforeAdvisor
17:24:39.678 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'theAdvisor'
aroundAdvisor.....before
测试XML配置方式实现AOP
17:24:39.700 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'theAdvisor'
afterReturnindAdvisor
aroundAdvisor.....after
17:24:39.700 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'theAdvisor'
afterAdvisor

执行顺序
前置通知
环绕开始
目标方法执行
后置返回通知
环绕结束
后置通知

(2)测试前置、后置、环绕通知、后置返回、异常–未抛出异常执行结果–目标方法无返回值

执行结果

beforeAdvisor
17:33:47.846 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'theAdvisor'
aroundAdvisor.....before
测试XML配置方式实现AOP
17:33:47.868 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'theAdvisor'
afterReturnindAdvisor
aroundAdvisor.....after
17:33:47.868 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'theAdvisor'
afterAdvisor

执行顺序:
前置通知
环绕开始
目标方法执行
后置返回通知
环绕结束
后置通知

(3)测试前置、后置、环绕通知、后置返回、异常–抛出异常执行结果–目标方法有返回值

执行结果

beforeAdvisor
17:28:26.589 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'theAdvisor'
aroundAdvisor.....before
测试XML配置方式实现AOP
17:28:26.651 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'theAdvisor'
exceptionAdvisor
java.lang.ArithmeticException: / by zero
	at com.xmlaop.TestBean.test(TestBean.java:22)
	at com.xmlaop.TestBean$$FastClassBySpringCGLIB$$4b9bd518.invoke(<generated>)
aroundAdvisor.....after
17:28:26.654 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'theAdvisor'
afterAdvisor
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:643)
	at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:632)

执行顺序:
前置通知
环绕开始
目标方法执行
目标方法报错,执行异常通知
环绕结束
后置通知

(4)测试前置、后置、环绕通知、后置返回、异常–抛出异常执行结果–目标方法无返回值

执行结果

beforeAdvisor
17:36:14.319 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'theAdvisor'
aroundAdvisor.....before
测试XML配置方式实现AOP
17:36:14.341 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'theAdvisor'
exceptionAdvisor
java.lang.ArithmeticException: / by zero
	at com.xmlaop.TestBean.test(TestBean.java:22)
	at com.xmlaop.TestBean$$FastClassBySpringCGLIB$$4b9bd518.invoke(<generated>)
	at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
aroundAdvisor.....after
17:36:14.343 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'theAdvisor'
afterAdvisor

执行顺序:
前置通知
环绕开始
目标方法执行
目标方法报错,执行异常通知
环绕结束
后置通知

2.注解方式
(1)测试前置、后置、环绕通知、后置返回、异常–未抛出异常执行结果–目标方法有返回值

执行结果

around.....before
beforeTest
这是一个苦逼的程序员
around.....after
afterTest
afterReturnindAdvisor

执行顺序:
环绕开始
前置通知
目标方法执行
环绕结束
后置通知
返回通知

(2)测试前置、后置、环绕通知、后置返回、异常–未抛出异常执行结果–目标方法无返回值

执行结果

around.....before
beforeTest
这是一个苦逼的程序员
around.....after
afterTest
afterReturnindAdvisor

执行顺序:
环绕开始
前置通知
目标方法执行
环绕结束
后置通知
返回通知

(3)测试前置、后置、环绕通知、后置返回、异常–抛出异常执行结果–目标方法有返回值

执行结果

around.....before
beforeTest
这是一个苦逼的程序员
afterTest
exceptionAdvisor==========/ by zero
Exception in thread "main" java.lang.ArithmeticException: / by zero
	at com.aop.TestBean.test(TestBean.java:22)
	at com.aop.TestBean$$FastClassBySpringCGLIB$$f5e4153b.invoke(<generated>)

执行顺序:
环绕开始
前置通知
目标方法执行
后置通知
异常通知

(4)测试前置、后置、环绕通知、后置返回、异常–抛出异常执行结果–目标方法无返回值

执行结果

around.....before
beforeTest
这是一个苦逼的程序员
afterTest
exceptionAdvisor==========/ by zero
Exception in thread "main" java.lang.ArithmeticException: / by zero
	at com.aop.TestBean

执行顺序:
环绕开始
前置通知
目标方法执行
后置通知
异常通知

3.小结

从执行结果来看,有以下几点
1.无论使用XML配置还是注解方式,目标方法执行有无异常,有无返回值,后置通知即@After都会执行。

2.无论使用XML配置还是注解方式,目标方法执行异常,后置返回通知@AfterReturning不会执行,这里需要注意的是,如果对异常进行了捕获,异常通知将不执行,而后置返回通知会执行。

3.在进行统一异常处理时,异常通知的异常类型级别必须大于或等于程序实际运行抛出的异常,不然捕获不到,异常通知也就不会执行。

4.XML配置方式和注解方式执行顺序还是存在差异。

XML配置方式执行顺序在正常情况下(即执行无异常,无论有无返回值)

前置通知
环绕开始
目标方法执行
后置返回通知
环绕结束
后置通知

XML配置方式执行顺序(执行异常未捕获,无论有无返回值)

前置通知
环绕开始
目标方法执行
目标方法报错,执行异常通知【与无异常相比,这里后置返回通知没有执行,执行了异常通知,其他无差别】
环绕结束
后置通知

注解方式通知执行顺序(执行无异常,无论是否有返回值)

环绕开始
前置通知
目标方法执行
环绕结束
后置通知
返回通知

注解方式通知执行顺序(执行出现异常未捕获,无论是否有返回值)

环绕开始
前置通知
目标方法执行
后置通知
异常通知

参考文章

execution表达式https://blog.csdn.net/zz210891470/article/details/54175107

AOP 使用
https://www.cnblogs.com/java-chen-hao/p/11589531.html

轻松理解 AOP思想 https://www.cnblogs.com/Wolfmanlq/p/6036019.html

面向切面编程 AOP https://www.zhihu.com/question/24863332

关于Spring AOP你该知晓的一切
https://zhuanlan.zhihu.com/p/25522841

关注一下吧~【Pretty Kathy】
请添加图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值