参考
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容器中