SpringBoot-自动装配对象及源码ImportSelector分析
SpringBoot框架已经很流行了,笔者做项目也一直在用,使用久了,越来越觉得有必要理解SpringBoot框架中的一些原理了,目前的面试几乎都会用问到底层原理。我们在使用过程中基本上是搭建有一个框架拿来现用,在此过程中遇到问题就去百度来解决相应的问题,但是,对其原理不理解的情况下,虽然问题能够解决,还是不会有多大收获。下次再遇到问题的时候仍感觉力不从心。在了解了相关问题及解决方案之后,笔者总结了一些原理,这里作为学习笔记,与大家共勉。
一、Springboot环境搭建
这里我是用的环境及开发工具是JDK8+IntelliJIDEA
(1)创建Spring Boot项目



名称根据需要进行更改。


输入项目名称或默认,点Finish。至此,SpringBoot工程创建完毕,结构如下:

二、Spring Boot使用
工程创建完毕之后,我们在项目中创建一个类:Animal,

那么如果想要把这个类交给Spring去管理,怎么办呢?使用过SpringBoot的人都会,我们通过Animal类上面加上@component注解

加了这个注解之后,Spring在启动的时候就会扫描该类,完成初始化,并把它放入Spring容器中,启动,看效果如下:

非常简单的方式就实现了,仅仅是通过一个注解,那么,这个Animal类,通过这样一个注解是怎么交给Spring去管理的呢,中间的过程经历了什么呢?内部又是怎样的一种机制呢?为了看到它的实现过程,这里面主要是两个注解,@SpringBootApplication 和@EnableAutoConfiguration。这两个注解都是复合注解。
2.1 @SpringBootApplication实现原理
点击进去看@SpringBootApplication的实现过程:

这里面有很多注解,首先来看@SpringBootConfiguration这个注解,从字面来看,他应该是一个配置注解,通过实现过程,我们知道,他应该就是Spring提供的一个配置注解。
,
那么,这个注解有什么作用呢?其实我们也用到过,来演示一下他的使用。首先,建立一个config包,然后我们在包里创建两个类:User,Car,如图:


这两个类没有加入任何注解,我们在MyConfig这个配置类中,通过手动装配的方式,进行类的初始化,即使用@Configuration这个注解和@Bean注解的方式,启动项目运行,效果如下:如图:

通过这样的方式,我们照样可以将自定义Bean交给Spring容器去管理。这里需要注意的是config这个包一定要和启动类在同一个文件夹下,否则,不加指定扫描的包,Spring默认是不会扫描到的。其实,将@Configuration换成@SpringBootConfiguration效果是一样的,因为后者是一个复合注解,只不过是多包装了一层而已。那么该注解的作用就很明显了,就是将@configuration注解下面所有带有Bean注解的对象进行装配就交给Spring容器去管理。这就是SpringBoot框架自动装配的一部分。SpringBoot在启动的时候,会将很多的Bean进行自动装配,通过什么方式呢?打开源码:

我们看到,其实这里面配置了很多自动装配的类,当我们启动服务的时候,他会扫描这个配置文件当中的所有配置项进行自动装配,这里面包含我们几乎能用到的所有组件、包括Redis、Elasticsearch、JDBC等。但是我们知道SpringBoot在启动的时候并不会把所有的类都一起初始化加入到容器当中,这个是有前提条件的,我们随便点进去一个:

我们注意到,哪个类会被自动装配是会有条件的,从@ConditionalOnClass来看,条件就是该类存在,并且在Spring容器中存在实例的情况下才会进行装配进而运行装配类。比如说,我在项目中用到了Elastic search,那么我们通过@Configuration下的@Bean配置就会加载该实例,这是一个实际应用:

2.2 @EnableAutoConfiguration实现原理
从上面的分析可知,我们启动类中的@EnableAutoConfiguration注解它会将所有满足条件的Bean进行自动装配。那么问题又来了,@EnableAutoConfiguration它又是如何实现的呢?继续看实现过程:


即,通过@import将所有SpringBoot需要装配的类导入进来。那么@Import该如何理解呢,来看个例子,我们把MyConfig中的@Configuration注解去掉,这时候,启动项目,User类和Car类都不会被加载


但是,我们还想使用这两个类,怎么办呢?通过@Import注解来实现。首先我们看@Import它的实现过程:需要传入Class类型的参数,是一个数组,那么在启动类中加入这个注解看一下运行效果:



通过这种方式,同样,实现了Bean的初始化。那么这样的原理是什么呢?也就是说SpringBoot在启动的时候会默认拿到和启动类在同一级的文件夹(包路径),然后对其所有带有注解的类进行扫描,接着,使用AutoConfigurationImportSelector.class这个类加载,该类实现了DeferredImportSelector,DeferredImportSelector继承了ImportSelector,这就是自动导入的原理了。


这个类中有一个方法:selectImports,该方法返回一个String类型的数组,该数组中放的就是自动装配进来的包路径了,作用找到满足配置的所有带有注解的类,然后交由Spring去管理。

了解了这个原理之后,那么,我们就可以自己来实现把某一些类交给Spring去管理的方法了。
首先,去掉配置类中的@Configuration注解以及类中的@compoent注解,这时候运行项目,我们的自定义类是无法交给Spring去管理的,接下来,我们自己来实现,模拟SpringBoot的类加载过程:
然后,创建自定义类ModelImportSelector并实现ImportSelector接口并重写selectImports方法,重写该方法的目的就是把返回结果中的每一个元素,即类的全名称,交给Spring容器去管理。我们放两个类进去,分别是
Animal,Car,然后在启动类中通过import注解引入,过程如下:
public class ModelImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
return new String[]{
"com.springboot.demo.model.Animal",
"com.springboot.demo.model.Car"
};
}
}
@SpringBootApplication
@Import(ModelImportSelector.class)
public class DemoApplication { public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run( DemoApplication.class, args );
context.close();
}
}
运行结果:

这种方式实现了将我们想要交给Spring管理的类进行了托管,但是,如果有N多个类的话,这种写法会累死的,因此,需要通过递归的方式加载包路径名,然后统一初始化,将该包路径下的所有类进行湿实例化。首先我们自己创建一个注解:MyImport,并加入Spring的@Import注解,然后将启动类中的@Import注解改成我们自定义注解。
目的:扫描 "com.springboot.demo.model"下面的所有类,初始化并交给Spring容器管理
自定义注解:
**
* Created by ${USRE} on 2019/6/20 on 14:46
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ModelImportSelector.class)
public @interface MyImport {
String [] packages();
}
启动类加入自定义注解:
@SpringBootApplication
@MyImport(packages = "com.springboot.demo.model")
public class DemoApplication { public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run( DemoApplication.class, args );
context.close();
}
重写selectImports方法:
private List<String> classList=new ArrayList <>();
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) { //通过字节码文件解读,获取元素据信息,通过注解名称,返回注解所对应的属性的Map集合
String [] packages = (String [])annotationMetadata.getAnnotationAttributes( MyImport.class.getName() ).get( "packages" );
if (packages == null) {
return null;
}
scanPackagesRecursion(packages);
//获取注解中packages中配置的内容
//获取需要扫描的包的所有类的路径
return !classList.isEmpty()?classList.toArray( new String[classList.size()] ):null;
} private void scanPackagesRecursion(String [] packages){
for(String path : packages){
doScanPackages(path);
}
} /**
* 方法递归
* @param path
*/
private void doScanPackages(String path){
URL resource = this.getClass().getClassLoader().
getResource( path.replaceAll( "\\.", "/" ) );
File file = new File( resource.getFile() );
File[] files=file.listFiles();
for (File fileSub : files) {
if(fileSub.isDirectory()){
doScanPackages(path+"."+fileSub.getName());
}else{
String fileName=fileSub.getName();
System.out.println("fileName:"+fileName);
if(fileName.endsWith( ".class" )){
String classPath=path+"."+fileName.replaceAll( "\\.class","" );
this.classList.add( classPath );
System.out.println("classPath:"+classPath);
}
}
}
}
运行效果:

三、总结
这里总结了几种将我们自定类交给Spring管理的方式,分别是:一、通过@Component注解,让Spring自动扫描完成配置管理自动装配;二、通过@Import注解,加入包路径,让Spring扫描,然后进行自动装配;三、通过,创建配置类,通过@Configuration和@Bean完成类的加载和自动装配。不管是哪一种方式,我们都可以完成自定义类由Spring容器去管理,理解了这种方式之后,我们再也不需要,使用类的时候new对象了,需要哪些对象,只需要从Spring容器中获取便可。
源码地址:https://files-cdn.cnblogs.com/files/10158wsj/SpringbootDemo.zip
SpringBoot-自动装配对象及源码ImportSelector分析的更多相关文章
- Spring笔记(2) - 生命周期/属性赋值/自动装配及部分源码解析
一.生命周期 @Bean自定义初始化和销毁方法 //====xml方式: init-method和destroy-method==== <bean id="person" c ...
- SpringBoot源码学习1——SpringBoot自动装配源码解析+Spring如何处理配置类的
系列文章目录和关于我 一丶什么是SpringBoot自动装配 SpringBoot通过SPI的机制,在我们程序员引入一些starter之后,扫描外部引用 jar 包中的META-INF/spring. ...
- 一步步从Spring Framework装配掌握SpringBoot自动装配
目录 Spring Framework模式注解 Spring Framework@Enable模块装配 Spring Framework条件装配 SpringBoot 自动装配 本章总结 Spring ...
- Spring Boot之从Spring Framework装配掌握SpringBoot自动装配
Spring Framework模式注解 模式注解是一种用于声明在应用中扮演“组件”角色的注解.如 Spring Framework 中的 @Repository 标注在任何类上 ,用于扮演仓储角色的 ...
- springboot自动装配原理
最近开始学习spring源码,看各种文章的时候看到了springboot自动装配实现原理.用自己的话简单概括下. 首先打开一个基本的springboot项目,点进去@SpringBootApplica ...
- SpringBoot | 2.1 SpringBoot自动装配原理
@ 目录 前言 1. 引入配置文件与配置绑定 @ImportResource @ConfigurationProperties 1.1 @ConfigurationProperties + @Enab ...
- springboot自动装配
Spring Boot自动配置原理 springboot自动装配 springboot配置文件 Spring Boot的出现,得益于“习惯优于配置”的理念,没有繁琐的配置.难以集成的内容(大多数流行第 ...
- SpringBoot启动流程分析(五):SpringBoot自动装配原理实现
SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...
- SpringBoot自动装配原理解析
本文包含:SpringBoot的自动配置原理及如何自定义SpringBootStar等 我们知道,在使用SpringBoot的时候,我们只需要如下方式即可直接启动一个Web程序: @SpringBoo ...
随机推荐
- Windows10 图标变白修复
Windows10 图标变白修复 本文作者:天析 作者邮箱:2200475850@qq.com 发布时间: Tue, 16 Jul 2019 10:54:00 +0800 这种问题多半是ico缓存造成 ...
- 第三篇:Python基本数据类型
在了解基本数据类型的时候,我们需要了解基本数据类型有哪些?数字int.布尔值bool.字符串str.列表list.元组tuple.字典dict等,其中包括他们的基本用法和其常用的方法,这里会一一列举出 ...
- 【DRF框架】序列化组件——ModelSerializer
ModelSerializer 1.ModelSerializer类似于ModelForm 2.根据模型自动生成一组字段 3.自带实现了.update()以及.create()方法 ModelSeri ...
- LeetCode:137. 只出现一次的数字 II
LeetCode:137. 只出现一次的数字 II 给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现了三次.找出那个只出现了一次的元素. 说明: 你的算法应该具有线性时间复杂度. ...
- 用js刷剑指offer(二叉搜索树的后序遍历序列)
题目描述 输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果.如果是则输出Yes,否则输出No.假设输入的数组的任意两个数字都互不相同. 牛客网链接 js代码 function Verif ...
- 第八届蓝桥杯C/C++程序设计本科B组决赛 ——发现环(编程大题_签到题_tarjan判环)
标题:发现环 小明的实验室有N台电脑,编号1~N.原本这N台电脑之间有N-1条数据链接相连,恰好构成一个树形网络.在树形网络上,任意两台电脑之间有唯一的路径相连. 不过在最近一次维护网络时,管理员误操 ...
- 详细讲解vue.js里的父子组件通信(props和$emit)
在进入这个话题之前,首先我们先来想一下在vue里,如何写一个父子组件.为了简单起见,下面的代码我都没用脚手架来构建项目,直接在html文件里引入vue.js来作为例子.父子组件的写法如下: <d ...
- tomcat启动之后,Chrome浏览器可以访问,IE不行(IE无法访问8080 端口)
方法简单粗暴,在windows中关闭IE服务,然后再重新安装服务. 请注意,在输入框输入: http://localhost:8080/myproject 不要直接输入localhost:8080/ ...
- Java 重要知识点,踩过的坑
(1),关于 LinkedHashMap TreeMap HashMap 之间的区别: HashMap 是无序的,LinkedHashMap 由于内部维护了一个记录的链表,数据操作的前后顺序都会在链 ...
- boost::swap
boost::swap是对标准库里的std::swap的增强和泛化,为交换两个变量的值提供便捷的方法. 为了使用需包含头文件: #include <boost/swap.hpp> 原理 c ...