@Import 注解

@Import 注解

@Import 注解

@Import注解是Spring基于Java注解配置的主要组成部分,它通过快速导入的方式实现把实例加入到Spring的IOC容器中。@Import 只能用在类上,通常用在带有 @Configuration注解的配置类上,但也可以用在普通类上。类似于 @Bean注解,@Import 提供了一种将bean定义导入到Spring容器中的方式,但它更侧重于批量导入或基于条件的动态导入。

下面分别对 @Import 注解四种使用方式测试验证

直接导入其他类Bean

@Import可以直接指定实体类,加载这个类定义到 context中。

创建一个Student类

image-nrvu.png

创建一个配置类使用 @Import导入Student类

image-xrel.png

从spring容器中获取 Student类执行方法

image-cvzb.png

可以看到 Student类的确被创建到了IOC容器中

特别注意一下,使用@Import注解导入类Bean时,优先级会高于作用的类本身,比如前面例子中如果MyConfig配置类中也声明Studeng类的Bean创建,但是优先级低于@Import。我们可以声明一个接口,使用两个实现,然后看控制台打印结果

Persion接口

image-scor.png

Student类

image-qxee.png

Teacher类

image-ovri.png

Config类,这里getPersion方法添加 @ConditionalOnMissingBean注解是因为防止创建两个Persion实例,不然后面获取时候会报错,提示有两个匹配Bean,同时也能证明当我们Student类Bean创建后 @ConditionalOnMissingBean 生效从而不会创建Teacher类的Bean,证明 @Import注解优先级更高

image-iofj.png

控制台打印

image-pgps.png

可以看到输出确实是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方法查看输出结果

image-jbfs.png

证明 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.修改 MyImportSelectorselectImports 方法实现

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的非注解类,因为 AnnotationMetadataImport注解所在的类属性,如果所在类是注解类,则延伸至应用这个注解类的非注解类为止

4.执行查看控制台输出

image-zwbp.png

修改一下 @EnableDemo注解name值,让它加载不上

image-ydev.png

最后实现了使用 @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对象会被成功加载,来看控制台输出

image-trdk.png

可以看到 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.执行看控制台输出

image-xzii.png

输出结果证明我们在 MyImportBeanDefinitionRegistrarMyImportBeanDefinitionRegistrar方法中做的修改生效,创建了 Doctor类的 BeanDefinition并且给 Doctor构造参数赋值,值来自于 @EnableDemo 注解中的值。并且 Student类的成功创建可以验证 ImportBeanDefinitionRegistrar 方式 类的创建优先级要低于 配置类中的@Bean操作,不然 @ConditionalOnMissingBean 注解不会让 Student 类实例化的。

以上就是我们对 @Import注解的一个学习与运用,经过前面的代码测试,我们对 @Import注解的几种使用方式都有了一定程度的掌握。但是对 @Import注解本身如何实现还缺乏了解,后续会补充学习

@Import 源码解析

............... 还没写