1、前言

从Spring转到SpringBoot的xdm应该都有这个感受,以前整合Spring + MyBatis + SpringMVC我们需要写一大堆的配置文件,堪称配置文件地狱,我们还要在pom.xml文件里引入各种类型的jar包,Mybatis的、SpringMVC的、Spring-aop的,Spring-context等等。

自从使用SpringBoot后,新建一个项目几乎不需要做任何改动,我们就可以运行起来。pom文件里,我们只需要引入一个spring-boot-starter-web就可以,之前我们所做的一切,SpringBoot都在底层帮我们做了。

写过SSM的xdm应该都记得dispatcherServlet和characterEncoding,这是我们在web.xml中必须配置的两个选项,我们还需要配置文件上传解析器multipartResolver,需要配置数据源druidDataSource,如果解析jsp,还需要配置视图解析器viewResolver。。。到最后,你就会有一坨的配置文件。

然后我们使用SpringBoot后,好像也从来没有配置过这些东西了,以前闭着眼睛都能写出来的各种配置文件,突然之间好像离我们很遥远了,而我们也渐渐忘记了各种Filter(过滤器)和Interceptor(拦截器)的名字。这既是SpringBoot简化了配置带给我们的好处,也是它带来的坏处。

这篇文章就一起来学习一下SpringBoot是如何做依赖管理以及自动配置的。

2、依赖管理

2.1 父项目做依赖管理

每个SpringBoot项目,pom.xml文件都会给我们定义一个parent节点

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

该节点指定了version版本号,所以在pom.xml文件里我们很多引入的jar都没有定义版本号,但这样也不会出错,因为SpringBoot帮我们为一些常用的jar包指定了版本号。

ctrl + 鼠标右键点击进入spring-boot-starter-parent这个jar包,会发现它的父项目是spring-boot-dependencies

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.1.3.RELEASE</version>
<relativePath>../../spring-boot-dependencies</relativePath>
</parent>

而在这个jar包里,就声明了很多开发中常用jar的版本号

所以在你pom.xml文件中引入jar的时候,如果该jar在spring-boot-dependencies中定义了版本号,那么你可以不写。如果你想使用其他的版本号,那么也可以在pom.xml中定义version,遵循就近原则。比如你想使用自定义版本号的MySQL驱动,只需在pom.xml中进行定义

<properties>
<mysql.version>5.1.43</mysql.version>
</properties>

2.2 starter场景启动器

在SpringBoot项目中,我们只需要引入spring-boot-starter-web包就可以写接口并且进行访问,因为在这个starter中整合了我们之前写Spring项目时引入的spring-aopspring-contextspring-webmvc等jar包,包括tomcat,所以SpringBoot项目不需要外部的tomcat,只需要启动application类使用内置的tomcat服务器即可。

在SpringBoot项目中,根据官方文档,有各种场景的spring-boot-starter-*可以使用,只要引入了starter,这个场景所有常规需要的依赖就会自动引入。(https://docs.spring.io/spring-boot/docs/current/reference/html/using.html#using.build-systems.starters)

所有场景启动器最底层的依赖就是spring-boot-starter,该jar包是核心启动包,包含了自动配置的支持,日志以及YAML。Core starter, including auto-configuration support, logging and YAML,这是官方对它的描述。

而这个spring-boot-autoconfigure就关系到我们接下来要说的SpringBoot自动配置功能。

3、容器功能

了解SpringBoot的自动配置功能之前,需要先了解一下SpringBoot的容器管理功能。学习Spring的时候就知道,Spring的IOC和AOP。

IOC容器帮助我们存放对象,并且管理对象,包括:创建、装配、销毁,这样就将原本由程序完成的工作交给了Spring框架来完成。学习的核心在于如何将对象放在Spring中以及从Spring中取出。

3.1 SpringBoot的默认包扫描路径

在SpringBoot中,我们没有指定任何一个包的扫描路径,但你注册进容器中的对象却都可以拿到,这是因为SpringBoot有默认的包扫描路径,在这个路径下的目标对象,都会被注册进容器中。默认的扫描路径是Main Application Class所在的目录以及子目录。可以通过scanBasePackages属性改变扫描路径

@SpringBootApplication(scanBasePackages = "xxx.xxx.xxx")

该属性其实和@ComponentScan注解的basePackages属性绑定了,所以使用@ComponentScan也能达到一样的效果。

获取默认扫描路径在代码在ComponentScanAnnotationParser类的parse方法中,在对应的行打上断点,启动主类进行调试

调试后就会发现,其实这个declaringClass就是项目的启动类,然后启动类所在的包就会加入basePackages中。

3.2 组件添加

(1)@Configuration和@Bean

@Configuration注解表示这个类是个配置类,@Bean注解往容器中注册实例。

import com.codeliu.entity.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; @Configuration
public class MyConfig { @Bean
public User user() {
User user = new User("秃头哥", 20);
return user;
}
}

然后在启动类中进行测试,可以发现容器中的实例都是单例的,即多次拿到的都是同一个对象。

@SpringBootApplication
public class DockerTestApplication { public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(DockerTestApplication.class, args);
User user1 = run.getBean("user", User.class);
User user2 = run.getBean(User.class);
// true
System.out.println(user1 == user2);
}
}

@Configuration注解中的proxyBeanMethods属性即代理bean的方法,决定是否是单例模式,默认为true。Full模式(proxyBeanMethods = true)和Lite(proxyBeanMethods = false)模式,Full模式保证每个@Bean方法被调用多少次返回的组件都是单实例的,而Lite模式每个@Bean方法被调用多少次返回的组件都是新创建的。组件依赖必须使用Full模式默认,其他默认是否Lite模式

(2)@Component、@Controller、@Service、@Repository

四大法王,使用在pojo、mapper、service、controller类上的注解。

(3)@Import

该注解定义如下,只有一个value属性,你可以传入一个Class数组,在启动过程中,会自动帮你把类注册进容器。

@Configuration
@Import({User.class, DBHelper.class})
public class MyConfig { }
@SpringBootApplication
public class DockerTestApplication { public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(DockerTestApplication.class, args);
User user1 = run.getBean(User.class);
User user2 = run.getBean(User.class);
// true
System.out.println(user1 == user2); User user = run.getBean(User.class);
// com.codeliu.entity.User@63411512
System.out.println(user); DBHelper dbHelper = run.getBean(DBHelper.class);
// ch.qos.logback.core.db.DBHelper@35cd68d4
System.out.println(dbHelper);
}
}

可以看到,默认组件的名字是全类名。

(4)@Conditional条件装配

意思就是满足@Conditional指定的条件,才进行组件注入。

import com.codeliu.entity.User;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; @Configuration
// 没有名字为test的bean,才进行注册,该注解可以放在类型,也可以放在方法上,作用范围不一样
@ConditionalOnMissingBean(name = "test")
public class MyConfig {
@Bean
public User user() {
User user = new User("秃头哥", 20);
return user;
}
}

其他注解类似。在SpringBoot进行自动配置的时候,底层使用了很多条件装配,达到按需加载的目的。

3.3 原生配置文件引入

@ImportResource注解可以导入Spring的配置文件,让配置文件里的内容生效。因为有些项目bean定义在xml文件里,但你必须知道xml文件的路径,这样在项目启动的时候Spring才会加载配置文件。那对于SpringBoot项目来说,所有的bean都是通过java配置实现,xml没有用武之地了吗?

@Configuration搭配@ImportResource可以实现xml配置的装载。

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource; @Configuration
@ImportResource("classpath:beans.xml")
public class MyConfig { }

3.4 配置绑定

很多时候我们需要读取properties文件中的属性,封装到对应的Java bean中。我们可以通过代码进行读取

public class getProperties {
public static void main(String[] args) throws FileNotFoundException, IOException {
Properties pps = new Properties();
pps.load(new FileInputStream("a.properties"));
Enumeration enum1 = pps.propertyNames();//得到配置文件的名字
while(enum1.hasMoreElements()) {
String strKey = (String) enum1.nextElement();
String strValue = pps.getProperty(strKey);
System.out.println(strKey + "=" + strValue);
//封装到JavaBean。
}
}
}

当配置文件中属性很多的时候,极其不方便。

(1)@Component和@ConfigurationProperties

在Java bean上使用这两个注解,可以和配置文件中的属性相关联,不过要注意的是,Java bean必须有setter/getter方法,否则无法赋值,另外就是配置文件中的属性不能有大写字母,否则启动报错。

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component; @Component
@ConfigurationProperties(prefix = "my-user")
public class User {
private String names;
private int age;
....
}

省略了getter/setter和toString方法。@ConfigurationProperties注解中指定配置文件中相关属性的前缀,在配置文件中

my-user.names=CodeTiger
my-user.age=22

启动类中进行测试获取user对象,输出就会发现属性已经赋值。

(2)@ConfigurationProperties和@EnableConfigurationProperties

@ConfigurationProperties注解加载java bean上指定前缀,而@EnableConfigurationProperties注解则加在配置类上,该注解有两个作用:开启配置绑定功能、把指定的java bean注册到容器中。因为该注解会把java bean注册到容器中,所以在java bean上就不需要加@Component注解了。

@Configuration
@EnableConfigurationProperties({User.class})
public class MyConfig { }

4、自动配置原理

了解了上面的知识,就来学习一下SpringBoot底层是如何帮我们自动配置bean的。从加在启动类上的@SpringBootApplication注解开始。

上面四个注解跟我们本次学习无关,可以忽略。

(1)@SpringBootConfiguration

查看该注解的定义,发现其上标有@Configuration,并且里面有一个唯一的属性即proxyBeanMethods。前面我们讲@Configuration注解的时候讲过这个属性,这里就不重复讲了。这说明被@SpringBootConfiguration修饰的类也是一个配置类。

(2)@ComponentScan

指定扫描哪些Spring注解。

(3)@EnableAutoConfiguration

这是SpringBoot自动配置的入口,该注解定义如下

4.1 自动配置basePackage

@AutoConfigurationPackage注解,顾名思义,自动配置包。

@Import注解前面讲过,将一个组件注入容器中,所以我们看看AutoConfigurationPackages.Registrar长啥样子。它调用了register方法进行组件的注册,那么是注册哪里的组件呢?看它第二个参数,是去获取basePackage,所以可以猜出@AutoConfigurationPackage注解应该是在启动项目的时候,自动把默认包或者我们指定的包路径下面的组件注册进容器。

我们进入PackageImports类,在对应行上打上断点进行调试,看看项目启动后这些值是什么

可以发现,它会拿到启动类所在的包路径,然后返回给register方法作为它的第二个参数传入。这就是为什么在启动时,我们不需要配置任何路径,SpringBoot就可以帮我们把组件注入容器的原因。

4.2 自动配置导入包的配置类

这个是干什么的呢?前面我们说过,在Spring中我们会配置dispatcherServlet和characterEncoding等需要的组件,但在SpringBoot中,我们却啥都没做。

因为在SpringBoot底层帮我们做了。就是EnableAutoConfiguration注解上标注的@Import(AutoConfigurationImportSelector.class)注解。再贴一遍

所以就得去看看AutoConfigurationImportSelector类了。

AutoConfigurationImportSelector类中有一个getAutoConfigurationEntry方法,该方法就是给容器中批量导入一些组件。那么是导入哪些组件呢?在该方法中,拿到一个configurations,然后对configurations又是去重又是删除。那获取看看这个变量里面存的是个啥玩意就明白了。

在相应行打上断点,运行后

这是啥?一个长度为130的数组,而里面都是一些AutoConfiguration,而且我们还看到了熟悉的AopAutoConfiguration。往下找找,发现还有我们熟悉的DispatcherServlet

那么这些自动配置类是从哪里读取的呢?看方法里的getCandidateConfigurations方法

该方法中调用loadFactoryNames方法,而loadFactoryNames方法则调用loadSpringFactories方法, 利用工厂加载得到所有 META-INF/spring.factories文件中的组件。

META-INF/spring.factories文件存在于我们导入的jar包。它会扫描所有jar包中的 META-INF/spring.factories文件,然后进行去重以及移除掉我们exclude掉的组件。

在我测试的项目中,获取到的组件数目为130,就是在 spring-boot-autoconfigure-2.4.4.jar包中,里面刚好有130个组件。

到这里,总结一下大致的流程如下:

(1)利用getAutoConfigurationEntry(annotationMetadata);给容器中批量导入一些组件。

(2)调用List configurations = getCandidateConfigurations(annotationMetadata, attributes)获取到所有需要导入到容器中的配置类。

(3)利用工厂加载 Map<String, List> loadSpringFactories(@Nullable ClassLoader classLoader);得到所有的组件。

(4)从META-INF/spring.factories位置来加载一个文件。默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件。

4.3 按需开启自动配置项

SpringBoot在启动的时候为我们加载了这么多组件,我们不可能全部用得上,那如果用不上的还注册进容器,岂不是耗费资源。其实底层使用了条件装配@Conditional,在我们需要的情况下才会注册对应的组件。

在我测试的项目中,因为启动的时候都是加载的 spring-boot-autoconfigure-2.4.4.jar包中的组件,所以我可以去看看该jar包中的xxxAutoConfiguration的源码。比如AopAutoConfiguration

在项目中,如果我们没有引入aspectj的jar,就不会有Advice类,那么jdk动态代理和cglib代理都不会生效。而此时生效的是基础代理,只作用于框架内部的advisors,项目中我们自定义的切面是不会被AOP代理的。

其他AutoConfiguration也是类似的,这里就不一一看了。

4.4 用户优先

啥叫用户优先?就是SpringBoot底层虽然会为我们自动加载组件,但如果我们想用我们自己定义的呢?来看看HttpEncodingAutoConfiguration

首先应用是Servlet应用以及存在CharacterEncodingFilter类的时候,才会进行注册。而且该类和配置文件进行了绑定,可以在配置文件中对属性进行赋值。在注册CharacterEncodingFilter的时候,如果系统中不存在这个bean的时候,才会进行注册,防止重复注册,并且组件的值是进行动态赋值的,即如果我们编码不想使用utf-8,那我们可以在配置文件中进行修改,系统注册时候,就会使用我们自定义的值。

根据官方文档,有以下属性可以进行设置。

5、总结

本来主要分析了SpringBoot是如何进行依赖管理和自动配置的,相比于Spring,很多工作都是在底层帮我们做了。虽然我们写代码可能用不上这些,但知其然并且知其所以然,纸上得来终觉浅,绝知此事要躬行。

面试官:给我讲讲SpringBoot的依赖管理和自动配置?的更多相关文章

  1. SpringBoot原理深入及源码剖析(一) 依赖管理及自动配置

    前言 传统的Spring框架实现一个Web服务需要导入各种依赖jar包,然后编写对应的XML配置文件等,相较而言,SpringBoot显得更加方便.快捷和高效.那么,SpringBoot究竟是如何做到 ...

  2. 使用maven-pom进行依赖管理与自动构建

    使用maven-pom进行依赖管理与自动构建 span.kw { color: #007020; font-weight: bold; } /* Keyword */ code > span.d ...

  3. springboot web项目创建及自动配置分析(thymeleaf+flyway)

    @ 目录 源码分析 webjars thymeleaf thymeleaf语法 springmvc 启动配置原理 集成flyway插件 springboot 创建web项目只需要引入对应的web-st ...

  4. SpringBoot入门(四)——自动配置

    本文来自网易云社区 SpringBoot之所以能够快速构建项目,得益于它的2个新特性,一个是起步依赖前面已经介绍过,另外一个则是自动配置.起步依赖用于降低项目依赖的复杂度,自动配置负责减少人工配置的工 ...

  5. SpringBoot | 4.1 SpringMVC的自动配置

    目录 前言 1. SpringMVC框架的设计与流程 1.1 SpringMVC框架的示意图 1.2 SpringMVC的组件流程 2. *自动配置的源码分析 2.1 导入Web场景启动器 2.2 找 ...

  6. SpringBoot Beans管理和自动配置

    原 SpringBoot Beans管理和自动配置 火推 02 2017年12月20日 21:37:01 阅读数:220 SpringBoot Beans管理和自动配置 @SpringBootAppl ...

  7. 接管SpringBoot对Activiti的数据源自动配置

    SpringBoot的自动配置真的让人又爱又恨,但还是爱更多一点. SpringBoot想要帮我们自动配置好一切,但是有时候配置的却并不是我们需要的,甚至有时候会默默的坑我们. 我的项目是一个多数据源 ...

  8. SpringBoot 2.X集成 jdbc自动配置原理探究

    前言 Springboot对于数据访问层,不管是 SQL还是 NOSQL,Spring Boot 底层都是采用 Spring Data 的方式统一处理.Spring Data 是 Spring 家族中 ...

  9. springboot(八) 嵌入式Servlet容器自动配置原理和容器启动原理

    1.嵌入式Servlet容器自动配置原理 1.1 在spring-boot-autoconfigure-1.5.9.RELEASE.jar => springboot自动配置依赖 jar包下,E ...

随机推荐

  1. node-redis基本操作

    //npm install redis var redis = require("redis"), client = redis.createClient(); client.se ...

  2. 3-5年以上的Android原生开发如何深入进阶?高级工程师必须要掌握哪些?

    前言 曾听过很多人说Android学习很简单,做个App就上手了,工作机会多,毕业后也比较容易找工作.这种观点可能是很多Android开发者最开始入行的原因之一. 在工作初期,工作主要是按照业务需求实 ...

  3. 【python】Leetcode每日一题-颠倒二进制位

    [python]Leetcode每日一题-颠倒二进制位 [题目描述] 颠倒给定的 32 位无符号整数的二进制位. 示例1: 输入: 00000010100101000001111010011100 输 ...

  4. Windows bat批处理删除指定N天前的文件

    1:新建批处理文件:del_old_file.bat,更改系统时间为7天前,在c盘sql back 目录下新建测试文件,再将系统时间改为正确时间 2:编辑内容: rem 删除C:\sql back目录 ...

  5. VS2010编写32位和64位dll环境配置,以及判断dll是多少位

    前言 最近在编写dll注入程序的时候,因为想注入到系统进程,结果发现总是注入失败.自闭了好长一会发现我忘记了我的系统是64位的,系统进程也是64位的,而我编写的待注入的DLL是32位了,所以才导致失败 ...

  6. 正则表达式、编辑器(vi、sed、awk)

    1. vi 2. 正则表达式 3. sed 1)打印命令:p 2)删除命令:d 3)替换命令:s 4. awk 1)awk 基本用途 2)匹配打印 3)判断打印 4)数组 1. vi vi 是 Lin ...

  7. istio sidecar流量处理机制及配置

    sidecar 介绍 在istio的流量管理等功能,都需要通过下发的配置应用到应用运行环境执行后生效,负责执行配置规则的组件在service mesh中承载应用代理的实体被称为side-car Ist ...

  8. [Web] 网络安全(SSH SSL HTTPS)

    概念 SSH(Secure Shell) 一种安全通信协议 为shell提供加密通信服务 使用了非对称加密和对称加密 对称加密(Symmetric-Key Encryption):只用一个密钥来进行加 ...

  9. 华为eNSP模拟器— telnet实验

    华为eNSP模拟器-telnet实验 一.实验一 路由交换之间实现telnet登陆 实验拓扑 实验目的: 路由器作为 telnet 服务器 交换机作为客户端去连接路由器 实验步骤: 路由器配置 < ...

  10. Kali Linux 安装中文输入法

    1.设置源 vim /etc/apt/sources.list 添加一行 deb http://mirrors.aliyun.com/kali kali-rolling main non-free c ...