The role and usage of Spring's @Import annotation

@Import annotation

@Import is the main component of Spring based on Java annotation configuration. The @Import annotation provides the function of @Bean annotation, as well as the original Spring function to organize multiple scattered xml files based on the <import> tag in the xml configuration file. Of course, here is to organize multiple scattered @Configuration classes.

The functions of @Import annotation will be explained below.

1. Introduce other @Configuration

Suppose there are the following interfaces and two implementation classes:

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");
    }
}
复制代码

Two @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();
    }
}
复制代码

Create AnnotationConfigApplicationContext through ConfigA and get ServiceInterface to see which implementation is:

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigA.class);
    ServiceInterface bean = ctx.getBean(ServiceInterface.class);
    bean.test();
}
复制代码

The output is: ServiceB. It proves that @Import takes precedence over its own class definition loading.

2. Directly initialize other types of Bean

After Spring 4.2 , @Import can directly specify the entity class and load this class definition into the context. For example, if the @Import of ConfigA in the above code is modified to @Import(ServiceB.class), the Bean of ServiceB will be generated into the container context, and then the main method will be run, and the output will be: ServiceB. Prove that @Import takes precedence over its own Class definition loading.

3. Specify the class that implements ImportSelector (and DefferredServiceImportSelector) for personalized loading

Specify the class that implements ImportSelector, and dynamically load the class through the attributes in AnnotationMetadata. AnnotationMetadata is the attribute of the class where the Import annotation is located (if the class is an annotation class, it extends to the non-annotation class to which this annotation class is applied).

The selectImports method needs to be implemented to return a String array of @Configuation or the fully qualified name of the specific Bean class to be loaded.

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();
    }
}
复制代码

Run the main method again and output: ServiceB. It proves that @Import takes precedence over its own class definition loading. Generally, if dynamic loading of classes is implemented based on AnnotationMetadata parameters in the framework, an additional Enable annotation is generally written for use in conjunction. E.g:

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];
    }
}
复制代码

After that, add the annotation @EnableService(name = "B") in ConfigA

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

Run the main method again and output: ServiceB.

You can also implement the DeferredImportSelector interface, so that the classes returned by selectImports are all loaded last, instead of being loaded first like the @Import annotation. E.g:

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];
    }
}
复制代码

Modify the EnableService annotation:

@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.TYPE)
@Import(DefferredServiceImportSelector.class)
@interface EnableService {
    String name();
}
复制代码

In this way, ConfigA will be loaded prior to the ConfigB returned by DefferredServiceImportSelector, execute the main method, and output: ServiceA

4. Specify the class that implements ImportBeanDefinitionRegistrar for personalized loading

The usage and purpose are similar to ImportSelector, but if we want to redefine the Bean, such as dynamically injecting properties, changing the type and scope of the Bean, etc., we need to specify the class that implements ImportBeanDefinitionRegistrar. E.g:

Define 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);
    }
}
复制代码

Define ServiceImportBeanDefinitionRegistrar to dynamically register ServiceC, modify 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());
    }
}
复制代码

And according to the source code analysis later, you can know that ImportBeanDefinitionRegistrar is loaded after the @Bean annotation, so modify ConfigA to remove the Bean annotated by @ConditionalOnMissingBean, otherwise the ServiceInterface of ConfigA will be generated

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

After running main, output: TestServiceC

@Import related source code analysis

Load and parse the @Import annotation when it is processed by BeanFactoryPostProcessor:

The refresh method of AbstractApplicationContext

-> invokeBeanFactoryPostProcessors(beanFactory);

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

-> registryProcessor.postProcessBeanDefinitionRegistry(registry);

The registryProcessor here, we refer to 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());

	//省略后续清理逻辑
}
复制代码

Among them, the logic of parser.parse(candidates) is mainly implemented by org.springframework.context.annotation.ConfigurationClassParser, and the function is to load @Import annotations and also @Import annotations. The logic of reader.loadBeanDefinitions(configClasses); is mainly implemented by the loadBeanDefinitionsForConfigurationClass method of org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader. The function is to convert the configuration parsed above into BeanDefinition which is the Bean definition.

1. Load @Import annotation

org.springframework.context.annotation.ConfigurationClassParser

The first is the parse method

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`注解相关代码...
    //处理接口方法相关代码...
    //处理父类相关代码...
}
复制代码

Collect related @Import classes through the getImports method.

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"));
	}
}
复制代码

After collecting, it can be analyzed.

2. Parse @Import annotation

The analytical method is: 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();
		}
	}
}
复制代码

In this way, all @Import annotations related to the @Conditional class are loaded and parsed, which is a large recursive process.

3. Convert to BeanDefinition and register to the container

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());
}
复制代码

It can be seen from here, why the bean annotated by @Bean will be loaded in priority to the bean returned by ImportBeanDefinitionRegistrar.

Original link: https://juejin.cn/post/6917771718433964040

If you think this article is helpful to you, you can follow my official account and reply to the keyword [Interview] to get a compilation of Java core knowledge points and an interview gift package! There are more technical dry goods articles and related materials to share, let everyone learn and progress together!

 

Guess you like

Origin blog.csdn.net/weixin_48182198/article/details/112668093
Recommended