@Import 注解
@Import
注解是Spring基于Java注解配置的主要组成部分,它通过快速导入的方式实现把实例加入到Spring的IOC容器中。@Import
只能用在类上,通常用在带有 @Configuration
注解的配置类上,但也可以用在普通类上。类似于 @Bean
注解,@Import
提供了一种将bean定义导入到Spring容器中的方式,但它更侧重于批量导入或基于条件的动态导入。
下面分别对 @Import
注解四种使用方式测试验证
直接导入其他类Bean
@Import
可以直接指定实体类,加载这个类定义到 context
中。
创建一个Student类
创建一个配置类使用 @Import
导入Student类
从spring容器中获取 Student
类执行方法
可以看到 Student
类的确被创建到了IOC容器中
特别注意一下,使用@Import注解导入类Bean时,优先级会高于作用的类本身,比如前面例子中如果MyConfig配置类中也声明Studeng类的Bean创建,但是优先级低于@Import。我们可以声明一个接口,使用两个实现,然后看控制台打印结果
Persion接口
Student类
Teacher类
Config类,这里getPersion方法添加 @ConditionalOnMissingBean
注解是因为防止创建两个Persion实例,不然后面获取时候会报错,提示有两个匹配Bean,同时也能证明当我们Student类Bean创建后 @ConditionalOnMissingBean
生效从而不会创建Teacher类的Bean,证明 @Import
注解优先级更高
控制台打印
可以看到输出确实是Student类的实现,符合预期
ImportSelector方式
实现 ImportSelector
接口,在 selectImports
方法中返回要导入的类的全类名字符串数组,这种方式允许基于注解元数据进行动态的加载类。看测试
1.实现 ImportSelector
接口
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
String className = "com.tao.demo.Teacher";
return new String[]{className};
}
}
2.配置类上使用 @Import
导入 MyImportSelector
类
@Configuration
@Import(MyImportSelector.class)
public class MyConfig {
@Bean
@ConditionalOnMissingBean
public Persion getPersion(){
return new Student();
}
}
为了做区分和验证我们在 selectImports
返回的是 Teacher
类全路径名,在 Myconfig
类中创建 Student
类的Bean,最后执行main方法查看输出结果
证明 Teacher
类被加载,并且 @Import
注解的优先级高于配置类本身的类加载
配合EnableXXX注解使用
框架中如果要使用 @Import
注解基于 AnnotationMetadata
的参数实现动态加载类,一般会写一个额外的 EnableXX
注解,配合使用。比如SpringBoot框架中的注解:
//开启自动配置的注解
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
//开启缓存的注解
public @interface EnableCaching {
boolean proxyTargetClass() default false;
AdviceMode mode() default AdviceMode.PROXY;
int order() default Integer.MAX_VALUE;
}
//启用异步支持
@Import({AsyncConfigurationSelector.class})
public @interface EnableAsync {
Class<? extends Annotation> annotation() default Annotation.class;
boolean proxyTargetClass() default false;
AdviceMode mode() default AdviceMode.PROXY;
int order() default Integer.MAX_VALUE;
}
现在我们自己写一个 Enable
注解测试动态的加载类
1.创建EnableDemo注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(MyImportSelector.class)
public @interface EnableDemo {
String name();
}
2.修改配置类上的注解,使用 @EnableDemo
替换 @Import
@Configuration
//@Import(MyImportSelector.class)
@EnableDemo(name = "teacher")
public class MyConfig {
@Bean
@ConditionalOnMissingBean
public Persion getPersion(){
return new Student();
}
}
3.修改 MyImportSelector
类 selectImports
方法实现
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
//获取@EnableDemo注解字段值
Map<String, Object> map = importingClassMetadata.getAnnotationAttributes(EnableDemo.class.getName(), true);
String name = (String) map.get("name");
System.out.println(name);
//判断值是否符合预期
if ("teacher".equals(name)){
return new String[]{"com.tao.demo.Teacher"};
}else {
System.out.println("不符合预期");
return new String[0];
}
}
}
这里的
importingClassMetadata
针对的是使用@EnableService
的非注解类,因为AnnotationMetadata
是Import
注解所在的类属性,如果所在类是注解类,则延伸至应用这个注解类的非注解类为止
4.执行查看控制台输出
修改一下 @EnableDemo
注解name值,让它加载不上
最后实现了使用 @EnableDemo
注解开启Teacher类的自动配置,并且是否配置由我们指定的name参数决定,当入参不符合要求时候,没有去自动配置我们的Teacher类,那么 MyConfig
类中的条件满足,创建了Student类的Bean
实现 DeferredImportSelector 接口
还可以实现 DeferredImportSelector
接口,这样 selectImports
返回的类就都是最后加载的,区别于 @Import
注解优先加载
1.创建 MyDeferredImportSelector
类实现 DeferredImportSelector
接口
public class MyDeferredImportSelector implements DeferredImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"com.tao.demo.Teacher"};
}
}
2.修改 MyConfig
配置类
@Configuration
@Import(MyDeferredImportSelector.class)
//@EnableDemo(name = "doctor")
public class MyConfig {
//注意这里指定了Bean的名称,方便我们后面根据名称获取Bean,因为我们加载了两个Person对象,Teacher只是延迟加 载,如果根据Person.class获取又会报错说有两个Bean
@Bean(name = "student")
@ConditionalOnMissingBean
public Persion getPersion(){
return new Student();
}
}
3.修改启动类,根据Bean名称获取Bean对象
public static void main(String[] args) {
ConfigurableApplicationContext applicationContext = SpringApplication.run(StudyDemoApplication.class, args);
//Persion persion = applicationContext.getBean(Persion.class);
Persion persion = (Persion) applicationContext.getBean("student");
persion.say();
}
根据我们前面的学习,如果是 ImportSelector
接口那么控制台输出应该是Teacher类Bean的方法,现在使用的 DeferredImportSelector
接口那么应该延迟加载,所以 Student
类Bean对象会被成功加载,来看控制台输出
可以看到 Student
类确实被加载了
ImportBeanDefinitionRegistrar 方式
与 ImportSelector
用法与用途类似,但是如果我们想重定义 Bean
,例如动态注入属性,改变 Bean
的类型和 Scope
等等,就需要通过指定实现 ImportBeanDefinitionRegistrar
的类实现
1.创建Doctor类,实现 Persion
接口
public class Doctor implements Persion{
private String name;
private String age;
public Doctor(String name,String age){
this.name = name;
this.age = age;
}
@Override
public void say() {
System.out.println("我是医生");
}
@Override
public String toString() {
return "Doctor{" +
"name='" + name + '\'' +
", age='" + age + '\'' +
'}';
}
}
2.创建 MyImportBeanDefinitionRegistrar
实现 ImportBeanDefinitionRegistrar
接口,重写 registerBeanDefinitions
方法
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
ImportBeanDefinitionRegistrar.super.registerBeanDefinitions(importingClassMetadata, registry);
Map<String, Object> map = importingClassMetadata.getAnnotationAttributes(EnableDemo.class.getName(), true);
String name = (String) map.get("name");
String age = (String) map.get("value");
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.rootBeanDefinition(Doctor.class)
//增加构造参数
.addConstructorArgValue(name).addConstructorArgValue(age);
//注册Bean
registry.registerBeanDefinition("doctor", beanDefinitionBuilder.getBeanDefinition());
}
}
3.修改 EnableDemo
注解,添加属性
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(MyImportBeanDefinitionRegistrar.class)
public @interface EnableDemo {
String name();
String value();
}
4.修改 MyConfig
配置类
@Configuration
//@Import(MyDeferredImportSelector.class)
@EnableDemo(name = "doctor", value = "32")
public class MyConfig {
@Bean(name = "student")
@ConditionalOnMissingBean
public Persion getPersion(){
return new Student();
}
}
5.修改启动类,获取Doctor类Bean对象
public static void main(String[] args) {
ConfigurableApplicationContext applicationContext = SpringApplication.run(StudyDemoApplication.class, args);
//Persion persion = applicationContext.getBean(Persion.class);
Persion student = (Persion) applicationContext.getBean("student");
student.say();
System.out.println("=========================================");
Persion doctor = (Persion) applicationContext.getBean("doctor");
doctor.say();
System.out.println(doctor);
}
6.执行看控制台输出
输出结果证明我们在 MyImportBeanDefinitionRegistrar
类 MyImportBeanDefinitionRegistrar
方法中做的修改生效,创建了 Doctor
类的 BeanDefinition
并且给 Doctor
构造参数赋值,值来自于 @EnableDemo
注解中的值。并且 Student
类的成功创建可以验证 ImportBeanDefinitionRegistrar
方式 类的创建优先级要低于 配置类中的@Bean操作,不然 @ConditionalOnMissingBean
注解不会让 Student
类实例化的。
以上就是我们对 @Import
注解的一个学习与运用,经过前面的代码测试,我们对 @Import
注解的几种使用方式都有了一定程度的掌握。但是对 @Import
注解本身如何实现还缺乏了解,后续会补充学习
@Import 源码解析
............... 还没写