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 allWorkers() { List 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 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配置文件。