@Import 注解和 @ImportResource 注解的详细介绍

@Import 注解的作用

1.简介

在平时看源码或者很多配置类上面都会出现 @Import 注解,功能就是和Spring XML 里面的 一样。@Import 注解是用来导入配置类或者一些需要前置加载的类。

2.源码解析

2.1 导入配置的三种类型

@Import 支持三种方式

  1. 带有 @Configuration 的配置类(4.2 版本之前只可以导入配置类,4.2 版本之后也可以导入普通类
  2. ImportSelector 的实现
  3. 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 导入普通类

  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());
    }
}
  1. 新建一个 ImportConfig,在类上面加上 @Configuration,加上 @Configuration 是为了能让 Spring 扫描到这个类,并且直接通过 @Import 引入TestA 类
@Import({TestA.class})
@Configuration
public class ImportConfig {

}
  1. 测试结果
    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 的配置类

  1. 新建 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());
    }
}
  1. 在 ImportConfig.class 里面直接引入 TestB 类
@Import({TestA.class, TestB.class})
@Configuration
public class ImportConfig {

}
  1. 测试结果
    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 方式导入的类

  1. 新建 TestC 类
public class TestC {
    public void fun(String str) {
        System.out.println(str);
    }

    public void printName() {
        System.out.println("类名 :" + Thread.currentThread().getStackTrace()[1].getClassName());
    }
}
  1. 新建 SelfImportSelector.class 实现ImportSelector 接口,注入 TestC 类
public class SelfImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{"com.test.importdemo.TestC"};
    }
}
  1. ImportConfig 上面引入 SelfImportSelector.class
@Import({TestA.class, TestB.class, SelfImportSelector.class})
@Configuration
public class ImportConfig {

}
  1. 测试结果
    @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 方式导入的类

  1. 新建 TestD 类
public class TestD {
    public void fun(String str) {
        System.out.println(str);
    }

    public void printName() {
        System.out.println("类名 :" + Thread.currentThread().getStackTrace()[1].getClassName());
    }
}
  1. 新建 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);
    }
}
  1. ImportConfig 类上加上导入 SelfImportBeanDefinitionRegistrar.class
@Import({TestA.class, TestB.class, SelfImportSelector.class, 
        SelfImportBeanDefinitionRegistrar.class})
@Configuration
public class ImportConfig {

}
  1. 测试结果
    @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 里面的类,大致流程如下:

  1. 定义一个 visited 的集合,用作 是否已经 判断过的标志
  2. 这里就是获取 sourceClass 上面的 所有的 annotation,并挨个判断, 如果不是 @import,那就 进一步递归 调用对应的 annotation,直到全部结束
  3. 加载 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 这个方法 的代码逻辑也很清晰,流程图如下:
在这里插入图片描述
大致的流程如下:

  1. 判断 importCandidates 是否为空,为空 退出
  2. 判断 isChainedImportOnStack,如果为 true,加入 problemReporter 里面的 error,并退出
  3. 把当前的 configClass 加入到 ImportStack 里面,ImportStack 是继承了 ArrayDeque 和实现了 ImportRegistry
  4. 对 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 处理
  5. 如果是 ImportBeanDefinitionRegistrar 类型,这里也是先实例一个对象,然后加入到 importBeanDefinitionRegistrars 里面,后续会在 ConfigurationClassBeanDefinitionReader 这个类里面的 loadBeanDefinitionsFromRegistrars 方法处理的
  6. 如果上面两种类型都不是,那就是当初普通的带有 @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>
  • 6
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值