@Import注解
@Import 是 Spring 基于 Java 注解配置的主要组成部分。 @Import 注解提供了 @Bean 注解的功能,同时还有原来 Spring 基于 xml 配置文件里的
下面将分别说明 @Import 注解的功能。
1. 引入其他的@Configuration
假设有如下接口和两个实现类:
package com.test
interface ServiceInterface {
void test();
}
class ServiceA implements ServiceInterface {
@Override
public void test() {
System.out.println("ServiceA");
}
}
class ServiceB implements ServiceInterface {
@Override
public void test() {
System.out.println("ServiceB");
}
}
复制代码
两个 @Configuration ,其中 ConfigA``@Import``ConfigB :
package com.test
@Import(ConfigB.class)
@Configuration
class ConfigA {
@Bean
@ConditionalOnMissingBean
public ServiceInterface getServiceA() {
return new ServiceA();
}
}
@Configuration
class ConfigB {
@Bean
@ConditionalOnMissingBean
public ServiceInterface getServiceB() {
return new ServiceB();
}
}
复制代码
通过 ConfigA 创建 AnnotationConfigApplicationContext ,获取 ServiceInterface ,看是哪种实现:
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigA.class);
ServiceInterface bean = ctx.getBean(ServiceInterface.class);
bean.test();
}
复制代码
输出为: ServiceB .证明 @Import 的优先于本身的的类定义加载。
2. 直接初始化其他类的Bean
在 Spring 4.2 之后, @Import 可以直接指定实体类,加载这个类定义到 context 中。 例如把上面代码中的 ConfigA 的 @Import 修改为 @Import(ServiceB.class) ,就会生成 ServiceB 的Bean 到容器上下文中,之后运行 main 方法,输出为: ServiceB .证明 @Import 的优先于本身的的类定义加载.
3. 指定实现ImportSelector(以及DefferredServiceImportSelector)的类,用于个性化加载
指定实现 ImportSelector 的类,通过 AnnotationMetadata 里面的属性,动态加载类。 AnnotationMetadata 是 Import 注解所在的类属性(如果所在类是注解类,则延伸至应用这个注解类的非注解类为止)。
需要实现 selectImports 方法,返回要加载的 @Configuation 或者具体 Bean 类的全限定名的String 数组。
package com.test;
class ServiceImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
//可以是@Configuration注解修饰的类,也可以是具体的Bean类的全限定名称
return new String[]{"com.test.ConfigB"};
}
}
@Import(ServiceImportSelector.class)
@Configuration
class ConfigA {
@Bean
@ConditionalOnMissingBean
public ServiceInterface getServiceA() {
return new ServiceA();
}
}
复制代码
再次运行 main 方法,输出: ServiceB .证明 @Import 的优先于本身的的类定义加载。 一般的,框架中如果基于 AnnotationMetadata 的参数实现动态加载类,一般会写一个额外的 Enable注解,配合使用。例如:
package com.test;
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.TYPE)
@Import(ServiceImportSelector.class)
@interface EnableService {
String name();
}
class ServiceImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
//这里的importingClassMetadata针对的是使用@EnableService的非注解类
//因为`AnnotationMetadata`是`Import`注解所在的类属性,如果所在类是注解类,则延伸至应用这个注解类的非注解类为止
Map<String , Object> map = importingClassMetadata.getAnnotationAttributes(EnableService.class.getName(), true);
String name = (String) map.get("name");
if (Objects.equals(name, "B")) {
return new String[]{"com.test.ConfigB"};
}
return new String[0];
}
}
复制代码
之后,在 ConfigA 中增加注解 @EnableService(name = "B")
package com.test;
@EnableService(name = "B")
@Configuration
class ConfigA {
@Bean
@ConditionalOnMissingBean
public ServiceInterface getServiceA() {
return new ServiceA();
}
}
复制代码
再次运行 main 方法,输出: ServiceB .
还可以实现 DeferredImportSelector 接口,这样 selectImports 返回的类就都是最后加载的,而不是像 @Import 注解那样,先加载。 例如:
package com.test;
class DefferredServiceImportSelector implements DeferredImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
Map<String, Object> map = importingClassMetadata.getAnnotationAttributes(EnableService.class.getName(), true);
String name = (String) map.get("name");
if (Objects.equals(name, "B")) {
return new String[]{"com.test.ConfigB"};
}
return new String[0];
}
}
复制代码
修改 EnableService 注解:
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.TYPE)
@Import(DefferredServiceImportSelector.class)
@interface EnableService {
String name();
}
复制代码
这样 ConfigA 就优先于 DefferredServiceImportSelector 返回的 ConfigB 加载,执行 main方法,输出: ServiceA
4. 指定实现ImportBeanDefinitionRegistrar的类,用于个性化加载
与 ImportSelector 用法与用途类似,但是如果我们想重定义 Bean ,例如动态注入属性,改变Bean 的类型和 Scope 等等,就需要通过指定实现 ImportBeanDefinitionRegistrar 的类实现。例如:
定义 ServiceC
package com.test;
class ServiceC implements ServiceInterface {
private final String name;
ServiceC(String name) {
this.name = name;
}
@Override
public void test() {
System.out.println(name);
}
}
复制代码
定义 ServiceImportBeanDefinitionRegistrar 动态注册 ServiceC ,修改 EnableService
package com.test;
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.TYPE)
@Import(ServiceImportBeanDefinitionRegistrar.class)
@interface EnableService {
String name();
}
class ServiceImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
Map<String, Object> map = importingClassMetadata.getAnnotationAttributes(EnableService.class.getName(), true);
String name = (String) map.get("name");
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.rootBeanDefinition(ServiceC.class)
//增加构造参数
.addConstructorArgValue(name);
//注册Bean
registry.registerBeanDefinition("serviceC", beanDefinitionBuilder.getBeanDefinition());
}
}
复制代码
并且根据后面的源代码解析可以知道, ImportBeanDefinitionRegistrar 在 @Bean 注解之后加载,所以要修改 ConfigA 去掉其中被 @ConditionalOnMissingBean 注解的 Bean ,否则一定会生成 ConfigA 的 ServiceInterface
package com.test;
@EnableService(name = "TestServiceC")
@Configuration
class ConfigA {
// @Bean
// @ConditionalOnMissingBean
// public ServiceInterface getServiceA() {
// return new ServiceA();
// }
}
复制代码
之后运行 main ,输出: TestServiceC
@Import相关源码解析
加载解析 @Import 注解位于 BeanFactoryPostProcessor 处理的时候:
AbstractApplicationContext 的 refresh 方法
-> invokeBeanFactoryPostProcessors(beanFactory);
-> PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
-> registryProcessor.postProcessBeanDefinitionRegistry(registry);
这里的 registryProcessor ,我们指 ConfigurationClassPostProcessor
ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(registry)
-> processConfigBeanDefinitions(registry) :
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
//省略一些配置检查与设置的逻辑
//根据@Order注解,排序所有的@Configuration类
configCandidates.sort((bd1, bd2) -> {
int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
return Integer.compare(i1, i2);
});
// 创建ConfigurationClassParser解析@Configuration类
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry);
//剩余没有解析的@Configuration类
Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
//已经解析的@Configuration类
Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
do {
//解析
parser.parse(candidates);
parser.validate();
Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
configClasses.removeAll(alreadyParsed);
// 生成类定义读取器读取类定义
if (this.reader == null) {
this.reader = new ConfigurationClassBeanDefinitionReader(
registry, this.sourceExtractor, this.resourceLoader, this.environment,
this.importBeanNameGenerator, parser.getImportRegistry());
}
this.reader.loadBeanDefinitions(configClasses);
alreadyParsed.addAll(configClasses);
candidates.clear();
if (registry.getBeanDefinitionCount() > candidateNames.length) {
//省略检查是否有其他需要加载的配置的逻辑
}
}
while (!candidates.isEmpty());
//省略后续清理逻辑
}
复制代码
其中 parser.parse(candidates) 的逻辑主要由 org.springframework.context.annotation.ConfigurationClassParser 实现,功能是加载 @Import 注解还有即系 @Import 注解。 reader.loadBeanDefinitions(configClasses); 的逻辑主要由 org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader 的 loadBeanDefinitionsForConfigurationClass 方法实现,功能是将上面解析的配置转换为 BeanDefinition 就是 Bean 定义。
1. 加载@Import注解
org.springframework.context.annotation.ConfigurationClassParser
首先是 parse 方法
public void parse(Set<BeanDefinitionHolder> configCandidates) {
for (BeanDefinitionHolder holder : configCandidates) {
BeanDefinition bd = holder.getBeanDefinition();
try {
if (bd instanceof AnnotatedBeanDefinition) {
//这里的parse实际上就是调用下面即将分析的doProcessConfigurationClass
parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
}
else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
}
else {
parse(bd.getBeanClassName(), holder.getBeanName());
}
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
}
}
//最后处理所有的`DeferredImportSelector`,符合上面提到的`DeferredImportSelector`的功能
this.deferredImportSelectorHandler.process();
}
@Nullable
protected final SourceClass doProcessConfigurationClass(
ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
throws IOException {
//处理`@Component`注解的MemberClass相关代码...
//处理`@PropertySource`注解相关代码...
//处理`@ComponentScan`注解相关代码...
//处理`@Import`注解:
processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
//处理`@ImportResource`注解相关代码...
//处理`@Bean`注解相关代码...
//处理接口方法相关代码...
//处理父类相关代码...
}
复制代码
通过 getImports 方法,采集相关的 @Import 里面的类。
private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {
Set<SourceClass> imports = new LinkedHashSet<>();
Set<SourceClass> visited = new LinkedHashSet<>();
//递归查询所有注解以及注解的注解是否包含@Import
collectImports(sourceClass, imports, visited);
return imports;
}
private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited)
throws IOException {
//记录是否已经扫描过这个类,如果扫描过就不重复添加,防止重复或者死循环
if (visited.add(sourceClass)) {
for (SourceClass annotation : sourceClass.getAnnotations()) {
String annName = annotation.getMetadata().getClassName();
//对于非@Import注解,递归查找其内部是否包含@Import注解
if (!annName.equals(Import.class.getName())) {
collectImports(annotation, imports, visited);
}
}
//添加@Import注解里面的所有配置类
imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
}
}
复制代码
采集好之后,就可以解析了。
2. 解析@Import注解
解析的方法是: processImports
//在解析时,入栈,解析结束后,出栈,通过检查栈中是否有当前类,判断是否有循环依赖
private final ImportStack importStack = new ImportStack();
//记录所有的ImportBeanDefinitionRegistrar
private final Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> importBeanDefinitionRegistrars = new LinkedHashMap<>();
//解析也是递归方法
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
boolean checkForCircularImports) {
if (importCandidates.isEmpty()) {
return;
}
//通过importStack检查循环依赖
if (checkForCircularImports && isChainedImportOnStack(configClass)) {
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
}
else {
//入栈
this.importStack.push(configClass);
try {
for (SourceClass candidate : importCandidates) {
if (candidate.isAssignable(ImportSelector.class)) {
//处理ImportSelector接口的实现类
Class<?> candidateClass = candidate.loadClass();
//创建这些Selector实例
ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
this.environment, this.resourceLoader, this.registry);
//查看是否有过滤器
Predicate<String> selectorFilter = selector.getExclusionFilter();
if (selectorFilter != null) {
exclusionFilter = exclusionFilter.or(selectorFilter);
}
//如果是DeferredImportSelector,则用deferredImportSelectorHandler处理
if (selector instanceof DeferredImportSelector) {
this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
}
else {
//如果不是DeferredImportSelector,调用selectImports方法获取要加载的类全限定名称,递归调用本方法继续解析
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
}
}
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
// 处理ImportBeanDefinitionRegistrar接口的实现类
Class<?> candidateClass = candidate.loadClass();
//同样的,创建这些ImportBeanDefinitionRegistrar实例
ImportBeanDefinitionRegistrar registrar =
ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
this.environment, this.resourceLoader, this.registry);
//放入importBeanDefinitionRegistrar,用于后面加载
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
}
else {
//处理@Configuration注解类,或者是普通类(直接生成Bean)
//在栈加上这个类
this.importStack.registerImport(
currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
//递归回到doProcessConfigurationClass处理@Configuration注解类 processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
}
}
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to process import candidates for configuration class [" +
configClass.getMetadata().getClassName() + "]", ex);
}
finally {
this.importStack.pop();
}
}
}
复制代码
这样,所有的 @Conditional 类相关的 @Import 注解就加载解析完成了,这是一个大的递归过程。
3. 转换为BeanDefinition注册到容器
org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader 的 loadBeanDefinitionsForConfigurationClass 方法:
private void loadBeanDefinitionsForConfigurationClass(
ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
if (trackedConditionEvaluator.shouldSkip(configClass)) {
String beanName = configClass.getBeanName();
if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
this.registry.removeBeanDefinition(beanName);
}
this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
return;
}
//对Import完成的,加载其Import的BeanDefinition
if (configClass.isImported()) {
registerBeanDefinitionForImportedConfigurationClass(configClass);
}
//加载@Bean注解的方法生成的Bean的Definition
for (BeanMethod beanMethod : configClass.getBeanMethods()) {
loadBeanDefinitionsForBeanMethod(beanMethod);
}
//@ImportResource 注解加载的
loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
//加载ImportBeanDefinitionRegistrar加载的Bean的Definition
loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}
复制代码
通过这里可以看出,为啥之前说 @Bean 注解的 Bean 会优先于 ImportBeanDefinitionRegistrar 返回的 Bean 加载。
如果觉得本文对你有帮助,可以关注一下我公众号,回复关键字【面试】即可得到一份Java核心知识点整理与一份面试大礼包!另有更多技术干货文章以及相关资料共享,大家一起学习进步!