转载

Spring探索01 – @Import注解

Overview

Spring中 @ Import 注解最初主要是在配置类中使用,目的是引入其他的配置类( @ Configuration )并实现自动注入。

目前 Import 并不只是支持引入 @ Configuration 注解的类,也支持引入 ImportSelector ImportBeanDefinitionRegistrar 接口的实现类,甚至可以引入普通的Java Bean并完成注入。

写了一个简单的应用来进行测试: spring-boot-import 。

做些说明。在应用中定义了一个 Worker 类,应用做的事情就是结合 @ Import 注解用不同的方式注入 Worker 类的多个Bean实例。 每个 Worker Bean的实例通过name进行区分。

代码的一个核心是 MyConfig 类,代码如下:

@Configuration
@Import({MyAnotherConfig.class,
        MyImportSelector.class,
        WorkerBeanDefinitionRegistrar.class})
public class MyConfig {
 
 
    @Bean("tom")
    public Worker worker() {
        return new Worker("tom", 2);
    }
 
 
}

这个类中包含 @ Configuration 注解,说明是一个配置类,Spring会自动注入这个类的实例。此外这个类还通过 @ Bean 注解注入了一个Worker Bean实例“tom”,又通过 @ Import 接口引用三个其他类,目的是尝试注入其他的Worker Bean实例。最后在 WorkerService 中尝试获取并逐行打印注入的Worker实例:

public class WorkerService {
 
    @Autowired
    private WorkerBeanFactory factory;
 
    public List<Worker> allWorkers() {
        List<Worker> list = new ArrayList<>(4);
        list.add(factory.get("tom"));
        list.add(factory.get("anotherTom"));
        list.add(factory.get("selectTom"));
        list.add(factory.get("jerry"));
 
        list.stream().forEach(System.out::println);
 
        return list;
    }
}

接下来详细介绍下这个过程中是如何使用 @ Import 接口的。

引入普通的Java Bean

MyConfig 类中使用 @ Import 注解注入的 MyAnotherConfig 类没有继承任何超类或实现任何接口:

public class MyAnotherConfig {
 
    @Bean("anotherTom")
    public Worker worker() {
        return new Worker("anotherTom", 2);
    }
 
}

可以看到,如果不是内部的一个方法使用了 @ Bean 注解,它就是一个普通的Java Bean了。也是通过这个 @ Bean 注解,实现了另一个Worker Bean的注入。

引入ImportSelector实现类

根据Spring的文档, ImportSelector 的作用是根据一些注解的属性来决定使用哪些 @ Configuration 类。也就是配置类的选择器。通常在spring的引用包中会看到 ImportSelector 的实现。

因此这里定义了另一个配置类 MySelectConfig ,不过为了避免当前应用下Spring的自动注入,没有在这个类中添加 @ Configuration 注解。

public class MySelectConfig {
 
    @Bean("selectTom")
    public Worker worker() {
        return new Worker("selectTom", 1);
    }
}

看起来和前面的 MyAnotherConfig 是一样的。不过和前例不一样的是: MySelectConfig 类的注入是通过 MyImportSelector 来实现的。

MyImportSelector 的实现如下:

public class MyImportSelector implements ImportSelector {
 
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{MySelectConfig.class.getName()};
    }
 
}

这里没有基于AnnotationMetadata进行判定就直接返回了配置类的名称,在实际工作中不是一个好的实践。不过我们这里只是做一个演示,不需纠结太多。

引入ImportBeanDefinitionRegistrar实现类

ImportBeanDefinitionRegistrar ImportSelector 的作用是有着根本上的不同的: ImportSelector 的作用是提供配置类;而 ImportBeanDefinitionRegistrar 的作用则是根据类定义完成相应Bean实例的创建。

通常 ImportBeanDefinitionRegistrar 多与 ClassPathMapperScanner 配合使用。 ClassPathMapperScanner 可以用来扫描指定的package,获取目标类并完成相应实例的创建。具体应用如MyBatis的 @ Mapper 注解的解释。

看下在我们示例中的使用:

public class WorkerBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, 
                                        BeanDefinitionRegistry registry) {
        
        BeanDefinitionBuilder workerBuilder = BeanDefinitionBuilder.rootBeanDefinition(Worker.class);
        registry.registerBeanDefinition("jerry", workerBuilder.getBeanDefinition());
    }
 
}

在这里通过 Worker 类的定义创建了一个名为“jerry”的实例。需要注意:这里虽然完成了 Worker 实例的创建,但是并没有配置任何属性。等在输出注入的Worker Bean的时候我们会看到这个实例的属性都是默认值。

引入Spring Component

使用 @ Import 注解不仅可以引入普通的Java Bean,也可以引入Spring组件类,即需要使用 @ Component 或者 @ Service 等注解标记的类。组建类中通过 @ Autowired 注解引用的其他组件也会被递归引用并注入。

示例应用中的 WorkerService 类并没有使用任何注解标记,而是在使用的时候通过 @ Import 注解进行的引入。

@RestController
@RequestMapping("/worker")
@Import(WorkerService.class)
public class WorkerController {
 
    @Autowired
    private WorkerService service;
 
    @GetMapping("/all")
    public List<Worker> all(){
        return service.allWorkers();
    }
 
}

这样虽然也可以使用,但并不建议这么做。

引入@Configuration注解的类

这个留到最后是因为一开始比较困惑:既然已经有 @ Configuration 注解了,Spring就一定会自动引入这个类的,应该就没必要再使用 @ Import 注解进行引用并注入了。

后来意识到我的想法是有漏洞的:比如一些第三方spring组件包中的配置类,既没有配置packageScan,也没有配置starter,直接使用肯定是不行的。此时使用 @ Import 注解来导入相关的配置类及组件是一个很好地解决方案。

测试

执行 WorkerControllerTest 类的测试方法 all ( ) ,观察测试结果,期间会输出我们创建的几个Worker Bean实例:

Worker{name='tom', age=2}
Worker{name='anotherTom', age=2}
Worker{name='selectTom', age=1}
Worker{name='null', age=0}

可以看到一个Worker实例的属性都是默认值,这个实例即是通过 ImportBeanDefinitionRegistrar 创建的Worker Bean “jerry”。

其他

关于 @ Import 注解的实现原理可以参考 AbstractApplicationContext . refresh -> BeanFactoryPostProcessor -> ConfigurationClassPostProcessor -> ConfigurationClassParser . processImports ( ) 。具体就不展开了。

此外,还有另外一个注解 @ ImportResource 主要用来引入xml或groovy配置文件。

原文  https://www.zhyea.com/2019/06/22/spring-explore-01-import-anno.html
正文到此结束
Loading...