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