目录
@Import 注解的作用
1.简介
在平时看源码或者很多配置类上面都会出现 @Import 注解,功能就是和Spring XML 里面的 一样。@Import 注解是用来导入配置类或者一些需要前置加载的类。
2.源码解析
2.1 导入配置的三种类型
@Import 支持三种方式
- 带有 @Configuration 的配置类(4.2 版本之前只可以导入配置类,4.2 版本之后也可以导入
普通类
) - ImportSelector 的实现
- ImportBeanDefinitionRegistrar 的实现
2.2 源码解释
/**
* Indicates one or more {@link Configuration @Configuration} classes to import.
*
*功能类似XML 里面的 <import/> ,可以导入 @Configuration配置类,ImportSelector、
* ImportBeanDefinitionRegistrar 的实现,4.2 版本之后可以导入普通类(类似AnnotationConfigApplicationContext#register
* )
* <p>Provides functionality equivalent to the {@code <import/>} element in Spring XML.
* Allows for importing {@code @Configuration} classes, {@link ImportSelector} and
* {@link ImportBeanDefinitionRegistrar} implementations, as well as regular component
* classes (as of 4.2; analogous to {@link AnnotationConfigApplicationContext#register}).
*
* 可以在类级别声明或作为元注释声明
* <p>May be declared at the class level or as a meta-annotation.
* 如需要引入XML或其他类型的文件,使用@ImportResource注解
* <p>If XML or other non-{@code @Configuration} bean definition resources need to be
* imported, use the {@link ImportResource @ImportResource} annotation instead.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
/**
* {@link Configuration}, {@link ImportSelector}, {@link ImportBeanDefinitionRegistrar}
* or regular component classes to import.
*/
Class<?>[] value();
}
3、测试例子
3.1 导入普通类
- 新建一个 TestA 类
public class TestA {
public void fun(String str) {
System.out.println(str);
}
public void printName() {
System.out.println("类名 :" + Thread.currentThread().getStackTrace()[1].getClassName());
}
}
- 新建一个 ImportConfig,在类上面加上 @Configuration,加上 @Configuration 是为了能让 Spring 扫描到这个类,并且直接通过 @Import 引入TestA 类
@Import({TestA.class})
@Configuration
public class ImportConfig {
}
- 测试结果
TestA 是一个普通的类,现在可以被 @Autowired 注释然后调用,就直接说明已经被Spring 注入并管理了,普通的类都是需要先实例化
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = ApplicationMain.class)
public class ImportAnnotionTest {
@Autowired
TestA testA;
@Test
public void TestA() {
testA.printName();
}
}
打印:
类名 :com.test.importdemo.TestA
3.2 导入带有 @Configuration 的配置类
- 新建 TestB 类
@Configuration
public class TestB {
public void fun(String str) {
System.out.println(str);
}
public void printName() {
System.out.println("类名 :" + Thread.currentThread().getStackTrace()[1].getClassName());
}
}
- 在 ImportConfig.class 里面直接引入 TestB 类
@Import({TestA.class, TestB.class})
@Configuration
public class ImportConfig {
}
- 测试结果
TestB.class 的类上面已经有了 @Configuration 注解,本身就会配 Spring 扫到并实例,@import 引入带有 @Configuration 的配置文件,是需要先实例这个配置文件再进行相关操作
@Autowired
TestB testB;
@Test
public void TestB(){
testB.printName();
}
打印:
ImportAnnotionTest in 8.149 seconds (JVM running for 10.104)
类名 :com.test.importdemo.TestB
3.3 通过 ImportSelector 方式导入的类
- 新建 TestC 类
public class TestC {
public void fun(String str) {
System.out.println(str);
}
public void printName() {
System.out.println("类名 :" + Thread.currentThread().getStackTrace()[1].getClassName());
}
}
- 新建 SelfImportSelector.class 实现ImportSelector 接口,注入 TestC 类
public class SelfImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"com.test.importdemo.TestC"};
}
}
- ImportConfig 上面引入 SelfImportSelector.class
@Import({TestA.class, TestB.class, SelfImportSelector.class})
@Configuration
public class ImportConfig {
}
- 测试结果
@Autowired
TestC testC;
@Test
public void TestC() {
testC.printName();
}
打印:
ImportAnnotionTest in 7.23 seconds (JVM running for 9.065)
类名 :com.test.importdemo.TestC
3.4 通过 ImportBeanDefinitionRegistrar 方式导入的类
- 新建 TestD 类
public class TestD {
public void fun(String str) {
System.out.println(str);
}
public void printName() {
System.out.println("类名 :" + Thread.currentThread().getStackTrace()[1].getClassName());
}
}
- 新建 SelfImportBeanDefinitionRegistrar.class,实现接口ImportBeanDefinitionRegistrar,注入 TestD 类
public class SelfImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
RootBeanDefinition root = new RootBeanDefinition(TestD.class);
registry.registerBeanDefinition("testD", root);
}
}
- ImportConfig 类上加上导入 SelfImportBeanDefinitionRegistrar.class
@Import({TestA.class, TestB.class, SelfImportSelector.class,
SelfImportBeanDefinitionRegistrar.class})
@Configuration
public class ImportConfig {
}
- 测试结果
@Autowired
TestD testD;
@Test
public void TestD() {
testD.printName();
}
打印:
ImportAnnotionTest in 7.817 seconds (JVM running for 9.874)
类名 :com.test.importdemo.TestD
通过以上几种方法都是能成功注入 Spring。
4. 详细过程解析
这里主要看 ConfigurationClassParser.java 里面的doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass) 这个方法。具体定位到源码的302行代码
为啥不从头梳理,这里 Spring 启动过程比较复杂,要是从头梳理,涉及的东西比较多,好多人看了就会累了,放弃了,我们就单个点熟悉,最后再进行汇总
4.1 getImports 方法
在分析这个方法之前,我们先看一下 getImports 方法,这个方法就是获取所有的 @import 里面的类
这里是获取 @import 里面的类,大致流程如下:
- 定义一个 visited 的集合,用作 是否已经 判断过的标志
- 这里就是获取 sourceClass 上面的 所有的 annotation,并挨个判断, 如果不是 @import,那就 进一步
递归 调用
对应的 annotation,直到全部结束 - 加载 sourceClass 里面 的 @Import annotation 里面对应的类名,最后返回
private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {
Set<SourceClass> imports = new LinkedHashSet<>();
Set<SourceClass> visited = new LinkedHashSet<>();
collectImports(sourceClass, imports, visited);
return imports;
}
// 这里就是获取sourceClass上面的所有的annotation,如果不是@import,那就进一步递归调用对应的annotation,直到全部结束
private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited)
throws IOException {
if (visited.add(sourceClass)) {
for (SourceClass annotation : sourceClass.getAnnotations()) {
String annName = annotation.getMetadata().getClassName();
if (!annName.equals(Import.class.getName())) {
collectImports(annotation, imports, visited);
}
}
imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
}
}
4.2 processImports 方法
processImports 这个方法 的代码逻辑也很清晰,流程图如下:
大致的流程如下:
- 判断 importCandidates 是否为空,为空 退出
- 判断 isChainedImportOnStack,如果为 true,加入 problemReporter 里面的 error,并退出
- 把当前的 configClass 加入到 ImportStack 里面,ImportStack 是继承了 ArrayDeque 和实现了 ImportRegistry
- 对 getImports 里面获取到的需要 import 的类进行遍历处理
4.1 如果是 ImportSelector 类型,首先实例一个 ImportSelector 对象,然后 对其进行 Aware 扩展(如果实现了 Aware 接口)
4.1.1 进一步判断是否是 DeferredImportSelector 类型,如果是,加入到deferredImportSelectors 里面,最后处理,这里可以看一下方法parse(Set configCandidates),里面最后一行才调用
,这也就是有的时候,如果想最后注入,就可以定义为 deferredImportSelectors 类型
4.1.2 如果不是 DeferredImportSelector 类型,那就调用 selectImports 方法,获取到所有的需要 注入的类,这时再次调用 processImports 方法,这里调用 processImports 方法,其实是把这些需要注入的类当成普通的 @Configuration 处理 - 如果是 ImportBeanDefinitionRegistrar 类型,这里也是先实例一个对象,然后加入到 importBeanDefinitionRegistrars 里面,后续会在 ConfigurationClassBeanDefinitionReader 这个类里面的 loadBeanDefinitionsFromRegistrars 方法处理的
- 如果上面两种类型都不是,那就是当初普通的带有 @Configuration 的类进行处理了
@ImportResource 注解的作用
1.简介
@ImportResource 注解用于导入Spring 的配置文件。
2.作用解析
以前写的 springmvc.xml、applicationContext.xml 在 SpringBoot 里面并没有,我们自己编写的配置文件也不能自动识别,这里就可以使用 @ImportResource 标注在一个配置类上让 Spring 的配置文件生效。
注意!不使用 @ImportResource 注解,程序根本不能对我们 Spring 的配置文件进行加载
3.测试例子
@ImportResource(locations = "classpath:applicationContext.xml")
@SpringBootApplication
@RestController
public class FirstSpringbootApplication {
public static void main(String[] args) {
SpringApplication.run(FirstSpringbootApplication.class, args);
}
}
@SpringBootTest
class FirstSpringbootApplicationTests {
@Autowired
ApplicationContext applicationContext;
@Test
void testapplication() {
Object a = applicationContext.getBean("dog1");
System.out.println(a);
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="dog1" class="com.yangzhenxu.firstspringboot.bean.Dog">
<property name="name" value="zhangxue"/>
<property name="age" value="27"/>
</bean>
</beans>