1.什么是SpringBoot
-
一个javaweb的开发框架,和SpringMVC类似,对比其他javaweb框架的好处,官方说是简化开发,约定大于配置, you can "just run",能迅速的开发web应用,几行代码开发一个http接口。
-
所有的技术框架的发展似乎都遵循了一条主线规律:从一个复杂应用场景 衍生 一种规范框架,人们只需要进行各种配置而不需要自己去实现它,这时候强大的配置功能成了优点;发展到一定程度之后,人们根据实际生产应用情况,选取其中实用功能和设计精华,重构出一些轻量级的框架;之后为了提高开发效率,嫌弃原先的各类配置过于麻烦,于是开始提倡“约定大于配置”,进而衍生出一些一站式的解决方案。
-
是的这就是Java企业级应用->J2EE->spring->springboot的过程。
-
随着 Spring 不断的发展,涉及的领域越来越多,项目整合开发需要配合各种各样的文件,慢慢变得不那么易用简单,违背了最初的理念,甚至人称配置地狱。Spring Boot 正是在这样的一个背景下被抽象出来的开发框架,目的为了让大家更容易的使用 Spring 、更容易的集成各种常用的中间件、开源软件;
-
Spring Boot 基于 Spring 开发,Spirng Boot 本身并不提供 Spring 框架的核心特性以及扩展功能,只是用于快速、敏捷地开发新一代基于 Spring 框架的应用程序。也就是说,它并不是用来替代 Spring 的解决方案,而是和 Spring 框架紧密结合用于提升 Spring 开发者体验的工具。Spring Boot 以约定大于配置的核心思想,默认帮我们进行了很多设置,多数 Spring Boot 应用只需要很少的 Spring 配置。同时它集成了大量常用的第三方库配置(例如 Redis、MongoDB、Jpa、RabbitMQ、Quartz 等等),Spring Boot 应用中这些第三方库几乎可以零配置的开箱即用。
-
简单来说就是SpringBoot其实不是什么新的框架,它默认配置了很多框架的使用方式,就像maven整合了所有的jar包,spring boot整合了所有的框架 。
-
Spring Boot 出生名门,从一开始就站在一个比较高的起点,又经过这几年的发展,生态足够完善,Spring Boot 已经当之无愧成为 Java 领域最热门的技术。
Spring Boot的主要优点:
● 能够快速创建基于Spring的应用程序
● 能够直接使用java main方法启动内嵌的Tomcat服务器运行SpringBoot程序,不需要部署war包文件
● 提供约定的starter POM来简化Maven配置,让Maven的配置变得简单
● 自动化配置,根据项目的Maven依赖配置,Springboot自动配置Spring、Spring mvc等
● 提供了程序的健康检查等功能
● 基本可以完全不使用XML配置文件,采用注解配置
SpringBoot四大核心
● 自动配置
针对很多Spring应用程序和常见的应用功能,SpringBoot能自动提供相关配置
● 起步依赖
告诉SpringBoot需要什么功能,它就能引入需要的依赖库
● Actuator
让你能够深入运行中的SpringBoot应用程序,一探SpringBoot程序的内部信息
● 命令行界面
这是SpringBoot的可选特性,主要针对Groovy语言使用;
Groovy是一种基于JVM(Java虚拟机) 的敏捷开发语言,它结合了Python、Ruby和Smalltalk的许多强大的特性,Groovy 代码能够与Java代码很好地结合,也能用于扩展现有代码,由于其运行在JVM上的特性,Groovy可以使用其他Java语言编写的库。
2.第一个spring boot程序
2.1.使用 IDEA 直接创建项目
1、创建一个新项目
2、选择spring initalizr , 可以看到默认就是去官网的快速构建工具那里实现
3、填写项目信息
4、选择初始化的组件(初学勾选 Web 即可)
5、填写项目路径
6、等待项目构建成功



2.2.pom.xml 分析
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- 父依赖 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.6</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>springbootOne</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springbootOne</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- web场景启动器 -->
<!--web依赖:tomcat,dispatcherServlet,xml...-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--spring-boot-starter:所有的springboot依赖都是使用这个开头的-->
<!-- springboot单元测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<!-- 剔除依赖 -->
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<!-- 打包插件 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
复制代码
-
项目元数据:创建时候输入的Project Metadata部分,也就是Maven项目的基本元素,包括:groupId、artifactId、version、name、description等
-
parent:继承
spring-boot-starter-parent的依赖管理,控制版本与打包等内容 -
dependencies:项目具体依赖,这里包含了
spring-boot-starter-web用于实现HTTP接口(该依赖中包含了Spring MVC),官网对它的描述是:使用Spring MVC构建Web(包括RESTful)应用程序的入门者,使用Tomcat作为默认嵌入式容器。spring-boot-starter-test用于编写单元测试的依赖包。更多功能模块的使用将在后面逐步展开。 -
build:构建配置部分。默认使用了
spring-boot-maven-plugin,配合spring-boot-starter-parent就可以把Spring Boot应用打包成JAR来直接运行。
2.3.编写一个http接口
1、在主程序的同级目录下,新建一个controller包,一定要在同级目录下,否则识别不到


2.4.将项目打成jar包
点击 maven的 package,等待生成。

如果测试用例影响到打包,可以跳过
<!--
在工作中,很多情况下我们打包是不想执行测试用例的
可能是测试用例不完事,或是测试用例会影响数据库数据
跳过测试用例执
-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<!--跳过项目运行测试用例-->
<skipTests>true</skipTests>
</configuration>
</plugin>
复制代码
打成了jar包后,就可以在任何地方运行了

2.5.端口
有没有看到我的端口被占用了

2.6.生成项目模板
为方便我们初始化项目,Spring Boot给我们提供一个项目模板生成网站。
-
打开浏览器,访问:start.spring.io/
-
根据页面提示,选择构建工具,开发语言,项目信息等。
-
点击 Generate the project,生成项目模板,生成之后会将压缩包下载到本地。

3.运行原理探究
我们之前写的HelloSpringBoot,到底是怎么运行的呢,我们从pom.xml文件探究起;
3.1.父依赖
pom.xml
- spring-boot-dependencies:核心依赖在父工程中!
- 我们在写或者引入一些Springboot依赖的时候,不需要指定版本,就因为有这些版本仓库
1、其中它主要是依赖一个父项目,主要是管理项目的资源过滤及插件!
<!-- 父依赖 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.6</version>
<relativePath/>
</parent>
复制代码
2、点进去,发现还有一个父依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.5.6</version>
</parent>
复制代码
3、这里才是真正管理SpringBoot应用里面所有依赖版本的地方,SpringBoot的版本控制中心;
4、以后我们导入依赖默认是不需要写版本;但是如果导入的包没有在依赖中管理着就需要手动配置版本了
3.2.启动器 spring-boot-starter
-
依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> 复制代码 -
springboot-boot-starter-xxx,说白了就是Springboot的启动场景
-
比如spring-boot-starter-web,他就会帮我们自动导入web的所有依赖
-
springboot会将所有的功能场景,都变成一个个的启动器
-
我们要使用什么功能,就只需要找到对应的启动器就好了
start
3.3.主程序
3.3.1.默认的主启动类
package com.example.springbootone;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
//@SpringBootApplication 来标注一个主程序类
//说明这是一个Spring Boot应用
@SpringBootApplication
public class SpringbootOneApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootOneApplication.class, args);
}
}
复制代码
3.3.2注解(@SpringBootApplication)
-
作用:标注在某个类上说明这个类是SpringBoot的主配置
-
SpringBoot就应该运行这个类的main方法来启动SpringBoot应用;
-
进入这个注解:可以看到上面还有很多其他注解!

@ComponentScan
-
这个注解在Spring中很重要 ,它对应XML配置中的元素。
-
作用:自动扫描并加载符合条件的组件或者bean , 将这个bean定义加载到IOC容器中
@SpringBootConfiguration
-
作用:SpringBoot的配置类 ,标注在某个类上 , 表示这是一个SpringBoot的配置类;
-
我们继续进去这个注解查看

-
这里的 @Configuration,说明这是一个spring的配置类 ,配置类就是对应Spring的xml 配置文件;
-
@Component 这就说明,启动类本身也是Spring中的一个组件而已,负责启动应用!
-
我们回到 SpringBootApplication 注解中继续看。
@EnableAutoConfiguration
-
开启自动配置功能
- 以前我们需要自己配置的东西,而现在SpringBoot可以自动帮我们配置 ;
- @EnableAutoConfiguration告诉SpringBoot开启自动配置功能,这样自动配置才能生效;
点进注解接续查看:
-
@AutoConfigurationPackage :自动配置包


-
-
@import :Spring底层注解@import , 给容器中导入一个组件
-
Registrar.class 作用:自动配置包注册,将主启动类的所在包及包下面所有子包里面的所有组件扫描到Spring容器 ;
-
这个分析完了,退到上一步,继续看
-
-
@Import({AutoConfigurationImportSelector.class}) :给容器导入组件 ;
- AutoConfigurationImportSelector :自动配置导入选择器,那么它会导入哪些组件的选择器呢?我们点击去这个类看源码:
// 获取所有的配置
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
复制代码
-
获得候选的配置
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { // 和下面的方法对应 //这里的getSpringFactoriesLoaderFactoryClass()方法 //返回的就是我们最开始看的启动自动导入配置文件的注解类;EnableAutoConfiguration List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader()); Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct."); return configurations; } //和上面的类的方法loadFactoryNames里面的第一个参数对应 //这里的getSpringFactoriesLoaderFactoryClass()方法 //返回的就是我们最开始看的启动自动导入配置文件的注解类;EnableAutoConfiguration protected Class<?> getSpringFactoriesLoaderFactoryClass() { return EnableAutoConfiguration.class; } 复制代码 -
这个方法
getCandidateConfigurations()又调用了SpringFactoriesLoader类的静态方法!我们进入SpringFactoriesLoader类loadFactoryNames() 方法,获取所有的加载配置public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) { String factoryClassName = factoryClass.getName(); //这里它又调用了 loadSpringFactories 方法 return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList()); } 复制代码 -
我们继续点击查看 loadSpringFactories 方法
- 项目资源:
META-INF/spring.factories - 系统资源:
META-INF/spring.factories - 从这些资源中配置了所有的nextElement(自动配置),分装成properties
//将所有的资源加载到配置类中(将下面的抽离出来分析,第15行) Properties properties = PropertiesLoaderUtils.loadProperties(resource); 复制代码private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) { //获得classLoader , 我们返回可以看到这里得到的就是EnableAutoConfiguration标注的类本身 MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader); if (result != null) { return result; } else { try { //去获取一个资源 "META-INF/spring.factories" Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories"); LinkedMultiValueMap result = new LinkedMultiValueMap(); //判断有没有更多的元素,将读取到的资源循环遍历,封装成为一个Properties while(urls.hasMoreElements()) { URL url = (URL)urls.nextElement(); UrlResource resource = new UrlResource(url); Properties properties = PropertiesLoaderUtils.loadProperties(resource); Iterator var6 = properties.entrySet().iterator(); while(var6.hasNext()) { Entry<?, ?> entry = (Entry)var6.next(); String factoryClassName = ((String)entry.getKey()).trim(); String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue()); int var10 = var9.length; for(int var11 = 0; var11 < var10; ++var11) { String factoryName = var9[var11]; result.add(factoryClassName, factoryName.trim()); } } } cache.put(classLoader, result); return result; } catch (IOException var13) { throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13); } } } 复制代码 - 项目资源:
-
发现一个多次出现的文件:spring.factories
3.3.3.spring.factories
我们根据源头打开spring.factories , 看到了很多自动配置的文件;这就是自动配置根源所在!

3.3.4.WebMvcAutoConfiguration
在上面的自动配置类随便找一个打开看看,比如 :WebMvcAutoConfiguration
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
public static final String DEFAULT_PREFIX = "";
public static final String DEFAULT_SUFFIX = "";
private static final String[] SERVLET_LOCATIONS = { "/" };
@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
复制代码
可以看到这些一个个的都是JavaConfig配置类,而且都注入了一些Bean,可以找一些自己认识的类,看着熟悉一下!
所以,自动配置真正实现是从classpath中搜寻所有的META-INF/spring.factories配置文件 ,并将其中对应的 org.springframework.boot.autoconfigure. 包下的配置项,通过反射实例化为对应标注了 @Configuration的JavaConfig形式的IOC容器配置类 , 然后将这些都汇总成为一个实例并加载到IOC容器中
3.3.5.结论:
-
SpringBoot在启动的时候从类路径下的
META-INF/spring.factories中获取EnableAutoConfiguration指定的值 -
将这些值作为自动配置类导入容器 , 自动配置类就生效 , 帮我们进行自动配置工作;
-
以前我们需要自动配置的东西,现在springboot帮我们做了
-
整合JavaEE,整体解决方案和自动配置的东西都在
springboot-autoconfigure的jar包中; -
它会把所有需要导入的组件,以类名的方式返回,这些组件就会被添加到容器中
-
它会给容器中导入非常多的自动配置类 (xxxAutoConfiguration), 就是给容器中导入这个场景需要的所有组件 , 并自动配置,@Configuration(javaConfig) ;
-
有了自动配置类 , 免去了我们手动编写配置注入功能组件等的工作;

3.4启动
@SpringBootApplication
public class Springboot01HellowordApplication {
public static void main(String[] args) {
//该方法返回一个ConfigurableApplicationContext对象
//参数一:应用入口的类; 参数二:命令行参数
SpringApplication.run(Springboot01HellowordApplication.class, args);
}
}
复制代码
SpringApplication.run分析
- 分析该方法主要分两部分
- 一是SpringApplication的实例化,
- 二是run方法的执行;
3.4.1.SpringApplication

这个类主要做了以下四件事情:
-
推断应用的类型是普通的项目还是Web项目
-
查找并加载所有可用初始化器 , 设置到initializers属性中
-
找出所有的应用程序监听器,设置到listeners属性中
-
推断并设置main方法的定义类,找到运行的主类
查看构造器:

3.4.2.run方法流程分析

4.yaml语法学习
4.1.配置文件
SpringBoot使用一个全局的配置文件 , 配置文件名称是固定的
-
application.properties
-
- 语法结构 :key=value
-
application.yaml
-
- 语法结构 :key:空格 value
**配置文件的作用 :**修改SpringBoot自动配置的默认值,因为SpringBoot在底层都给我们自动配置好了;
比如我们可以在配置文件中修改Tomcat 默认启动的端口号!测试一下!
server:
port: 8081
复制代码
4.2.YAML
yaml概述
-
YAML是 "YAML Ain't a Markup Language" (YAML不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:"Yet Another Markup Language"(仍是一种标记语言)
-
这种语言以数据作为中心,而不是以标记语言为重点!
-
以前的配置文件,大多数都是使用xml来配置;比如一个简单的端口配置,我们来对比下yaml和xml
-
传统xml配置:
<server> <port>8081<port> </server> 复制代码 -
yaml配置:
server: prot: 8080 复制代码
-
yaml基础语法
说明:语法要求严格!
-
空格不能省略
-
以缩进来控制层级关系,只要是左边对齐的一列数据都是同一个层级的。
-
属性和值的大小写都是十分敏感的。
字面量:普通的值 [ 数字,布尔值,字符串 ]
-
字面量直接写在后面就可以 , 字符串默认不用加上双引号或者单引号;
k: v注意:
-
“ ” 双引号,不会转义字符串里面的特殊字符 , 特殊字符会作为本身想表示的意思;
比如 :name: "wang\n jingmo" 输出 :wang换行 jingmo
-
'' 单引号,会转义特殊字符 , 特殊字符最终会变成和普通字符一样输出
比如 :name: ‘wang\n jingmo’ 输出 :wang\n jingmo
-
对象、Map(键值对)
#对象、Map格式
k:
v1:
v2:
复制代码
在下一行来写对象的属性和值得关系,注意缩进;比如:
student:
name: wangyanling
age: 3
复制代码
行内写法
student: {name: wangyanling,age: 3}
复制代码
数组( List、set )
用 - 值表示数组中的一个元素,比如:
pets:
- cat
- dog
- pig
复制代码
行内写法
pets: [cat,dog,pig]
复制代码
修改SpringBoot的默认端口号
配置文件中添加,端口号的参数,就可以切换端口;
server:
port: 8082
复制代码
4.3.注入配置文件
yaml文件强大的地方在于可以给实体类直接注入匹配值
4.3.1. yaml注入配置文件
① 在springboot项目中的resources目录下新建一个文件 application.yml
② 编写一个实体类 Dog
package com.wyl.springboot.pojo;
@Component //注册bean到容器中
public class Dog {
private String name;
private Integer age;
//有参无参构造、get、set方法、toString()方法
}
复制代码
③ 试着用@Value给bean注入属性值
@Component //注册bean
public class Dog {
@Value("旺财")
private String name;
@Value("1")
private Integer age;
}
复制代码
④ 在SpringBoot的测试类下注入并输出
@SpringBootTest
class DemoApplicationTests {
@Autowired //将狗狗自动注入进来
Dog dog;
@Test
public void contextLoads() {
System.out.println(dog); //打印看下狗狗对象
}
}
复制代码
结果成功输出,@Value注入成功
Dog{name='旺财', age=1}
复制代码
⑤ 再编写一个复杂点的实体类
@Component //注册bean到容器中
public class Person {
private String name;
private Integer age;
private Boolean happy;
private Date birth;
private Map<String,Object> maps;
private List<Object> lists;
private Dog dog;
//有参无参构造、get、set方法、toString()方法
}
复制代码
⑥ 使用yaml配置的方式进行注入
写的时候注意区别和优势,首先编写一个yaml配置
person:
name: wjm
age: 3
happy: false
birth: 2000/01/01
maps: {k1: v1,k2: v2}
lists:
- code
- girl
- music
dog:
name: 旺财
age: 1
复制代码
⑦ 把对象的所有值都写好后,注入到类中
/*
@ConfigurationProperties作用:
将配置文件中配置的每一个属性的值,映射到这个组件中;
告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定
参数 prefix = “person” : 将配置文件中的person下面的所有属性一一对应
*/
@Component //注册bean
@ConfigurationProperties(prefix = "person")
public class Person {
private String name;
private Integer age;
private Boolean happy;
private Date birth;
private Map<String,Object> maps;
private List<Object> lists;
private Dog dog;
}
复制代码
⑧ IDEA 提示,springboot配置注解处理器没有找到
Not Found
The requested URL /spring-boot/docs/2.3.3.RELEASE/reference/html/configuration-metadata.html was not found on this server.
复制代码
查看文档(在网址中更改版本获得,如回到2.1.9),找到一个依赖
<!-- 导入配置文件处理器,配置文件进行绑定就会有提示,需要重启 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
复制代码
⑨ 确认以上配置都完成后,去测试类中测试
@SpringBootTest
class DemoApplicationTests {
@Autowired
Person person; //将person自动注入进来
@Test
public void contextLoads() {
System.out.println(person); //打印person信息
}
}
复制代码
结果:所有值全部注入成功
4.3.2. 加载指定配置文件
@PropertySource :加载指定的配置文件;
@configurationProperties:默认从全局配置文件中获取值
- 在resources目录下新建一个person.properties文件
name=hello
复制代码
- 在代码中指定加载person.properties文件
@PropertySource(value = "classpath:person.properties")
@Component //注册bean
public class Person {
@Value("${name}")
private String name;
......
}
复制代码
- 再次输出测试,指定配置文件绑定成功
4.3.3.配置文件占位符
配置文件还可以编写占位符生成随机数
person:
name: wangjingmo${random.uuid} # 随机uuid
age: ${random.int} # 随机int
happy: false
birth: 2000/01/01
maps: {k1: v1,k2: v2}
lists:
- code
- girl
- music
dog:
name: ${person.hello:other}_旺财
age: 1
复制代码
4.3.4.回顾properties配置
上面采用的yaml方法都是最简单的方式,也是开发中最常用的、pringboot所推荐的
接下来看看其他的实现方式,原理都是相同的,写还是那样写
配置文件除了yml还有之前常用的properties
【注意】properties配置文件在写中文的时候会有乱码 , 需要去IDEA中设置编码格式为UTF-8:settings-->FileEncodings 中配置
测试步骤
- 新建一个实体类User
@Component //注册bean
public class User {
private String name;
private int age;
private String sex;
}
复制代码
- 编辑配置文件 user.properties
user1.name=wyl
user1.age=18
user1.sex=男
复制代码
- 在User类上使用@Value来进行注入
@Component //注册bean
@PropertySource(value = "classpath:user.properties")
public class User {
//直接使用@value
@Value("${user.name}") //从配置文件中取值
private String name;
@Value("#{9*2}") // #{SPEL} Spring表达式
private int age;
@Value("男") // 字面量
private String sex;
}
复制代码
- Springboot测试
SpringBootTest
class DemoApplicationTests {
@Autowired
User user;
@Test
public void contextLoads() {
System.out.println(user);
}
}
复制代码
结果正常输出
4.3.4.对比小结
@Value使用起来并不友好!我们需要为每个属性单独注解赋值比较麻烦

- @ConfigurationProperties只需要写一次即可 , @Value则需要每个字段都添加
- 松散绑定:这个什么意思呢? 比如yml中写的last-name,这个和lastName是一样的,
-后面跟着的字母默认是大写的。这就是松散绑定 - JSR303数据校验 ,可以在字段是增加一层过滤器验证 , 保证数据的合法性
- 复杂类型封装,yml中可以封装对象 , 使用value就不支持
结论:
- 配置yml和配置properties都可以获取到值 , 强烈推荐 yml;
- 如果在某个业务中,只需要获取配置文件中的某个值,可以使用一下 @value;
- 如果专门编写了一个JavaBean来和配置文件进行一一映射,就直接使用@configurationProperties
5.JSR303数据校验
Springboot中可以用@validated来校验数据,如果数据异常则会统一抛出异常,方便异常中心统一处理。这里来写个注解让name只能支持Email格式
@Component //注册bean
@ConfigurationProperties(prefix = "person")
@Validated //数据校验
public class Person {
@Email(message="邮箱格式错误") //name必须是邮箱格式
private String name;
}
复制代码
运行结果:default message [不是一个合法的电子邮件地址]
使用数据校验,可以保证数据的正确性; 下面列出一些常见的使用
@NotNull(message="名字不能为空")
private String userName;
@Max(value=120,message="年龄最大不能查过120")
private int age;
@Email(message="邮箱格式错误")
private String email;
空检查
@Null 验证对象是否为null
@NotNull 验证对象是否不为null, 无法查检长度为0的字符串
@NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空
格.
@NotEmpty 检查约束元素是否为NULL或者是EMPTY.
Booelan检查
@AssertTrue 验证 Boolean 对象是否为 true
@AssertFalse 验证 Boolean 对象是否为 false
长度检查
@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内
@Length(min=, max=) string is between min and max included.
日期检查
@Past 验证 Date 和 Calendar 对象是否在当前时间之前
@Future 验证 Date 和 Calendar 对象是否在当前时间之后
@Pattern 验证 String 对象是否符合正则表达式的规则
.......等等
除此以外,我们还可以自定义一些数据校验规则
复制代码
5.1.多环境切换
profile是Spring对不同环境提供不同配置功能的支持,可以通过激活不同的环境版本,实现快速切换环境
5.1.1.多配置文件
在主配置文件编写的时候,文件名可以是application-{profile}.properties/yml , 用来指定多个环境版本。例如:application-test.properties 代表测试环境配置 application-dev.properties代表开发环境配置
但是Springboot并不会直接启动这些配置文件,它默认使用application.properties主配置文件。但可以通过配置来选择需要激活的环境
#比如在配置文件中指定使用dev环境,我们可以通过设置不同的端口号进行测试;
#我们启动SpringBoot,就可以看到已经切换到dev下的配置了;
spring.profiles.active=dev
复制代码
5.1.2.yml的多文档块
和properties配置文件中一样,但使用yml去实现不需要创建多个配置文件,更加方便
server:
port: 8081
#选择要激活那个环境块
spring:
profiles:
active: prod
---
server:
port: 8083
spring:
profiles: dev #配置环境的名称
---
server:
port: 8084
spring:
profiles: prod #配置环境的名称
复制代码
注意:如果yml和properties同时都配置了端口,并且没有激活其他环境 , 默认会使用properties配置文件的
5.1.3.配置文件加载位置
外部加载配置文件的方式很多,一般选择最常用的即可,在开发的资源文件中进行配置
springboot 启动会扫描以下位置的application.properties或者application.yml文件作为Spring boot的默认配置文件
优先级1:项目路径下的config文件夹配置文件
优先级2:项目路径下配置文件
优先级3:资源路径下的config文件夹配置文件
优先级4:资源路径下配置文件
复制代码
优先级由高到底,高优先级的配置会覆盖低优先级的配置;
SpringBoot会从这四个位置全部加载主配置文件;互补配置
4.4.4.运维小技巧
指定位置加载配置文件
我们还可以通过spring.config.location来改变默认的配置文件位置
项目打包好以后,我们可以使用命令行参数的形式,启动项目的时候来指定配置文件的新位置;
这种情况,一般是后期运维做的多,相同配置,外部指定的配置文件优先级最高
java -jar spring-boot-config.jar --spring.config.location=F:/application.properties
复制代码
6.自动配置原理
----联系---- spring.factories
SpringBoot官方文档中有大量的配置,我们无法全部记住,官网:docs.spring.io/spring-boot…

6.1.分析自动配置原理
-
SpringBoot启动的时候加载主配置类,开启了自动配置功能 @EnableAutoConfiguration
-
@EnableAutoConfiguration 作用
-
利用EnableAutoConfigurationImportSelector给容器中导入一些组件
-
可以查看selectImports()方法的内容,他返回了一个autoConfigurationEnty,来自
this.getAutoConfigurationEntry(autoConfigurationMetadata,annotationMetadata);这个方法我们继续来跟踪: -
这个方法有一个值:
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);叫做获取候选的配置 ,我们点击继续跟踪SpringFactoriesLoader.loadFactoryNames()- 扫描所有jar包类路径下
META-INF/spring.factories - 把扫描到的这些文件的内容包装成properties对象
- 从properties中获取到EnableAutoConfiguration.class类(类名)对应的值,然后把他们添加在容器中
-
在类路径下,
META-INF/spring.factories里面配置的所有EnableAutoConfiguration的值加入到容器中:
-

-
- 每一个这样的 xxxAutoConfiguration类都是容器中的一个组件,都加入到容器中;用他们来做自动配置;
-
每一个自动配置类进行自动配置功能;
-
我们以**HttpEncodingAutoConfiguration(Http编码自动配置)**为例解释自动配置原理;
//表示这是一个配置类,和以前编写的配置文件一样,也可以给容器中添加组件; @Configuration //启动指定类的ConfigurationProperties功能; //进入这个HttpProperties查看,将配置文件中对应的值和HttpProperties绑定起来; //并把HttpProperties加入到ioc容器中 @EnableConfigurationProperties({HttpProperties.class}) //Spring底层@Conditional注解 //根据不同的条件判断,如果满足指定的条件,整个配置类里面的配置就会生效; //这里的意思就是判断当前应用是否是web应用,如果是,当前配置类生效 @ConditionalOnWebApplication( type = Type.SERVLET ) //判断当前项目有没有这个类CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器; @ConditionalOnClass({CharacterEncodingFilter.class}) //判断配置文件中是否存在某个配置:spring.http.encoding.enabled; //如果不存在,判断也是成立的 //即使我们配置文件中不配置pring.http.encoding.enabled=true,也是默认生效的; @ConditionalOnProperty( prefix = "spring.http.encoding", value = {"enabled"}, matchIfMissing = true ) public class HttpEncodingAutoConfiguration { //他已经和SpringBoot的配置文件映射了 private final Encoding properties; //只有一个有参构造器的情况下,参数的值就会从容器中拿 public HttpEncodingAutoConfiguration(HttpProperties properties) { this.properties = properties.getEncoding(); } //给容器中添加一个组件,这个组件的某些值需要从properties中获取 @Bean @ConditionalOnMissingBean //判断容器没有这个组件? public CharacterEncodingFilter characterEncodingFilter() { CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter(); filter.setEncoding(this.properties.getCharset().name()); filter.setForceRequestEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.REQUEST)); filter.setForceResponseEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.RESPONSE)); return filter; } //。。。。。。。 } 复制代码
一句话总结 :根据当前不同的条件判断,决定这个配置类是否生效!
-
一但这个配置类生效;这个配置类就会给容器中添加各种组件;
-
这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的;
-
所有在配置文件中能配置的属性都是在xxxxProperties类中封装着;
-
配置文件能配置什么就可以参照某个功能对应的这个属性类
//从配置文件中获取指定的值和bean的属性进行绑定 @ConfigurationProperties(prefix = "spring.http") public class HttpProperties { // ..... } 复制代码
我们去配置文件里面试试前缀,看提示!

这就是自动装配的原理!
6.2.总结
-
SpringBoot启动会加载大量的自动配置类
-
我们看我们需要的功能有没有在SpringBoot默认写好的自动配置类当中;
-
我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件存在在其中,我们就不需要再手动配置了)
-
给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们只需要在配置文件中指定这些属性的值即可;
**xxxxAutoConfigurartion:自动配置类;**给容器中添加组件
xxxxProperties:封装配置文件中相关属性;
6.3.@Conditional
了解完自动装配的原理后,我们来关注一个细节问题,自动配置类必须在一定的条件下才能生效;
@Conditional派生注解(Spring注解版原生的@Conditional作用)
作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效;
| @Conditional扩展注解 | 作用(判断是否满足当前指定条件) |
|---|---|
| @ConditionalOnJava | 系统的java版本是否符合要求 |
| @ConditionalOnJava | 容器中存在指定Bean ; |
| @ConditionalOnMissingBean | 容器中不存在指定Bean ; |
| @ConditionalOnExpression | 满足SpEL表达式指定 |
| @ConditionalOnClass | 系统中有指定的类 |
| @ConditionalOnMissingClass | 系统中没有指定的类 |
| @ConditionalOnSingleCandidate | 容器中只有一个指定的Bean ,或者这个Bean是首选Bean |
| @ConditionalOnProperty | 系统中指定的属性是否有指定的值 |
| @ConditionalOnResource | 类路径下是否存在指定资源文件 |
| @ConditionalOnWebApplication | 当前是web环境 |
| @ConditionalOnNotWebApplication | 当前不是web环境 |
| @ConditionalOnJndi | JNDI存在指定项 |
那么多的自动配置类,必须在一定的条件下才能生效;也就是说,我们加载了这么多的配置类,但不是所有的都生效了。
6.4.自动配置类是否生效
我们可以在application.properties通过启用 debug=true属性;
在控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效;
#开启springboot的调试类
debug=true
复制代码
-
Positive matches:(自动配置类启用的:正匹配)
-
Negative matches:(没有启动,没有匹配成功的自动配置类:负匹配)
-
Unconditional classes: (没有条件的类)
6.5.自定义Starter
我们分析完毕了源码以及自动装配的过程,我们可以尝试自定义一个启动器来玩玩!
6.5.1.说明
启动器模块是一个 空 jar 文件,仅提供辅助性依赖管理,这些依赖可能用于自动装配或者其他类库;
命名归约:
官方命名:
- 前缀:spring-boot-starter-xxx
- 比如:spring-boot-starter-web....
自定义命名:
- xxx-spring-boot-starter
- 比如:mybatis-spring-boot-starter
6.5.2.编写启动器
-
在IDEA中新建一个空项目 spring-boot-starter-diy
-
新建一个普通Maven模块:kuang-spring-boot-starter
-
新建一个Springboot模块:kuang-spring-boot-starter-autoconfigure
-
点击apply即可,基本结构
-
在我们的 starter 中 导入 autoconfigure 的依赖!
<!-- 启动器 --> <dependencies> <!-- 引入自动配置模块 --> <dependency> <groupId>com.kuang</groupId> <artifactId>kuang-spring-boot-starter-autoconfigure</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> </dependencies> 复制代码 -
将 autoconfigure 项目下多余的文件都删掉,Pom中只留下一个 starter,这是所有的启动器基本配置!
-
我们编写一个自己的服务
package wyl.ss; public class HelloService { HelloProperties helloProperties; public HelloProperties getHelloProperties() { return helloProperties; } public void setHelloProperties(HelloProperties helloProperties) { this.helloProperties = helloProperties; } public String sayHello(String name){ return helloProperties.getPrefix() + name + helloProperties.getSuffix(); } } 复制代码 -
编写
HelloProperties配置类package wyl.ss; import org.springframework.boot.context.properties.ConfigurationProperties; // 前缀 kuang.hello @ConfigurationProperties(prefix = "kuang.hello") public class HelloProperties { private String prefix; private String suffix; public String getPrefix() { return prefix; } public void setPrefix(String prefix) { this.prefix = prefix; } public String getSuffix() { return suffix; } public void setSuffix(String suffix) { this.suffix = suffix; } } 复制代码 -
编写我们的自动配置类并注入bean,测试!
package wyl.ss; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration @ConditionalOnWebApplication //web应用生效 @EnableConfigurationProperties(HelloProperties.class) public class HelloServiceAutoConfiguration { @Autowired HelloProperties helloProperties; @Bean public HelloService helloService(){ HelloService service = new HelloService(); service.setHelloProperties(helloProperties); return service; } } 复制代码 -
在resources编写一个自己的
META-INF\spring.factories# Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ wyl.ss.HelloServiceAutoConfiguration 复制代码 -
编写完成后,可以安装到maven仓库中!
6.5.3.新建项目测试我们自己写的启动器
-
新建一个SpringBoot 项目
-
导入我们自己写的启动器
<dependency> <groupId>wyl.ss</groupId> <artifactId>ss-spring-boot-starter</artifactId> <version>1.0-SNAPSHOT</version> </dependency> 复制代码 -
编写一个
HelloController进行测试我们自己的写的接口!package wyl.ss.controller; @RestController public class HelloController { @Autowired HelloService helloService; @RequestMapping("/hello") public String hello(){ return helloService.sayHello("zxc"); } } 复制代码 -
编写配置文件
application.propertiesss.hello.prefix="ppp" ss.hello.suffix="sss" 复制代码 -
启动项目进行测试,结果成功 !
7.整合JDBC
7.1.SpringData简介
对于数据访问层,无论是 SQL(关系型数据库) 还是 NOSQL(非关系型数据库),Spring Boot 底层都是采用 Spring Data 的方式进行统一处理。
Spring Boot 底层都是采用 Spring Data 的方式进行统一处理各种数据库,Spring Data 也是 Spring 中与 Spring Boot、Spring Cloud 等齐名的知名项目。
Sping Data 官网
数据库相关的启动器 :可以参考官方文档
7.2.创建项目
7.2.1.新建项目测试
引入相应的模块:Spring Web、SQL中的JDBC API、MySql Driver
项目建好之后,Spring Boot自动导入了启动器
7.2.2.编写yaml配置文件,连接数据库
新建一个 application.yml
spring:
datasource:
username: root
password: 123456
#?serverTimezone=UTC解决时区的报错
url: jdbc:mysql://localhost:3306/数据库名称?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver
复制代码
7.2.3.测试
配置完这一些东西后就可以直接去使用了,SpringBoot已经默认进行了自动配置
@SpringBootTest
class SpringbootDataJdbcApplicationTests {
//DI注入数据源
@Autowired
DataSource dataSource;
@Test
public void contextLoads() throws SQLException {
//看一下默认数据源
System.out.println(dataSource.getClass());
//获得连接
Connection connection = dataSource.getConnection();
System.out.println(connection);
//关闭连接
connection.close();
}
}
复制代码
可以看到默认配置的数据源为 class com.zaxxer.hikari.HikariDataSource, 我们并没有手动配置
HikariDataSource 号称 Java WEB 当前速度最快的数据源,相比于传统的 C3P0 、DBCP、Tomcat jdbc 等连接池更加优秀
可以使用 spring.datasource.type 指定自定义的数据源类型,值为使用的连接池实现的完全限定名
有了数据库连接,就可以 CRUD 操作数据库了。但需要先了解对象 JdbcTemplate
7.2.4.源码
打开 DataSourceProperties 的源码,能配置的所有东西都在这
public class DataSourceProperties implements BeanClassLoaderAware, InitializingBean {
private ClassLoader classLoader;
private String name;
private boolean generateUniqueName = true;
private Class<? extends DataSource> type;
private String driverClassName;
private String url;
private String username;
private String password;
............
复制代码
打开 DataSourceAutoConfiguration 源码,数据源的所有自动配置都在这里
7.2.5.JDBCTemplate
- 有了数据源(com.zaxxer.hikari.HikariDataSource),然后可以拿到数据库连接(java.sql.Connection),有了连接,就可以使用原生的 JDBC 语句来操作数据库
- 即使不使用第三方第数据库操作框架,如 MyBatis等,Spring 本身也对原生的JDBC 做了轻量级的封装,即JdbcTemplate。
- 数据库操作的所有 CRUD 方法都在 JdbcTemplate 中。
- Spring Boot 不仅提供了默认的数据源,同时默认已经配置好了 JdbcTemplate 放在了容器中,程序员只需自己注入即可使用
- JdbcTemplate 的自动配置是依赖 org.springframework.boot.autoconfigure.jdbc 包下的 JdbcTemplateConfiguration 类
JdbcTemplate主要提供以下几类方法:
- execute方法:可以用于执行任何SQL语句,一般用于执行DDL语句;
- update方法及batchUpdate方法:update方法用于执行新增、修改、删除等语句;batchUpdate方法用于执行批处理相关语句;
- query方法及queryForXXX方法:用于执行查询相关语句;
- call方法:用于执行存储过程、函数相关语句。
测试
新建一个 controller 目录,在里面编写一个 JdbcController
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/jdbc")
public class JdbcController {
/**
* Spring Boot 默认提供了数据源,默认提供了 org.springframework.jdbc.core.JdbcTemplate
* JdbcTemplate 中会自己注入数据源,用于简化 JDBC操作
* 还能避免一些常见的错误,使用起来也不用再自己来关闭数据库连接
*/
@Autowired
JdbcTemplate jdbcTemplate;
//查询employee表中所有数据
//List 中的1个 Map 对应数据库的 1行数据
//Map 中的 key 对应数据库的字段名,value 对应数据库的字段值
@GetMapping("/userList")
public List<Map<String,Object>> userList(){
String sql = "select * from user";
List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
return maps;
}
//新增一个用户
@GetMapping("/add")
public String addUser(){
//插入语句,注意时间问题
String sql = "insert into mybatis.user(id, name,pwd)" +
" values (9,'Java编程思想','qqwweer987')";
jdbcTemplate.update(sql);
//查询
return "addOk";
}
//修改用户信息
@GetMapping("/update/{id}")
public String updateUser(@PathVariable("id") int id){
//插入语句
String sql = "update mybatis.user set name=?,pwd=? where id="+id;
//数据封装
Object[] objects = new Object[2];
objects[0] = "大威天龙";
objects[1] = "qwert123";
jdbcTemplate.update(sql,objects);
//查询
return "update-Ok";
}
//删除用户
@GetMapping("/delete/{id}")
public String delUser(@PathVariable("id") int id){
//插入语句
String sql = "delete from mybatis.user where id=?";
jdbcTemplate.update(sql,id);
//查询
return "delete-Ok";
}
}
复制代码
8.集成 druid
8.1.druid简介
-
Java程序很大一部分要操作数据库,为了提高性能操作数据库的时候,又不得不使用数据库连接池。
-
Druid 是阿里巴巴开源平台上一个数据库连接池实现,结合了 C3P0、DBCP 等 DB 池的优点,同时加入了日志监控。
-
Druid 可以很好的监控 DB 池连接和 SQL 的执行情况,天生就是针对监控而生的 DB 连接池。
-
Druid已经在阿里巴巴部署了超过600个应用,经过一年多生产环境大规模部署的严苛考验。
-
Spring Boot 2.0 以上默认使用 Hikari 数据源,可以说 Hikari 与 Driud 都是当前 Java Web 上最优秀的数据源,我们来重点介绍 Spring Boot 如何集成 Druid 数据源,如何实现数据库监控。
-
Github地址:github.com/alibaba/dru…
8.2.配置
加入druid相关配置(.yml配置文件)
spring:
#数据库配置
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/springboot?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&useSSL=false
username: root
password: root
druid:
# 初始连接数
initial-size: 10
# 最大连接池数量
max-active: 100
# 最小连接池数量
min-idle: 10
# 配置获取连接等待超时的时间
max-wait: 60000
# 打开PSCache,并且指定每个连接上PSCache的大小
pool-prepared-statements: true
max-pool-prepared-statement-per-connection-size: 20
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
min-evictable-idle-time-millis: 300000
validation-query: SELECT 1 FROM DUAL
test-while-idle: true
test-on-borrow: false
test-on-return: false
stat-view-servlet:
enabled: true
url-pattern: /druid/*
filter:
stat:
log-slow-sql: true
slow-sql-millis: 1000
merge-sql: false
wall:
config:
multi-statement-allow: true
复制代码
com.alibaba.druid.pool.DruidDataSource 基本配置参数如下:
| 配置 | 缺省值 | 说明 |
|---|---|---|
| name | 配置这个属性的意义在于,如果存在多个数据源,监控的时候可以通过名字来区分开来。 如果没有配置,将会生成一个名字,格式是:“DataSource-” + System.identityHashCode(this) | |
| jdbcUrl | 连接数据库的url,不同数据库不一样。例如: mysql : jdbc:mysql://10.20.153.104:3306/druid2 oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto | |
| username | 连接数据库的用户名 | |
| password | 连接数据库的密码。如果你不希望密码直接写在配置文件中,可以使用ConfigFilter。详细看这里:github.com/alibaba/dru… | |
| driverClassName | 根据url自动识别 | 这一项可配可不配,如果不配置druid会根据url自动识别dbType,然后选择相应的driverClassName(建议配置下) |
| initialSize | 0 | 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时 |
| maxActive | 8 | 最大连接池数量 |
| maxIdle | 8 | 已经不再使用,配置了也没效果 |
| minIdle | 最小连接池数量 | |
| maxWait | 获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。 | |
| poolPreparedStatements | false | 是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。 |
| maxOpenPreparedStatements | -1 | 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100 |
| validationQuery | 用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用。 | |
| validationQueryTimeout | 单位:秒,检测连接是否有效的超时时间。底层调用jdbc Statement对象的void setQueryTimeout(int seconds)方法 |
|
| testOnBorrow | true | 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能 |
| testOnReturn | false | 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能 |
| testWhileIdle | false | 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效 |
| timeBetweenEvictionRunsMillis | 1分钟 ( 1.0.14 ) |
有两个含义: 1) Destroy线程会检测连接的间隔时间 2) testWhileIdle的判断依据,详细看testWhileIdle属性的说明 |
| numTestsPerEvictionRun | 不再使用,一个DruidDataSource只支持一个EvictionRun | |
| minEvictableIdleTimeMillis | 30分钟 ( 1.0.14 ) |
连接保持空闲而不被驱逐的最长时间 |
| connectionInitSqls | 物理连接初始化的时候执行的sql | |
| exceptionSorter | 根据dbType自动识别 | 当数据库抛出一些不可恢复的异常时,抛弃连接 |
| filters | 属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有: 监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall | |
| proxyFilters | 类型是List<com.alibaba.druid.filter.Filter>,如果同时配置了filters和proxyFilters,是组合关系,并非替换关系 |
引入druid依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.9</version>
</dependency>
复制代码
项目启动后可通过show processlist查看mysql的全部线程是否和配置的initial-size一致。
8.3.druid监控测试
访问ip:port/druid验证即可,url中的/druid要和配置文件中的url-pattern一致
stat-view-servlet:
enabled: true
url-pattern: /druid/*
复制代码
效果如下:

9.整合mybatis
9.1.导入 MyBatis 所需要的依赖
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
复制代码
9.2.配置数据库连接信息
spring:
datasource:
username: root
password: admin
#?serverTimezone=UTC解决时区的报错
url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
#Spring Boot 默认是不注入这些属性值的,需要自己绑定
#druid 数据源专有配置
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
#配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
#如果允许时报错 java.lang.ClassNotFoundException: org.apache.log4j.Priority
#则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
复制代码
9.3.创建实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String name;
private String pwd;
}
复制代码
9.4.创建mapper
UserMapper.java
// 这个注解表示了这是一个 mybatis 的 mapper 类
@Mapper
@Repository
public interface UserMapper {
List<User> queryUserList();
User queryUserById(int id);
int addUser(User user);
int updateUser(User user);
int deleteUser(int id);
}
复制代码
-
对应的Mapper映射文件
UserMapper.xml<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!--namespace=绑定一个对应的Dao/Mapper接口--> <mapper namespace="com.wyl.mapper.UserMapper"> <select id="queryUserList" resultType="User"> select * from mybatis.user; </select> <select id="queryUserById" resultType="User"> select * from mybatis.user where id = #{id}; </select> <insert id="addUser" parameterType="User"> insert into mybatis.user (id, name, pwd) values (#{id},#{name},#{pwd}); </insert> <update id="updateUser" parameterType="User"> update mybatis.user set name=#{name},pwd = #{pwd} where id = #{id}; </update> <delete id="deleteUser" parameterType="int"> delete from mybatis.user where id = #{id} </delete> </mapper> 复制代码 -
maven配置资源过滤问题
<resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.xml</include> </includes> <filtering>true</filtering> </resource> </resources> 复制代码 -
编写部门的 UserController 进行测试!
@RestController public class UserController { @Autowired private UserMapper userMapper; @GetMapping("/queryUserList") public List<User> queryUserList() { List<User> userList = userMapper.queryUserList(); for (User user : userList) { System.out.println(user); } return userList; } //添加一个用户 @GetMapping("/addUser") public String addUser() { userMapper.addUser(new User(7,"wyl","123456")); return "ok"; } //修改一个用户 @GetMapping("/updateUser") public String updateUser() { userMapper.updateUser(new User(7,"wjm","123456")); return "ok"; } @GetMapping("/deleteUser") public String deleteUser() { userMapper.deleteUser(7); return "ok"; } } 复制代码
启动项目访问进行测试!
9.5.分页
添加相关依赖
首先,我们需要在 pom.xml 文件中添加分页插件依赖包。
pom.xml
<!-- pagehelper -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.5</version>
</dependency>
复制代码
添加相关配置
然后在 application.yml 配置文件中添加分页插件有关的配置。
application.yml
# pagehelper
pagehelper:
helperDialect: mysql
reasonable: true
supportMethodsArguments: true
params: count=countSql
复制代码
编写分页代码
首先,在 DAO 层添加一个分页查找方法。这个查询方法跟查询全部数据的方法除了名称几乎一样。
SysUserMapper.java
import java.util.List;
import com.louis.springboot.demo.model.SysUser;
public interface SysUserMapper {
int deleteByPrimaryKey(Long id);
int insert(SysUser record);
int insertSelective(SysUser record);
SysUser selectByPrimaryKey(Long id);
int updateByPrimaryKeySelective(SysUser record);
int updateByPrimaryKey(SysUser record);
/**
* 查询全部用户
* @return
*/
List<SysUser> selectAll();
/**
* 分页查询用户
* @return
*/
List<SysUser> selectPage();
}
复制代码
然后在 SysUserMapper.xml 中加入selectPage的实现,当然你也可以直接用@Select注解将查询语句直接写在DAO代码,但我们这里选择写在XML映射文件,这是一个普通的查找全部记录的查询语句,并不需要写分页SQL,分页插件会拦截查询请求,并读取前台传来的分页查询参数重新生成分页查询语句。
SysUserMapper.xml
<select id="selectPage" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from sys_user
</select>
复制代码
服务层通过调用DAO层代码完成分页查询,这里统一封装分页查询的请求和结果类,从而避免因为替换ORM框架而导致服务层、控制层的分页接口也需要变动的情况,替换ORM框架也不会影响服务层以上的分页接口,起到了解耦的作用。
SysUserService.java
import java.util.List;
import com.louis.springboot.demo.model.SysUser;
import com.louis.springboot.demo.util.PageRequest;
import com.louis.springboot.demo.util.PageResult;
public interface SysUserService {
/**
* 根据用户ID查找用户
* @param userId
* @return
*/
SysUser findByUserId(Long userId);
/**
* 查找所有用户
* @return
*/
List<SysUser> findAll();
/**
* 分页查询接口
* 这里统一封装了分页请求和结果,避免直接引入具体框架的分页对象, 如MyBatis或JPA的分页对象
* 从而避免因为替换ORM框架而导致服务层、控制层的分页接口也需要变动的情况,替换ORM框架也不会
* 影响服务层以上的分页接口,起到了解耦的作用
* @param pageRequest 自定义,统一分页查询请求
* @return PageResult 自定义,统一分页查询结果
*/
PageResult findPage(PageRequest pageRequest);
}
复制代码
服务实现类通过调用分页插件完成最终的分页查询,关键代码是 PageHelper.startPage(pageNum, pageSize),将前台分页查询参数传入并拦截MyBtis执行实现分页效果。
SysUserServiceImpl.java
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.louis.springboot.demo.dao.SysUserMapper;
import com.louis.springboot.demo.model.SysUser;
import com.louis.springboot.demo.service.SysUserService;
import com.louis.springboot.demo.util.PageRequest;
import com.louis.springboot.demo.util.PageResult;
import com.louis.springboot.demo.util.PageUtils;
@Service
public class SysUserServiceImpl implements SysUserService {
@Autowired
private SysUserMapper sysUserMapper;
@Override
public SysUser findByUserId(Long userId) {
return sysUserMapper.selectByPrimaryKey(userId);
}
@Override
public List<SysUser> findAll() {
return sysUserMapper.selectAll();
}
@Override
public PageResult findPage(PageRequest pageRequest) {
return PageUtils.getPageResult(pageRequest, getPageInfo(pageRequest));
}
/**
* 调用分页插件完成分页
* @param pageQuery
* @return
*/
private PageInfo<SysUser> getPageInfo(PageRequest pageRequest) {
int pageNum = pageRequest.getPageNum();
int pageSize = pageRequest.getPageSize();
PageHelper.startPage(pageNum, pageSize);
List<SysUser> sysMenus = sysUserMapper.selectPage();
return new PageInfo<SysUser>(sysMenus);
}
}
复制代码
在控制器SysUserController中添加分页查询方法,并调用服务层的分页查询方法。
SysUserController.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.louis.springboot.demo.service.SysUserService;
import com.louis.springboot.demo.util.PageRequest;
@RestController
@RequestMapping("user")
public class SysUserController {
@Autowired
private SysUserService sysUserService;
@GetMapping(value="/findByUserId")
public Object findByUserId(@RequestParam Long userId) {
return sysUserService.findByUserId(userId);
}
@GetMapping(value="/findAll")
public Object findAll() {
return sysUserService.findAll();
}
@PostMapping(value="/findPage")
public Object findPage(@RequestBody PageRequest pageQuery) {
return sysUserService.findPage(pageQuery);
}
}
复制代码
分页查询请求封装类。
PageRequest.java
/**
* 分页请求
*/
public class PageRequest {
/**
* 当前页码
*/
private int pageNum;
/**
* 每页数量
*/
private int pageSize;
public int getPageNum() {
return pageNum;
}
public void setPageNum(int pageNum) {
this.pageNum = pageNum;
}
public int getPageSize() {
return pageSize;
}
public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}
}
复制代码
分页查询结果封装类。
PageResult.java
import java.util.List;
/**
* 分页返回结果
*/
public class PageResult {
/**
* 当前页码
*/
private int pageNum;
/**
* 每页数量
*/
private int pageSize;
/**
* 记录总数
*/
private long totalSize;
/**
* 页码总数
*/
private int totalPages;
/**
* 数据模型
*/
private List<?> content;
public int getPageNum() {
return pageNum;
}
public void setPageNum(int pageNum) {
this.pageNum = pageNum;
}
public int getPageSize() {
return pageSize;
}
public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}
public long getTotalSize() {
return totalSize;
}
public void setTotalSize(long totalSize) {
this.totalSize = totalSize;
}
public int getTotalPages() {
return totalPages;
}
public void setTotalPages(int totalPages) {
this.totalPages = totalPages;
}
public List<?> getContent() {
return content;
}
public void setContent(List<?> content) {
this.content = content;
}
}
复制代码
分页查询相关工具类。
PageUtils.java
import com.github.pagehelper.PageInfo;
public class PageUtils {
/**
* 将分页信息封装到统一的接口
* @param pageRequest
* @param page
* @return
*/
public static PageResult getPageResult(PageRequest pageRequest, PageInfo<?> pageInfo) {
PageResult pageResult = new PageResult();
pageResult.setPageNum(pageInfo.getPageNum());
pageResult.setPageSize(pageInfo.getPageSize());
pageResult.setTotalSize(pageInfo.getTotal());
pageResult.setTotalPages(pageInfo.getPages());
pageResult.setContent(pageInfo.getList());
return pageResult;
}
}
复制代码
编译测试运行
启动应用,访问:localhost:8088/swagger-ui.html,找到对应接口,模拟测试,结果如下。
参数:pageNum: 1, pageSize: 5
10.事务管理
SpringBoot 使用事务非常简单,底层依然采用的是Spring本身提供的事务管理
• 在入口类中使用注解 @EnableTransactionManagement 开启事务支持
• 在访问数据库的Service方法上添加注解 @Transactional 即可
案例思路
通过SpringBoot +MyBatis实现对数据库学生表的更新操作,在service层的方法中构建异常,查看事务是否生效;
项目名称:springboot--transacation
10.1.实现步骤
10.1.1.StudentController
@Controller
public class SpringBootController {
@Autowired
private StudentService studentService;
@RequestMapping(value = "/springBoot/update")
public @ResponseBody Object update() {
Student student = new Student();
student.setId(1);
student.setName("Mark");
student.setAge(100);
int updateCount = studentService.update(student);
return updateCount;
}
}
复制代码
10.1.2.StudentService接口
public interface StudentService {
/**
* 根据学生标识更新学生信息
* @param student
* @return
*/
int update(Student student);
}
复制代码
10.1.3.StudentServiceImpl
接口实现类中对更新学生方法进行实现,并构建一个异常,同时在该方法上加@Transactional注解
@Override
@Transactional //添加此注解说明该方法添加的事务管理
public int update(Student student) {
int updateCount = studentMapper.updateByPrimaryKeySelective(student);
System.out.println("更新结果:" + updateCount);
//在此构造一个除数为0的异常,测试事务是否起作用
int a = 10/0;
return updateCount;
}
复制代码
10.1.4.Application
在类上加@EnableTransactionManagement开启事务支持
@EnableTransactionManagement可选,但是@Service必须添加事务才生效
@SpringBootApplication
@EnableTransactionManagement //SpringBoot开启事务的支持
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
复制代码
10.1.5.启动Application
1.查看数据库,并没有修改数据,通过以上结果,说明事务起作用了。
2.注释掉StudentServiceImpl上的@Transactional测试----数据库发上来改变。
11.SpringMVC注解
SpringBoot下的SpringMVC和之前的SpringMVC使用是完全一样的,主要有以下注解:
1.@Controller
Spring MVC的注解,处理http请求
2.@RestController
Spring4后新增注解,是@Controller注解功能的增强,是@Controller与@ResponseBody的组合注解;
如果一个Controller类添加了@RestController,那么该Controller类下的所有方法都相当于添加了@ResponseBody注解;
用于返回字符串或json数据。
案例:
• 创建MyUserController类,演示@RestController替代@Controller + @ResponseBody
@RestController
public class MyUserController {
@Autowired
private UserService userService;
@RequestMapping("/user/getUser")
public Object getUser(){
return userService.getUser(1);
}
}
复制代码
3.@RequestMapping(常用)
支持Get请求,也支持Post请求
4.@GetMapping
RequestMapping和Get请求方法的组合只支持Get请求;Get请求主要用于查询操作。
5.@PostMapping
RequestMapping和Post请求方法的组合只支持Post请求;Post请求主要用户新增数据。
6.@PutMapping
RequestMapping和Put请求方法的组合只支持Put请求;Put通常用于修改数据。
7.@DeleteMapping
RequestMapping 和 Delete请求方法的组合只支持Delete请求;通常用于删除数据。
12.RESTful实现
Spring boot开发RESTFul 主要是几个注解实现:
@PathVariable:获取url中的数据,该注解是实现RESTFul最主要的一个注解。
@PostMapping:接收和处理Post方式的请求
@DeleteMapping:接收delete方式的请求,可以使用GetMapping代替
@PutMapping:接收put方式的请求,可以用PostMapping代替
@GetMapping:接收get方式的请求
RESTful的优点
• 轻量,直接基于http,不再需要任何别的诸如消息协议,get/post/put/delete为CRUD操作
• 面向资源,一目了然,具有自解释性。
• 数据描述简单,一般以xml,json做数据交换。
• 无状态,在调用一个接口(访问、操作资源)的时候,可以不用考虑上下文,不用考虑当前状态,极大的降低了复杂度。
• 简单、低耦合
12.1.案例
使用RESTful风格模拟实现对学生的增删改查操作
该项目集成了MyBatis、spring、SpringMVC,通过模拟实现对学生的增删改查操作。
1.创建RESTfulController,并编写代码
@RestController
public class RESTfulController {
/**
* 添加学生
* 请求地址:http://localhost:8080/springboot-restful/springBoot/student/wyl/23
* 请求方式:POST
* @param name
* @param age
* @return
*/
@PostMapping(value = "/springBoot/student/{name}/{age}")
public Object addStudent(@PathVariable("name") String name,
@PathVariable("age") Integer age) {
Map retMap = new HashMap();
retMap.put("name",name);
retMap.put("age",age);
return retMap;
}
/**
* 删除学生
* 请求地址:http://localhost:8080/springboot-restful/springBoot/student/1
* 请求方式:Delete
* @param id
* @return
*/
@DeleteMapping(value = "/springBoot/student/{id}")
public Object removeStudent(@PathVariable("id") Integer id) {
return "删除的学生id为:" + id;
}
/**
* 修改学生信息
* 请求地址:http://localhost:8080/springboot-restful/springBoot/student/2
* 请求方式:Put
* @param id
* @return
*/
@PutMapping(value = "/springBoot/student/{id}")
public Object modifyStudent(@PathVariable("id") Integer id) {
return "修改学生的id为" + id;
}
@GetMapping(value = "/springBoot/student/{id}")
public Object queryStudent(@PathVariable("id") Integer id) {
return "查询学生的id为" + id;
}
}
复制代码
12.2.RESTful原则
• 增post请求、删delete请求、改put请求、查get请求
• 请求路径不要出现动词
例如:查询订单接口
/boot/order/1021/1(推荐)
/boot/queryOrder/1021/1(不推荐)
• 分页、排序等操作,不需要使用斜杠传参数
例如:订单列表接口 /boot/orders?page=1&sort=desc
一般传的参数不是数据库表的字段,可以不采用斜杠
13.静态资源处理
13.1.对哪些目录映射?
classpath:/META-INF/resources/
classpath:/resources/
classpath:/static/
classpath:/public/
/:当前项目的根路径
复制代码
就我们在上面五个目录下放静态资源(比如:a.png等),可以直接访问(http://localhost:8080/a.png),类似于以前web项目的webapp下;放到其他目录下无法被访问。
优先级:resources > static(默认) > public
13.2.源码分析
SpringBoot自动配置的WebMvcAutoConfiguration.java类
-
SpringBoot中,SpringMVC的web配置都在 WebMvcAutoConfiguration 这个配置类里面;
-
我们可以去看看 WebMvcAutoConfigurationAdapter 中有很多配置方法;
-
有一个方法:addResourceHandlers 添加资源处理
@Override public void addResourceHandlers(ResourceHandlerRegistry registry) { if (!this.resourceProperties.isAddMappings()) { // 已禁用默认资源处理 logger.debug("Default resource handling disabled"); return; } // 缓存控制 Duration cachePeriod = this.resourceProperties.getCache().getPeriod(); CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl(); // webjars 配置 if (!registry.hasMappingForPattern("/webjars/**")) { customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**") .addResourceLocations("classpath:/META-INF/resources/webjars/") .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl)); } // 静态资源配置 String staticPathPattern = this.mvcProperties.getStaticPathPattern(); if (!registry.hasMappingForPattern(staticPathPattern)) { customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern) .addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations())) .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl)); } } 复制代码读一下源代码:比如所有的
/webjars/**, 都需要去classpath:/META-INF/resources/webjars/找对应的资源;
13.2.1.什么是webjars 呢?
Webjars本质就是以jar包的方式引入我们的静态资源 , 我们以前要导入一个静态资源文件,直接导入即可。
13.2.2.第一种静态资源映射规则
使用SpringBoot需要使用Webjars,我们可以去搜索一下:
要使用jQuery,我们只要要引入jQuery对应版本的pom依赖即可!
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.4.1</version>
</dependency>
复制代码
导入完毕,查看webjars目录结构,并访问Jquery.js文件!
访问:只要是静态资源,SpringBoot就会去对应的路径寻找资源,我们这里访问:http://localhost:8080/webjars/jquery/3.4.1/jquery.js

13.2.3.第二种静态资源映射规则
1、那我们项目中要是使用自己的静态资源该怎么导入呢?我们看下一行代码;

2、我们去找staticPathPattern发现第二种映射规则 :/** , 访问当前的项目任意资源,它会去找 resourceProperties 这个类,我们可以点进去看一下分析:
// 进入方法
public String[] getStaticLocations() {
return this.staticLocations;
}
// 找到对应的值
private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
// 找到路径
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = {
"classpath:/META-INF/resources/",
"classpath:/resources/",
"classpath:/static/",
"classpath:/public/"
};
复制代码
3、ResourceProperties 可以设置和我们静态资源有关的参数;这里面指向了它会去寻找资源的文件夹,即上面数组的内容。
4、所以得出结论,以下四个目录存放的静态资源可以被我们识别:
"classpath:/META-INF/resources/"
"classpath:/resources/"
"classpath:/static/"
"classpath:/public/"
复制代码
5、我们可以在resources根目录下新建对应的文件夹,都可以存放我们的静态文件;

6、比如我们访问 http://localhost:8080/1.js , 他就会去这些文件夹中寻找对应的静态资源文件;

13.2.4.引用
不用写static 路径
<link rel="stylesheet" href="/layui/css/layui.css">
<link rel="stylesheet" href="/easyui/default/easyui.css">
<script src="/layui/jquery-1.10.2.min.js" type="text/javascript"></script>
<script src="/easyui/jquery.easyui.min.js" type="text/javascript"></script>
复制代码
13.3.自定义静态资源路径
首先,自定义会覆盖默认!所以没十足把握的情况下,不建议覆盖,但可以添加。
两种方式
13.3.1.配置类代码实现
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**").addResourceLocations("file:F:/AppFiles/");
}
}
复制代码
13.3.2.配置文件
spring.resources.static-locations=classpath:/META-INF/resources/,classpath:/resources/,\
classpath:/static/,classpath:/public/,file:c:/appfiles/
复制代码
13.3.3.相对路径配置
以上的情况均是绝对路径,受限于环境,开发生产LinuxWin等。这种变化情况多,建议理清思路,再决定是否适用。
解决方案
String gitPath = path.getParentFile().getParentFile().getParent()
+ File.separator + "logistics" + File.separator + "uploads"
+ File.separator;
复制代码
工具类
spring框架自带的ResourceUtils,或者结合第三方工具
13.3.4. 欢迎页与图标

14.国际化
国际化(Internationalization 简称 I18n,其中“I”和“n”分别为首末字符,18 则为中间的字符数)是指软件开发时应该具备支持多种语言和地区的功能。换句话说就是,开发的软件需要能同时应对不同国家和地区的用户访问,并根据用户地区和语言习惯,提供相应的、符合用具阅读习惯的页面和数据,例如,为中国用户提供汉语界面显示,为美国用户提供提供英语界面显示。
在 Spring 项目中实现国际化,通常需要以下 3 步:
- 编写国际化资源(配置)文件;
- 使用 ResourceBundleMessageSource 管理国际化资源文件;
- 在页面获取国际化内容。
14.1. 编写国际化资源文件
在 Spring Boot 的类路径下创建国际化资源文件,文件名格式为:基本名_语言代码_国家或地区代码,例如 login_en_US.properties、login_zh_CN.properties。
以 spring-boot-springmvc-demo1为例,在 src/main/resources 下创建一个 i18n 的目录,并在该目录中按照国际化资源文件命名格式分别创建以下三个文件,
- login.properties:无语言设置时生效
- login_en_US.properties :英语时生效
- login_zh_CN.properties:中文时生效
以上国际化资源文件创建完成后,IDEA 会自动识别它们,并转换成如下的模式:

打开任意一个国际化资源文件,并切换为 Resource Bundle 模式,然后点击“+”号,创建所需的国际化属性,如下图。

14.2.配置文件生效探究
Spring Boot 已经对 ResourceBundleMessageSource 提供了默认的自动配置。
Spring Boot 通过 MessageSourceAutoConfiguration 对 ResourceBundleMessageSource 提供了默认配置,其部分源码如下。
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(name = AbstractApplicationContext.MESSAGE_SOURCE_BEAN_NAME, search = SearchStrategy.CURRENT)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Conditional(org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration.ResourceBundleCondition.class)
@EnableConfigurationProperties
public class MessageSourceAutoConfiguration {
private static final Resource[] NO_RESOURCES = {};
// 将 MessageSourceProperties 以组件的形式添加到容器中
// MessageSourceProperties 下的每个属性都与以 spring.messages 开头的属性对应
@Bean
@ConfigurationProperties(prefix = "spring.messages")
public MessageSourceProperties messageSourceProperties() {
return new MessageSourceProperties();
}
//Spring Boot 会从容器中获取 MessageSourceProperties
// 读取国际化资源文件的 basename(基本名)、encoding(编码)等信息
// 并封装到 ResourceBundleMessageSource 中
@Bean
public MessageSource messageSource(MessageSourceProperties properties) {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
//读取国际化资源文件的 basename (基本名),并封装到 ResourceBundleMessageSource 中
if (StringUtils.hasText(properties.getBasename())) {
messageSource.setBasenames(StringUtils
.commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(properties.getBasename())));
}
//读取国际化资源文件的 encoding (编码),并封装到 ResourceBundleMessageSource 中
if (properties.getEncoding() != null) {
messageSource.setDefaultEncoding(properties.getEncoding().name());
}
messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale());
Duration cacheDuration = properties.getCacheDuration();
if (cacheDuration != null) {
messageSource.setCacheMillis(cacheDuration.toMillis());
}
messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat());
messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage());
return messageSource;
}
...
}
复制代码
从以上源码可知:
Spring Boot 将 MessageSourceProperties 以组件的形式添加到容器中;
MessageSourceProperties 的属性与配置文件中以“spring.messages”开头的配置进行了绑定;
Spring Boot 从容器中获取 MessageSourceProperties 组件,并从中读取国际化资源文件的 basename(文件基本名)、encoding(编码)等信息,将它们封装到 ResourceBundleMessageSource 中;
Spring Boot 将 ResourceBundleMessageSource 以组件的形式添加到容器中,进而实现对国际化资源文件的管理。
查看 MessageSourceProperties 类,其代码如下。
public class MessageSourceProperties {
private String basename = "messages";
private Charset encoding;
@DurationUnit(ChronoUnit.SECONDS)
private Duration cacheDuration;
private boolean fallbackToSystemLocale;
private boolean alwaysUseMessageFormat;
private boolean useCodeAsDefaultMessage;
public MessageSourceProperties() {
this.encoding = StandardCharsets.UTF_8;
this.fallbackToSystemLocale = true;
this.alwaysUseMessageFormat = false;
this.useCodeAsDefaultMessage = false;
}
...
}
复制代码
通过以上代码,我们可以得到以下 3 点信息:
- MessageSourceProperties 为 basename、encoding 等属性提供了默认值;
- basename 表示国际化资源文件的基本名,其默认取值为“message”,即 Spring Boot 默认会获取类路径下的 message.properties 以及 message_XXX.properties 作为国际化资源文件;
- 在 application.porperties/yml 等配置文件中,使用配置参数“spring.messages.basename”即可重新指定国际化资源文件的基本名。
通过以上源码分析可知,Spring Boot 已经对国际化资源文件的管理提供了默认自动配置,我们这里只需要在 Spring Boot 全局配置文件中,使用配置参数“spring.messages.basename”指定我们自定义的国际资源文件的基本名即可,代码如下(当指定多个资源文件时,用逗号分隔)。
spring.messages.basename=i18n.login
复制代码
14.3. 获取国际化内容
由于页面使用的是 Tymeleaf 模板引擎,因此我们可以通过表达式 #{...} 获取国际化内容。
以 spring-boot-adminex 为例,在 login.html 中获取国际化内容,代码如下。
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
<meta name="description" content="">
<meta name="author" content="ThemeBucket">
<link rel="shortcut icon" href="#" type="image/png">
<title>Login</title>
<!--将js css 等静态资源的引用修改为 绝对路径-->
<link href="css/style.css" th:href="@{/css/style.css}" rel="stylesheet">
<link href="css/style-responsive.css" th:href="@{/css/style-responsive.css}" rel="stylesheet">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
<script src="js/html5shiv.js" th:src="@{/js/html5shiv.js}"></script>
<script src="js/respond.min.js" th:src="@{/js/respond.min.js}"></script>
<![endif]-->
</head>
<body class="login-body">
<div class="container">
<form class="form-signin" th:action="@{/user/login}" method="post">
<div class="form-signin-heading text-center">
<h1 class="sign-title" th:text="#{login.btn}">Sign In</h1>
<img src="/images/login-logo.png" th:src="@{/images/login-logo.png}" alt=""/>
</div>
<div class="login-wrap">
<p style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>
<input type="text" class="form-control" name="username" placeholder="User ID" autofocus
th:placeholder="#{login.username}"/>
<input type="password" class="form-control" name="password" placeholder="Password"
th:placeholder="#{login.password}"/>
<label class="checkbox">
<input type="checkbox" value="remember-me" th:text="#{login.remember}">
<span class="pull-right">
<a data-toggle="modal" href="#myModal" th:text="#{login.forgot}"> </a>
</span>
</label>
<button class="btn btn-lg btn-login btn-block" type="submit">
<i class="fa fa-check"></i>
</button>
<div class="registration">
<!--Thymeleaf 行内写法-->
[[#{login.not-a-member}]]
<a class="" href="/registration.html" th:href="@{/registration.html}">
[[#{login.signup}]]
</a>
<!--thymeleaf 模板引擎的参数用()代替 ?-->
<br/>
<a class="btn btn-sm" th:href="@{/index.html(l='zh_CN')}">中文</a>|
<a class="btn btn-sm" th:href="@{/index.html(l='en_US')}">English</a>
</div>
</div>
<!-- Modal -->
<div aria-hidden="true" aria-labelledby="myModalLabel" role="dialog" tabindex="-1" id="myModal"
class="modal fade">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h4 class="modal-title">Forgot Password ?</h4>
</div>
<div class="modal-body">
<p>Enter your e-mail address below to reset your password.</p>
<input type="text" name="email" placeholder="Email" autocomplete="off"
class="form-control placeholder-no-fix">
</div>
<div class="modal-footer">
<button data-dismiss="modal" class="btn btn-default" type="button">Cancel</button>
<button class="btn btn-primary" type="button">Submit</button>
</div>
</div>
</div>
</div>
<!-- modal -->
</form>
</div>
<!-- Placed js at the end of the document so the pages load faster -->
<!-- Placed js at the end of the document so the pages load faster -->
<script src="js/jquery-1.10.2.min.js" th:src="@{/js/jquery-1.10.2.min.js}"></script>
<script src="js/bootstrap.min.js" th:src="@{/js/bootstrap.min.js}"></script>
<script src="js/modernizr.min.js" th:src="@{/js/modernizr.min.js}"></script>
</body>
</html>
复制代码
14.4.区域信息解析器自动配置
我们知道,Spring MVC 进行国际化时有 2 个十分重要的对象:
- Locale:区域信息对象
- LocaleResolver:区域信息解析器,容器中的组件,负责获取区域信息对象
我们可以通过以上两个对象对区域信息的切换,以达到切换语言的目的。
Spring Boot 在 WebMvcAutoConfiguration 中为区域信息解析器(LocaleResolver)进行了自动配置,源码如下。
@Bean
@ConditionalOnMissingBean(name = DispatcherServlet.LOCALE_RESOLVER_BEAN_NAME)
@SuppressWarnings("deprecation")
public LocaleResolver localeResolver() {
if (this.webProperties.getLocaleResolver() == WebProperties.LocaleResolver.FIXED) {
return new FixedLocaleResolver(this.webProperties.getLocale());
}
if (this.mvcProperties.getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) {
return new FixedLocaleResolver(this.mvcProperties.getLocale());
}
AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
Locale locale = (this.webProperties.getLocale() != null) ? this.webProperties.getLocale()
: this.mvcProperties.getLocale();
localeResolver.setDefaultLocale(locale);
return localeResolver;
}
复制代码
从以上源码可知:
- 该方法默认向容器中添加了一个区域信息解析器(LocaleResolver)组件,它会根据请求头中携带的“Accept-Language”参数,获取相应区域信息(Locale)对象。
- 该方法上使用了 @ConditionalOnMissingBean 注解,其参数 name 的取值为 localeResolver(与该方法注入到容器中的组件名称一致),该注解的含义为:当容器中不存在名称为 localResolver 组件时,该方法才会生效。换句话说,当我们手动向容器中添加一个名为“localeResolver”的组件时,Spring Boot 自动配置的区域信息解析器会失效,而我们定义的区域信息解析器则会生效。
手动切换语言
- 修改 login.html 切换语言链接,在请求中携带国际化区域信息,代码如下。
<!--thymeleaf 模板引擎的参数用()代替 ?-->
<a class="btn btn-sm" th:href="@{/index.html(l='zh_CN')}">中文</a>|
<a class="btn btn-sm" th:href="@{/index.html(l='en_US')}">English</a>
复制代码
- 创建一个 component 包,并在该包中创建一个区域信息解析器 MyLocalResolver,代码如下。
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.LocaleResolver;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;
//自定义区域信息解析器
public class MyLocalResolver implements LocaleResolver {
@Override
public Locale resolveLocale(HttpServletRequest request) {
//获取请求中参数
String l = request.getParameter("l");
//获取默认的区域信息解析器
Locale locale = Locale.getDefault();
//根据请求中的参数重新构造区域信息对象
if (StringUtils.hasText(l)) {
String[] s = l.split("_");
locale = new Locale(s[0], s[1]);
}
return locale;
}
@Override
public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
}
}
复制代码
为了让我们的区域化信息能够生效,我们需要再配置一下这个组件!在我们自己的MvcConofig下添加bean;
@Bean
public LocaleResolver localeResolver(){
return new MyLocalResolver();
}
复制代码
15.swagger
15.1.添加依赖
<!-- swagger -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
复制代码
15.2.添加配置类
添加一个swagger 配置类,在工程下新建 config 包并添加一个 SwaggerConfig 配置类。
SwaggerConfig.java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket createRestApi(){
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any()).build();
}
private ApiInfo apiInfo(){
return new ApiInfoBuilder()
.title("Kitty API Doc")
.description("This is a restful api document of Kitty.")
.version("1.0")
.build();
}
}
复制代码
15.3.添加控制器
添加一个控制器,在工程下新建 controller包并添加一个 HelloController控制器。
HelloController.java
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
/* 类注解 */
@Api(value = "desc of class")
@RestController
public class HelloController {
/* 方法注解 */
@ApiOperation(value = "desc of method", notes = "")
@GetMapping(value="/hello")
public Object hello( /* 参数注解 */ @ApiParam(value = "desc of param" , required=true ) @RequestParam String name) {
return "Hello " + name + "!";
}
}
复制代码
运行即可
15.4.常用注解说明
swagger 通过注解接口生成文档,包括接口名,请求方法,参数,返回信息等。
@Api: 修饰整个类,用于controller类上
@ApiOperation: 描述一个接口,用户controller方法上
@ApiParam: 单个参数描述
@ApiModel: 用来对象接收参数,即返回对象
@ApiModelProperty: 对象接收参数时,描述对象的字段
@ApiResponse: Http响应其中的描述,在ApiResonse中
@ApiResponses: Http响应所有的描述,用在
@ApiIgnore: 忽略这个API
@ApiError: 发生错误的返回信息
@ApiImplicitParam: 一个请求参数
@ApiImplicitParam: 多个请求参数
更多使用说明,参考 Swagger 使用手册。
15.5.添加请求参数
在很多时候,我们需要在调用我们每一个接口的时候都携带上一些通用参数,比如采取token验证逻辑的往往在接口请求时需要把token也一起传入后台,接下来,我们就来讲解一下如何给Swagger添加固定的请求参数。
修改SwaggerConfig配置类,替换成如下内容,利用ParameterBuilder构成请求参数。
SwaggerConfig.java
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket createRestApi(){
// 添加请求参数,我们这里把token作为请求头部参数传入后端
ParameterBuilder parameterBuilder = new ParameterBuilder();
List<Parameter> parameters = new ArrayList<Parameter>();
parameterBuilder.name("token").description("令牌")
.modelRef(new ModelRef("string")).parameterType("header").required(false).build();
parameters.add(parameterBuilder.build());
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select()
.apis(RequestHandlerSelectors.any()).paths(PathSelectors.any())
.build().globalOperationParameters(parameters);
// return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo())
// .select()
// .apis(RequestHandlerSelectors.any())
// .paths(PathSelectors.any()).build();
}
private ApiInfo apiInfo(){
return new ApiInfoBuilder()
.title("Swagger API Doc")
.description("This is a restful api document of Swagger.")
.version("1.0")
.build();
}
}
复制代码
完成之后重新启动应用,再次查看hello接口,可以看到已经支持发送token请求参数了。
15.6.配置API分组
如果没有配置分组,默认是default。通过groupName()方法即可配置分组
@Bean
public Docket docket(Environment environment) {
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo())
.groupName("group1") // 配置分组
....
}
复制代码
如何配置多个分组?配置多个分组只需要配置多个docket即可:
@Bean
public Docket docket1(){
return new Docket(DocumentationType.SWAGGER_2).groupName("group1");
}
@Bean
public Docket docket2(){
return new Docket(DocumentationType.SWAGGER_2).groupName("group2");
}
@Bean
public Docket docket3(){
return new Docket(DocumentationType.SWAGGER_2).groupName("group3");
}
复制代码
16.拦截器
在 Spring Boot 项目中,使用拦截器功能通常需要以下 3 步:
-
定义拦截器;
-
注册拦截器;
-
指定拦截规则(如果是拦截所有,静态资源也会被拦截)。
16.1.定义拦截器
在 Spring Boot 中定义拦截器十分的简单,只需要创建一个拦截器类,并实现 HandlerInterceptor 接口即可。
HandlerInterceptor 接口中定义以下 3 个方法,如下表。
| 返回值类型 | 方法声明 | 描述 |
|---|---|---|
| boolean | preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) | 该方法在控制器处理请求方法前执行,其返回值表示是否中断后续操作,返回 true 表示继续向下执行,返回 false 表示中断后续操作。 |
| void | postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) | 该方法在控制器处理请求方法调用之后、解析视图之前执行,可以通过此方法对请求域中的模型和视图做进一步修改。 |
| void | afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) | 该方法在视图渲染结束后执行,可以通过此方法实现资源清理、记录日志信息等工作。 |
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
/**
* 目标方法执行前
*
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Object loginUser = request.getSession().getAttribute("loginUser");
if (loginUser == null) {
//未登录,返回登陆页
request.setAttribute("msg", "您没有权限进行此操作,请先登陆!");
request.getRequestDispatcher("/index.html").forward(request, response);
return false;
} else {
//放行
return true;
}
}
/**
* 目标方法执行后
*
* @param request
* @param response
* @param handler
* @param modelAndView
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("postHandle执行{}", modelAndView);
}
/**
* 页面渲染后
*
* @param request
* @param response
* @param handler
* @param ex
* @throws Exception
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("afterCompletion执行异常{}", ex);
}
}
复制代码
16.2.注册拦截器
创建一个实现了 WebMvcConfigurer 接口的配置类(使用了 @Configuration 注解的类),重写 addInterceptors() 方法,并在该方法中调用 registry.addInterceptor() 方法将自定义的拦截器注册到容器中。
在配置类 MyMvcConfig 中,添加以下方法注册拦截器,代码如下。
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
......
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor());
}
}
复制代码
16.3.指定拦截规则
在使用 registry.addInterceptor() 方法将拦截器注册到容器中后,我们便可以继续指定拦截器的拦截规则了,代码如下
@Slf4j
@Configuration
public class MyConfig implements WebMvcConfigurer {
......
@Override
public void addInterceptors(InterceptorRegistry registry) {
log.info("注册拦截器");
registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**") //拦截所有请求,包括静态资源文件
.excludePathPatterns("/", "/login", "/index.html", "/user/login", "/css/**", "/images/**", "/js/**", "/fonts/**"); //放行登录页,登陆操作,静态资源
}
}
复制代码
在指定拦截器拦截规则时,调用了两个方法,这两个方法的说明如下:
- addPathPatterns:该方法用于指定拦截路径,例如拦截路径为“/**”,表示拦截所有请求,包括对静态资源的请求。
- excludePathPatterns:该方法用于排除拦截路径,即指定不需要被拦截器拦截的请求。
至此,拦截器的基本功能已经完成,接下来,我们先实现 spring-boot-adminex 的登陆功能,为验证登陆拦截做准备。
17.异常处理
Spring Boot 提供了一套默认的异常处理机制,一旦程序中出现了异常,Spring Boot 会自动识别客户端的类型(浏览器客户端或机器客户端),并根据客户端的不同,以不同的形式展示异常信息。
- 对于浏览器客户端而言,Spring Boot 会响应一个“ whitelabel”错误视图,以 HTML 格式呈现错误信息
- 对于机器客户端而言,Spring Boot 将生成 JSON 响应,来展示异常消息。
{
"timestamp": "2021-07-12T07:05:29.885+00:00",
"status": 404,
"error": "Not Found",
"message": "No message available",
"path": "/m1ain.html"
}
复制代码
Spring Boot 异常处理自动配置原理
Spring Boot 通过配置类 ErrorMvcAutoConfiguration 对异常处理提供了自动配置,该配置类向容器中注入了以下 4 个组件。
-
ErrorPageCustomizer:该组件会在在系统发生异常后,默认将请求转发到“/error”上。
-
BasicErrorController:处理默认的“/error”请求。
-
DefaultErrorViewResolver:默认的错误视图解析器,将异常信息解析到相应的错误视图上。
-
DefaultErrorAttributes:用于页面上共享异常信息。
下面,我们依次对这四个组件进行详细的介绍。
ErrorPageCustomizer
ErrorMvcAutoConfiguration 向容器中注入了一个名为 ErrorPageCustomizer 的组件,它主要用于定制错误页面的响应规则。
@Bean
public ErrorPageCustomizer errorPageCustomizer(DispatcherServletPath dispatcherServletPath) {
return new ErrorPageCustomizer(this.serverProperties, dispatcherServletPath);
}
复制代码
ErrorPageCustomizer 通过 registerErrorPages() 方法来注册错误页面的响应规则。当系统中发生异常后,ErrorPageCustomizer 组件会自动生效,并将请求转发到 “/error”上,交给 BasicErrorController 进行处理,其部分代码如下。
@Override
public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
//将请求转发到 /errror(this.properties.getError().getPath())上
ErrorPage errorPage = new ErrorPage(this.dispatcherServletPath.getRelativePath(this.properties.getError().getPath()));
// 注册错误页面
errorPageRegistry.addErrorPages(errorPage);
}
复制代码
BasicErrorController
ErrorMvcAutoConfiguration 还向容器中注入了一个错误控制器组件 BasicErrorController,代码如下。
@Bean
@ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
public BasicErrorController basicErrorController(ErrorAttributes errorAttributes,
ObjectProvider<ErrorViewResolver> errorViewResolvers) {
return new BasicErrorController(errorAttributes, this.serverProperties.getError(),
errorViewResolvers.orderedStream().collect(Collectors.toList()));
}
复制代码
BasicErrorController 的定义如下。
//BasicErrorController 用于处理 “/error” 请求
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {
......
/**
* 该方法用于处理浏览器客户端的请求发生的异常
* 生成 html 页面来展示异常信息
* @param request
* @param response
* @return
*/
@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
//获取错误状态码
HttpStatus status = getStatus(request);
//getErrorAttributes 根据错误信息来封装一些 model 数据,用于页面显示
Map<String, Object> model = Collections
.unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
//为响应对象设置错误状态码
response.setStatus(status.value());
//调用 resolveErrorView() 方法,使用错误视图解析器生成 ModelAndView 对象(包含错误页面地址和页面内容)
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
}
/**
* 该方法用于处理机器客户端的请求发生的错误
* 产生 JSON 格式的数据展示错误信息
* @param request
* @return
*/
@RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
HttpStatus status = getStatus(request);
if (status == HttpStatus.NO_CONTENT) {
return new ResponseEntity<>(status);
}
Map<String, Object> body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));
return new ResponseEntity<>(body, status);
}
......
}
复制代码
Spring Boot 通过 BasicErrorController 进行统一的错误处理(例如默认的“/error”请求)。Spring Boot 会自动识别发出请求的客户端的类型(浏览器客户端或机器客户端),并根据客户端类型,将请求分别交给 errorHtml() 和 error() 方法进行处理。
| 返回值类型 | 方法声明 | 客户端类型 | 错误信息返类型 |
|---|---|---|---|
| ModelAndView | errorHtml(HttpServletRequest request, HttpServletResponse response) | 浏览器客户端 | text/html(错误页面) |
| ResponseEntity<Map<String, Object>> | error(HttpServletRequest request) | 机器客户端(例如安卓、IOS、Postman 等等) | JSON |
换句话说,当使用浏览器访问出现异常时,会进入 BasicErrorController 控制器中的 errorHtml() 方法进行处理,当使用安卓、IOS、Postman 等机器客户端访问出现异常时,就进入error() 方法处理。
在 errorHtml() 方法中会调用父类(AbstractErrorController)的 resolveErrorView() 方法,代码如下。
protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status,
Map<String, Object> model) {
//获取容器中的所有的错误视图解析器来处理该异常信息
for (ErrorViewResolver resolver : this.errorViewResolvers) {
//调用错误视图解析器的 resolveErrorView 解析到错误视图页面
ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);
if (modelAndView != null) {
return modelAndView;
}
}
return null;
}
复制代码
从上述源码可以看出,在响应页面的时候,会在父类的 resolveErrorView 方法中获取容器中所有的 ErrorViewResolver 对象(错误视图解析器,包括 DefaultErrorViewResolver 在内),一起来解析异常信息。
DefaultErrorViewResolver
ErrorMvcAutoConfiguration 还向容器中注入了一个默认的错误视图解析器组件 DefaultErrorViewResolver,代码如下。
@Bean
@ConditionalOnBean(DispatcherServlet.class)
@ConditionalOnMissingBean(ErrorViewResolver.class)
DefaultErrorViewResolver conventionErrorViewResolver() {
return new DefaultErrorViewResolver(this.applicationContext, this.resources);
}
复制代码
当发出请求的客户端为浏览器时,Spring Boot 会获取容器中所有的 ErrorViewResolver 对象(错误视图解析器),并分别调用它们的 resolveErrorView() 方法对异常信息进行解析,其中自然也包括 DefaultErrorViewResolver(默认错误信息解析器)。
DefaultErrorViewResolver 的部分代码如下。
public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered {
private static final Map<HttpStatus.Series, String> SERIES_VIEWS;
static {
Map<HttpStatus.Series, String> views = new EnumMap<>(HttpStatus.Series.class);
views.put(Series.CLIENT_ERROR, "4xx");
views.put(Series.SERVER_ERROR, "5xx");
SERIES_VIEWS = Collections.unmodifiableMap(views);
}
......
@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
//尝试以错误状态码作为错误页面名进行解析
ModelAndView modelAndView = resolve(String.valueOf(status.value()), model);
if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
//尝试以 4xx 或 5xx 作为错误页面页面进行解析
modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
}
return modelAndView;
}
private ModelAndView resolve(String viewName, Map<String, Object> model) {
//错误模板页面,例如 error/404、error/4xx、error/500、error/5xx
String errorViewName = "error/" + viewName;
//当模板引擎可以解析这些模板页面时,就用模板引擎解析
TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName,
this.applicationContext);
if (provider != null) {
//在模板能够解析到模板页面的情况下,返回 errorViewName 指定的视图
return new ModelAndView(errorViewName, model);
}
//若模板引擎不能解析,则去静态资源文件夹下查找 errorViewName 对应的页面
return resolveResource(errorViewName, model);
}
private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
//遍历所有静态资源文件夹
for (String location : this.resources.getStaticLocations()) {
try {
Resource resource = this.applicationContext.getResource(location);
//静态资源文件夹下的错误页面,例如error/404.html、error/4xx.html、error/500.html、error/5xx.html
resource = resource.createRelative(viewName + ".html");
//若静态资源文件夹下存在以上错误页面,则直接返回
if (resource.exists()) {
return new ModelAndView(new DefaultErrorViewResolver.HtmlResourceView(resource), model);
}
} catch (Exception ex) {
}
}
return null;
}
......
}
复制代码
DefaultErrorViewResolver 解析异常信息的步骤如下:
- 根据错误状态码(例如 404、500、400 等),生成一个错误视图 error/status,例如 error/404、error/500、error/400。
- 尝试使用模板引擎解析 error/status 视图,即尝试从 classpath 类路径下的 templates 目录下,查找 error/status.html,例如 error/404.html、error/500.html、error/400.html。
- 若模板引擎能够解析到 error/status 视图,则将视图和数据封装成 ModelAndView 返回并结束整个解析流程,否则跳转到第 4 步。
- 依次从各个静态资源文件夹中查找 error/status.html,若在静态文件夹中找到了该错误页面,则返回并结束整个解析流程,否则跳转到第 5 步。
- 将错误状态码(例如 404、500、400 等)转换为 4xx 或 5xx,然后重复前 4 个步骤,若解析成功则返回并结束整个解析流程,否则跳转第 6 步。
- 处理默认的 “/error ”请求,使用 Spring Boot 默认的错误页面(Whitelabel Error Page)。
DefaultErrorAttributes
ErrorMvcAutoConfiguration 还向容器中注入了一个组件默认错误属性处理工具 DefaultErrorAttributes,代码如下。
@Bean
@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
public DefaultErrorAttributes errorAttributes() {
return new DefaultErrorAttributes();
}
复制代码
DefaultErrorAttributes 是 Spring Boot 的默认错误属性处理工具,它可以从请求中获取异常或错误信息,并将其封装为一个 Map 对象返回,其部分代码如下。
public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver, Ordered {
......
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
Map<String, Object> errorAttributes = getErrorAttributes(webRequest, options.isIncluded(Include.STACK_TRACE));
if (!options.isIncluded(Include.EXCEPTION)) {
errorAttributes.remove("exception");
}
if (!options.isIncluded(Include.STACK_TRACE)) {
errorAttributes.remove("trace");
}
if (!options.isIncluded(Include.MESSAGE) && errorAttributes.get("message") != null) {
errorAttributes.remove("message");
}
if (!options.isIncluded(Include.BINDING_ERRORS)) {
errorAttributes.remove("errors");
}
return errorAttributes;
}
private Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
Map<String, Object> errorAttributes = new LinkedHashMap<>();
errorAttributes.put("timestamp", new Date());
addStatus(errorAttributes, webRequest);
addErrorDetails(errorAttributes, webRequest, includeStackTrace);
addPath(errorAttributes, webRequest);
return errorAttributes;
}
......
}
复制代码
在 Spring Boot 默认的 Error 控制器(BasicErrorController)处理错误时,会调用 DefaultErrorAttributes 的 getErrorAttributes() 方法获取错误或异常信息,并封装成 model 数据(Map 对象),返回到页面或 JSON 数据中。该 model 数据主要包含以下属性:
- timestamp:时间戳;
- status:错误状态码
- error:错误的提示
- exception:导致请求处理失败的异常对象
- message:错误/异常消息
- trace: 错误/异常栈信息
- path:错误/异常抛出时所请求的URL路径




近期评论