Spring注解驱动开发(三)
注:此笔记为尚硅谷Spring注解驱动教程(雷丰阳源码级讲解)学习笔记,并同时参考[https://blog.csdn.net/xjhqre/article/details/123264069]博主文章,其中包含个人的笔记和理解,仅做学习笔记之用。
13、AOP
AOP:【动态代理】,指在程序运行期间动态的将某段代码切入到指定方法指定位置进行运行的编程方式
Spring的AOP(面向切面编程)是通过动态代理实现的,它允许在程序运行时将额外的代码织入到现有的类和方法中,从而实现横切关注点的功能,如日志记录、事务管理等。AOP的核心思想是将系统划分为多个模块,每个模块处理一个特定的关注点,而不是将关注点分散在整个应用中。Spring AOP主要通过代理机制和切点(Pointcut)来实现。
Spring AOP的核心概念:
切面(Aspect): 切面是一组横切关注点的定义,它包括切点和通知。通知定义了在何时、何地执行横切逻辑。
连接点(Join Point): 连接点是在应用执行过程中能够插入切面的点,例如方法的调用、异常的处理等。
切点(Pointcut): 切点定义了在何处应用通知,它是连接点的集合。通知只在切点被满足时才被执行。
通知(Advice): 通知定义了在连接点上执行的横切逻辑,包括"前置通知"、“后置通知”、"环绕通知"等。
引入(Introduction): 引入允许在现有的类中添加新的方法或属性。
目标对象(Target Object): 目标对象是被代理的对象,它包含了连接点。
代理(Proxy): 代理是一个包装了目标对象的对象,它拦截对目标对象的访问,允许在目标对象的方法执行前后执行额外的逻辑。
AOP使用步骤:
1.导入aop模块;Spring AOP:(spring-aspects)
2.定义一个业务逻辑类(MathCalculator);在业务逻辑运行的时候将日志进行打印(方法之前、方法运行结束、方法出现异常,xxx)
3.定义一个日志切面类(LogAspects):切面类里面的方法需要动态感知MathCalculator.div运行到哪里然后执行;
4.给切面类的目标方法标注何时何地运行(通知注解)
5.将切面类和业务逻辑类(目标方法所在类)都加入到容器中
6.必须告诉Spring哪个类是切面类(给切面类上加一个注解:@Aspect)
7.给配置类中加 @EnableAspectJAutoProxy 【开启基于注解的aop模式】.
通知方法说明:
- 前置通知(@Before):logStart:在目标方法(div)运行之前运行
- 后置通知(@After):logEnd:在目标方法(div)运行结束之后运行(无论方法正常结束还是异常结束)
- 返回通知(@AfterReturning):logReturn:在目标方法(div)正常返回之后运行
- 异常通知(@AfterThrowing):logException:在目标方法(div)出现异常以后运行
- 环绕通知(@Around):动态代理,手动推进目标方法运行(joinPoint.procced())
*表示所有方法,…表示任意类型的形参
13.1、AOP实例
13.1.1、编写配置类
@EnableAspectJAutoProxy
@Configuration
public class MainConfigOfAop {
//业务逻辑类加入容器中
@Bean
public MathCalculator calculator() {
return new MathCalculator();
}
//切面类加入到容器中
@Bean
public LogAspects logAspects() {
return new LogAspects();
}
}
13.1.2、编写切面类
切面表达式 "execution(public int com.xjhqre.aop.MathCalculator.*(..))"
中 表示所有方法,…表示任意类型的形参*。
事实上,每个AOP扩展的方法都有一个切入点表达式,但是为了书写方便,如果切入点表达式都一样,一般都会把切入点表达式公共的部分中抽取出来,这个公共切入点表达式需要绑定增强类,使用Pointcut注解声明pointCut()方法,这个pointCut()方法,就可以被各个@注解绑定
JoinPoint joinPoint必须写在形参的第一位。
joinPoint可以获取被增强方法的签名,如方法名、参数列表、方法的返回值、方法抛出的异常等等
@Aspect
public class LogAspects {
//抽取公共的切入点表达式
//1、本类引用
//2、其他的切面引用,引用方法的全类名:"com.atguigu.aop.LogAspects.pointCut()"
@Pointcut("execution(public int com.xjhqre.aop.MathCalculator.*(..))")
public void pointCut() {
}
//@Before在目标方法之前切入;切入点表达式(指定在哪个方法切入)
@Before("pointCut()")
public void logStart(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
System.out.println("" + joinPoint.getSignature().getName() + "运行。。。@Before:参数列表是:{" + Arrays.asList(args) + "}");
}
@After("pointCut()")
public void logEnd(JoinPoint joinPoint) {
System.out.println("" + joinPoint.getSignature().getName() + "结束。。。@After");
}
//JoinPoint一定要出现在参数表的第一位
@AfterReturning(value = "pointCut()", returning = "result")
public void logReturn(JoinPoint joinPoint, Object result) {
System.out.println("" + joinPoint.getSignature().getName() + "正常返回。。。@AfterReturning:运行结果:{" + result + "}");
}
@AfterThrowing(value = "pointCut()", throwing = "exception")
public void logException(JoinPoint joinPoint, Exception exception) {
System.out.println("" + joinPoint.getSignature().getName() + "异常。。。异常信息:{" + exception + "}");
}
}
13.1.4、编写被增强类
被增强类其实代码没有改变,但是在AOP作用下,它的功能却得到了增强
public class MathCalculator {
public int div(int i, int j) {
System.out.println("MathCalculator...div...");
return i / j;
}
}
13.1.5、测试
@Test
public void test12() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfAop.class);
//1、不要自己创建对象,因为自己创建对象,就无法使用到容器中的AOP提供的切面类的功能,必须将创建对象的流程,交由Spring来管理
//MathCalculator mathCalculator = new MathCalculator();
//mathCalculator.div(1, 1);
MathCalculator bean = applicationContext.getBean(MathCalculator.class);
bean.div(1, 1);
}
13.1.6、AOP使用总结
AOP使用的三个步骤:
1.将业务逻辑组件和切面类都加入到容器中;告诉Spring哪个是切面类(@Aspect)
2.在切面类上的每一个通知方法上标注通知注解,告诉Spring何时何地运行(切入点表达式)
3.开启基于注解的aop模式;@EnableAspectJAutoProxy
13.2、AOP原理
13.2.1、@EnableAspectJAutoProxy
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
// 引入AspectJAutoProxyRegister.class对象
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
// true——使用CGLIB基于类创建代理;false——使用java接口创建代理
boolean proxyTargetClass() default false;
// 是否通过aop框架暴露该代理对象,aopContext能够访问.
boolean exposeProxy() default false;
}
该注解引入了AspectJAutoProxyRegistrar
,AspectJAutoProxyRegister
给容器中导入了给容器中导入了一个AspectJAutoProxyRegistrar
自定义给容器注册bean
在AspectJAutoProxyRegistrar
中有一个registerBeanDefinitions
方法,在该方法中使用AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
语句注册了一个名为internalAutoProxyCreator
的组件,该组件的类型为AnnotationAwareAspectJAutoProxyCreator
13.2.2、AnnotationAwareAspectJAutoProxyCreator分析
以下是AnnotationAwareAspectJAutoProxyCreator
的层次结构:
这里主要关注后置处理器和自动装备BeanFactory相关的方法:SmartInstantiationAwareBeanPostProcessor
(后置处理器), BeanFactoryAware
(自动装配BeanFactory).
接下来去AnnotationAwareAspectJAutoProxyCreator及其父类中寻找有关后置处理器和BeanFactory相关的方法
AbstractAutoProxyCreator:
- setBeanFactory
- postProcessAfterInitialization
- postProcessBeforeInstantiation
- postProcessProperties
AbstractAdvisorAutoProxyCreator:
- setBeanFactory,在该方法中调用了initBeanFactory方法
AnnotationAwareAspectJAutoProxyCreator:
- initBeanFactory
13.2.3、注册AnnotationAwareAspectJAutoProxyCreator流程
断点测试程序执行流程:
注册AnnotationAwareAspectJAutoProxyCreator
的后置处理器的方法registerBeanPostProcessors(beanFactory)
在IOC创建的refresh()
方法中
registerBeanPostProcessors(beanFactory):注册bean的后置处理器来方便拦截bean的创建;
1.先获取ioc容器已经定义了的需要创建对象的所有BeanPostProcessor
2.给容器中加别的BeanPostProcessor
3.优先注册实现了PriorityOrdered接口的BeanPostProcessor
4.再给容器中注册实现了Ordered接口的BeanPostProcessor
5.注册没实现优先级接口的BeanPostProcessor;
6.注册BeanPostProcessor,实际上就是创建BeanPostProcessor对象,保存在容器中;下面以创建internalAutoProxyCreator(类型为AnnotationAwareAspectJAutoProxyCreator)的后置处理器BeanPostProcessor步骤为例:
? 1.创建Bean的实例
? 2.populateBean;给bean的各种属性赋值
? 3.initializeBean:初始化bean步骤:
? 1)invokeAwareMethods():处理Aware接口的方法回调
? 2)applyBeanPostProcessorsBeforeInitialization():应用所有后置处理器的 postProcessBeforeInitialization(),初始化前的逻辑
? 3)invokeInitMethods();执行自定义的初始化方法
? 4)applyBeanPostProcessorsAfterInitialization();执行所欲后置处理器的postProcessAfterInitialization();初始化后的逻辑
? 4.AnnotationAwareAspectJAutoProxyCreator的后置处理器创建成功,生成aspectJAdvisorsBuilder
7.把BeanPostProcessor注册到BeanFactory中:beanFactory.addBeanPostProcessor(postProcessor)
注册AnnotationAwareAspectJAutoProxyCreator的后置处理器流程图:
13.2.4、AnnotationAwareAspectJAutoProxyCreator执行时机
我们来分析IOC创建方法refresh()
中的finishBeanFactoryInitialization(beanFactory);
方法
1.finishBeanFactoryInitialization(beanFactory);完成BeanFactory初始化工作;创建剩下的单实例bean
? 1)遍历获取容器中所有的Bean,依次创建对象getBean(beanName);getBean->doGetBean()- >getSingleton()->
? 2)创建bean
? ①先从缓存中获取当前bean:Object sharedInstance = getSingleton(beanName);
,如果能获取到,说明bean是之前被创建过的,直接使用,否则再创建;只要创建好的Bean都会被缓存起来
? ②createBean();创建bean;
? <1>resolveBeforeInstantiation(beanName, mbdToUse);
解析BeforeInstantiation
希望后置处理 器在此能返回一个代理对象;如果能返回代理对象就使用,如果不能就继续
? [1]后置处理器先尝试返回对象;
? bean = applyBeanPostProcessorsBeforeInstantiation();
? 拿到所有后置处理器,如果是InstantiationAwareBeanPostProcessor
,就执行postProcessBeforeInstantiation
? 如果不是则执行bean = applyBeanPostProcessorsAfterInitialization(bean, beanName);
? [2]若后置处理器返回对象失败,则会真正的去创建一个bean实例:doCreateBean(beanName, mbdToUse, args);
AnnotationAwareAspectJAutoProxyCreator执行时机流程图:
总结:
- AnnotationAwareAspectJAutoProxyCreator在所有bean创建之前会有一个拦截,它继承了InstantiationAwareBeanPostProcessor,会调用postProcessBeforeInstantiation()方法
- AnnotationAwareAspectJAutoProxyCreator会在任何bean创建之前先尝试返回bean的实例,
- BeanPostProcessor是在Bean对象创建完成初始化前后调用的
- InstantiationAwareBeanPostProcessor是在创建Bean实例之前先尝试用后置处理器返回对象的
13.2.5、AnnotationAwareAspectJAutoProxyCreator作用
AnnotationAwareAspectJAutoProxyCreator
的类型是InstantiationAwareBeanPostProcessor
1.每一个bean创建之前,调用postProcessBeforeInstantiation()
1)判断当前bean是否在advisedBeans
中(里面保存了所有需要增强bean, 如MathCalculator)
2)判断当前bean是否是基础类型的,是否实现了Advice、Pointcut、Advisor、AopInfrastructureBean接口,或者是否是切面(是否标注了@Aspect注解)
3)判断是否需要跳过,获取候选的增强器(切面里面的通知方法)封装在List<Advisor> candidateAdvisors
集合中,判断每一个增强器是否是 AspectJPointcutAdvisor
类型的,如果是则返回true,但我们的增强器是InstantiationModelAwarePointcutAdvisor
类型,所以返回false
2.创建对象后,调用postProcessAfterInitialization
,该方法返回一个包装bean,return wrapIfNecessary(bean, beanName, cacheKey)
包装如果需要的情况下
1)调用getAdvicesAndAdvisorsForBean
方法获取当前bean的所有增强器(通知方法),封装在集合Object[] specificInterceptors
中
①找到候选的所有的增强器(找哪些通知方法是需要切入当前bean方法的)
②获取到能在bean使用的增强器。
③给增强器排序
2)保存当前bean在advisedBeans中
3)如果当前bean需要增强,调用createProxy
创建当前bean的代理对象;
①获取所有增强器(通知方法)
②保存到proxyFactory
③调用proxyFactory.getProxy
创建代理对象:Spring自动决定,创建JdkDynamicAopProxy(config)
jdk动态代理或ObjenesisCglibAopProxy(config)
cglib的动态代理;
4)给容器中返回当前组件使用cglib增强了的代理对象;
5)以后容器中获取到的就是这个组件的代理对象,执行目标方法的时候,代理对象就会执行通知方法的流程;
AnnotationAwareAspectJAutoProxyCreator配置代理流程图:
13.2.6、拦截器链分析
容器中保存了组件的代理对象(cglib增强后的对象),这个对象里面保存了详细信息(比如增强器,目标对象,xxx);
? 1.CglibAopProxy.intercept();
拦截目标方法的执行
? 2.根据ProxyFactory对象获取将要执行的目标方法拦截器链;List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
? 1)List<Object> interceptorList
保存所有拦截器 长度为5,里面有一个默认的ExposeInvocationInterceptor
和 4个增强器;
? 2)遍历所有的增强器,将其转为Interceptor:registry.getInterceptors(advisor);
? 3)将增强器转为List<MethodInterceptor>
,如果是MethodInterceptor
,直接加入到集合中;如果不是,使用AdvisorAdapter
将增强器转为MethodInterceptor;
转换完成返回MethodInterceptor
数组;
? 3.如果没有拦截器链,直接执行目标方法,拦截器链(每一个通知方法又被包装为方法拦截器,利用MethodInterceptor
机制)
? 4.如果有拦截器链,把需要执行的目标对象,目标方法,拦截器链等信息传入,创建一个 CglibMethodInvocation
对象,并调用 Object retVal = mi.proceed();
? 5.拦截器链的触发过程;
? 1)如果没有拦截器执行执行目标方法,或者拦截器的索引和拦截器数组-1大小一样(指定到了最后一个拦截器)执行目标方法;
? 2)链式获取每一个拦截器,拦截器执行invoke方法,每一个拦截器等待下一个拦截器执行完成返回以后再来执行;拦截器链的机制,保证通知方法与目标方法的执行顺序;
拦截器链的机制
执行目标方法到获取拦截器链的流程图:
13.2.7、总结
1.@EnableAspectJAutoProxy
开启AOP功能
2.@EnableAspectJAutoProxy
会给容器中注册一个组件 AnnotationAwareAspectJAutoProxyCreator
3.AnnotationAwareAspectJAutoProxyCreator
是一个后置处理器
4.容器的创建流程:
1)registerBeanPostProcessors
注册后置处理器;创建AnnotationAwareAspectJAutoProxyCreator
对象
2)finishBeanFactoryInitialization
初始化剩下的单实例bean
①创建业务逻辑组件和切面组件
②AnnotationAwareAspectJAutoProxyCreator
拦截组件的创建过程
③组件创建完之后,判断组件是否需要增强,如果需要则将切面的通知方法,包装成增强器(Advisor),给业务逻辑组件创建一个代理对象(cglib)
5.执行目标方法:
1)代理对象执行目标方法
2)CglibAopProxy.intercept()
①得到目标方法的拦截器链(增强器包装成拦截器MethodInterceptor)
②利用拦截器的链式机制,依次进入每一个拦截器进行执行;
③正常执行:前置通知-》目标方法-》后置通知-》返回通知
出现异常:前置通知-》目标方法-》后置通知-》异常通知
Spring注解驱动开发(三)的学习笔记到此完结,笔者归纳、创作不易,大佬们给个3连再起飞吧
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!