Spring 学习之扩展点总结之@Import(五)

Spring 源码系列

1、Spring 学习之扩展点总结之后置处理器(一)
2、Spring 学习之扩展点总结之后置处理器(二)
3、Spring 学习之扩展点总结之自定义事件(三)
4、Spring 学习之扩展点总结之内置事件(四)
5、Spring 学习之扩展点总结之@Import(五)
6、Spring 学习之AOP总结之基础介绍(六)
7、Spring 学习之AOP总结之实现代理(七)
8、SpringBoot 学习之自动配置基本原理(八)
9、SpringBoot 学习之启动原理(九)
10、ElasticSearch学习随笔之SpringBoot Starter 操作
11、图数据库 Neo4j 学习之SpringBoot整合
12、SpringBoot 学习之常用 Filter / Advice 总结
13、SpringBoot+FastJson 优雅的过滤 Response Body
14、Spring Security + Oauth2 认证授权

前言

在 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 类,ImportSelectorImportBeanDefinitionRegistrar 的实现类,还可以注册普通类到容器中。
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 感觉差不太多,具体底层原理和各种高级用法,请看下篇分解。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值