Spring 源码系列
1、Spring 学习之扩展点总结之后置处理器(一)
2、Spring 学习之扩展点总结之后置处理器(二)
3、Spring 学习之扩展点总结之自定义事件(三)
4、Spring 学习之扩展点总结之内置事件(四)
5、Spring 学习之扩展点总结之@Import(五)
文章目录
前言
在 Spring之扩展点总结 一、二、三、四(见文章开始处)种分别介绍了Spring的各种后置处理器,以及自定义事件,内置事件的使用,但是还没完,Spring 还提供了更加方便的注册 Bean 到上下文的途径,@Import 注解。
一、什么是 @Import 注解
网上四处看了看网友们说的,基本都差不多,官网查找内容太多,无奈之下,直接查看源码注释翻译出来。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
//@Configuration, ImportSelector, ImportBeanDefinitionRegistrar, or regular component classes to import.
Class<?>[] value();
}
源码注释:
Indicates one or more component classes to import — typically @Configuration classes.
Provides functionality equivalent to the element in Spring XML. Allows for importing @Configuration classes, ImportSelector and ImportBeanDefinitionRegistrar implementations, as well as regular component classes (as of 4.2; analogous to AnnotationConfigApplicationContext.register).
@Bean definitions declared in imported @Configuration classes should be accessed by using @Autowired injection. Either the bean itself can be autowired, or the configuration class instance declaring the bean can be autowired. The latter approach allows for explicit, IDE-friendly navigation between @Configuration class methods.
May be declared at the class level or as a meta-annotation.
If XML or other non-@Configuration bean definition resources need to be imported, use the @ImportResource annotation instead.
源码中这样解释 @Import 的:
1、表明一个或者多个组件类导入,典型的就是导入**@Configuration** 配置类(作者:在Spring Boot中体现),提供了和 Spring 的 XML 配置文件一样的功能性的导入,允许导入 @Configuration 类,ImportSelector、ImportBeanDefinitionRegistrar 的实现类,还可以注册普通类到容器中。
2、在 @Configuration 配置类中用 @Bean 定义的实体应该用 @Autowired 导入。任何一个Bean本身可以自动注入,或者声明配置类声明的 Bean 的配置类实例也可以自动。
3、如果是 XML 或者是 其他非 @Configuration 配置类定义的资源需要导入时,用 @ImportResource 代替导入。
二、@Import注解四种用法
2.1 import 普通实体类
1、新建 BaseService 类,这是一个普通的类,并没有继承或者实现其他接口,也没有注解。
BaseService 类中有个 hello() 方法,为了方便测试,打印一句话。
public class BaseService {
public void hello(){
System.out.println("BaseService:hello");
}
}
2、新建 MainConfig 的配置类,用 @Configuration 标注,然后用 @Import 引入 BaseService 类。
@Configuration
@Import(value = BaseService.class)
public class MainConfig {
}
3、main 测试
在 main() 方法中,实例化容器,可见 @ComponentScan 注解只扫描了 com.self.test.iocTest 包,然后我们获取普通类的实例,看能不能获取到。
如果能获取到 BaseService 实例,我们调用 hello() 方法看能不能成功。
@ComponentScan("com.self.test.iocTest")
public class MainStartApp {
public static void main(String[] args) throws Exception {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainStartApp.class);
BaseService baseService = applicationContext.getBean(BaseService.class);
baseService.hello();
}
}
测试结果:
从测试结果中可以看出,成功拿到 BaseService 的实例并且调用了 hello() 方法。
2.2 import ImportSelector的实现类
1、依旧是需要 BaseService 这个普通类,并没有继承或者实现其他接口,也没有注解。
还是要 BaseService 类中有个 hello() 方法,为了方便测试,打印一句话。
public class BaseService {
public void hello(){
System.out.println("BaseService:hello");
}
}
2、新建 ImportSelectorTest 类并且实现 ImportSelector 接口。
ImportSelectorTest 类只是实现了 ImportSelector 接口,并没有注解之类的东西,复写了 selectImports() 方法并且返回一个 String[] 数据,是类名的全路径,Spring 会通过返回的类的全路径来实例化这些 Bean。
public class ImportSelectorTest implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"com.self.test.iocTest.BaseService"};
}
@Override
public Predicate<String> getExclusionFilter() {
return null;
}
}
3、还是需要 MainConfig 配置类,用 @Configuration 标注,这次是用 @Import 引入我们上面新建的 ImportSelectorTest 类。
@Configuration
@Import(value = ImportSelectorTest.class)
public class MainConfig {
}
4、main() 测试
main() 方法还是不变,获取 BaseService 的实例并且调用 hello() 方法。
@ComponentScan("com.self.test.iocTest")
public class MainStartApp {
public static void main(String[] args) throws Exception {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainStartApp.class);
BaseService baseService = applicationContext.getBean(BaseService.class);
baseService.hello();
}
}
测试结果:
5、ImportSelector 接口还提供了 getExclusionFilter() 方法,此方法返回了 Predicate 预判断,不满足 Predicate 的 Spring 则不会进行实例化。
在getExclusionFilter() 方法中,判断如果类名是 “Base” 开头的,那么返回 true, 则可以注册成功,这里我用了取反,那么 Spring 应该不会实例化注册成功的。
public class ImportSelectorTest implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"com.self.test.iocTest.BaseService"};
}
@Override
public Predicate<String> getExclusionFilter() {
return s -> !s.startsWith("Base");
}
}
测试结果如下:
报错了,说明 getExclusionFilter() 起了作用,我们把代码改成这样,return s -> s.startsWith(“Base”); 在 main() 方法中获取 BaseService 成功。
6、为了验证 BaseService 是否是通过 ImportSelectorTest 类导入进来的,我们把 ImportSelectorTest 类里面的 selectImports() 方法返回的类路径修改一下,再看能不能获取到 BaseService 实体了。
我们在 BaseService 类名修改成 BaseService1111。
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"com.self.test.iocTest.BaseService1111"};
}
main() 方法测试结果如下:
报错了,没有找到 BaseService1111 类,所以 ImportSelectorTest 起作用了。
2.3 import DeferredImportSelector的实现类
DeferredImportSelector 接口继承了 ImportSelector 接口,和 ImportSelector 的不同点就是,DeferredImportSelector 是延迟执行的,而且加入了 Group 的概念,具体实现看代码。
1、依旧是需要 BaseService 这个普通类。
public class BaseService {
public void hello(){
System.out.println("BaseService:hello");
}
}
2、新建 DeferredImportSelectorTest 类 实现接口 DeferredImportSelector 并且复写 selectImports() 方法,getExclusionFilter() 的用法和 ImportSelector 一样的。
不过在 DeferredImportSelectorTest 类中,我们还复写了一个 getImportGroup() 方法,此方法返回一个 Group 类型,所以我们还需要新建一个类 SelectorTestGroup 并且实现内部接口 DeferredImportSelector.Group,然后复写 process() 方法和 selectImports() 方法。
在 process() 方法提供的参数 selector 我们可以获取到 DeferredImportSelectorTest.selectImports() 方法返回的类名,然后加入到一个组里面,再由 SelectorTestGroup.selectImports() 返回进行注册。
public class DeferredImportSelectorTest implements DeferredImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"com.self.test.iocTest.BaseService"};
}
@Override
public Predicate<String> getExclusionFilter() {
return null;
}
@Override
public Class<? extends Group> getImportGroup() {
return SelectorTestGroup.class;
}
}
class SelectorTestGroup implements DeferredImportSelector.Group{
private final List<Entry> importInstances = new ArrayList<>();
@Override
public void process(AnnotationMetadata metadata, DeferredImportSelector selector) {
Arrays.stream(selector.selectImports(metadata)).forEach(className -> {
importInstances.add(new Entry(metadata, className));
});
}
@Override
public Iterable<Entry> selectImports() {
return importInstances;
}
}
3、main 测试
在实例化了容器之后,我们可以从容器中 获取到 BaseService 的实例。
public static void main(String[] args) throws Exception {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainStartApp.class);
BaseService baseService = applicationContext.getBean(BaseService.class);
baseService.hello();
}
测试结果:
从测试结果中看,我们从容器中获取到了 BaseService 实例,调用了 hello() 方法,打印了这句话。
三、总结
@Import 使用起来十分方便,是一个Spring很重要的一个扩展点,后面的 SpringBoot 的自动配置封装就是通过这个注解引入了SpringBoot 的 各种 starter 组件中的配置类实现了主动装配。
DeferredImportSelector 的和 ImportSelector 感觉差不太多,具体底层原理和各种高级用法,请看下篇分解。