学过Spring的小伙伴想必都会知道AOP的存在,或者听过这个名字。那么它在项目中是如何使用的呢?以及切面是如何工作的呢?一文搞懂AOP的切点,连接点等术语及原理❤️
AOP是什么
- 指在程序运行期间动态的将某段代码切入到指定方法指定位置进行运行的编程方式
面向切面编程。那么什么是切面呢?- 试想这个case:你去超市买面包,在买面包之前要看一下生产日期,没过期的话去收银台付款,过期了,这个面包就不买了。面包买完你会将面包放在袋子里带回家,如果超市没有面包,没买到也会回家。
- 上面的case可以通俗的解释
AOP中的一些术语:- “在买面包之前” 或者 买“完面包之后” 这个时机叫做
连接点 - “你去超市买面包” 这整个的动作叫做
切点在切点上会存在一部分连接点(特定时机),例如 买面包前 买面包后 - “看生产日期”和“付款” 这样的在
连接点上的动作,叫做通知。- 例如“买面包前看日期”这个行为 叫做
前置通知, - “买面包后带回家”这个行为 叫做
后置通知, - “面包过期了不买了”这个行为叫做
异常通知, - “超市没有面包,没买到也会回家” 不管买没买到面包都会回家 这个动作叫做
最终通知 - “买面包前看日期”+“买面包后带回家” 这个动作叫做
环绕通知,这个通知是最强大的一个通知类型,它不光可以在前后自定义自己的行为,也可以决定是否继续向下执行,例如买面包之前看生产日期过期了,生气了 面包不买了也是可以的。
- 例如“买面包前看日期”这个行为 叫做
- 在
连接点上的一系列行为叫做切面/方面例如“看生产日期”,“带回家” 这些动作 - 你就叫做
目标对象即被通知的对象
- “在买面包之前” 或者 买“完面包之后” 这个时机叫做
买面包的case可以通俗的理解一些术语,下面直接代码操作
AOP使用
- 定义一个切面类,切面类里面的方法需要感知方法运行到哪一步,来执行相应的通知:
@Before:前置通知,在目标方法执行之前运行@After:后置通知,在目标方法执行结束之后运行@AfterReturning:在目标方法正常返回之后运行@AfterThrowing:在目标方法抛出异常之后运行@Around:动态代理,手动推进目标方法运行
- 将切面类和业务逻辑类加到容器中
- 给切面类加
@Aspect注解,将之声明称一个切面 - 给配置类加上
@EnableAspectJAutoProxy,开启基于注解的aop模式
@EnableAapectJAutoProxy
@PropertySource(value={"classpath:/db.properties"})
@Configuration
//包扫描,注意要扫到切面
@ComponentScan({"com.*"})
public class ApplicationConfig{}
//切面类
@Asprct
@Component
public class LogAspects{
@PointCut("execution(public void com.aop.Math.div(int,int))")
public void pointCut(){}
//在方法执行之前执行
@Before("@PointCut()")
public void logStart(){
System.out.println("开始...");
}
//在方法执行之后执行
@After("@PointCut()")
public void logEnd(){
System.out.println("结束...");
}
//在方法正常返回之后执行
@AfteReturning(value="@PointCut()", returning="result")
public void logReturn(Object result){
System.out.println("正常返回...返回值是:"+result);
}
//在方法出现异常之后执行
@AfteThrowing(value="@PointCut()", throwing="exception")
public void logException(Exception exception){
System.out.println("出现异常...异常是"+exception);
}
}
AOP注册原理
- 看给容器中注册了什么组件,这个组件什么时候工作,这个组件的功能是什么
@EnableAspectJAutoProxy这个注解给容器中注册了一个AnnotationAwareAspectJAutoProxyCreator- 传入配置类,创建
IOC容器 - 注册配置类,调用
refresh()刷新容器 registerBeanPostProcessors(beanFactory); 注册bean的后置处理器来拦截bean的创建- 先获取
IOC容器已经定义了的需要创建对象的所有BeanPostProcessor - 给容器中加别的
BeanPostProcessor - 优先注册实现了
PriorityOrdered接口的BeanPostProcessor - 在给容器中注册实现了
Ordered接口的BeanPostProcessor - 注册没实现优先级接口的
BeanPostProcessor - 注册
BeanPostProcessor,实际上就是创建BeanPostProcessor对象,保存在容器中 - 把
BeanPostProcessor注册到BeanFactory中
- 先获取
- 至此就完成了
AnnotationAwareAspectJAutoProxyCreator的创建和注册 finishBeanFactoryInitialization(beanFactory); 完成BeanFactory初始化操作,创建剩下的单实例bean- 每一个
bean创建之前,调用postProcessBeforeInstantiation()主要关心目标类 和切面类的创建- 判断当前
bean是否在advisedBeans中,(保存了所有需要增强的bean) - 判断当前
Bean是否是基础类型的Advice,Pointcut,Advisor,AopInfrastructureBean,或者是否是用@Aspect注解标注的切面 - 是否需要跳过
- 获取候选的增强器(切面里面的通知方法)
List candidateAdvisors每一个封装的通知方法的增强器是InstantiationModelAwarePointcutAdvisor;判断每一个增强器是否是AspectJPointcutAdvisor类型的,返回true - 永远返回false
- 获取候选的增强器(切面里面的通知方法)
- 判断当前
- 调用
postProcessAfterInstantiation(); return wrapIfNecessary(bean,beanName,cacheKey);包装如果需要的情况下- 获取当前
bean的所有增强器(通知方法)Object[]- 找到候选的所有的增强器(找哪些通知方法是需要切入当前
bean方法的) - 找到能在当前
bean使用的增强器 - 给增强器排序
- 找到候选的所有的增强器(找哪些通知方法是需要切入当前
- 保存当前
bean在advisedBeans中 - 如果当前
bean需要增强,创建当前bean的代理对象- 获取所有增强器(通知方法)
- 保存到
proxyFactory - 创建代理对象,
Sprign自动决定JdkDynamicaAopProxy(config);jdk的动态代理ObjenesisCglibAopProxy(config);cglib的动态代理
- 给容器中返回当前组件使用
cglib增强了的代理对象 - 以后容器中获取到的就是这个组件的代理对象,执行目标方法的时候,代理对象就会执行通知方法的流程
- 获取当前
目标方法的执行
- 容器中保存了组件的代理对象(cglib增强后的对象),这个对象里面保存了详细信息(比如增强器,目标对象,xxx)
CglibAopProxy.intercept();拦截目标方法执行- 根据
ProxyFactory对象获取将要执行的目标方法的拦截器链 - 如果没有拦截器链,直接执行目标方法
- 如果有拦截器链:把需要执行的目标对象,目标方法,拦截器链等信息传入创建一个
CglibbMethodInvocation对象,并调用其proceed()方法 - 拦截器链的触发过程:
- 如果没有拦截器执行目标方法,或者拦截器的索引和拦截器数组-1大小一样(指定到了最后一个拦截器)执行目标方法
- 链式获取每一个拦截器,执行invoke方法,每一个拦截器等待下一个拦截器执行完成返回以后再来执行。拦截器链的机制,保证通知方法与目标方法的执行顺序
到此Spring AOP的概念介绍,代码示例,以及源码层面的注册过程,目标方法的执行过程介绍完毕,越研究越会觉得自己的渺小,大家一起共勉,欢迎讨论🔥