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 接口的。
MyConfig 类中使用 @ Import 注解注入的 MyAnotherConfig 类没有继承任何超类或实现任何接口:
public class MyAnotherConfig { @Bean("anotherTom") public Worker worker() { return new Worker("anotherTom", 2); } }
可以看到,如果不是内部的一个方法使用了 @ Bean 注解,它就是一个普通的Java Bean了。也是通过这个 @ Bean 注解,实现了另一个Worker Bean的注入。
根据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 与 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的时候我们会看到这个实例的属性都是默认值。
使用 @ 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 注解了,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配置文件。