前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >每日一面 - Spring 的 @Import 注解的作用与用法

每日一面 - Spring 的 @Import 注解的作用与用法

作者头像
干货满满张哈希
发布2021-04-09 17:25:10
2.7K0
发布2021-04-09 17:25:10
举报
文章被收录于专栏:干货满满张哈希

@Import注解

@ImportSpring基于 Java 注解配置的主要组成部分。@Import注解提供了@Bean注解的功能,同时还有原来Spring基于 xml 配置文件里的标签组织多个分散的xml文件的功能,当然在这里是组织多个分散的@Configuration的类。

下面将分别说明@Import注解的功能。

1. 引入其他的@Configuration

假设有如下接口和两个实现类:

代码语言:javascript
复制
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:

代码语言:javascript
复制
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,看是哪种实现:

代码语言:javascript
复制
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),就会生成ServiceBBean到容器上下文中,之后运行main方法,输出为:ServiceB.证明@Import的优先于本身的的类定义加载.

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

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

需要实现selectImports方法,返回要加载的@Configuation或者具体Bean类的全限定名的String数组。

代码语言:javascript
复制
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注解,配合使用。例如:

代码语言:javascript
复制
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 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")

代码语言:javascript
复制
package com.test;
@EnableService(name = "B")
@Configuration
class ConfigA {
    @Bean
    @ConditionalOnMissingBean
    public ServiceInterface getServiceA() {
        return new ServiceA();
    }
}

再次运行main方法,输出:ServiceB.

还可以实现DeferredImportSelector接口,这样selectImports返回的类就都是最后加载的,而不是像@Import注解那样,先加载。 例如:

代码语言:javascript
复制
package com.test;
class DefferredServiceImportSelector implements DeferredImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        Map 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注解:

代码语言:javascript
复制
@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

代码语言:javascript
复制
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

代码语言:javascript
复制
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 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,否则一定会生成ConfigAServiceInterface

代码语言:javascript
复制
package com.test;
@EnableService(name = "TestServiceC")
@Configuration
class ConfigA {
//    @Bean
//    @ConditionalOnMissingBean
//    public ServiceInterface getServiceA() {
//        return new ServiceA();
//    }
}

之后运行main,输出:TestServiceC

@Import相关源码解析

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

AbstractApplicationContextrefresh方法

-> invokeBeanFactoryPostProcessors(beanFactory);

-> PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());

-> registryProcessor.postProcessBeanDefinitionRegistry(registry);

这里的registryProcessor,我们指ConfigurationClassPostProcessor

ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(registry)

-> processConfigBeanDefinitions(registry):

代码语言:javascript
复制
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 candidates = new LinkedHashSet<>(configCandidates);
	//已经解析的@Configuration类
	Set alreadyParsed = new HashSet<>(configCandidates.size());
	do {
	    //解析
		parser.parse(candidates);
		parser.validate();

		Set 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.ConfigurationClassBeanDefinitionReaderloadBeanDefinitionsForConfigurationClass方法实现,功能是将上面解析的配置转换为BeanDefinition就是Bean定义。

1. 加载@Import注解

org.springframework.context.annotation.ConfigurationClassParser

首先是parse方法

代码语言:javascript
复制
public void parse(Set 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();
}
代码语言:javascript
复制
@Nullable
protected final SourceClass doProcessConfigurationClass(
			ConfigurationClass configClass, SourceClass sourceClass, Predicate filter)
			throws IOException {
	//处理`@Component`注解的MemberClass相关代码...
	//处理`@PropertySource`注解相关代码...
	//处理`@ComponentScan`注解相关代码...
    //处理`@Import`注解:
    processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
    //处理`@ImportResource`注解相关代码...
    //处理`@Bean`注解相关代码...
    //处理接口方法相关代码...
    //处理父类相关代码...
}

通过getImports方法,采集相关的@Import里面的类。

代码语言:javascript
复制
private Set getImports(SourceClass sourceClass) throws IOException {
	Set imports = new LinkedHashSet<>();
	Set visited = new LinkedHashSet<>();
	//递归查询所有注解以及注解的注解是否包含@Import
	collectImports(sourceClass, imports, visited);
	return imports;
}
private void collectImports(SourceClass sourceClass, Set imports, Set 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

代码语言:javascript
复制
//在解析时,入栈,解析结束后,出栈,通过检查栈中是否有当前类,判断是否有循环依赖
private final ImportStack importStack = new ImportStack();

//记录所有的ImportBeanDefinitionRegistrar
private final Map importBeanDefinitionRegistrars = new LinkedHashMap<>();

//解析也是递归方法
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
			Collection importCandidates, Predicate 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 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 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.ConfigurationClassBeanDefinitionReaderloadBeanDefinitionsForConfigurationClass方法:

代码语言:javascript
复制
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加载。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021/01/15 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • @Import注解
    • 1. 引入其他的@Configuration
      • 2. 直接初始化其他类的Bean
        • 3. 指定实现ImportSelector(以及DefferredServiceImportSelector)的类,用于个性化加载
          • 4. 指定实现ImportBeanDefinitionRegistrar的类,用于个性化加载
          • @Import相关源码解析
            • 1. 加载@Import注解
              • 2. 解析@Import注解
                • 3. 转换为BeanDefinition注册到容器
                相关产品与服务
                容器服务
                腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档