@Import注解的作用
Spring 3.0之前,我们的Bean可以通过xml配置文件与扫描特定包下面的类来将类注入到Spring IOC容器内。Spring 3.0之后提供了JavaConfig的方式,也就是将IOC容器里Bean的元信息以java代码的方式进行描述。我们可以通过@Configuration与@Bean这两个注解配合使用来将原来配置在xml文件里的bean通过java代码的方式进行描述。@Import注解提供了@Bean注解的功能,同时还有xml配置文件里标签组织多个分散的xml文件的功能,当然在这里是组织多个分散的@Configuration。
其源码:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
/**
* {@link Configuration}, {@link ImportSelector}, {@link ImportBeanDefinitionRegistrar}
* or regular component classes to import.
*/
Class<?>[] value();
}
通过分析注释我们可以看出,@Import注解有四个作用:
分析类注释得出结论:
- 声明一个bean
- 导入@Configuration注解的配置类
- 导入ImportSelector的实现类
- 导入ImportBeanDefinitionRegistrar的实现类
声明Bean和导入@Configuration
导入普通的Java类并将其声明为一个Bean注入到Spring容器中的功能在Sprin4.2之后才可以使用,在 spring 4.2 以前,该注解,只能导入配置类,其功能与 标签类似。
@Configuration
@Import(value = FirstBean.class)
public class MyConfig {
}
class FirstBean {
}
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
String[] beanNames = context.getBeanDefinitionNames();
for(int i=0;i<beanNames.length;i++){
System.out.println("bean名称为:"+beanNames[i]);
}
}
class FirstBean {}
我们分别尝试用@Import和不用@Import引入FirstBean,控制台得到输出:
bean名称为:org.springframework.context.annotation.internalConfigurationAnnotationProcessor
bean名称为:org.springframework.context.annotation.internalAutowiredAnnotationProcessor
bean名称为:org.springframework.context.annotation.internalCommonAnnotationProcessor
bean名称为:org.springframework.context.event.internalEventListenerProcessor
bean名称为:org.springframework.context.event.internalEventListenerFactory
bean名称为:myConfig
bean名称为:com.example.demo.importTest.FirstBean
就是多出了FirstBean这个Bean,也证明了@Import这个注解确实可以注入Bean。
导入ImportSelector的实现类
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.
*/
String[] selectImports(AnnotationMetadata importingClassMetadata);
}
ImportSelector接口注释说了几点内容:
- 这个接口是用来搜集需要导入配置类的
- 如果该接口的实现类同时实现EnvironmentAware, BeanFactoryAware ,BeanClassLoaderAware或者ResourceLoaderAware,那么在调用其selectImports方法之前先调用上述接口中对应的方法
- 如果需要在所有的@Configuration处理完在导入时可以实现DeferredImportSelector接口
为了验证我们上面分析的,我们看一下SpringBoot中对@Import的使用:@EnableAutoConfiguration这个注解是开启自动配置的注解,该注解上使用了@Import(AutoConfigurationImportSelector.class),我们简单看一下AutoConfigurationImportSelector这个类:
public class AutoConfigurationImportSelector
implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware,
BeanFactoryAware, EnvironmentAware, Ordered {
}
AutoConfigurationImportSelector正是实现了上述的几个接口。
导入ImportBeanDefinitionRegistrar的实现类
ImportBeanDefinitionRegistrar是spring对外提供动态注册beanDefinition的接口,spring内部大部分套路也是用该接口进行动态注册beanDefinition的。关于其的用法,SpringBoot的@AutoConfigurationPackage注解就是一个典型例子:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
}
/ **
* 我们可以看到Registrar正是实现了ImportBeanDefinitionRegistrar接口
*/
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
register(registry, new PackageImport(metadata).getPackageName());
}
@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImport(metadata));
}
}
我们再看register方法的实现:
// 我在这个方法内打了断点,发现packageNames正是被@SpringBootApplication注解修饰的启动类所在的包
// registry是Spring的Bean管理器
public static void register(BeanDefinitionRegistry registry, String... packageNames) {
// 判断是否有org.springframework.boot.autoconfigure.AutoConfigurationPackages这个Bean来决定是新增一个bean还是对现有bean进行更新
if (registry.containsBeanDefinition(BEAN)) {
BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
ConstructorArgumentValues constructorArguments = beanDefinition
.getConstructorArgumentValues();
constructorArguments.addIndexedArgumentValue(0,
addBasePackages(constructorArguments, packageNames));
}
else {
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(BasePackages.class);
beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0,
packageNames);
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
// 注册一个新的bean
registry.registerBeanDefinition(BEAN, beanDefinition);
}
}
通过上面的例子,给我们提供了一个向Spring容器中注入Bean的思路。