文章目录
1. 概述
@Import 是 Spring 基于 Java 注解配置的主要组成部分,@Import 注解提供了类似 @Bean 注解的功能。本文将介绍@Import注解的使用,并详细分析该注解的实现原理,同时会学习到Spring当中ImportSelector接口的和ImportBeanDefinitionRegistrar接口的使用和实现原理。
2. 初识@Imprt注解
下图是Spring当中Import注解的源码,可以看出Import可以配合 Configuration, ImportSelector, ImportBeanDefinitionRegistrar 来使用,也可以用于一个普通类的导入。
@Target表明了它能作用的范围,可以作用于类、接口、枚举类
属性仅有一个value,表示的是一个类对象数组。例如value = {xx.class,yy.class},表示要将xx和yy交给Spring容器管理。
那么这些类又有什么特点呢?
大体可以分成三大类
- 普通类
- 实现了ImportSelector接口的类
- 实现了ImportBeanDefinitionRegistrar接口的类
下面来看看这3种类配合上@Import注解的使用区别
3. @Import注解的使用
3.1 普通类注入Spring容器的方式
首先准备一个普通的实体类
package hdu.gongsenlin;
/**
* @author gongsenlin
* @version 1.0
* @date 2021-01-25 16:26
* @Description:
*/
public class NorMal {
private String msg;
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
测试代码,使用基于java配置类的方式加载Spring的应用上下文。
AppConfig如下,配置类比较简单,只是提供了扫描包的路径
import org.springframework.context.annotation.ComponentScan;
/**
* @author gongsenlin
* @version 1.0
* @date 2021-01-25 16:25
*/
@ComponentScan("hdu.gongsenlin")
public class AppConfig {
}
重头戏来了,Import标签作用在下面这个类上
package hdu.gongsenlin;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/**
* @author gongsenlin
* @version 1.0
* @date 2021-01-25 18:15
* @Description:
*/
@Configuration
@Import(NorMal.class)
public class MyConfigure {
}
测试代码如下
import hdu.gongsenlin.NorMal;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* @author gongsenlin
* @version 1.0
* @date 2021-01-25 16:28
* @Description:
*/
public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
ac.start();
NorMal normal = ac.getBean(NorMal.class);
System.out.println(normal != null);
}
}
运行的结果是输出true,表示Normal对象可以通过Spring容器获取,也就证明了Import注解起作用了。
3.2 实现了ImportSelector接口的类注入Spring容器的方式
先来看看ImportSelector接口的定义,其中有两个方法
-
String[] selectImports(AnnotationMetadata importingClassMetadata)
返回一个包含了类全限定名的数组,这些类会注入到Spring容器当中
-
Predicate< String > getExclusionFilter()
返回一个谓词接口,该方法制定了一个对类全限定名的排除规则来过滤一些候选的导入类,默认不排除过滤。该接口可以不实现。
public interface ImportSelector {
/**
* Select and return the names of which class(es) should be imported based on
* the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
* @return the class names, or an empty array if none
*/
String[] selectImports(AnnotationMetadata importingClassMetadata);
/**
* Return a predicate for excluding classes from the import candidates, to be
* transitively applied to all classes found through this selector's imports.
* <p>If this predicate returns {@code true} for a given fully-qualified
* class name, said class will not be considered as an imported configuration
* class, bypassing class file loading as well as metadata introspection.
* @return the filter predicate for fully-qualified candidate class names
* of transitively imported configuration classes, or {@code null} if none
* @since 5.2.4
*/
@Nullable
default Predicate<String> getExclusionFilter() {
return null;
}
}
下面来编写自己的ImportSelector接口的实现类
package hdu.gongsenlin;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
import java.util.function.Predicate;
/**
* @author gongsenlin
* @version 1.0
* @date 2021-01-27 19:17
* @Description:
*/
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{
"hdu.gongsenlin.Normal"};
}
}
修改上面的MyConfigure配置类,仅仅将Import注解里的属性改一下
@Configuration
@Import(MyImportSelector.class)
public class MyConfigure {
}
测试代码如下,没发生变化。
import hdu.gongsenlin.NorMal;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* @author gongsenlin
* @version 1.0
* @date 2021-01-25 16:28
* @Description:
*/
public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
ac.start();
NorMal normal = ac.getBean(NorMal.class);
System.out.println(normal != null);
}
}
控制台的输出结果:
如果实现了ImportSelector接口的getExclusionFilter()方法,那么执行的结果会有什么变化呢?
MyImportSelector改为如下,表示过滤掉类全限定名为"hdu.gongsenlin.Normal"的类,该类将不会被注入到Spring容器当中。当然这里可以使用通配符的匹配方式,读者可以自己去尝试一下。
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{
"hdu.gongsenlin.Normal"};
}
@Override
public Predicate<String> getExclusionFilter() {
Predicate<String> predicate = new Predicate<String>() {
@Override
public boolean test(String s) {
if (s.matches("hdu.gongsenlin.Normal")) {
return true;
}
return false;
}
};
return predicate;
}
}
此时再次执行测试代码,控制台输出如下:
找不到这个类了,说明该类被过滤掉了,没有被注入到Spring容器当中。
3.3 实现了ImportBeanDefinitionRegistrar接口的类注入Spring容器的方式
该接口的目的是实现类的动态注入,同样的,先来看看ImportBeanDefinitionRegistrar接口的定义。
一共定义了两个同名方法,都是用于将类的BeanDefinition注入。
唯一的区别就是,2个参数的方法,只能手动的输入beanName,而3个参数的方法,可以利用BeanNameGenerator根据beanDefinition自动生成beanName
public interface ImportBeanDefinitionRegistrar {
/**
* Register bean definitions as necessary based on the given annotation metadata of
* the importing {@code @Configuration} class.
* <p>Note that {@link BeanDefinitionRegistryPostProcessor} types may <em>not</em> be
* registered here, due to lifecycle constraints related to {@code @Configuration}
* class processing.
* <p>The default implementation delegates to
* {@link #registerBeanDefinitions(AnnotationMetadata, BeanDefinitionRegistry)}.
* @param importingClassMetadata annotation metadata of the importing class
* @param registry current bean definition registry
* @param importBeanNameGenerator the bean name generator strategy for imported beans:
* {@link ConfigurationClassPostProcessor#IMPORT_BEAN_NAME_GENERATOR} by default, or a
* user-provided one if {@link ConfigurationClassPostProcessor#setBeanNameGenerator}
* has been set. In the latter case, the passed-in strategy will be the same used for
* component scanning in the containing application context (otherwise, the default
* component-scan naming strategy is {@link AnnotationBeanNameGenerator#INSTANCE}).
* @since 5.2
* @see ConfigurationClassPostProcessor#IMPORT_BEAN_NAME_GENERATOR
* @see ConfigurationClassPostProcessor#setBeanNameGenerator
*/
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
BeanNameGenerator importBeanNameGenerator) {
registerBeanDefinitions(importingClassMetadata, registry);
}
/**
* Register bean definitions as necessary based on the given annotation metadata of
* the importing {@code @Configuration} class.
* <p>Note that {@link BeanDefinitionRegistryPostProcessor} types may <em>not</em> be
* registered here, due to lifecycle constraints related to {@code @Configuration}
* class processing.
* <p>The default implementation is empty.
* @param importingClassMetadata annotation metadata of the importing class
* @param registry current bean definition registry
*/
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
}
}
下面来编写ImportBeanDefinitionRegistrar的实现类
package hdu.gongsenlin;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
/**
* @author gongsenlin
* @version 1.0
* @date 2021-01-25 16:34
* @Description:
*/
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(NorMal.class);
AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
registry.registerBeanDefinition("normal", beanDefinition);
}
//或者 使用如下的方法也可以,自动生成beanName
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(NorMal.class);
AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
String beanName = importBeanNameGenerator.generateBeanName(beanDefinition, registry);
registry.registerBeanDefinition(beanName, beanDefinition);
}
}
测试代码不变,执行结果为true,这里就不贴图了。
4. 源码分析
通过上面的例子,@Import注解的三种使用方式相信读者已经掌握了。接下来就到了分析源码,看看Spring中是何时解析@Import注解,又是如何将3种类型的类注入到Spring容器当中的。
4.1 解析@Import注解的时机
在之前的一篇博客当中,详细的介绍过invokeBeanFactoryPostProcessors方法的执行逻辑,该方法是Spring容器启动的时候,会触发的一个函数。不清楚的读者不妨看看这篇博客《Spring IOC—invokeBeanFactoryPostProcessors》。在这里Spring当中内置的扫描器会执行它的扫描处理逻辑,而对于@Import的解析就发生在其中。下面来看看源码:
代码定位到ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry
调用processConfigBeanDefinitions,看名字就知道是处理配置类,也就是加了@Configuration的类,除此之外,Appconfig也属于配置类。
processConfigBeanDefinitions代码如下:
这一部分是遍历当前容器中已经存在的BeanDefinition,将配置类加入到configCandidates集合当中。如果该集合为空,会直接返回。本文中测试代码的Debug的结果显示,此处有一个配置类appConfig。疑问来了不是还有一个MyConfigure配置类吗?因为此时Spring内置的扫描器还没有执行工作,所以此时容器中还不存在MyConfigure这个配置类,此时只有appConfig一个配置类。
接着继续看下面的代码,根据@Order注解排序,一系列的检测,不重要,暂时跳过。
接下来的代码就比较重要了,初始化一个ConfigurationClassParser,调用parse方法用于解析配置类。
parse方法如下,遍历配置类候选集合,此时只有一个appConfig,它属于AnnotatedBeanDefinition类型,通过第一个if,执行parse(AnnotationMetadata metadata, String beanName)。
parse(AnnotationMetadata metadata, String beanName)代码如下:
将元数据信息和beanName封装成ConfigurationClass对象,调用processConfigurationClass方法:
具体的处理逻辑由doProcessConfigurationClass实现,红色框框内执行扫描,此时只有Spring内置的扫描器,扫描结束后,会得到扫描后的BeanDefinitionHolder集合。
遍历BeanDefinitionHolder集合,如果存在加了注解@Configuration的类,则会递归的调用parse。回到刚才分析过的parse接着往下执行。
之后还是会进入到doProcessConfigurationClass方法,此时解析的是MyConfigure。终于到了本节要找的地方了,@Import注解就是在这里执行的。
总结:@Import注解执行的时机,解析配置类的时候,由ConfigurationClassParser当中的processImports来处理。
4.2 3种不同类型的类如何通过@Import注解注入到Spring容器当中源码解析
接着上面的分析,看看processImports是如何实现的
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
boolean checkForCircularImports) {
if (importCandidates.isEmpty()) {
//准备注入的候选类集合为空 直接返回
return;
}
if (checkForCircularImports && isChainedImportOnStack(configClass)) {
//循环注入的检查
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
}
else {
this.importStack.push(configClass);
try {
for (SourceClass candidate : importCandidates) {
//遍历注入的候选集合
/**
* 如果是实现了ImportSelector接口的类
*/
if (candidate.isAssignable(ImportSelector.class)) {
// Candidate class is an ImportSelector -> delegate to it to determine imports
Class<?> candidateClass = candidate.loadClass();
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);//过滤注入的类
}
if (selector instanceof DeferredImportSelector) {
//todo 延迟注入 这里还没有研究 有空了看看
this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
}
else {
//调用selector当中的selectImports方法,得到要注入的类的全限定名
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
// 获得元类信息
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
// 递归的处理注入的类
processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
}
}
/**
* 如果是ImportBeanDefinitionRegistrar 则configClass.addImportBeanDefinitionRegistrar 提前放到一个map当中
*/
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
// Candidate class is an ImportBeanDefinitionRegistrar ->
// delegate to it to register additional bean definitions
Class<?> candidateClass = candidate.loadClass();
ImportBeanDefinitionRegistrar registrar =
ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
this.environment, this.resourceLoader, this.registry);//实例化
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());//放到一个map中
}
/**
* 如果是普通类
*/
else {
// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
// process it as an @Configuration class
this.importStack.registerImport(
currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
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();
}
}
}
- 判断注入的候选集合是否为空,空的话直接返回
- 循环注入的检查
- 遍历注入的候选集合
- 3种类型的lei执行不同的逻辑
- 实现了ImportSelector接口的类,调用getExclusionFilter()方法,如果不为空,那么就进行过滤,过滤后调用selectImports方法(),得到要注入的类的全限定名。根据类全限定名,得到类元信息。然后递归的调用processImports方法
- 实现了ImportBeanDefinitionRegistrar接口的类,会实例化这个类,放入集合importBeanDefinitionRegistrars当中。
- 普通类型的类(上面两个都不满足),那么就把它当作是配置类来处理,调用processConfigurationClass方法,最终会放入到configurationClasses这个集合当中。
- 经过一系列的递归调用,实现了ImportBeanDefinitionRegistrar接口的类,会放入到importBeanDefinitionRegistrars集合当中,其余的类都放入到configurationClasses集合当中。
之后就会回到processConfigBeanDefinitions方法,也就是执行完了ConfigurationClassParser的parse方法
此时会执行loadBeanDefinitions将configurationClasses集合当中类加载的Spring容器当中,并且从 importBeanDefinitionRegistrars 缓存当中拿到所有的ImportBeanDefinitionRegistrar并执行registerBeanDefinitions方法。
5. 后续
本文遗留了两个问题
- DeferredImportSelector,延迟注入是什么意思,如何实现的?
- 循环注入是如何产生的又是如何避免的?
这两个问题我也还不清楚,读者可以去研究一下,私下一起讨论。