@import注解的工作原理

@Import注解是用来整合所有@Configuration注解中定义的bean配置。

这其实很像我们将多个xml配置文件导入到单个文件的情形。@Import注解实现了相同的功能。

为什么要拆分XML配置文件呢?原因很简单,为了对配置文件进行分类,避免配置文件过大,这是一种管理的思想。


一,Import注解源码


public @interface Import {

    Class<?>[] value();

}

从源码我们可以看到,Import注解只有一个属性,就是这个value,value的类型是一个Class数组,也就是说,Import注解可以配置多个Class配置类。


二,Import注解的功能


Import注解等价于XML配置文件中的import标签。可以在主配置文件中导入其他配置文件。Import注解实现的功能其实也是类似的,在主配置类中导入其他配置类,这样的设计符合单一职责的原则,方便扩展和重组。

在Spring4.2之前,Import注解只支持导入配置类,什么是配置类呢?其实就是标有@Configuration注解的类。

从Spring4.2开始,Import注解支持导入普通的类。


三,Import注解的实现原理


Import注解概括来说,支持三种类型的类的注入。

1,普通类。

2,实现ImportSelector接口的类。

3,实现ImportSelectorDefinitionRegistrar接口的类。

这个逻辑在ConfigurationClassParse. processImport()中。感兴趣的朋友可以自行查看。


OK,接下来我们看一下具体实现,关键代码都在这个后置处理器:

ConfigurationClassPostProcessor


第一步:解析得到beanName

读取所有标有@Import注解的类,这里使用的读取器是ConfigurationClassBeanDefinitionReader。

这里使用的beaName生成器是BeanNameGenerator或者AnnotationBeanNameGenerator。

String[] candidateNames = registry. getBeanDefinitionNames();

最终,ConfigurationClassPostProcessor调用BeanDefinitionRegistry的getBeanDefinitionNames()方法,该方法返回一个字符串数组,数组中存放的就是beanName。

注意:这里获得的candidateNames数组,是标注有@Configuration注解的java类。


第二步:解析处理获得beanDefinition

BeanDefinition beanDef = registry. getBeanDefinition(beanName);

这里的registry对象和上面一样,都是BeanDefinitionRegistry类的实例。

BeanDefinitionRegistry是一个接口,这个接口定义了7个方法,如下:

// 注册bean

void registerBeanDefinition(String beanName, BeanDefinition beanDef);

// 移出bean

void removeBeanDefinition(String beanName);

// 获取BeanDefinition

BeanDefinition getBeanDefinition(String beanName);

// 判断是否存在某个bean

boolean containsBeanDefinition(String beanName);

// 获取beanName数组

String[] getBeanDefinitionNames();

// 统计所有的bean

int getBeanDefinitionCount();

// 判断bean是否在使用

boolean isBeanInUse(String beanName);


第三步:根据注解类型进行分类处理。

首先来看注解分类,BeanDefinition的属性CONFIGURATION_CLASS_ATTRIBUTE,该属性有3个可选值:

full:标注@Configuration注解的类

lite:标注candidateIndicators类型注解的类。candidateIndicators可选值有4个:

Component.class.getName()

ComponentScan.class.getName()

Import.class.getName()

ImportResource.class.getName()

其实对应的就是下面4个注解:

@Component

@ComponentScan

@Import

@ImportResource

空:其他注解。

OK,搞清楚注解的分类以后,我们接下来继续分析。

首先,把标注@Configuration注解的类存入configCandidate这个容器。

接下来,第四步处理的就是这个容器中的bean,也就是标注@Configuration注解的类。


第四步:ConfigurationClassParser

Set<BeanDefinitionHolder> candidates = new LinkedHashSet(configCandidate);

parser. parse(candidates);

这行代码的主要功能就是解析标有@Configuration注解的类。

解析的流程是什么样的呢?解析操作主要做了什么?

真正处理@Configuration注解的方法是ConfigurationClassParser.processConfigurationClass()。这里首先处理@Configuration注解,然后会迭代处理@Import注解导入的其他配置类。

主要处理符合以下条件的配置类:

成员内部类。

核心方法:processMemberClasses。

@ComponentScan注解指定的包下的配置类。

关键类:ComponentScanAnnotationParser。

@Import直接指定的配置类。

核心方法:this.processImports。

@ImportResource注解指定的XML文件中的配置类。

核心方法:attributesForRepeatable。

@Bean注解的方法。

核心方法:retrieveBeanMethodMetaData。

@Bean注解的接口默认方法。

核心方法:processInterfaces。


parser. validate();

这行代码的功能是校验。校验什么呢?主要校验2点:

第一点:校验类是否是final类型的。

@Configuration class may not be final

也就是说标注@Configuration注解的类不能被final修饰。

第二点:校验类中的方法是否可以重写。

@Bean method must not be private or final

也就是说标注@Configuration注解的类中的标注@Bean注解的方法,不能被private或final修饰。

为什么要做这个校验呢?因为标注有@Configuration注解的类后续会通过动态代理的方式生成子类。


第五步,把解析得到的beanName和beanDefinition信息经过包装,然后调用DefaultListableBeanFactory的registerSingleton()方法,注册bean到IOC容器。

这里使用的包装器是BeanDefinitionHolder。经过BeanDefinitionHolder的处理以后,bean的定义信息结构如下:

beanName

BeanDefinition对象

aliases字符串数组



四,总结


主要流程如下:


图片发自简书App

关键组件

后置处理器:ConfigurationClassBeanProcessor

@Configuration注解处理类:ConfigurationClassParser

读取器:ConfigurationClassBeanDefinitionReader

beanDefunition处理器:BeanDefinitionRegistry

beaName生成器:BeanNameGenerator或者AnnotationBeanNameGenerator。

beanDefinition包装器:BeanDefinitionHolder

bean工厂:DefaultListableBeanFactory

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