这次介绍一下 Spring
中的一个重要的注解 @Import
以及向容器中添加 Bean 的几种方式 ,该注解在 SpringBoot
自动转配中起到重要的作用。
本文的组织结构如下:
Spring 版本 5.1.2.RELEASE
先来回想一下我们将组件注册到容器中的几种方法:
@Bean
注解,可以导入第三方包里面的组件,局限性是必须一个一个的导入,而且必须写一个方法;@Import
快速的给容器中导入组件: @Import
:容器中会自动注册该组件,id
默认是全类名;ImportSelector
:返回需要导入的组件的全类名数组,组件名为全类名;ImportBeanDefinitionRegistrar
:手动注册 Bean 到容器中,可以自定义组件名。Spring
提供的 FactoryBean
(工厂 Bean 的方式)。其中 @Bean
是自定义创建对象的方式,而包扫描和 @Import
是调用无参构造创建对象放入容器中,而 FactoryBean
是使用简单工厂模式,调用 get***
方法获取对象。
可以在类上面直接加上这个注解 @Import(Color.class)
,容器中就会有 Color
这个类。
也可以同时导入多个类 @Import({Color.class, Red.class})
。
top.wsuo.bean.Color
top.wsuo.bean.Red
该注解只接收类数组,只有一个属性 value:
Class<?>[] value();
我们可以直接传入类名,或者 ImportSelector
和 ImportBeanDefinitionRegistrar
的实现类,这两个接口下面会详细介绍。
我们先在配置类中加上注解:
@Configuration
@Import({Color.class, Red.class, MyImportSelector.class, MyImportBeanDefinitionRegistrar.class})
public class MainConfig2 {
其中 MyImportSelector
和 MyImportBeanDefinitionRegistrar
后面会介绍;
ImportSelector
是一个接口,需要我们自己实现该类,需要实现一个方法叫做 selectImports
:
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[0];
}
}
该方法有一个参数 AnnotationMetadata
,它可以获取到标注了 @Import
注解类的所有注释信息;
返回值就是要导入到容器中的组件的全类名。
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"top.wsuo.bean.Blue", "top.wsuo.bean.Yellow"};
}
这样的话容器中就有这几个类了:
top.wsuo.bean.Color
top.wsuo.bean.Red
top.wsuo.bean.Blue
top.wsuo.bean.Yellow
它也是一个接口,需要自己写实现类,实现一个方法叫做 registerBeanDefinitions
。
这个方法有一个参数 BeanDefinitionRegistry
,他有一个方法 registerBeanDefinition
,可以自定义注册组件到容器中,第一个参数是自定义的组件名称,第二个参数是要求是 BeanDefinition
类型的,一般我们使用它的一个实现类 RootBeanDefinition
传入我们想导入的组件。
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
/**
* @param importingClassMetadata 当前类的注解信息
* @param registry BeanDefinition 注册类: 调用它的 registerBeanDefinition 方法将需要添加到容器中的 Bean 手工注册进来;
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
boolean hasRed = registry.containsBeanDefinition("top.wsuo.bean.Red");
boolean hasBlue = registry.containsBeanDefinition("top.wsuo.bean.Blue");
if (hasRed && hasBlue) {
registry.registerBeanDefinition("rainBow", new RootBeanDefinition(RainBow.class));
}
}
}
这里的实例方法的逻辑是:先判断容器中有没有 Red
和 Blue
类,如果都有就将 RainBow
注册到容器中。
之前提到过 FactoryBean
的方式注册组件,现在来详细的讲解一下,当然这种方式和 @Import 注解无关。
我们和之前一样也是写一个实现类来继承 Spring 提供的接口 FactoryBean
:
public class ColorFactoryBean implements FactoryBean<Color> {
/**
* 获取对象
*
* @return 返回类型
* @throws Exception 异常
*/
@Override
public Color getObject() throws Exception {
return new Color();
}
/**
* 获取类型
*
* @return 返回类型
*/
@Override
public Class<?> getObjectType() {
return Color.class;
}
/**
* 控制是单例吗:
* - 如果返回 true 表示是单实例
* - 如果返回 false 表示是多实例
*/
@Override
public boolean isSingleton() {
return false;
}
}
需要重写 3 个方法,方法的含义注释标识出来了。
我们在使用的时候可以通过 @Bean
的方式注册进去,看起来注册的是 FactoryBean
对象,实际上是 Color
对象,也就是该接口指定的泛型。
如果想要获取工厂 Bean 本身,需要给 id 前面加一个 &
标识。