Spring AOP源码解读1 - 程序入口

前言

最近看了《从零开始写JavaWeb框架》,想比较一下Spring AOP的实现方式和书的上实现方式有什么不同,所以先把Spring AOP的源码读一下,再进行比较。

Spring的源码实在是复杂,在读的过程中参考了很多书和网上的文章,本文算是这些文章的总结,再加上一些我自己对另个细节的理解。

本文分成 3 部分:

  • 程序入口
  • 切面和增强的取得
  • 代理的生成

一,注册AspectJAnnotationAutoProxyCreator

如果使用<aop:aspectj-autoproxy />标签来自动生成代理的话,入口程序是AopNamespaceHandler。在AopNamespaceHandler中,下面一段代码是对<aop:aspectj-autoproxy />标签执行的调用:

registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());

AspectJAutoProxyBeanDefinitionParser解析器中,首先调用的parse方法。parse方法中有一行代码:

AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element);

我们看一下registerAspectJAnnotationAutoProxyCreatorIfNecessary方法的实际内容:

public static void registerAutoProxyCreatorIfNecessary(
    ParserContext parserContext, Element sourceElement) {
    // 注册或更新 AutoProxyCreator 定义 beanName 为 org.Springframework.aop.config.internalAutoProxyCreator的BeanDefinition
    // 如果internalAutoProxyCreator的BeanDefinition已经存在,而根据优先级更新BeanDefinition
    // 在这里我们注册的是AnnotationAwareAspectJAutoProxyCreator
    BeanDefinition beanDefinition = AopConfigUtils.registerAutoProxyCreatorIfNecessary(
            parserContext.getRegistry(), parserContext.extractSource(sourceElement));
    // 对于 proxy-target-class 以及 expose-proxy 属性的处理
    useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
    // 注册组件并通知,便于监听器作进一步处理
    // 其中 beanDefinition 的 className 为 AnnotationAwareAspectJAutoProxyCreator
    registerComponentIfNecessary(beanDefinition, parserContext);
}



那为什么注册AnnotationAwareAspectJAutoProxyCreator,注册AnnotationAwareAspectJAutoProxyCreator有什么用呢?

其实,实现AOP处理是其实是通过BeanPostProcessor机制实现的。AnnotationAwareAspectJAutoProxyCreator的父类也实现一个BeanPostProcessor类型的接口,而生成代理的逻辑就在AnnotationAwareAspectJAutoProxyCreator的BeanPostProcessor接口实现里面。
更严谨地说,AnnotationAwareAspectJAutoProxyCreator的父类实现的接口是
SmartInstantiationAwareBeanPostProcessor,主要是Spring框架内部使用的一个接口。而这个接口的父接口InstantiationAwareBeanPostProcessor 是实现代理的重点之一。

这3个接口的关系如下:
SmartInstantiationAwareBeanPostProcessor -> InstantiationAwareBeanPostProcessor -> BeanPostProcessor

为什么说是是InstantiationAwareBeanPostProcessor接口的子接口,接口是重点之一?那InstantiationAwareBeanPostProcessor接口是什么接口呢?

BeanPostProcessor主要作用于Bean实例化后,初始化前后。InstantiationAwareBeanPostProcessor虽然是BeanPostProcessor的子接口,但它的调用时间点其发生在Bean实例化前,在真正调用doCreate()创建bean实例之前。
在创建Bean实例之前,会先调用resolveBeforeInstantiation方法,这个方法是生成Bean代理的地方。如果此方法返回值不为空则直接返回生成的Bean的代理,如果为空就向下走正常的Bean生成流程。

spring注释“Give BeanPostProcessors a chance to return a proxy instead of the target bean instance. ”给BeanPostProcessors一个机会返回代理proxy对象。

InstantiationAwareBeanPostProcessor接口方法,就是在resolveBeforeInstantiation方法中调用的。所以可以看出,BeanPostProcessore有很多,但Spring AOP的实现就是通过InstantiationAwareBeanPostProcessor这个BeanPostProcessor实现的。看一下源码:

protected Object createBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)
        throws BeanCreationException {
    ......
    try {
        // Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
        // 如果返回值不为空,说明生成成了此BeanName的代理,直接返回代理对象
        Object bean = resolveBeforeInstantiation(beanName, mbd);
        if (bean != null) {
            return bean;
        }
    }
    catch (Throwable ex) {
        ......
    }

    // 如果没有生成代理对象,就按正常流程走,生成Bean对象
    Object beanInstance = doCreateBean(beanName, mbd, args);
    .....
    return beanInstance;
}
protected Object resolveBeforeInstantiation(String beanName, RootBeanDefinition mbd) {
    Object bean = null;
    if (!Boolean.FALSE.equals(mbd.beforeInstantiationResolved)) {
        // Make sure bean class is actually resolved at this point.
        if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
            Class<?> targetType = determineTargetType(beanName, mbd);
            if (targetType != null) {
                // 调用InstantiationAwareBeanPostProcessor接口的地方
                bean = applyBeanPostProcessorsBeforeInstantiation(targetType, beanName);
                if (bean != null) {
                    bean = applyBeanPostProcessorsAfterInitialization(bean, beanName);
                }
            }
        }
        mbd.beforeInstantiationResolved = (bean != null);
    }
    return bean;
}
    protected Object applyBeanPostProcessorsBeforeInstantiation(Class<?> beanClass, String beanName)
            throws BeansException {

        for (BeanPostProcessor bp : getBeanPostProcessors()) {
            if (bp instanceof InstantiationAwareBeanPostProcessor) {
                // 转换成InstantiationAwareBeanPostProcessor接口,并调用
                InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
                Object result = ibp.postProcessBeforeInstantiation(beanClass, beanName);
                if (result != null) {
                    return result;
                }
            }
        }
        return null;
    }

顺带说一下,注册AnnotationAwareAspectJAutoProxyCreator的目的是:把这个类的BeanDefinition通过registerBeanDefinition方法(DefaultListableBeanFactory类中)加入到beanDefinitionMap中,作为一个Bean让Spring管理,这样Spring就可以随意取得它了。



如果使用<aop:aspectj-autoproxy />标签来自动生成代理的话,入口程序是AopNamespaceHandler。那AopNamespaceHandler 是在什么地方被调用的呢?
这个问题让我们从容器启动的地方开始说明。以FileSystemXmlApplicationContext 为例,这个类的入口是构造函数里面的refresh() 方法。从refresh() 方法开始,调用流程是这样的:(以下流程全部是嵌套调用的关系)

1. refresh() ->
   刷新容器 
2. obtainFreshBeanFactory() ->
   获得刷新后的Bean容器
3. refreshBeanFactory() ->
   刷新Bean容器
4. loadBeanDefinitions() ->
   加载BeanDefinition
5. XmlBeanDefinitionReader#loadBeanDefinitions
   新建一个XmlBeanDefinitionReader实例(new XmlBeanDefinitionReader(beanFactory)),调用这个实例的loadBeanDefinitions方法。
6. XmlBeanDefinitionReader#doLoadBeanDefinitions
7. XmlBeanDefinitionReader#registerBeanDefinitions
8. XmlBeanDefinitionReader#createBeanDefinitionDocumentReader
   在这个方法中取得了DefaultBeanDefinitionDocumentReader实例。接下来调用这个实例的方法。
9. DefaultBeanDefinitionDocumentReader#registerBeanDefinitions
   在这个方法中,根据URI判断是否使用AopNamespaceHandler
10. DefaultBeanDefinitionDocumentReader#parseBeanDefinitions
   从registerBeanDefinitions方法开始,内部连续调用一系列方法,一直调用到parseBeanDefinitions方法。在这个方法中,根据XML文件的URI判断使用哪些解析器,例如是<Beans>类标签解析器,还是<aop>类标签解析器。
   如果是需要<aop>标签解析器的话,在this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri)代码中,从配置文件(spring.handlers)从取出AopNamespaceHandler的类名,生成这个类的实例,然后调用这个类的parse方法。
11. AopNamespaceHandler#parse
   这个方法的功能是,根据具体标签调用具体解析器的parse方法。
   - <aop:aspectj-autoproxy>:AspectJAutoProxyBeanDefinitionParser
   - <aop:config>:ConfigBeanDefinitionParser
   等。
   这个方法的调用,又回到了我们最初讲的AopNamespaceHandler入口的地方。

到此为止,从容器到AopNamespaceHandler类调用的过程也讲完了。
            

二,AspectJAnnotationAutoProxyCreator的流程

通过上面的内容,我们知道了注册AnnotationAwareAspectJAutoProxyCreator的意义,并且知道了生成代理是在它的BeanPostProcessor接口里做的,现在看看被实现的接口的内容。(postProcessAfterInitialization 具体实现是在其父类 AbstractAutoProxyCreator 中完成的):

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
   if (bean != null) {
      // 根据给定的 bean 的 class 和 name 构建出个 key,格式:beanClassName_beanName
      Object cacheKey = getCacheKey(bean.getClass(), beanName);
      if (!this.earlyProxyReferences.contains(cacheKey)) {
         // 如果它适合被代理,则需要封装指定 bean。
         return wrapIfNecessary(bean, beanName, cacheKey);
      }
   }
   return bean;
}
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
   // 是否已经处理过
   if (beanName != null && this.targetSourcedBeans.contains(beanName)) {
      return bean;
   }
   // 无需增强
   if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
      return bean;
   }
   // 给定的 bean 类是否代表一个基础设施类,基础设施类不应代理,或者配置了指定 bean 不需要自动代理
   if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
      this.advisedBeans.put(cacheKey, Boolean.FALSE);
      return bean;
   }

   // 如果存在增强方法则创建代理(*重要*)
   Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
   // 如果获取到了增强则需要针对增强创建代理
   if (specificInterceptors != DO_NOT_PROXY) {
      this.advisedBeans.put(cacheKey, Boolean.TRUE);
      // 创建代理(*重要*)
      Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
      this.proxyTypes.put(cacheKey, proxy.getClass());
      return proxy;
   }

   this.advisedBeans.put(cacheKey, Boolean.FALSE);
   return bean;
}

这里有两个方法特别重要:

  1. getAdvicesAndAdvisorsForBean:如果Bean是要被代理的对象的话,取得Bean相关的Interceptor
  2. createProxy:创建代理

下一篇文章,我们就这两个方法的下面的流程分开来分析一下,首先分析getAdvicesAndAdvisorsForBean相关代码。




关于接口实现的补充:

AnnotationAwareAspectJAutoProxyCreator 一共实现了2个BeanPostProcessor 的接口,的4个方法:

  • postProcessBeforeInstantiation(InstantiationAwareBeanPostProcessor)
  • postProcessAfterInstantiation(InstantiationAwareBeanPostProcessor)
  • postProcessBeforeInitialization(BeanPostProcessor)
  • postProcessAfterInitialization(BeanPostProcessor)

postProcessBeforeInstantiationInstantiationAwareBeanPostProcessor接口)方法中,这个方法是在AnnotationAwareAspectJAutoProxyCreator的基类AbstractAutoProxyCreator中实现的。细看一下,在postProcessAfterInitialization方法中也有类似的生成代理的代码。这是为什么呢?

上网找了一些资料,在postProcessBeforeInstantiation方法中有一个判断:

如果某个Bean设置了自定义TargetSource的话,就在本方法中进行生成代理

postProcessAfterInitialization则没有这样的判断,只是在生成代理前判断了一下代理是否已经生成。具体为什么有这样的必须还不清楚(以后有需要调查一下),但结果就是:

  • 如果Bean设置了自定义TargetSource,就在postProcessBeforeInstantiation中生成代理
  • 如果没有,就在postProcessAfterInitialization中生成代理。

最后,不管理在哪个方法里生成代理,在创建每个Bean时都会被调用这两个方法,代理的生成逻辑就是在这两个方法中实现的。

关于TargetSource:spring-aop组件详解——TargetSource目标源
关于自定义TargetSource:《Spring揭密》的9.6章节

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,594评论 18 139
  • 什么是Spring Spring是一个开源的Java EE开发框架。Spring框架的核心功能可以应用在任何Jav...
    jemmm阅读 16,438评论 1 133
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,733评论 6 342
  • Spring简介 spring框架由Rod Johnson开发,2004年发布了Spring框架的第一版。Spri...
    qiuqiu_hz阅读 1,075评论 0 15
  • 1.项目基本算是完成了 但遗留了两个bug 明天解决 做项目时"小"错误❌ a.接口文档未细致看 b.单词错误 c...
    小小太阳000阅读 238评论 0 0