@Import注解的使用
- 在springboot的源码中大量使用到了@Import注解,所以我们有必要了解一下该注解的作用.
- 使用位置:@Import只能用在类上
- 属性: Class<?>[] value(); 类的字节码数组
- 作用:@Import通过快速导入的方式实现把实例加入spring的IOC容器中
一 @Import引入一个普通类
- 需要创建对象的类:Student类
- Config1类,通过Import导入Student.class ,Student是一个普通类
@Configuration
@Import({Student.class})
public class Config1 {
}
public class App1 {
public static void main(String[] args) {
//通过传入一个配置类Config1.class相当于配置文件,来得到对应容器对象AnnotationConfigApplicationContext
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Config1.class);
for (String beanDefinitionName : ac.getBeanDefinitionNames()) {
System.out.println(beanDefinitionName);
}
}
}
观察: 被注入对象的bean的名字是类的全路径类名,且首字母大写
- 结论: 使用@Import导入普通类的话,会将该类的实例放到Spring容器中
@Import引入一个配置类,同时创建配置类中声明的bean对象
- 所谓的配置类就是加了@Configuration注解的类,该类里面有用于创建其他的bean
- 创建要导入的配置类:
@Configuration
public class MyConfig {
@Bean
public Dog dog(){ //里面使用@Bean注入一个dog对象到容器中
return new Dog();
}
}
- 使用@Import导入
@Configuration
@Import({Student.class,MyConfig.class})
public class Config1 {
}
- 输出容器对象中所有的bean
public class App1 {
public static void main(String[] args) {
//通过传入一个配置类Config1.class相当于配置文件,来得到对应容器对象AnnotationConfigApplicationContext
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Config1.class);
for (String beanDefinitionName : ac.getBeanDefinitionNames()) {
System.out.println(beanDefinitionName);
}
}
}
- 结论: 使用@Import注解导入配置类的话,不光会创建配置类的对象,同时在配置类中所有需要创建的对象(例如使用@Bean注解)也会被加载到spring容器中
二 @Import引入ImportSelector的实现类(重点)
- springboot的自动装配中大量使用到导入ImportSelector的实现类
- ImportSelector接口:
public interface ImportSelector {
//selectorImports方法是重点
String[] selectImports(AnnotationMetadata importingClassMetadata);
@Nullable
default Predicate<String> getExclusionFilter() {
return null;
}
}
解释:
String[] selectImports(AnnotationMetadata importingClassMetadata);
- 返回值: 一个String的字符串数组
- 参数:
AnnotationMetadata
类型 ,可以获取导入了ImportSelector实现类并加了@Import注解所在的类,的一些信息
- 创建一个ImportSelector的实现类,并重写selectImports方法
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
String[] strings = {"com.example.pojo.Dog"}; //返回的字符串是Dog类的全路径类名
return strings;
}
}
- 导入这个实现类
@Configuration
@Import({MyImportSelector.class}) //导入ImportSelector的实现类
public class Config1 {
}
- 运行程序
public class App1 {
public static void main(String[] args) {
//得到容器对象AnnotationConfigApplicationContext
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Config1.class);
for (String beanDefinitionName : ac.getBeanDefinitionNames()) {
System.out.println(beanDefinitionName);
}
}
}
发现创建了selectImports方法返回字符串全限定类名对应的实例到容器中
- 所以我们折腾一圈还是为了创建某个类的实例到容器中,那为什么要使用这种方式呢?又比较麻烦
- 这种方法最终的效果: 将ImportSelector的实现类中的selectImports方法的返回值对应类的全限定类名字符串的实例注入到容器中.
- 和之前注入bean实例的区别,selectImports方法中的参数
AnnotationMetadata importingClassMetadata
,注解元数据,该参数可以拿到很多信息,来帮助我们决定最后返回的字符串中究竟要创建哪些实例.
该参数可以使用的一些方法:
- 既然这个参数可以获取这么多信息,但是究竟它获取到的是哪个类上的信息呢?
- 这样就可以实现更加灵活的注入实例到容器中,可以加一些自己的逻辑条件,满足某些要求时,我就返回某些类的全限定类名,里面的逻辑可以自己控制.
- 比如:如果@Import注解所在类上加了@Configuration注解我就创建Student对象,否则我就创建Dog对象
@Configuration
@Import({MyImportSelector.class})
public class Config1 {
}
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
System.out.println("---------------");
System.out.println(importingClassMetadata.hasAnnotation("org.springframework.context.annotation.Configuration"));
if(importingClassMetadata.hasAnnotation("org.springframework.context.annotation.Configuration")){
return new String[]{"com.example.pojo.Student"};
}else{
return new String[]{"com.example.pojo.Dog"};
}
}
}
- 如果我将@Configuration注解去掉
- 当然
AnnotationMetadata
可以获取的信息还有很多,不仅是可以判断是否加了某些注解,还能获取到注解中的属性值,这些都可以作为我们判断的条件,根据判断来决定我们最终需要注入的实例究竟是哪些.
三 @Import引入ImportBeanDefinitionRegistrar的实现类(重点)
- 在 ImportBeanDefinitionRegistrar接口中有一个方法registerBeanDefinitions方法,里面可以定义bean到容器中
public interface ImportBeanDefinitionRegistrar {
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
this.registerBeanDefinitions(importingClassMetadata, registry);
}
//我们重点使用这个方法,上面的和这个是差不多的,只是参数多一个
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
}
}
- 在这里面创建的bean的优先级比使用注解@Service,@Component这几个注解创建的bean的优先级更高,也就是这里面创建的bean如果同名的话能够覆盖前面创建的
- 编写一个实现类:
public class MyBeanDefinition implements ImportBeanDefinitionRegistrar{
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//importingClassMetadata是元注解,也是可以获取到Import使用所在类上的所有注解信息,也可以作为判断条件
//获取BeanDefinition对象的方式不唯一,有很多
BeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(Student.class).getBeanDefinition();
//注册bean,在这里面注册bean的自由度更大,bean的名字还有其他属性都可以自定义
registry.registerBeanDefinition("myStudent",beanDefinition);
}
}
- 将该实现类导入
@Configuration
@Import({MyBeanDefinition.class})
public class Config1 {
}
- 运行程序