参考
B站颜群老师Spring教程,B站雷丰阳老师Spring教程,《Spring实战》
https://zhuanlan.zhihu.com/p/137507309
https://liayun.blog.csdn.net/article/details/115053350
AOP切面
AOP(Aspect Orient Programming),直译过来就是面向切面编程。AOP
是一种编程思想,是面向对象编程(OOP)的一种补充。面向对象编程将程
序抽象成各个层次的对象,而面向切面编程是将程序抽象成各个切面
所谓切面,其实就相当于应用对象间的横切点,我们可以将其单独抽象为
单独的模块。总之一句话:AOP是指在程序的运行期间动态地将某段代码
切入到指定方法、指定位置进行运行的编程方式。AOP的底层是使用动态
代理实现的
实战案例
导入AOP依赖,Spring AOP对面向切面编程做了一些简化操作,我们只
需要加上几个核心注解,AOP就能工作起来
1 2 3 4 5
| <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>4.3.12.RELEASE</version> </dependency>
|
定义目标类,在com.atguigu.aop 包下创建一个业务逻辑类,用于处理
数学计算上的一些逻辑。比如,我们在MathCalculator类中定义了一个
除法操作,返回两个整数类型值相除之后的结果
1 2 3 4 5 6
| public class MathCalculator { public int div(int i, int j) { System.out.println("MathCalculator...div..."); return i / j; } }
|
现在我们希望在以上这个业务逻辑类中的除法运算之前,记录一下日志
,例如记录一下哪个方法运行了,用的参数是什么,运行结束之后它的
返回值又是什么,顺便可以将其打印出来,还有如果运行出异常了,那
么就捕获一下异常信息
定义切面类
创建一个切面类,例如LogAspects,在该切面类中定义几个打印日志的
方法,以这些方法来动态地感知MathCalculator类中的div()方法的运
行情况。如果需要切面类来动态地感知目标类方法的运行情况,那么就
需要使用Spring AOP中的一系列通知方法了。AOP中的通知方法及其对
应的注解与含义如下:
- 前置通知(对应的注解是@Before):在目标方法运行之前运行
- 后置通知(对应的注解是@After):在目标方法运行结束之后运行,
无论目标方法是正常结束还是异常结束都会执行
- 返回通知(对应的注解是@AfterReturning):在目标方法正常返回
之后运行
- 异常通知(对应的注解是@AfterThrowing):在目标方法运行出现
异常之后运行
- 环绕通知(对应的注解是@Around):动态代理,我们可以直接手动
推进目标方法运行(joinPoint.procced())
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| public class LogAspects {
@Before("public int com.atguigu.aop.MathCalculator.*(..)") public void logStart() { System.out.println("除法运行......@Before,参数列表是:{}"); } @After("public int com.atguigu.aop.MathCalculator.*(..)") public void logEnd() { System.out.println("除法结束......@After"); } @AfterReturning("public int com.atguigu.aop.MathCalculator.*(..)") public void logReturn() { System.out.println("除法正常返回......@AfterReturning,运行结果是:{}"); } @AfterThrowing("public int com.atguigu.aop.MathCalculator.*(..)") public void logException() { System.out.println("除法出现异常......异常信息:{}"); } }
|
如果切入点表达式都一样的情况下,那么我们可以抽取出一个公共的
切入点表达式
1 2 3 4 5 6
| public class LogAspects { @Pointcut("execution(public int com.meimeixia.aop.MathCalculator.*(..))") public void pointCut() {} ... }
|
pointCut()方法就是抽取出来的一个公共的切入点表达式,其实该方
法的方法名随便写啥都行,但是方法体中啥都别写。第一种情况,如
果是本类引用,那么可以像下面这样写
1 2 3 4 5 6 7 8 9 10
| public class LogAspects { @Pointcut("execution(public int com.meimeixia.aop.MathCalculator.*(..))") public void pointCut() {} @Before("pointCut()") public void logStart() { System.out.println("除法运行......@Before,参数列表是:{}"); } ... }
|
第二种情况,如果是外部类(即其他的切面类)引用,那么就得在
通知注解中写方法的全名了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
@Aspect public class LogAspects { @Pointcut("execution(public int com.meimeixia.aop.MathCalculator.*(..))") public void pointCut() {} @After("com.meimeixia.aop.LogAspects.pointCut()") public void logEnd() { System.out.println("除法结束......@After"); } }
|
将目标类和切面类加入到IOC容器
新建一个配置类,例如MainConfigOfAOP,并使用@Configuration注解
标注这是一个Spring 的配置类,同时使用@EnableAspectJAutoProxy
注解开启基于注解的AOP模式。在该配置类中,使用@Bean注解将业务逻
辑类(目标方法所在类)和切面类都加入到IOC容器中
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @EnableAspectJAutoProxy @Configuration public class MainConfigOfAOP { @Bean public MathCalculator calculator() { return new MathCalculator(); } @Bean public LogAspects logAspects() { return new LogAspects(); } }
|
测试
创建一个单元测试类IOCTest_AOP,并在该测试类中创建一个test01()方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class IOCTest_AOP { @Test public void test01() { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfAOP.class); MathCalculator mathCalculator = applicationContext.getBean( MathCalculator.class); mathCalculator.div(1, 1); applicationContext.close(); } }
|
执行了切面类中的方法,并打印出了相关信息,顺序是Before After
AfterReturning。但是并没有打印参数列表和运行结果,要想打印出
参数列表和运行结果,就需要对LogAspects切面类中的方法进行优化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| @Aspect public class LogAspects { @Pointcut("execution(public int com.meimeixia.aop.MathCalculator.*(..))") public void pointCut() {} @Before("pointCut()") public void logStart(JoinPoint joinPoint) { Object[] args = joinPoint.getArgs(); System.out.println(joinPoint.getSignature().getName() + "运行......@Before,参数列表是:{" + Arrays.asList(args) + "}"); } @AfterReturning(value="pointCut()", returning="result") public void logReturn(JoinPoint joinPoint, Object result) {
System.out.println(joinPoint.getSignature().getName() + "正常返回......@AfterReturning,运行结果是:{" + result + "}"); } }
|
如果目标方法运行时出现了异常,而我们又想拿到这个异常信息,
只须对LogAspects切面类中的logException()方法进行优化即可
1 2 3 4 5
| @AfterThrowing(value="pointCut()", throwing="exception") public void logException(JoinPoint joinPoint, Exception exception) { System.out.println(joinPoint.getSignature().getName() + "出现异常......异常信息:{" + exception + "}"); }
|
执行了切面类中的方法,并打印出了相关信息,顺序是Before After
和异常信息
小结
搭建AOP测试环境时,只要牢牢记住以下三点
- 将切面类和业务逻辑组件(目标方法所在类)都加入到容器中,并且
要告诉Spring哪个类是切面类(标注了@Aspect注解的那个类)
- 在切面类上的每个通知方法上标注通知注解,告诉Spring何时何地运
行,当然最主要的是要写好切入点表达式,这个切入点表达式可以参照官
方文档来写
- 开启基于注解的AOP模式,即加上@EnableAspectJAutoProxy注解,
这是最关键的一点
@EnableAspectJAutoProxy注解
在配置类上添加@EnableAspectJAutoProxy注解,便能够开启注解版的
AOP功能。也就是说,如果要使注解版的AOP功能起作用的话,那么就得
需要在配置类上添加@EnableAspectJAutoProxy注解
1 2 3 4 5 6 7 8 9
| @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Import({AspectJAutoProxyRegistrar.class}) public @interface EnableAspectJAutoProxy { boolean proxyTargetClass() default false;
boolean exposeProxy() default false; }
|
使用@Import注解给容器中引入了AspectJAutoProxyRegister组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar { AspectJAutoProxyRegistrar() { }
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary (registry); AnnotationAttributes enableAspectJAutoProxy = AnnotationConfigUtils .attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class); if (enableAspectJAutoProxy != null) { if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) { AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry); } if (enableAspectJAutoProxy.getBoolean("exposeProxy")) { AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry); } }
} }
|
可以通过ImportBeanDefinitionRegistrar接口实现将自定义的组件添
加到IOC容器中
1 2 3 4 5 6 7 8 9 10 11
| public interface ImportBeanDefinitionRegistrar { default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) { this.registerBeanDefinitions(importingClassMetadata, registry); }
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { } }
|
也就是说,@EnableAspectJAutoProxy注解使用AspectJAutoProxyRegistrar
对象自定义组件,并将相应的组件添加到了IOC容器中