@Import简介
@Import表示用来导入配置类或者一些需要前置加载的类.。
@Import支持 三种方式
1.带有@Configuration的配置类(4.2 版本之前只可以导入配置类, 4.2版本之后 也可以导入 普通类)
2.ImportSelector 的实现
3.ImportBeanDefinitionRegistrar 的实现
导入@Configuration的配置类
1、代码结构见上图,RootConfig和OtherConfig在两个目录中
//ComponentScan包扫描,默认扫描当前路径下所有文件和子目录文件
//所有会扫描到RootConfig而扫描不到OtherConfig
@ComponentScan
public class SpringApplicationContext {
public static void main(String[] args) {
//创建Annotation上下文
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringApplicationContext.class);
//得到AService的bean
AService bean = context.getBean(AService.class);
bean.service();
//得到BService的bean
BService bean2 = context.getBean(BService.class);
bean2.service();
//关闭容器
context.close();
}
}
2、先看RootConfig 和AService代码
//Configuration等同于xml配置
//类中@bean注解的会被注册到spring容器中
//@import 将另外一个Configuration导入到当前Configuration中
//等于xml中<import>另一个xml
@Configuration
@Import(OtherConfig.class)
public class RootConfig {
@Bean
public AService orderDemoService(){
return new AService();
}
}
//AService 无任何注解,表示没有交给Spring管理
public class AService {
public void service(){
System.out.println("AService run");
}
}
3、再看OtherConfig 和BService代码
//Configuration等同于xml配置
//类中@bean注解的会被注册到spring容器中
@Configuration
public class OtherConfig {
@Bean
public BService omsService(){
return new BService();
}
}
//BService 无任何注解,表示没有交给Spring管理
public class BService {
public void service(){
System.out.println("BService run");
}
}
4、最后看执行效果
执行结果表示,AService和BService 都是spring的bean,交给容器管理,说明此处import见效。
总结下执行过程:
- 容器类注解@ComponentScan,故启动时会扫描当前路径
- 扫描到RootConfig类,由于RootConfig类注解@Import(OtherConfig.class),故扫描OtherConfig类
- RootConfig和OtherConfig中分别存在@Bean注解,会将@Bean注解的方法的返回值放入Spring容器中
- 容器启动后AService和BService都在容器中,故可getBean拿到bean后使用
导入ImportSelector 的实现
1、先看启动类
//导入ImportSelector的实现类
//面向接口编程
@ComponentScan
@Import(MySelector.class)
public class SpringApplicationContext {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringApplicationContext.class);
//得到MyBean的bean
MyBean myBean = context.getBean(MyBean.class);
myBean.service();
//关闭容器
context.close();
}
}
2、继续上MySelector 和MyBean代码
//实现ImportSelector接口中selectImports方法
public class MySelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{MyBean.class.getName()};
}
}
//MyBean 无任何注解,表示没有交给Spring管理
public class MyBean {
public void service(){
System.out.println("MyBean run");
}
}
3、最后看运行结果
执行结果表示,MyBean在容器中,说明此处import见效。
总结下执行过程:
- 容器类注解@ComponentScan,故启动时会扫描当前路径
- 扫描启动类时发现@Import(MySelector.class),会取出MySelector类中selectImports方法的返回值,返回值为String数组,数组内容为类的全限定名
- 容器会加载这些类,我们示例中会加载MySelector
- 容器启动后MySelector在容器中,故可getBean拿到bean后使用
ImportBeanDefinitionRegistrar 的实现
1、先看启动类
//导入ImportBeanDefinitionRegistrar的实现类
//可批量加入bean
@ComponentScan
@Import(MyImportBeanDefinitionRegistrar.class)
public class SpringApplicationContext {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringApplicationContext.class);
//得到类的实例
MyRegistrarBean myRegistrarBean = context.getBean(MyRegistrarBean.class);
myRegistrarBean.service();
//关闭容器
context.close();
}
}
2、继续上MyImportBeanDefinitionRegistrar和MyRegistrarBean代码
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
String beanName = MyRegistrarBean.class.getSimpleName();
//beanName转化为驼峰式
beanName = StringUtils.uncapitalize(beanName);
//新建RootBeanDefinition放入BeanDefinition注册器中
RootBeanDefinition beanDefinition = new RootBeanDefinition(MyRegistrarBean.class);
registry.registerBeanDefinition(beanName,beanDefinition);
}
}
public class MyRegistrarBean {
public void service(){
System.out.println("MyRegistrarBean service");
}
}
3、最后看运行结果
执行结果表示,MyRegistrarBean 在容器中,说明此处import见效。
总结下执行过程:
- 容器类注解@ComponentScan,故启动时会扫描当前路径
- 扫描启动类时发现@Import(MyImportBeanDefinitionRegistrar.class),因为我们在MyImportBeanDefinitionRegistrar类中往BeanDefinition注册器中添加了MyRegistrarBean的BeanDefinition
- 容器会加载这些类
- 容器启动后MyRegistrarBean 在容器中,故可getBean拿到bean后使用
- ImportBeanDefinitionRegistrar因为直接添加BeanDefinition,故更强大灵活,也对开发者有一定的spring基础要求
总结
1、我们在springboot中经常见到导入ImportSelector和ImportBeanDefinitionRegistrar的代码,用的就是这个原理。
2、在日常开发中,我们可以通到配置XML或者外部资源配置管理类全限定名,起到手动加载自己想要的类的目的。
3、ImportSelector源码实现是在初始化beanFactory时完成解析,源码较复杂,有兴趣可自己查看。
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass, Collection<SourceClass> importCandidates, boolean checkForCircularImports) throws IOException {
//删除无关代码
// Import 注解中配置的是 ImportSelector 类型
if (candidate.isAssignable(ImportSelector.class)) {
Class<?> candidateClass = candidate.loadClass();
ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class); // 实例化
ParserStrategyUtils.invokeAwareMethods(selector, this.environment, this.resourceLoader, this.registry); // 如果该类有实现对应的 Aware 接口,则注入对应的属性
if (this.deferredImportSelectors != null && selector instanceof DeferredImportSelector) { // DeferredImportSelector 类型的放到集合中待后续处理
this.deferredImportSelectors.add(new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector));
}
else { // 直接调用 selectImports 方法取得对应的类
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
processImports(configClass, currentSourceClass, importSourceClasses, false);
}
}
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
// 如果是 ImportBeanDefinitionRegistrar 类型
Class<?> candidateClass = candidate.loadClass();
ImportBeanDefinitionRegistrar registrar = BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
ParserStrategyUtils.invokeAwareMethods(registrar, this.environment, this.resourceLoader, this.registry);
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
}
else {
// 如果非上面两种类型,那么代表这是一个与 @Configuration 相关的类
this.importStack.registerImport(currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
processConfigurationClass(candidate.asConfigClass(configClass));
}
}