spring源码学习(七)-springAOP的使用和源码

AOP是指在程序运行期间动态的将代码切入到指定方法的指定位置进行运行

使用步骤:

* 1.在配置类上添加@EnableAspectJAutoProxy注解;如果是XML配置方式,在配置文件中加上<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

* 2.定义业务逻辑类(就是实际的业务处理代码),

* 3.定义一个切面(Aspects),并添加通知方法

* @Before 前置通知

* @After 后置通知

* @AfterReturning 返回通知

* @AfterThrowing 异常通知

* @Aroud 环绕通知

* 4.需要再定义一个切点

* 5.将切面和业务逻辑类都加入到容器中,@Component注解

*

*

AOP中的AnnotationAwareAspectJAutoProxyCreator是在注册后置处理器(registerBeanPostProcessors(beanFactory);)的时候,完成创建和注册的

需要哪些注解:

需要在配置类加上@EnableAspectJAutoProxy注解

需要声明一个切面,切面加@Component和@Aspect注解

在切面中声明切点和advice方法(before after around ...)

原理(基于注解版的):

1.首先要从一个注解说起:

@EnableAspectJAutoProx,该注解会import一个AspectJAutoProxyRegistrar.class

AspectJAutoProxyRegistrar中,会在beanDefinitionMap中注册一个bean AnnotationAwareAspectJAutoProxyCreator.class

AnnotationAwareAspectJAutoProxyCreator

AspectJAwareAdvisorAutoProxyCreator

AbstractAdvisorAutoProxyCreator

AbstractAutoProxyCreator、BeanFactoryAware

SmartInstantiationAwareBeanPostProcessor

所以,这个注解的作用是在spring初始化的时候,注入了一个bean的后置处理器,这个处理器就是用来处理AOP的

2.回到spring初始化的流程中,在refresh方法中,在执行invokeBeanFactoryPostprocessor方法的时候,我们知道,invokeBeanFactoryPostProcessors()方法主要是完成了bean的扫描,将bean扫描到beanDefinitionMap中;之前有说过,spring扫描bean,大致分为了三种情况:

  [email protected]注解;

  [email protected]注解,import注解又分为了两种:ImportSelector和ImportBeanDefinitionRegistrar;

  [email protected];

而AOP的注解就是利用了@Import来给spring中注册bean的后置处理器;

AspectJAutoProxyRegistrar类是实现了ImportBeanDefinitionRegistrar接口,所以在扫描bean的时候,会按照ImportBeanDefinitionRegistrar来处理,将AnnotationAwareAspectJAutoProxyCreator注入到beanDefinitionMap中

@EnableAspectJAutoProxy的核心代码是

AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);

3.在执行registerBeanPostProcessors()方法的时候,会把所有的beanPostProcessor的实现类,add到beanFactory的一个List集合中beanPostProcessors,这个集合中在执行完registerBeanPostProcessor方法之后,存放了spring自己的以及程序员提供的bean后置处理器的实现类;在这里添加后置处理器到list的时候,会有一个优先级的区分,

  List<BeanPostProcessor> priorityOrderedPostProcessors = new ArrayList<>();

  List<String> orderedPostProcessorNames = new ArrayList<>();

  List<String> nonOrderedPostProcessorNames = new ArrayList<>();

这三个list中,从上到下,分别存储的是

  实现了PriorityOrdered接口的后置处理器实现类;

  实现了Ordered接口的bean后置处理器实现类

  没有实现Ordered和PriorityOrdered接口的bean后置处理器实现类

在把bean后置处理器添加大beanPostProcessors的时候,会按照从上到下的优先级进行添加

这个方法中,把所有的beanPostProcessor添加到集合中,是因为在spring初始化实例化bean的时候,都是依赖beanPostProcessor的各个实现类来实现的;所以,放到list中,方便后面遍历

4.说完了bean后置处理器的初始化之后,我们该说在什么时候,进行动态代理了;

当然,在spring初始化一个bean的时候,会涉及到多个后置处理器的工作,就是我另外一篇博客里面提到的,spring声明周期中用到的九大后置处理器,除了最后一个销毁时用到的,其他的八个在初始化和实例化的时候都会用到,但是spring的AOP中最主要的,用来生成代理对象的是第八个org.springframework.beans.factory.config.BeanPostProcessor#postProcessAfterInitialization;所以,这里只说这一个后置处理器

这里就要说到刚才第一步中说的,添加的后置处理器了 AnnotationAwareAspectJAutoProxyCreator

在说之前,先说一下这个后置处理器中的postProcessBeforeInstantiation方法,是在spring整个生命周期中,用到的第一个后置处理器方法,只不过,在aop中,在AnnotationAwareAspectJAutoProxyCreator中,这个方法是用来判断哪些bean是一定不需要进行代理的,这里说的是,spring如果判断当前bean一定不需要代理,就会把bean放到一个map中,key是beanClass;并把value设置为false。advisedBeans;这个map在afterInitialization方法中有用到;这里是如何推断当前bean一定不需要动态代理呢?

  boolean retVal = Advice.class.isAssignableFrom(beanClass) ||

          Pointcut.class.isAssignableFrom(beanClass) ||

          Advisor.class.isAssignableFrom(beanClass) ||

          AopInfrastructureBean.class.isAssignableFrom(beanClass);

除了这几个判断条件,还会判断当前bean是否是切面(判断当前bean是否有@Aspect注解)

    return (super.isInfrastructureClass(beanClass) ||

          (this.aspectJAdvisorFactory != null && this.aspectJAdvisorFactory.isAspect(beanClass)));

所以,在beforeInstantiation中,会把一定不需要生成动态代理的bean添加到advisedBeans中,并把value设置为false

在实例化bean的时候,会调用后置处理器来判断当前bean是否需要增强,调用的是 AbstractAutoProxyCreator(AnnotationAwareAspectJAutoProxyCreator 继承了该抽象类)的postProcessAfterInitialization方法,判断当前bean是否需要增强;

在判断是否需要增强的时候,会拿到所有的切面,以及切入规则;其中个人认为一个比较重要的方法是

canApply(pca.getPointcut(), targetClass, hasIntroductions);

pointCut的pointCutExpression会保存切点的匹配规则(就是execution里面的表达式)

然后,spring会把当前bean中所有符合切点规则的通知方法,放到一个list里面,如果list大于0,就通过CGLIB或者jdk动态代理来生成代理对象

1 Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);      //这里就是判断返回的是否是null,如果不是null,就生成代理对象
2     if (specificInterceptors != DO_NOT_PROXY) {
3         this.advisedBeans.put(cacheKey, Boolean.TRUE);
4         Object proxy = createProxy(
5                 bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
6         this.proxyTypes.put(cacheKey, proxy.getClass());
7         return proxy;
8 }

5.执行目标方法:

  1.在执行目标方法的时候,由于我这里是CGLIB生成的代理对象,所以会调用到代理对象CglibAopProxy的intercept方法

  2.首先会根据目标方法,获取到当前方法的拦截器链(拦截器链就是把当前方法要用到的通知方法转换成了methodIntercept)

  3.如果拦截器链为空,就直接执行目标方法

  4.否则CglibMethodInvocation的proceed方法

  5.在执行proceed方法的时候,通过一个currentInterceptorindex来控制执行哪个通知方法;并通过一个递归调用,来控制执行顺序;关于这里的执行顺序,还是没怎么搞明白,需要后面再学习一下

这是最后执行通知方法的代码,就是在这个方法中,控制了通知方法的优先级

 1 public Object proceed() throws Throwable {
 2     //    We start with an index of -1 and increment early.
 3     if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
 4         return invokeJoinpoint();
 5     }
 6
 7     Object interceptorOrInterceptionAdvice =
 8             this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
 9     if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
10         // Evaluate dynamic method matcher here: static part will already have
11         // been evaluated and found to match.
12         InterceptorAndDynamicMethodMatcher dm =
13                 (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
14         if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
15             return dm.interceptor.invoke(this);
16         }
17         else {
18             // Dynamic matching failed.
19             // Skip this interceptor and invoke the next in the chain.
20             return proceed();
21         }
22     }
23     else {
24         // It‘s an interceptor, so we just invoke it: The pointcut will have
25         // been evaluated statically before this object was constructed.
26         return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
27     }
28 }

原文地址:https://www.cnblogs.com/mpyn/p/12183429.html

时间: 2024-12-14 15:25:11

spring源码学习(七)-springAOP的使用和源码的相关文章

spring cloud深入学习(七)-----配置中心git示例

随着线上项目变的日益庞大,每个项目都散落着各种配置文件,如果采用分布式的开发模式,需要的配置文件随着服务增加而不断增多.某一个基础服务信息变更,都会引起一系列的更新和重启,运维苦不堪言也容易出错.配置中心便是解决此类问题的灵丹妙药. 市面上开源的配置中心有很多,BAT每家都出过,360的QConf.淘宝的diamond.百度的disconf都是解决这类问题.国外也有很多开源的配置中心Apache的Apache Commons Configuration.owner.cfg4j等等.这些开源的软件

猫猫学iOS之二维码学习,快速生成二维码

猫猫分享,必须精品 原创文章,欢迎转载.转载请注明:翟乃玉的博客 地址:http://blog.csdn.net/u013357243 二维码是一项项目中可能会用到的,iOS打开相机索取二维码的速度可不是Android能比的...(Android扫描二维码要来回来回晃...) 简单不多说,如何把一段资料(网址呀,字符串)变成二维码,直接上代码 步骤: 导入CoreImage框架 #import <CoreImage/CoreImage.h> 通过滤镜CIFilter生成二维码 #import

Spring源码学习笔记(6)

Spring源码学习笔记(六) 前言-- 最近花了些时间看了<Spring源码深度解析>这本书,算是入门了Spring的源码吧.打算写下系列文章,回忆一下书的内容,总结代码的运行流程.推荐那些和我一样没接触过SSH框架源码又想学习的,阅读郝佳编著的<Spring源码深度解析>这本书,会是个很好的入门. 上一篇中我们梳理到 Spring 加载 XML 配置文件, 完成 XML 的解析工作,接下来我们将进入 Spring 加载 bean 的逻辑. 我们使用 Spring 获取 XML

Spring源码学习笔记(3)

Spring源码学习笔记(三) 前言----     最近花了些时间看了<Spring源码深度解析>这本书,算是入门了Spring的源码吧.打算写下系列文章,回忆一下书的内容,总结代码的运行流程.推荐那些和我一样没接触过SSH框架源码又想学习的,阅读郝佳编著的<Spring源码深度解析>这本书,会是个很好的入门. DispatcherServlet 实现核心功能 和普通的 Servelt 类一样, DispatcherServlet 中的 doGet() 和 doPost() 方法

Spring源码学习笔记(5)

Spring源码学习笔记(五) 前言-- 最近花了些时间看了<Spring源码深度解析>这本书,算是入门了Spring的源码吧.打算写下系列文章,回忆一下书的内容,总结代码的运行流程.推荐那些和我一样没接触过SSH框架源码又想学习的,阅读郝佳编著的<Spring源码深度解析>这本书,会是个很好的入门 写下一句话,开篇不尴尬  ----  上篇文章中梳理到 Spring 加载资源文件后开始解析 Bean, 现在我们从两个解析函数 parseDefaultElement() 和 par

Spring 源码学习(一)

设计伊始       Spring 是为解决企业级应用开发的复杂性而设计,她可以做很多事.但归根到底支撑Spring的仅仅是少许的基本理念,而所有地这些的基本理念都能可以追溯到一个最根本的使命:简化开发.这是一个郑重的承诺,其实许多框架都声称在某些方面做了简化. 而Spring则立志于全方面的简化Java开发.对此,她主要采取了4个关键策略: 1,基于POJO的轻量级和最小侵入性编程: 2,通过依赖注入和面向接口松耦合: 3,基于切面和惯性进行声明式编程: 4,通过切面和模板减少样板式代码: 而

Spring源码学习笔记(7)

Spring源码学习笔记(七) 前言-- 最近花了些时间看了<Spring源码深度解析>这本书,算是入门了Spring的源码吧.打算写下系列文章,回忆一下书的内容,总结代码的运行流程.推荐那些和我一样没接触过SSH框架源码又想学习的,阅读郝佳编著的<Spring源码深度解析>这本书,会是个很好的入门 写前说句话, 开篇不尴尬 ---- 接下的这一篇当中, 我们将来回顾 Spring 中 AOP 功能的实现流程.  早上精力充沛, 开始新一天的学习 \(^o^)/~ 接触过 Spri

Spring源码学习之一下载和导入

折腾了两天spring源码,一直都不能成功导入eclipse,今天早上上班前试了最后一次,总算BUILD SUCCESSFUL了,特此纪念一下. 一.下载spring源码,https://github.com/spring-projects/spring-framework/tree/v4.2.3.RELEASE.在linux mint系统里用git下载了一遍了,window系统的git用起来实在掣肘,所以直接下载zip压缩包了,解压. 二.更新jdk1.8_66,spring4需要用到jdk8

Spring源码学习的初步体会

Spring源码学习的初步体会: 深入学习和巩固java的基础知识,其中的java知识范围全部,可以边研究源码边巩固复习基础知识 体会其中用到的设计思想:其中包含的设计原则和设计模式. 加深对spring的理解,在业务开发中使用spring更容易和深入,提高了生产率.