Spring进阶(十四)之@Import注解

目录

@Import的使用

使用步骤:

@import的value参数常见的六种用法

value为普通类

value为@Configuration标注的类

value为@CompontentScan标注的类

先来看一下相关的几个接口

ImportBeanDefinitioRegistrar接口

BeanDefinitionRegistry接口

BeanNameGenerator接口

BeanDefinition接口

value为ImportBeanDefinitionRegistrar接口类型

步骤:

案例:

value为ImportSelector接口类型

用法:

案例:

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版本以后也可以引入普通组件类

使用步骤:

  1. 将@Import标注在类上,设置value参数
  2. 将@Import标注的类作为AnnotationConfigApplicationContext构造参数创建 AnnotationConfigApplicationContext对象
  3. 使用AnnotationConfigApplicationContext对象

@import的value参数常见的六种用法

  1. value为普通的类
  2. value为@Configuration标注的类
  3. value为@CompontentScan标注的类
  4. value为ImportBeanDefinitionRegistrar接口类型
  5. value为ImportSelector接口类型
  6. 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返回值越小,越先被处理,此处就不做赘述了。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冰魄雕狼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值