目录
ImportBeanDefinitioRegistrar接口
value为ImportBeanDefinitionRegistrar接口类型
value为DeferredImportSelector接口类型
目前为止,注解的方式批量注册bean,我们介绍了2种方式:
- @Configuration结合@Bean注解的方式
- @CompontentScan扫描包的方式
但是也存在一定的局限性,我们来看看下面两种情况:
情况一:
如果需要注册的类是在第三方的jar中,那么我们如果想注册这些bean有2种方式:
- 通过@Bean的方式一个一个注册
- 通过@ComponentScan的方式。默认的@ComponentScan是无能为力的,他只能注册@Component,@Service等那四个注解标记的类,所以我们需要用自定义过滤器的方式
情况二:
通常我们的项目中有很多子模块,可能每个模块都是独立开发的,最后通过jar的方式引进来,每个模块中都有各自的@Configuration或者使用@CompontentScan标注的类,此时如果我们只想使用其中几个模块的配置类,怎么办?
@Import的使用
对于@Import,我们可以先来看看其源码:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
/**
* {@link Configuration @Configuration}, {@link ImportSelector},
* {@link ImportBeanDefinitionRegistrar}, or regular component classes to
import.
*/
Class<?>[] value();
}
spring中对它的解释是:和xml配置的标签作用一样,允许通过它引入@Configuration标注的类 , 引入ImportSelector接口和ImportBeanDefinitionRegistrar接口的实现, 4.2版本以后也可以引入普通组件类
使用步骤:
- 将@Import标注在类上,设置value参数
- 将@Import标注的类作为AnnotationConfigApplicationContext构造参数创建 AnnotationConfigApplicationContext对象
- 使用AnnotationConfigApplicationContext对象
@import的value参数常见的六种用法
- value为普通的类
- value为@Configuration标注的类
- value为@CompontentScan标注的类
- value为ImportBeanDefinitionRegistrar接口类型
- value为ImportSelector接口类型
- value为DeferredImportSelector接口类型
value为普通类
public class Service1 {
}
public class Service2 {
}
@Import({Service1.class, Service2.class})
public class MainConfig1 {
}
class ImportTestApplicationTests {
@Test
void test1() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig1.class);
for (String beanName:context.getBeanDefinitionNames()){
System.out.println(beanName+"--->"+context.getBean(beanName));
}
}
}
运行后输出:
mainConfig1--->com.example.config.MainConfig1@76ed1b7c
com.example.service.Service1--->com.example.service.Service1@11fc564b
com.example.service.Service2--->com.example.service.Service2@394a2528
由输出结果可以看出:
- 我们使用@Import注解成功将普通类对象注册到容器了,但是这些bean的名称是全类名,如果我们想指定名称的话,在对应的类上加上@Component注解指定名称即可。
- 使用@Import的时候,也会连@Import标记的类一起注册到容器中。
value为@Configuration标注的类
@Configuration
public class ConfigModule1 {
@Bean
public String module1(){
return "我是模块一配置类";
}
}
@Configuration
public class ConfigModule2 {
@Bean
public String module2(){
return "我是模块二配置类";
}
}
@Import({ConfigModule1.class, ConfigModule2.class})
public class MainConfig2 {
}
class ImportTestApplicationTests {
@Test
void test1() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig2.class);
for (String beanName:context.getBeanDefinitionNames()){
System.out.println(beanName+"--->"+context.getBean(beanName));
}
}
}
运行后输出:
mainConfig2--->com.example.config.MainConfig2@4c1909a3
com.example.service.ConfigModule1--->com.example.service.ConfigModule1$$EnhancerBySpringCGLIB$$3e30eb6c@428640fa
module1--->我是模块一配置类
com.example.service.ConfigModule2--->com.example.service.ConfigModule2$$EnhancerBySpringCGLIB$$76938e8d@d9345cd
module2--->我是模块二配置类
可以看出,value为@Configration标记的类时,可以把该类中所有bean都批量注册到容器中
value为@CompontentScan标注的类
public class Service1 {
}
public class Service2 {
}
@ComponentScan(
useDefaultFilters = false,
includeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = Service1.class)
)
public class module1 {
}
@ComponentScan(
useDefaultFilters = false,
includeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = Service2.class)
)
public class module2 {
}
@Import({module1.class, module2.class})
public class MainConfig3 {
}
运行后输出:
mainConfig3--->com.example.config.MainConfig3@41e1e210
service1--->com.example.service.Service1@be35cd9
service2--->com.example.service.Service2@4944252c
com.example.module1--->com.example.module1@44821a96
com.example.module2--->com.example.module2@a3d8174
可以看出,service1和service2两个对象均成功注册到容器。说明我们通过@Import可以把@componentScan管理的所有bean注入到容器
先来看一下相关的几个接口
ImportBeanDefinitioRegistrar接口
源码:
public interface ImportBeanDefinitionRegistrar {
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,BeanNameGenerator importBeanNameGenerator) {
registerBeanDefinitions(importingClassMetadata, registry);
}
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
}
}
两个默认方法有三个参数类型:
- AnnotationMetadata类型,通过这个可以获取被@Import注解标注的类所有注解的信息
- BeanDefinitionRegistry类型,是一个接口,内部提供了注册bean的各种方法
- BeanNameGenerator类型,是一个接口,内部有一个方法,用来生成bean的名称
BeanDefinitionRegistry接口
public interface BeanDefinitionRegistry extends AliasRegistry {
/**
* 注册一个新的bean定义
* beanName:bean的名称
* beanDefinition:bean定义信息
*/
void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException;
/**
* 通过bean名称移除已注册的bean
* beanName:bean名称
*/
void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;
/**
* 通过名称获取bean的定义信息
* beanName:bean名称
*/
BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;
/**
* 查看beanName是否注册过
*/
boolean containsBeanDefinition(String beanName);
/**
* 获取已经定义(注册)的bean名称列表
*/
String[] getBeanDefinitionNames();
/**
* 返回注册器中已注册的bean数量
*/
int getBeanDefinitionCount();
/**
* 确定给定的bean名称或者别名是否已在此注册表中使用
* beanName:可以是bean名称或者bean的别名
*/
boolean isBeanNameInUse(String beanName);
}
BeanNameGenerator接口
public interface BeanNameGenerator {
String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry);
}
spring为其内置了三个实现:
- DefaultBeanNameGenerator 默认bean名称生成器,xml中bean未指定名称的时候,默认就会使用这个生成器,默认为:完整的类名 #bean编号
- AnnotationBeanNameGenerator 注解方式的bean名称生成器,比如通过@Component(bean名称)的方式指定bean名称,如果没有通过 注解方式指定名称,默认会将完整的类名作为bean名称
- FullyQualifiedAnnotationBeanNameGenerator 将完整的类名作为bean的名称
BeanDefinition接口
用来表示bean定义信息的接口,我们向容器中注册bean之前,会通过xml或者其他方式定义bean的各种配置信息,bean的所有配置信息都会被转换为一个BeanDefinition对象,然后通过容器中 BeanDefinitionRegistry接口中的方法,将BeanDefinition注册到spring容器中,完成bean的注册操 作。
value为ImportBeanDefinitionRegistrar接口类型
步骤:
1. 定义ImportBeanDefinitionRegistrar接口实现类,在registerBeanDefinitions方法中使用 registry来注册bean
2. 使用@Import来导入步骤1中定义的类
3. 使用步骤2中@Import标注的类作为AnnotationConfigApplicationContext构造参数创建spring容器
4. 使用AnnotationConfigApplicationContext操作bean
案例:
public class Service1 {
}
public class Service2 {
private Service1 service1;
public void setService1(Service1 service1) {
this.service1 = service1;
}
@Override
public String toString() {
return "Service2{" +
"service1=" + service1 +
'}';
}
}
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//定义一个bean:Service1
BeanDefinition service1BeanDefinition = BeanDefinitionBuilder.genericBeanDefinition(Service1.class).getBeanDefinition();
//注册bean
registry.registerBeanDefinition("service1", service1BeanDefinition);
//定义一个bean:Service2,通过addPropertyReference注入service1
BeanDefinition service2BeanDefinition = BeanDefinitionBuilder
.genericBeanDefinition(Service2.class)
.addPropertyReference("service1", "service1")
.getBeanDefinition();
//注册bean
registry.registerBeanDefinition("service2", service2BeanDefinition);
}
}
注意上面的registerBeanDefinitions方法,内部注册了2个bean,Service1和Service2。
上面使用了BeanDefinitionBuilder这个类,这个是BeanDefinition的构造器,内部提供了很多静 态方法方便构建BeanDefinition对象。
@Import(MyImportBeanDefinitionRegistrar.class)
public class MainConfig4 {
}
class ImportTestApplicationTests {
@Test
void test1() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig4.class);
for (String beanName:context.getBeanDefinitionNames()){
System.out.println(beanName+"--->"+context.getBean(beanName));
}
}
}
运行后输出:
mainConfig4--->com.example.config.MainConfig4@1649b0e6
service1--->com.example.service.Service1@865dd6
service2--->Service2{service1=com.example.service.Service1@865dd6}
value为ImportSelector接口类型
我们可以先看看ImportSelector接口的源码:
public interface ImportSelector {
/**
* 返回需要导入的类名的数组,可以是任何普通类,配置类(@Configuration、@Bean、
@CompontentScan等标注的类)
* @importingClassMetadata:用来获取被@Import标注的类上面所有的注解信息
*/
String[] selectImports(AnnotationMetadata importingClassMetadata);
}
用法:
1. 定义ImportSelector接口实现类,在selectImports返回需要导入的类的名称数组
2. 使用@Import来导入步骤1中定义的类
3. 使用步骤2中@Import标注的类作为AnnotationConfigApplicationContext构造参数创spring容器
4. 使用AnnotationConfigApplicationContext操作bean
案例:
public class Service1 {
}
@Configuration
public class ConfigModule1 {
@Bean
public String module1(){
return "我是模块一配置类";
}
}
@Configuration
public class ConfigModule2 {
@Bean
public String module2(){
return "我是模块二配置类";
}
}
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{
Service1.class.getName(),
ConfigModule1.class.getName(),
ConfigModule2.class.getName()
};
}
}
@Import(MyImportSelector.class)
public class MainConfig5 {
}
class ImportTestApplicationTests {
@Test
void test1() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig5.class);
for (String beanName:context.getBeanDefinitionNames()){
System.out.println(beanName+"--->"+context.getBean(beanName));
}
}
}
运行后输出:
mainConfig5--->com.example.config.MainConfig5@3646a422
com.example.service.Service1--->com.example.service.Service1@750e2b97
com.example.service.ConfigModule1--->com.example.service.ConfigModule1$$EnhancerBySpringCGLIB$$835c609b@3e27aa33
module1--->我是模块一配置类
com.example.service.ConfigModule2--->com.example.service.ConfigModule2$$EnhancerBySpringCGLIB$$bbbf03bc@2e385cce
module2--->我是模块二配置类
value为DeferredImportSelector接口类型
DeferredImportSelector是ImportSelector的子接口,既然是ImportSelector的子接口,所以也可以通过@Import进行导入,这个接口和ImportSelector不同地方有两点:
- 延迟导入
- 指定导入的类的处理顺序
延迟导入
比如@Import的value包含了多个普通类、多个@Configuration标注的配置类、多个ImportSelector接 口的实现类,多个ImportBeanDefinitionRegistrar接口的实现类,还有DeferredImportSelector接口实 现类,此时spring处理这些被导入的类的时候,会将DeferredImportSelector类型的放在最后处理, 会先处理其他被导入的类,其他类会按照value所在的前后顺序进行处理。 那么我们是可以做很多事情的,比如我们可以在DeferredImportSelector导入的类中判断一下容器中是否已经注册了某个bean,如果没有注册过,那么再来注册。 以后我们会讲到另外一个注解@Conditional,这个注解可以按条件来注册bean,比如可以判断某个 bean不存在的时候才进行注册,某个类存在的时候才进行注册等等各种条件判断,通过@Conditional来 结合DeferredImportSelector可以做很多事情。
案例:
@Configuration
public class ConfigModule1 {
@Bean
public String module1(){
System.out.println("我是模块一配置类");
return "我是模块一配置类";
}
}
@Configuration
public class ConfigModule2 {
@Bean
public String module2(){
System.out.println("我是模块二配置类");
return "我是模块二配置类";
}
}
public class DeferredImportSelector1 implements DeferredImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{
ConfigModule1.class.getName()
};
}
}
@Import({
DeferredImportSelector1.class,
ConfigModule2.class
})
public class MainConfig6 {
}
运行后输出:
我是模块二配置类
我是模块一配置类
验证了我们上面的说法,spring在处理被导入的诸多类时,会将 DeferredImportSelector类型放到最后处理,其余的类则会按照顺序进行处理。
那么又有一个疑问了,如果有多个DeferredImportSelector类型的呢,这个时候就需要实现Oedered接口了,其中有一个getOrder方法,该方法返回值越小,优先级越高。所以当我们有多个DeferredImportSelector类型时,除去其它类型的,getOrder返回值越小,越先被处理,此处就不做赘述了。