Spring全解系列

本文基于 Spring 5.2.x

@Import注解

@ImportSpring基于 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}

两个@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}

输出为:ServiceB.证明@Import的优先于本身的的类定义加载。

2. 直接初始化其他类的Bean

Spring 4.2之后,@Import可以直接指定实体类,加载这个类定义到context中。 例如把上面代码中的ConfigA@Import修改为@Import(ServiceB.class),就会生成ServiceBBean到容器上下文中,之后运行main方法,输出为:ServiceB.证明@Import的优先于本身的的类定义加载.

3. 指定实现ImportSelector(以及DefferredServiceImportSelector)的类,用于个性化加载

指定实现ImportSelector的类,通过AnnotationMetadata里面的属性,动态加载类。AnnotationMetadataImport注解所在的类属性(如果所在类是注解类,则延伸至应用这个注解类的非注解类为止)。

需要实现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}

再次运行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}

之后,在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}

修改EnableService注解:

1@Retention(RetentionPolicy.RUNTIME) 2@Documented 3@Target(ElementType.TYPE) 4@Import(DefferredServiceImportSelector.class) 5@interface EnableService { 6 String name(); 7}

这样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}

定义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,否则一定会生成ConfigAServiceInterface

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}

之后运行main,输出:TestServiceC

@Import相关源码解析

加载解析@Import注解位于BeanFactoryPostProcessor处理的时候:

AbstractApplicationContextrefresh方法

-> 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}

其中parser.parse(candidates)的逻辑主要由org.springframework.context.annotation.ConfigurationClassParser实现,功能是加载@Import注解还有即系@Import注解。reader.loadBeanDefinitions(configClasses);的逻辑主要由org.springframework.context.annotation.ConfigurationClassBeanDefinitionReaderloadBeanDefinitionsForConfigurationClass方法实现,功能是将上面解析的配置转换为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}

通过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}

采集好之后,就可以解析了。

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}

这样,所有的@Conditional类相关的@Import注解就加载解析完成了,这是一个大的递归过程。

3. 转换为BeanDefinition注册到容器

org.springframework.context.annotation.ConfigurationClassBeanDefinitionReaderloadBeanDefinitionsForConfigurationClass方法:

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}

通过这里可以看出,为啥之前说@Bean注解的Bean会优先于ImportBeanDefinitionRegistrar返回的Bean加载。

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

spring注解

随着越来越多地使用Springboot敏捷开发,更多地使用注解配置Spring,而不是Spring的applicationContext.xml文件。Configuration注解:Spring解析为配置类,相当于spring配置文件Bean注解:容器注册Bean组件,默认id为方法名@Configurat