文章目录
一、理解AOP思想
我们知道在面向对象OOP编程存在一些弊端,当需要为多个不具有继承关系的对象引入同一个公共行为时,例如日志、安全监测等,我们只有在每个对象里引入公共行为,这样程序中就产生了大量的重复代码,所以有了面向对象编程的补充,面向切面编程(AOP)。
使用AOP技术,可以将一些系统性相关的编程工作或重复代码,独立提取出来独立实现,然后通过切面切入程序中,可以让开发者更专注于业务逻辑的实现,减少了大量重复代码,提高了效率和可维护性。
AOP(Aspect Oriented Programming),它是面向对象编程的一种补充,主要应用于处理一些具有横切性质的系统级服务,如日志收集、事务管理、安全检查、缓存、对象池管理等。
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两大特色或者说是核心即 IoC 和 AOP,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切面可以应用五种通知类型:
- 前置通知(Before):在目标方法被调用之前调用通知功能。
- 后置通知(After)(最终通知):在方法返回前执行通知,就算目标方法抛出异常,后置通知也会执行,即目标方法无论执行成功与否最终都会执行。
- 后置返回通知(After-Returning):在方法执行return后执行的,这个是不可能可以修改方法的返回值的,这里需要注意和后置通知不同,目标方法抛出异常,后置返回通知不会执行,即目标方法成功执行完毕并return后才会执行。
- 异常通知(After-throwing):在目标方法抛出异常后调用通知。
- 环绕通知(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.impl | AOP所切的服务的包名,即,我们的业务部分 |
包名后面的两个点 | 表示当前包及子包 |
**第二个 *** | 表示类名,即所有类 |
.*(点点) | 表示 任何方法名,括号表示参数,两个点表示任何参数类型 |
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.dao
,com.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】