1、SpringBoot的依赖管理机制
1.1、问题引出
首先,在我们上一节创建一个SpringBoot项目的时候,我们在pom文件里面导入了SpringBoot的jar配置相关依赖 ,如下所示: 引入了父工程和支持web开发的依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.4</version>
</parent>
<dependencies>
<!--支持web开发的依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
复制代码
就仅仅配置了这两个依赖,我们所有要用的jar包就进来了,开发过程中也无需关心任何导包问题,就可以进行项目的开发了,那其中的原因是什么呢?
1.2、父项目做依赖管理
首先,我们来分析一下它的父项目parent,每一个Spring Boot项目的Maven依赖中都具有一个父项目,父项目就是用来做依赖管理的,从而引入的任何Spring Boot项目依赖的版本均参照父项目中的version
,从而其他项目配置就不需要再写版本号了,比如我们上面写的web的依赖,spring-boot-starter-parent
依赖作为 Spring Boot 项目的统一父项目依赖管理;
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.4</version>
</parent>
复制代码
我们来深入探究一下为什么引入Spring Boot
项目的这些依赖不用写版本号,父项目又管理了什么,按住ctrl,点击spring-boot-starter-parent
,就可以进入其底层源文件,发现 spring-boot-starter-parent
底层还有一个父项目 spring-boot-dependencies
;
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.6.4</version>
</parent>
复制代码
spring-boot-dependencies
几乎声明了所有开发中常用的依赖的版本号,进入spring-boot-dependencies
底层源文件,其核心代码如下:
<properties>
<activemq.version>5.16.4</activemq.version>
<antlr2.version>2.7.7</antlr2.version>
<appengine-sdk.version>1.9.95</appengine-sdk.version>
<artemis.version>2.19.1</artemis.version>
<aspectj.version>1.9.7</aspectj.version>
<assertj.version>3.21.0</assertj.version>
<atomikos.version>4.0.6</atomikos.version>
<awaitility.version>4.1.1</awaitility.version>
<build-helper-maven-plugin.version>3.2.0</build-helper-maven-plugin.version>
<byte-buddy.version>1.11.22</byte-buddy.version>
<caffeine.version>2.9.3</caffeine.version>
<cassandra-driver.version>4.13.0</cassandra-driver.version>
<classmate.version>1.5.1</classmate.version>
.....
复制代码
从底层 spring-boot-dependencies
底层源文件可以看出,该文件通过标签对一些常用技术框架的依赖文件进行了统一版本号管理,依赖管理里面,父项目把我们常用开发中的 jar 包版本都帮我们弄好了,并且这些版本都是我们当前 SpringBoot环境支持的版本。如下:
这也就是pom.xml引入了父项目之后下面的所有依赖文件不需要标注依赖版本号的原因。 比如logback
包的版本约束为<logback.version>1.2.10</logback.version>
,并且对于所有的logback
依赖均使用这一约束:
再比如说,我们也不用写MySQL驱动的版本号,因为父项目已经引入了,如下:
<mysql.version>8.0.28</mysql.version>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
<exclusions>
<exclusion>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
</exclusion>
</exclusions>
</dependency>
复制代码
从而在项目Pom文件引入mysql坐标不用写版本号,如下
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
复制代码
总结:
通过层层继承,当在引用商用的依赖时无需登记版本号即可实现对应项目的Spring Boot的版本约束,这也就是Spring Boot的自动版本仲裁机制。spring-boot-starter-parent父依赖启动器的主要作用是进行版本统一管理;父项目中可能会生成非常多的依赖,子项目【都是某一个框架的】只要继承了这个父项目,子项目以后写依赖就不需要版本号了。
比如我要在pom文件中引入上面logback-access
的依赖,我就不需要写版本号了,因为父项目里面已经帮我们配置好了,也支持该环境下的版本号【引入非版本仲裁的jar,要写版本号。】
1.3、引入非版本仲裁的 jar
从上面叙述可知,我们的Mysql的依赖引入对应的父项目里面设置是8版本的
但是我们安装的客户端是5.7版本【驱动为5.1的】的,我们就得改父项目里面配置的版本约束。即需要修改自动版本仲裁的版本约束时,在pom文件中声明properties并引入需要的版本号即可:具体步骤如下:
- 查看 spring-boot-dependencies 里面规定当前依赖的版本。
- 搜索Maven仓库,找到满意的驱动。Maven Repository: Central (mvnrepository.com)
- 如果我们想要的版本号与自动仲裁的版本号不一样,我们可以修改驱动。在 pom.xml 文件中,重写
properties
- 如需要引入版本号为
5.1.43
的MySQL驱动,在pom中声明即可:
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.43</version>
</dependency>
复制代码
声明方式如下:使用<mysql.version>
属性
<properties>
<mysql.version>5.1.43</mysql.version>
</properties>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
复制代码
而后刷新maven依赖查看External Libraries
可以看到MySQL版本已经变更为5.1.43
1.4、场景Starter依赖启动器
以上面为例,我们开发一个 web 应用,无需关心到底要导入哪些 jar 包,只需要在 pom.xml 文件里导入 spring-web
相关场景:spring-boot-starter-web
,只要引入该starter
,这个场景的所有常规需要的依赖我们都自动引入。
<dependencies>
<!--支持web开发的依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
复制代码
那么项目运行依赖的 其他 jar
包是从何而来?进入 spring-boot-starter-web
底层源文件,核心代码如下:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.6.4</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
<version>2.6.4</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>2.6.4</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.3.16</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.16</version>
<scope>compile</scope>
</dependency>
</dependencies>
复制代码
也可以通过右键 spring-boot-starter-web
-> Diagrams
-> Show Dependencies
,查看分析依赖树;
可以看出,spring-boot-starter-web
依赖启动器主要作用是提供了 web 开发场景需要的底层所有依赖。所以在pom.xml
中引入 spring-boot-starter-web
依赖启动器时,就可以实现 web 场景开发,而不需要额外导入 Tomcat 服务器依赖以及其他 web 依赖文件。
但是,这些引入依赖的文件版本还是由 spring-boot-starter-parent
进行统一管理。
1.5、开发导入 starter 场景依赖启动器
Spring Boot 除了提供上述 web 场景的依赖启动器以外,还提供了许多场景所需要的依赖 spring-boot-starter-*
: *
代表各种应用场景,可以简化开发
- 只要引入 starter,这个场景的所有常规需要的依赖都自动引入。
- SpringBoot所有支持的场景依赖启动器参考地址:docs.spring.io/spring-boot…
补充:
见到的 *-spring-boot-starter
: 这是第三方为我们提供的简化开发的场景依赖启动器,例如:mybatis-spring-boot-starter
等。在需要的时候直接在 pom.xml
文件中导入即可,但是需要自己管理版本号。
Spring Boot官方文档中给予的解释为:
- The starters contain a lot of the dependencies that you need to get a project up and running quickly and with a consistent, supported set of managed transitive dependencies.
- 也就是说每一个
starter
都是一组依赖集合,提供了针对某种应用场景所需要的所有依赖集合,初级阶段学习某一个应用场景时引入对应的starter
即可实现所需依赖的引入了。在后期也可以创建自己定义的starter
,不过自定义的命名都是*-spring-boot-starter
这样
点击上文web starter
中的spring-boot-starter-web
即可查看web starter
的配置,其中可以看到如下配置的依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.6.4</version>
<scope>compile</scope>
</dependency>
复制代码
在任何一个starter场景启动器下均包含该依赖,这就是spring自动配置的核心依赖,也是所有场景配置器的基本依赖。 所有场景启动器最底层的依赖都是spring-boot-starter
1.6、依赖管理总结
-
父项目做依赖管理
-
开发导入starter场景启动器
-
无需关注版本号,自动版本仲裁
- 引入依赖默认都可以不写版本
- 引入非版本仲裁的jar,要写版本号
-
可以修改默认版本号
2、SpringBoot的自动配置特性
通过上节的 HelloWorld-web项目,可以体会到,SpringBoot 帮我们配置好了以下这些:
1、自动配好 Tomcat
引入 Tomcat 依赖(引入 spring-boot-starter-web 开发场景,就默认引入了 Tomcat 场景);
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>2.6.4</version>
<scope>compile</scope>
</dependency>
复制代码
然后就是自动配置 Tomcat 参考后面章节源码分析
2、自动配好 SpringMVC
- 引入 SpringMVC 开发的全套组件(引入 spring-boot-starter-web 开发场景,就默认引入了 spring-web、spring-webmvc 场景);
- 自动配好了 SpringMVC 常用组件(功能),如:字符编码问题的过滤器、DispatcherServlet、ViewResolver视图解析器等。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.3.16</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.16</version>
<scope>compile</scope>
</dependency>
复制代码
3、自动配好 Web 常见功能
SpringBoot 帮我们配置好了所有 Web 开发的场景。解决了以前SpringMVC中的 dispatcherServlet
组件、characterEncodingFilter
(字符标码拦截器解决字符乱码问题)、ViewResolver
(视图解析器)、multipartResolver
(文件上传解析器)的配置
我们可以在启动类通过下面程序来获取查看
@SpringBootApplication
public class MainApplication{
public static void main(String[] args) {
//1.返回我们IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
//2.容器里面包含我们当前应用的所有组件,
//查看容器里边的组件,只要有我们这个组件,说明这个组件就能工作,这就是原理
String[] names = run. getBeanDefinitionNames();//获取所有我们组件定义的名字;
for (String name : names) {
System.out.println(name);
}
}
}
复制代码
结果:
并且,如果我们让服务器返回中文字符串,也不会出错,没有中文乱码
4、默认的包结构
我们没有配置任何包扫描规则,但是主程序所在包及其下面的所有子包里面的组件都会被默认扫描进来;官方指示如下:
com
+- example
+- myapplication //主程序所在的包
+- MyApplication.java //主程序,启动类
|
+- customer
| +- Customer.java
| +- CustomerController.java
| +- CustomerService.java
| +- CustomerRepository.java
|
+- order
+- Order.java
+- OrderController.java
+- OrderService.java
+- OrderRepository.java
复制代码
The MyApplication.java
file would declare the main
method, along with the basic @SpringBootApplication
, as follows:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
复制代码
查看我们的项目结构,就是这样的
所以无需以前的包扫描配置 ,主程序所在包及其下面的所有子包里面的组件都会被默认扫描进来,但是若不将其他程序(如控制器代码)和主程序放在一起,即不和主程序放在同一个包或其所在的包的子包下,违反默认的包结构,如下 :
如果这样还想运行成功的话:
- 法1:启动类(主程序)修改
@SpringBootApplication
注解的默认配置【前面讲过使用默认结构就用它】,改变扫描路径,使扫描包的层级放大,即:@SpringBootApplication(scanBasePackages = “com.atguigu”)
;
@SpringBootApplication(scanBasePackages = "com.atguigu")
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
复制代码
- 法2:启动类(主程序)使用
@ComponentScan
包扫描,来指定扫描路径。由于@SpringBootApplication
是@SpringBootConfiguration
、@EnableAutoConfiguration
、@ComponentScan
三个注解的合并注解,所以所以直接改里面的@ComponentScan
扫描路径即可
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.atguigu")
public class MainApplication {
public static void main(String[ ] args){
SpringApplication.run(MainApplication.class, args);
}
}
复制代码
5、各种配置拥有默认值
SpringBoot为各个服务都设置了默认值,比如tomcat是默认8080端口,通过server.port
在 application.properties
中设置,又或者SpringMVC文件上传,Mybatis的配置,具体配置可以查看文档:docs.spring.io/spring-boot…
但是idea有自动提示功能,比如我要看一下SpringMVC文件上传可以配置哪些,如下:
比如配置:文件上传大小
#server.port=8080
#默认是1MB
spring.servlet.multipart.max-file-size=10MB
复制代码
进入该配置源码,发现该默认配置最终都是映射到某个类上,如:MultipartProperties
@ConfigurationProperties(
prefix = "spring.servlet.multipart",
ignoreUnknownFields = false
)
public class MultipartProperties {
private boolean enabled = true;
private String location;
private DataSize maxFileSize = DataSize.ofMegabytes(1L);
private DataSize maxRequestSize = DataSize.ofMegabytes(10L);
private DataSize fileSizeThreshold = DataSize.ofBytes(0L);
private boolean resolveLazily = false;
public MultipartProperties() {
}
public boolean getEnabled() {
return this.enabled;
}
复制代码
默认配置最终都是映射到某个类上,如:MultipartProperties,配置文件的值最终会绑定在某个类上,这个类会在容器中创建对象,对象的值就是和配置文件绑定的值,修改各种配置的默认值,在 application.properties
中修改即可【只有导入了对应的场景,对应的默认配置才会生效,才会在容器中创建对象】
6、按需加载所有自动配置项
SpringBoot引入了非常多的 starter,但是只有引入了哪些场景,哪些场景的自动配置才会开启,不引进来就不会开启,比如我们只引入了web的场景,所以web的自动配置才会开启,SpringBoot 所有的自动配置功能都在 spring-boot-autoconfigure
的jar包里面
重申:自动配置包里面不是所有自动配置类都会生效,只有导入了对应的场景,对应的默认配置才会生效,才会在容器中创建对象,比如我们这个自动配置类里面的批处理类:因为我们并没有导入批处理的starter场景,所以不会在容器中创建对象,它对应的配置就不会生效,配置了也是没有用。这就是按需加载