美文网首页Spring cloud
@import注解的工作原理

@import注解的工作原理

作者: 鸿雁长飞鱼龙潜跃 | 来源:发表于2019-05-06 17:54 被阅读24次

@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字符串数组

四,总结

主要流程如下:

@import注解的工作原理

关键组件

后置处理器:ConfigurationClassBeanProcessor

@Configuration注解处理类:ConfigurationClassParser

读取器:ConfigurationClassBeanDefinitionReader

beanDefunition处理器:BeanDefinitionRegistry

beaName生成器:BeanNameGenerator或者AnnotationBeanNameGenerator。

beanDefinition包装器:BeanDefinitionHolder

bean工厂:DefaultListableBeanFactory

相关文章

网友评论

    本文标题:@import注解的工作原理

    本文链接:https://www.haomeiwen.com/subject/lvvdoqtx.html