Overview
Spring中@Import
注解最初主要是在配置类中使用,目的是引入其他的配置类(@Configuration
)并实现自动注入。
目前Import
并不只是支持引入@Configuration
注解的类,也支持引入ImportSelector
和ImportBeanDefinitionRegistrar
接口的实现类,甚至可以引入普通的Java Bean并完成注入。
写了一个简单的应用来进行测试:spring-boot-import。
做些说明。在应用中定义了一个Worker
类,应用做的事情就是结合@Import
注解用不同的方式注入Worker
类的多个Bean实例。 每个Worker
Bean的实例通过name进行区分。
代码的一个核心是MyConfig
类,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
@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实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
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
类没有继承任何超类或实现任何接口:
1 2 3 4 5 6 7 8 |
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
注解。
1 2 3 4 5 6 7 |
public class MySelectConfig { @Bean("selectTom") public Worker worker() { return new Worker("selectTom", 1); } } |
看起来和前面的MyAnotherConfig
是一样的。不过和前例不一样的是:MySelectConfig
类的注入是通过MyImportSelector
来实现的。
MyImportSelector
的实现如下:
1 2 3 4 5 6 7 8 |
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
注解的解释。
看下在我们示例中的使用:
1 2 3 4 5 6 7 8 9 10 11 |
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
注解进行的引入。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
@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实例:
1 2 3 4 |
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配置文件。
发表评论