环境搭建:
<!-- 导入Spring AOP依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.3.12.RELEASE</version>
</dependency>
从这张图中,我们可以看出:所谓切面,其实就相当于应用对象间的横切点,我们可以将其单独抽象为单独的模块。
总之一句话:AOP是指在程序的运行期间动态地将某段代码切入到指定方法、指定位置进行运行的编程方式。AOP的底层是使用动态代理实现的。
实战案例:
1.导入AOP依赖
2.定义目标类
在com.sdehua.aop包下创建一个业务逻辑类,例如MathCalculator,用于处理数学计算上的一些逻辑。比如,我们在MathCalculator类中定义了一个除法操作,返回两个整数类型值相除之后的结果,如下所示。
package com.sdehua.aop;
public class MathCalculator {
public int div(int i, int j) {
System.out.println("MathCalculator【div】");
return i / j;
}
}
现在,我们希望在以上这个业务逻辑类中的除法运算之前,记录一下日志,例如记录一下哪个方法运行了,用的参数是什么,运行结束之后它的返回值又是什么,顺便可以将其打印出来,还有如果运行出异常了,那么就捕获一下异常信息。
或者,你会有这样一个需求,即希望在业务逻辑运行的时候将日志进行打印,而且是在方法运行之前、方法运行结束、方法出现异常等等位置,都希望会有日志打印出来。
3.定义切面类
在com.sdehua.aop包下创建一个切面类,例如LogAspects,在该切面类中定义几个打印日志的方法,以这些方法来动态地感知MathCalculator类中的div()方法的运行情况。如果需要切面类来动态地感知目标类方法的运行情况,那么就需要使用Spring AOP中的一系列通知方法了。
AOP中的通知方法及其对应的注解与含义如下:
- 前置通知(对应的注解是@Before):在目标方法运行之前运行
- 后置通知(对应的注解是@After):在目标方法运行结束之后运行,无论目标方法是正常结束还是异常结束都会执行
- 返回通知(对应的注解是@AfterReturning):在目标方法正常返回之后运行
- 异常通知(对应的注解是@AfterThrowing):在目标方法运行出现异常之后运行
- 环绕通知(对应的注解是@Around):动态代理,我们可以直接手动推进目标方法运行(joinPoint.procced())
在每一个通知方法上引用这个公共的切入点表达式呢?这得分两种情况来讨论:
第一种情况,如果是本类引用;
第二种情况,如果是外部类(即其他的切面类)引用,那么就得在通知注解中写方法的全名。
最后,必须告诉Spring哪个类是切面类,要做到这一点很简单,只需要给切面类上加上一个@Aspect注解即可。
package com.sdehua.aop;
import java.util.Arrays;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
/**
* 切面类
* @author coffee
*
*/
@Aspect
public class LogAspects {
// 如果切入点表达式都一样的情况下,那么我们可以抽取出一个公共的切入点表达式
@Pointcut("execution(public int com.sdehua.aop.MathCalculator.*(..))")
public void pointCut() {}
// @Before:在目标方法(即div方法)运行之前切入,public int com.meimeixia.aop.MathCalculator.div(int, int)这一串就是切入点表达式,指定在哪个方法切入
@Before("pointCut()")
public void logStart(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs(); // 拿到参数列表,即目标方法运行需要的参数列表
System.out.println("前置@Before除法运行【"+joinPoint.getSignature().getName()+"】,参数列表是:{"+Arrays.asList(args)+"}");
}
// 在目标方法(即div方法)结束时被调用
@After("pointCut()")
public void logEnd() {
System.out.println("后置@After除法结束");
}
// 在目标方法(即div方法)正常返回了,有返回值,被调用
@AfterReturning(value = "pointCut()", returning ="result")
// 一定要注意:JoinPoint这个参数要写,一定不能写到后面,它必须出现在参数列表的第一位,
//否则Spring也是无法识别的,就会报错。
public void logReturn(JoinPoint joinPoint, Object result) {
System.out.println("返回通知@AfterReturning除法正常返回【"+joinPoint.getSignature().getName()+"】,运行结果是:{"+result+"}");
}
// 在目标方法(即div方法)出现异常,被调用
@AfterThrowing(value="pointCut()", throwing="exception")
public void logException(JoinPoint joinPoint, Exception exception) {
System.out.println("异常通知@AfterThrowing除法出现异常【"+joinPoint.getSignature().getName()+"】,异常信息:{"+exception+"}");
}
}
4.将目标类和切面类加入到IOC容器
在com.sdehua.config包中,新建一个配置类,例如MainConfigOfAOP,并使用@Configuration注解标注这是一个Spring的配置类,同时使用@EnableAspectJAutoProxy注解开启基于注解的AOP模式。在MainConfigOfAOP配置类中,使用@Bean注解将业务逻辑类(目标方法所在类)和切面类都加入到IOC容器中,如下所示:
package com.sdehua.aop;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
/**
* AOP:面向切面编程,其底层就是动态代理
* 指在程序运行期间动态地将某段代码切入到指定方法指定位置进行运行的编程方式。
*
* @author coffee
*
*/
@EnableAspectJAutoProxy
@Configuration
public class MainConfigOfAOP {
// 将业务逻辑类(目标方法所在类)加入到容器中
@Bean
public MathCalculator calculator() {
return new MathCalculator();
}
// 将切面类加入到容器中
@Bean
public LogAspects logAspects() {
return new LogAspects();
}
}
记住给MainConfigOfAOP配置类标注@EnableAspectJAutoProxy注解。在Spring中,未来会有很多的@EnableXxx注解,它们的作用都是开启某一项功能,来替换我们以前的那些配置文件。
5.测试
在com.sdehua.test包中创建一个单元测试类,例如IOCTest_AOP,并在该测试类中创建一个test01()方法,如下所示:
package com.sdehua.aop.test;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.sdehua.aop.MainConfigOfAOP;
import com.sdehua.aop.MathCalculator;
public class IOCTest_AOP {
@Test
public void test() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfAOP.class);
MathCalculator mathCalculator = applicationContext.getBean(MathCalculator.class);
// 正常情况
mathCalculator.div(100, 10);
// 异常情况
mathCalculator.div(100, 0);
// 关闭容器
applicationContext.close();
}
}
6.运行结果
可以看到,正确的输出了切面中打印的信息,包括除零异常的信息,至此,我们的AOP测试环境就搭建成功了。
小结
搭建AOP测试环境时,要牢牢记住以下三点:
- 将切面类和业务逻辑组件(目标方法所在类)都加入到容器中,并且要告诉Spring哪个类是切面类(标注了@Aspect注解的那个类)。
- 在切面类上的每个通知方法上标注通知注解,告诉Spring何时何地运行,当然最主要的是要写好切入点表达式,这个切入点表达式可以参照官方文档来写。
- 开启基于注解的AOP模式,即加上@EnableAspectJAutoProxy注解,这是最关键的一点。
AOP原理总结
1.利用@EnableAspectJAutoProxy注解来开启AOP功能
2.这个AOP功能是怎么开启的呢?主要是通过@EnableAspectJAutoProxy注解向IOC容器中注册一个AnnotationAwareAspectJAutoProxyCreator组件来做到这点的
3.AnnotationAwareAspectJAutoProxyCreator组件是一个后置处理器
4.该后置处理器是怎么工作的呢?在IOC容器创建的过程中,我们就能清楚地看到这个后置处理器是如何创建以及注册的,以及它的工作流程。
1.首先,在创建IOC容器的过程中,会调用refresh()方法来刷新容器,而在刷新容器的过程中有一步是来注册后置处理器的,其实,这一步会为所有后置处理器都创建对象。如下所示:
// 注册后置处理器,
// 在这一步会创建AnnotationAwareAspectJAutoProxyCreator对象
registerBeanPostProcessors(beanFactory);
2.在刷新容器的过程中还有一步是来完成BeanFactory的初始化工作的,如下所示:
// 完成BeanFactory的初始化工作。
// 所谓的完成BeanFactory的初始化工作,其实就是来创建剩下的单实例bean的。
finishBeanFactoryInitialization(beanFactory);
很显然,剩下的单实例bean自然就包括MathCalculator(业务逻辑类)和LogAspects(切面类)这两个bean,因此这两个bean就是在这儿被创建的。
- 创建业务逻辑组件和切面组件
- 在这两个组件创建的过程中,最核心的一点就是AnnotationAwareAspectJAutoProxyCreator(后置处理器)会来拦截这俩组件的创建过程
- 怎么拦截呢?主要就是在组件创建完成之后,判断组件是否需要增强。如需要,则会把切面里面的通知方法包装成增强器,然后再为业务逻辑组件创建一个代理对象。在为业务逻辑组件创建代理对象的时候,使用的是cglib来创建动态代理的。如果业务逻辑类有实现接口,那么就使用jdk来创建动态代理。一旦这个代理对象创建出来了,那么它里面就会有所有的增强器。
这个代理对象创建完以后,IOC容器也就创建完了。接下来,便要来执行目标方法了。
[
](https://blog.csdn.net/yerenyuan_pku/article/details/111677048)
5.执行目标方法
- 此时,其实是代理对象来执行目标方法
- 使用CglibAopProxy类的intercept()方法来拦截目标方法的执行,拦截的过程如下:
- 得到目标方法的拦截器链,所谓的拦截器链其实就是每一个通知方法又被包装为了方法拦截器,即MethodInterceptor
- 利用拦截器的链式机制,依次进入每一个拦截器中进行执行
- 最终,整个的执行效果就会有两套:
- 目标方法正常执行:前置通知→目标方法→后置通知→返回通知
- 目标方法出现异常:前置通知→目标方法→后置通知→异常通知
[
](https://blog.csdn.net/yerenyuan_pku/article/details/111677048)