Import
https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans
源码注释
Indicates one or more component classes to import — typically @Configuration classes.
标记要导入的一个或者多个组件类,尤其是被 @Configuration 标记的类。
Provides functionality equivalent to the element in Spring XML. Allows for importing @Configuration classes, ImportSelector and ImportBeanDefinitionRegistrar implementations, as well as regular component classes (as of 4.2; analogous to AnnotationConfigApplicationContext.register).
和 Spring XML中的 import 标签相同的功能。允许导入@Configuration 类,ImportSelector 和ImportBeanDefinitionRegistrar 的实现类,以及常规组件类()
AnnotationConfigApplicationContext.register源码
@Override
public void register(Class<?>... componentClasses) {
Assert.notEmpty(componentClasses, "At least one component class must be specified");
StartupStep registerComponentClass = this.getApplicationStartup().start("spring.context.component-classes.register")
.tag("classes", () -> Arrays.toString(componentClasses));
this.reader.register(componentClasses);
registerComponentClass.end();
}
If XML or other non-@Configuration bean definition resources need to be imported, use the @ImportResource annotation instead.
如果XML或者其他非@Configuration bean definition 资源需要导入,使用@ImportResource注解替代。
源码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
Class<?>[] value();
}
只有一个参数接收Class数组。
常见用法
DataSourceAutoConfiguration
@Import({ DataSourcePoolMetadataProvidersConfiguration.class,
DataSourceInitializationConfiguration.class })
@Import(EmbeddedDataSourceConfiguration.class)
@Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class,
DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.OracleUcp.class,
DataSourceConfiguration.Generic.class, DataSourceJmxConfiguration.class })
问题一:Import数组的顺序性
在DefaultListableBeanFactory类中preInstantiateSingletons方法中的beanNames列表中
for (String beanName : beanNames) {}
由于源码中使用for循环解析,所以import导入的类是按照顺序依次加载的。
问题二 Import一个普通类
在注释上基本没有提到普通类,下面我们来测试一下。
public class Car {
String name = "123";
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
测试方法
@Test
void contextLoads() {
System.out.println(car.getName());
}
总结:Import可以导入一个普通类进入容器,并且可以使用Autowired注解来获取到实例。
ImportSelector
Import 引入、导入
Selector 选择器
源码
public interface ImportSelector {
//选择导入
String[] selectImports(AnnotationMetadata importingClassMetadata);
//获取过滤器
@Nullable
default Predicate<String> getExclusionFilter() {
return null;
}
}
根据源码分析,ImportSelector 可以获取注解中的一些值从而选择导入的类。
实现类
实例
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ImportSelectorImpl.class)
public @interface EnableImportSelector {
int value();
}
public class ImportSelectorImpl implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
Map<String, Object> value = importingClassMetadata.getAnnotationAttributes(EnableImportSelector.class.getName());
int type = (int) value.get("value");
String[] strs = new String[1];
if (type == 1) {
System.out.println("type == 1");
strs[0] = A.class.getName();
} else {
System.out.println("type == 其他");
strs[0] = B.class.getName();
}
return strs;
}
}
public class A {
public A() {
System.out.println("A is init ...");
}
}
public class B {
public B() {
System.out.println("B is init ...");
}
}
@EnableImportSelector(2)
type == 其他
B is init ...
将 @EnableImportSelector(2) 改为 1
type == 1
A is init ...
总结:通过ImportSelector中的selectImports方法来返回一个类名数组,然后容器会加载这些类。
ImportBeanDefinitionRegistrar
这是一个常见接口,经常出现在Enable注解中,通过这个接口实现注册BeanDefinitions。
实现类
可以看到在源码中有广泛应用,这里出现三份是因为我本地有三个不同版本。
源码
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry,BeanNameGenerator importBeanNameGenerator) {
registerBeanDefinitions(importingClassMetadata, registry);
}
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) { }
根据源码分析,ImportBeanDefinitionRegistrar 是根据注解信息来注册BeanDefinitions。我们需要继承接口然后重写第二个方法,第二个方法过滤掉了第三个参数BeanNameGenerator ,运行后它是这样一个类型。
springframework.context.annotation.FullyQualifiedAnnotationBeanNameGenerator@537b32ef
接口中有一个方法generateBeanName根据注释 Generate a bean name for the given bean definition.为给定的bean定义生成一个bean名称。
String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry);
我们进入FeignClientsRegistrar类找到registerBeanDefinitions发现并没有用到第三次参数,这个先忽略,后面用到再回来补充。
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
registerDefaultConfiguration(metadata, registry);
registerFeignClients(metadata, registry);
}
下面我们来扩展一下registerFeignClients
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
BeanDefinition b1 = new GenericBeanDefinition();
registry.registerBeanDefinition("beanName", b1);
BeanDefinition b2 = registry.getBeanDefinition("beanName");
logger.info("registerFeignClients ... ");
}
调用过程
private void loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) {
registrars.forEach((registrar, metadata) ->
registrar.registerBeanDefinitions(metadata, this.registry, this.importBeanNameGenerator));
}
loadBeanDefinitionsForConfigurationClass
Read a particular ConfigurationClass, registering bean definitions for the class itself and all of its Bean methods.
loadBeanDefinitionsForConfigurationClass(从配置类加载BeanDefinitions):157行,
//调用loadBeanDefinitionsFromRegistrars
loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
public Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> getImportBeanDefinitionRegistrars() {
return this.importBeanDefinitionRegistrars;
}
private final Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> importBeanDefinitionRegistrars = new LinkedHashMap<>();
loadBeanDefinitions
Read configurationModel, registering bean definitions with the registry based on its contents.
public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
//循环调用loadBeanDefinitionsForConfigurationClass
for (ConfigurationClass configClass : configurationModel) {
loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
}
}
参数是ConfigurationClass类型的set,
processConfigBeanDefinitions
Build and validate a configuration model based on the registry of Configuration classes.
//loadBeanDefinitions
this.reader.loadBeanDefinitions(configClasses);
//configClasses
Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
BeanDefinition
案例
官方示例
@Configuration
public class ConfigA {
@Bean
public A a() {
return new A();
}
}
@Configuration
@Import(ConfigA.class)
public class ConfigB {
@Bean
public B b() {
return new B();
}
}
只需要提供ConfigB,通过@Import(ConfigA.class)同样将A加入容器。
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);
// now both beans A and B will be available...
A a = ctx.getBean(A.class);
B b = ctx.getBean(B.class);
}
As of Spring Framework 4.2, @Import also supports references to regular component classes, analogous to the AnnotationConfigApplicationContext.register method. This is particularly useful if you want to avoid component scanning, by using a few configuration classes as entry points to explicitly define all your components.
DeferredImportSelector
public interface DeferredImportSelector extends ImportSelector