SpringBoot核心功能

SpringBoot的核心功能包括,配置文件、web开发、数据访问、单元测试、指标监控等,本文记录SpringBoot核心功能的相关学习过程。

配置文件

properties

Java中常见的配置文件,基本格式如下所示:

1
2
3
4
5
6
7
8
9
10
11
# 这是注释
str = helloworld
#以下为服务器、数据库信息
dbPort = localhost
databaseName = mydb
dbUserName = root
dbPassword = 123456
#以下为数据库表信息
dbTable = mytable
#以下为服务器信息
ip = 192.168.0.9

yaml

简介

YAML 是 “YAML Ain’t Markup Language”(YAML不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:”Yet Another Markup Language”(仍是一种标记语言)。

非常适合用来做以数据为中心的配置文件

基本语法

  • key: value;kv之间有空格

  • 大小写敏感

  • 使用缩进表示层级关系

  • 缩进不允许使用tab,只允许空格

  • 缩进的空格数不重要,只要相同层级的元素左对齐即可

  • ‘#’表示注释

  • 字符串无需加引号,如果要加,’’ 与 “” 表示字符串内容会被 转义 / 不转义

    1
    2
    3
    4
    5
    # 加上单引号会输出\n不换行,而字符串中有转义字符本身应该被换行,
    # 说明单引号再次转义了,抑制了其中的转义字符
    key1: 'hello \n tom'
    # 加上双引号会换行,说明其中的转义字符没有被抑制
    key2: "hello \n tom"

数据类型

  • 字面量:单个的、不可再分的值。date、boolean、string、number、null
1
2
3
4
5
key1: 123
key2: true
key3: "hello"
key4: null
key5: 2022/1/13 09:25:21
  • 对象:键值对的集合,例如:map、hash、set、object等
1
2
3
4
5
6
7
# 行内写法
key1:{k1: v1, k2: v2, k3: v3}
# 多行写法
k:
k1: v1
k2: v2
k3: v3

示例

配置一个person对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@ConfigurationProperties(prefix = "person")
@Component
@Data
public class Person {

private String userName;
private Boolean boss;
private Date birth;
private Integer age;
private Pet pet;
private String[] interests;
private List<String> animal;
private Map<String, Object> score;
private Set<Double> salarys;
private Map<String, List<Pet>> allPets;
}

@Data
public class Pet {
private String name;
private Double weight;
}

application.yaml文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# yaml表示以上对象
person:
userName: zhangsan
boss: false
birth: 2019/12/12 20:12:33
age: 18
pet:
name: tomcat
weight: 23.4
interests: [篮球,游泳]
animal:
- jerry
- mario
score:
english:
first: 30
second: 40
third: 50
math: [131,140,148]
chinese: {first: 128,second: 136}
salarys: [3999,4999.98,5999.99]
allPets:
sick:
- {name: tom}
- {name: jerry,weight: 47}
- name: dog
weight: 100
health: [{name: mario,weight: 47},{name: tiger, weight: 180}]

配置提示

自定义的类和配置文件在配置文件绑定时一般没有提示,加入依赖即可进行提示

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>

<!--打包时排除配置提示插件,否则将没有必要地占用不少资源-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>

Web开发

SpringMVC自动配置概览

Spring Boot provides auto-configuration for Spring MVC that works well with most applications.(大多场景我们都无需自定义配置)

The auto-configuration adds the following features on top of Spring’s defaults:

  • Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans.

    • 内容协商视图解析器和BeanName视图解析器
  • Support for serving static resources, including support for WebJars (covered later in this document)).

    • 静态资源(包括webjars)
  • Automatic registration of Converter, GenericConverter, and Formatter beans.

    • 自动注册 Converter,GenericConverter,Formatter
  • Support for HttpMessageConverters (covered later in this document).

    • 支持 HttpMessageConverters (后来我们配合内容协商理解原理)
  • Automatic registration of MessageCodesResolver (covered later in this document).

    • 自动注册 MessageCodesResolver (国际化用)
  • Static index.html support.

    • 静态index.html 页支持
  • Custom Favicon support (covered later in this document).

    • 自定义 Favicon
  • Automatic use of a ConfigurableWebBindingInitializer bean (covered later in this document).

    • 自动使用 ConfigurableWebBindingInitializer ,(DataBinder负责将请求数据绑定到JavaBean上)

简单功能分析

静态资源访问

默认的情况下,只需要静态资源放在类路径下,就可以通过资源名访问到静态资源:

  • /static
  • /public
  • /resources
  • /META-INF/resources

访问 : 当前项目根路径/ + 静态资源名 如:http://localhost:8080/hello.jpg

原理: 静态映射/**。

请求进来,先去找Controller看能不能处理。不能处理的所有请求又都交给静态资源处理器。静态资源也找不到则响应404页面

改变默认的静态资源路径

默认的静态资源访问是无前缀的,但此时若需要做拦截,与静态资源合理地做区分,而不影响效率,可以将静态资源的前缀进行修改:

1
2
3
4
5
6
7
8
# 表示所有的静态资源都以以下前缀为准进行访问
spring:
mvc:
static-path-pattern: /res/**

# 表示此时静态资源只允许放到/haha/文件夹下才能被访问
resources:
static-locations: [classpath:/haha/]

前后端不分离访问jquery

自动映射 /webjars/**

网址:https://www.webjars.org/

需要在pom.xml中引入相关的依赖

1
2
3
4
5
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.5.1</version>
</dependency>

访问地址:http://localhost:8080/webjars/jquery/3.5.1/jquery.js 后面地址要按照依赖里面的包路径

欢迎页支持

  • 第一种方式:静态资源路径下 index.html

    • 可以配置静态资源路径
    • 但是不可以配置静态资源的访问前缀。否则导致 index.html不能被默认访问
1
2
3
4
5
6
spring:
# mvc:
# static-path-pattern: /res/** 这个会导致welcome page功能失效

resources:
static-locations: [classpath:/haha/]
  • 第二种方式:存在controller能处理/index请求

自定义Favicon

favicon.ico放入到静态资源目录下即可使网站的图标被替换

1
2
3
spring:
# mvc:
# static-path-pattern: /res/** 这个会导致 Favicon 功能失效

请求参数的处理

普通参数与基本注解

注解

@PathVariable、@RequestHeader、@ModelAttribute、@RequestParam、@MatrixVariable、@CookieValue、@RequestBody

使用样例如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
@RestController
public class ParameterTestController {


// car/2/owner/zhangsan
@GetMapping("/car/{id}/owner/{username}")
public Map<String,Object> getCar(@PathVariable("id") Integer id,
@PathVariable("username") String name,
@PathVariable Map<String,String> pv,
@RequestHeader("User-Agent") String userAgent,
@RequestHeader Map<String,String> header,
@RequestParam("age") Integer age,
@RequestParam("inters") List<String> inters,
@RequestParam Map<String,String> params,
@CookieValue("_ga") String _ga,
@CookieValue("_ga") Cookie cookie){


Map<String,Object> map = new HashMap<>();

// map.put("id",id);
// map.put("name",name);
// map.put("pv",pv);
// map.put("userAgent",userAgent);
// map.put("headers",header);
map.put("age",age);
map.put("inters",inters);
map.put("params",params);
map.put("_ga",_ga);
System.out.println(cookie.getName()+"===>"+cookie.getValue());
return map;
}


@PostMapping("/save")
public Map postMethod(@RequestBody String content){
Map<String,Object> map = new HashMap<>();
map.put("content",content);
return map;
}


//1、语法: 请求路径:/cars/sell;low=34;brand=byd,audi,yd
//2、SpringBoot默认是禁用了矩阵变量的功能
// 手动开启:原理。对于路径的处理。UrlPathHelper进行解析。
// removeSemicolonContent(移除分号内容)支持矩阵变量的
//3、矩阵变量必须有url路径变量才能被解析
@GetMapping("/cars/{path}")
public Map carsSell(@MatrixVariable("low") Integer low,
@MatrixVariable("brand") List<String> brand,
@PathVariable("path") String path){
Map<String,Object> map = new HashMap<>();

map.put("low",low);
map.put("brand",brand);
map.put("path",path);
return map;
}

// /boss/1;age=20/2;age=10

@GetMapping("/boss/{bossId}/{empId}")
public Map boss(@MatrixVariable(value = "age",pathVar = "bossId") Integer bossAge,
@MatrixVariable(value = "age",pathVar = "empId") Integer empAge){
Map<String,Object> map = new HashMap<>();

map.put("bossAge",bossAge);
map.put("empAge",empAge);
return map;

}

}
Servlet API

WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId

ServletRequestMethodArgumentResolver以上的部分参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Override
public boolean supportsParameter(MethodParameter parameter) {
Class<?> paramType = parameter.getParameterType();
return (WebRequest.class.isAssignableFrom(paramType) ||
ServletRequest.class.isAssignableFrom(paramType) ||
MultipartRequest.class.isAssignableFrom(paramType) ||
HttpSession.class.isAssignableFrom(paramType) ||
(pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) ||
Principal.class.isAssignableFrom(paramType) ||
InputStream.class.isAssignableFrom(paramType) ||
Reader.class.isAssignableFrom(paramType) ||
HttpMethod.class == paramType ||
Locale.class == paramType ||
TimeZone.class == paramType ||
ZoneId.class == paramType);
}
复杂类型

Map、Model(map、model里面的数据会被放在request的请求域 request.setAttribute)、Errors/BindingResult、RedirectAttributes( 重定向携带数据)、ServletResponse(response)、SessionStatus、UriComponentsBuilder、ServletUriComponentsBuilder

使用map,model,request作为Controller的参数,向其中放置数据等于向requestScope中放数据

1
2
Map<String,Object> map,  Model model, HttpServletRequest request; 	//通过这些都可以给request域中放数据
request.getAttribute();

Map、Model类型的参数,会返回 mavContainer.getModel();得到 —> BindingAwareModelMap 是Model 也是Map

mavContainer.getModel(); 获取到值

自定义对象参数

可以自动类型转换与格式化,可以级联封装。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 姓名: <input name="userName"/> <br/>
* 年龄: <input name="age"/> <br/>
* 生日: <input name="birth"/> <br/>
* 宠物姓名:<input name="pet.name"/><br/>
* 宠物年龄:<input name="pet.age"/>
*/
@Data
public class Person {

private String userName;
private Integer age;
private Date birth;
private Pet pet;

}

@Data
public class Pet {

private String name;
private String age;

}

数据响应与内容协商

数据响应JSON

JSON数据响应使用jackson.jar + @ResponseBody实现功能

首先需要引用依赖pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!--web场景自动引入了json场景-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
<version>2.3.4.RELEASE</version>
<scope>compile</scope>
</dependency>

给前端自动返回JSON数据的原理如下:

1. 首先在15个返回值解析器中寻找目标类型所需的解析器

2. 通过MessageConverter将对象转换为前端需要的内容

其原理如下所示:

  1. 返回值处理器判断是否支持这种类型返回值supportsReturnType
  2. 返回值处理器调用handleReturnValue 进行处理
  3. RequestResponseBodyMethodProcessor 解析器可以处理返回值标记了@ResponseBody注解的。
    1. 利用MessageConverters进行处理将数据重新写为json格式
      1. 内容协商(浏览器默认会以请求头的方式告诉服务器他能接受什么样的内容类型)
      2. 服务器最终根据自己自身的能力,决定服务器能生产出什么样内容类型的数据
      3. SpringMVC会挨个遍历所有容器底层的 HttpMessageConverter ,看谁能处理?
        1. 得到MappingJackson2HttpMessageConverter可以将对象写为json
        2. 利用MappingJackson2HttpMessageConverter将对象转为json再写出去。

支持的返回值解析器有如下几种:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ModelAndView
Model
View
ResponseEntity
ResponseBodyEmitter
StreamingResponseBody
HttpEntity
HttpHeaders
Callable
DeferredResult
ListenableFuture
CompletionStage
WebAsyncTask
# 有 @ModelAttribute 且为对象类型的
RequestResponseBodyMethodProcessor;# 有@ResponseBody注解,通过该解析器解析成

支持的转换器类型有如下几种:

1
2
3
4
5
6
7
8
0 ByteArrayHttpMessageConverter - 只支持Byte类型的
1 StringHttpMessageConverter - String
2 ResourceHttpMessageConverter - Resource
3 ResourceRegionHttpMessageConverter - ResourceRegion
4 SourceHttpMessageConverter - DOMSource.class \ SAXSource.class) \ StAXSource.class \StreamSource.class \Source.class
5 AllEncompassingFormHttpMessageConverter - MultiValueMap
6 MappingJackson2HttpMessageConverter - 将对象转换为json类型
7 Jaxb2RootElementHttpMessaaeConverter - 支持注解方式xml处理的。

内容协商

引入xml依赖

引入xml依赖后,服务端允许按照xml回传数据,因为json和xml的依赖不同,因此需要导入xml依赖:

1
2
3
4
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
apifox测试返回json和xml数据

比如电脑端浏览器访问后端接口时只能处理xml数据,而手机app访问时只能处理json数据,应该如何写后端接口,保证前端能够获取所需的数据呢?

其实并不需要人为进行判断,spring可以解析http请求头中的Accept字段,该字段是Http协议中规定的,用于告诉服务器发起请求的客户端能够接收处理的数据类型,因此需要返回json或者xml格式,只需要在Accept中进行设定即可。

1
2
3
4
5
6
# 默认Accept如下:
Accept :*/*
# 获取json
Accept :application/json
# 获取xml
Accept :application/xml
开启浏览器使用参数内容进行返回值协商的功能

如果使用浏览器直接发送请求,除非使用Ajax,否则可能没有办法指定可以接收的类型,会按照顺序使用靠前的格式响应:

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9

因此可以开启返回值协商功能,达到相应的目的

1
2
3
spring:
contentnegotiation:
favor-parameter: true #开启请求参数内容协商模式

客户端通过参数协商获取json格式数据:

http://localhost:8080/test/person?format=json

客户端通过参数协商获取xml格式数据:

http://localhost:8080/test/person?format=xml

内容协商的原理

1、判断当前响应头中是否已经有确定的媒体类型(MediaType)。

2、获取客户端(PostMan、浏览器、app)支持接收的内容类型。(获取客户端Accept请求头字段)**[application/xml]**

3、遍历循环所有当前系统的 MessageConverter,看谁支持操作这个对象(Person)

4、找到支持操作Person类型的converter,把converter支持的媒体类型统计出来。

5、找到客户端所需的**[application/xml]**返回类型,进行内容协商,匹配最佳的媒体类型。

6、使用获取的支持将对象转换为最佳的匹配媒体类型的converter转换器,调用其进行相应的转化。

视图解析与模板引擎

视图解析:SpringBoot默认不支持JSP,因此需要引入第三方模板引擎技术实现页面渲染

视图解析的处理方式有以下几种:

  1. 转发
  2. 重定向
  3. 自定义视图

模板引擎-Thymeleaf介绍

Thymeleaf是现代化,服务端的java模板引擎

Thymeleaf文档:Tutorial: Using Thymeleaf

基本语法

表达式
表达式名字 语法 用途
变量取值 ${…} 获取请求域、session域、对象等值
选择变量 *{…} 获取上下文对象值
消息 #{…} 获取国际化等值
链接 @{…} 生成链接,自动拼接项目的路径
片段表达式 ~{…} jsp:include 作用,引入公共页面片段
字面量

文本值: ‘one text’ , ‘Another one!’ ,…数字: 0 , 34 , 3.0 , 12.3 ,…布尔值: true , false

空值: null

变量: one,two,…. 变量中不能出现空格

文本操作

字符串拼接: +

变量替换: |The name is ${name}|

数学运算

运算符: + , - , * , / , %

布尔运算

运算符: and , or

一元运算: ! , not

比较运算

比较: > , <** **,** **>= , <= ( gt , lt , ge , le **)**等式: == , != ( eq , ne )

条件运算

If-then: (if) ? (then)

If-then-else: (if) ? (then) : (else)

Default: (value) ?: (defaultvalue)

特殊操作

无操作: _

设置属性值 th:attr

设置单个值:

1
2
3
4
5
6
<form action="subscribe.html" th:attr="action=@{/subscribe}">
<fieldset>
<input type="text" name="email" />
<input type="submit" value="Subscribe!" th:attr="value=#{subscribe.submit}"/>
</fieldset>
</form>

设置多个值:

1
<img src="../../images/gtvglogo.png"  th:attr="src=@{/images/gtvglogo.png},title=#{logo},alt=#{logo}" />

替代写法:

1
2
<input type="submit" value="Subscribe!" th:value="#{subscribe.submit}"/>
<form action="subscribe.html" th:action="@{/subscribe}">
迭代
1
2
3
4
5
6
<!--类似于foreach循环遍历-->
<tr th:each="prod : ${prods}">
<td th:text="${prod.name}">Onions</td>
<td th:text="${prod.price}">2.41</td>
<td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
</tr>
1
2
3
4
5
<tr th:each="prod,iterStat : ${prods}" th:class="${iterStat.odd}? 'odd'">
<td th:text="${prod.name}">Onions</td>
<td th:text="${prod.price}">2.41</td>
<td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
</tr>
条件运算
1
2
3
4
5
6
7
8
9
<a href="comments.html"
th:href="@{/product/comments(prodId=${prod.id})}"
th:if="${not #lists.isEmpty(prod.comments)}">view</a>

<div th:switch="${user.role}">
<p th:case="'admin'">User is an administrator</p>
<p th:case="#{roles.manager}">User is a manager</p>
<p th:case="*">User is some other thing</p>
</div>
属性优先级

image-20220117140121149

thymeleaf的使用

引入Starter
1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

1、所有thymeleaf的配置值都在 ThymeleafProperties

2、配置好了 SpringTemplateEngine

3、配好了 ThymeleafViewResolver

4、我们只需要直接开发页面

1
2
3
4
5
6
7
8
9
10
// 已经自动配置好了,只需要直接开发即可
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(ThymeleafProperties.class)
@ConditionalOnClass({ TemplateMode.class, SpringTemplateEngine.class })
@AutoConfigureAfter({ WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class })
public class ThymeleafAutoConfiguration { }

// 前缀后缀已经被指定好了
public static final String DEFAULT_PREFIX = "classpath:/templates/";
public static final String DEFAULT_SUFFIX = ".html"; //xxx.html
页面开发
1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1 th:text="${msg}">你好哇!Hello world!</h1>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* @author heavytiger
* @version 1.0
*/
@Controller
public class ViewTestController {

@GetMapping("/heavytiger")
public String heavytiger(Model model){

model.addAttribute("msg", "来自后端的你好!");
// 只需要写名称即可,已经自动配置好视图解析器了
return "success";
}
}

若直接访问该页面,即没有后台传值,会正常显示标签内的数据:

image-20220117141914389

若后台开启,通过controller进行访问,得到的即是requestScope中的值

image-20220117211839008

可以看到此时标签内的值已经被后端传递的${msg}覆盖

拦截器

HandlerInterceptor接口

拦截器需要继承HandlerInterceptor接口即可运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
/**
* 登录检查
* 1、配置好拦截器要拦截哪些请求
* 2、把这些配置放在容器中
*/
@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 {

String requestURI = request.getRequestURI();
log.info("preHandle拦截的请求路径是{}",requestURI);

//登录检查逻辑
HttpSession session = request.getSession();

Object loginUser = session.getAttribute("loginUser");

if(loginUser != null){
//放行
return true;
}

//拦截住。未登录。跳转到登录页
request.setAttribute("msg","请先登录");
// re.sendRedirect("/");
request.getRequestDispatcher("/").forward(request,response);
return false;
}

/**
* 目标方法执行完成以后
* @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);
}
}

配置拦截器

所有的web相关的配置文件都继承WebMvcConfigurer接口即可

继承WebMvcConfigurer接口的addInterceptors方法,通过该方法的参数InterceptorRegistry注册拦截器的相关参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 1、编写一个拦截器实现HandlerInterceptor接口
* 2、拦截器注册到容器中(实现WebMvcConfigurer的addInterceptors)
* 3、指定拦截规则【如果是拦截所有,静态资源也会被拦截】
*/
@Configuration
public class AdminWebConfig implements WebMvcConfigurer {

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**") //所有请求都被拦截包括静态资源
.excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**","/js/**"); //放行的请求
}
}

拦截器的原理

1、根据当前请求,找到HandlerExecutionChain【可以处理请求的handler以及handler的所有拦截器】

2、先来顺序执行所有拦截器的preHandle方法

​ 1、如果当前拦截器prehandler返回为true。则执行下一个拦截器的preHandle

​ 2、如果当前拦截器返回为false。直接倒序执行所有已经执行了的拦截器的afterCompletion

3、如果任何一个拦截器返回false。直接跳出不执行目标方法

4、所有拦截器都返回True。执行目标方法

5、倒序执行所有拦截器的postHandle方法。

6、前面的步骤有任何异常都会直接倒序触发afterCompletion

7、页面成功渲染完成以后,也会倒序触发afterCompletion

文件上传

页面表单

1
2
3
4
5
6
7
<form method="post" action="/upload" enctype="multipart/form-data">
<!--上传单一文件-->
<input type="file" name="file" id="InputOneFile"><br>
<!--上传多文件-->
<input type="file" name="files" id="InputFiles" multiple>
<input type="submit" value="提交">
</form>

文件上传Controller代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
* MultipartFile 自动封装上传过来的文件
* @param email
* @param username
* @param headerImg
* @param photos
* @return
*/
@PostMapping("/upload")
public String upload(@RequestPart("file") MultipartFile headerImg,
@RequestPart("files") MultipartFile[] photos) throws IOException {

log.info("上传的信息:headerImg={},photos={}",
headerImg.getSize(), photos.length);

if(!headerImg.isEmpty()){
//保存到文件服务器,OSS服务器
String originalFilename = headerImg.getOriginalFilename();
headerImg.transferTo(new File("H:\\cache\\"+originalFilename));
}

if(photos.length > 0){
for (MultipartFile photo : photos) {
if(!photo.isEmpty()){
String originalFilename = photo.getOriginalFilename();
photo.transferTo(new File("H:\\cache\\"+originalFilename));
}
}
}
return "main";
}

文件上传自动配置原理

文件上传自动配置类-MultipartAutoConfiguration-MultipartProperties

  • 自动配置好了StandardServletMultipartResolver【文件上传解析器】

  • 原理步骤

    • 1、请求进来使用文件上传解析器判断(isMultipart)并封装(resolveMultipart,返回MultipartHttpServletRequest)文件上传请求
    • 2、参数解析器来解析请求中的文件内容封装成MultipartFile
    • 3、将request中文件信息封装为一个Map;MultiValueMap<String, MultipartFile>

FileCopyUtils。实现文件流的拷贝

异常处理

默认规则

  • 默认的情况下,Spring Boot提供/error处理所有错误的映射

  • 对于机器客户端,它将生成JSON响应,其中包含错误,HTTP状态和异常消息的详细信息。对于浏览器客户端,响应一个whitelabel错误视图,以HTML格式呈现相同的数据

    1
    2
    3
    4
    5
    6
    7
    8
    // json响应
    {
    "timestamp": "xxxxxxx",
    "status": 404,
    "error": "Not Found",
    "message": "No xxxx",
    "path": "/api/xxxx"
    }

替换错误页面

可以在templates/error文件夹下放置需要相应的页面,将会自动替换SpringBoot的白页报错,例如templates/error/4xx.htmltemplates/error/500.html

其逻辑如下:

  1. error/404.html error/5xx.html;

  2. 有精确的错误状态码页面就匹配精确,没有就找 4xx.html;

  3. 如果都没有就触发白页

数据访问

SQL数据库

数据源自动配置

使用HikariDataSource进行数据源的自动配置

首先要导入JDBC场景:

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>

会引入以下依赖:

  • HikanCP 数据源
  • spring-jdbc jdbc驱动
  • spring-tx 事务支持

我们会发现官方没有导入数据库驱动,原因是spring不知道开发人员会使用什么驱动,因此不导入,需要自己导入

导入数据库驱动:

默认版本是: <mysql.version>8.0.22</mysql.version>

数据库的版本需要和驱动版本对应

两种方法可以解决:

1
2
3
4
5
6
7
8
9
10
11
<!--1. 直接在依赖中修改,maven会进行仲裁,就近依赖原则,不选择patter中的版本号-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
<!--2. 修改properties属性,在其中标明版本号,属性就近优先原则-->
<properties>
<java.version>1.8</java.version>
<mysql.version>5.1.49</mysql.version>
</properties>

使用Druid数据源

导入依赖:

1
2
3
4
5
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.17</version>
</dependency>

创建数据源配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close">
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<property name="maxActive" value="20" />
<property name="initialSize" value="1" />
<property name="maxWait" value="60000" />
<property name="minIdle" value="1" />
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<property name="minEvictableIdleTimeMillis" value="300000" />
<property name="testWhileIdle" value="true" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<property name="poolPreparedStatements" value="true" />
<property name="maxOpenPreparedStatements" value="20" />
</bean>

@ConditionalOnMissingBean(DataSource.class) 的含义是:当容器中没有 DataSource(数据源类)时,Spring Boot 才会使用 HikariCP 作为其默认数据源。 也就是说,若我们向容器中添加 Druid 数据源类(DruidDataSource,继承自 DataSource)的对象时,Spring Boot 就会使用 Druid 作为其数据源,而不再使用 HikariCP。

例如,我们需要在config包中创建一个MyDataSourceConfig的配置类,并将 Druid 数据源对象添加到容器中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Configuration
public class MyDataSourceConfig implements WebMvcConfigurer {
/**
* 当向容器中添加了 Druid 数据源
* 使用 @ConfigurationProperties 将配置文件中 spring.datasource 开头的配置与数据源中的属性进行绑定
* @return
*/
@ConfigurationProperties("spring.datasource")
@Bean
public DataSource dataSource() throws SQLException {
DruidDataSource druidDataSource = new DruidDataSource();
//我们一般不建议将数据源属性硬编码到代码中,而应该在配置文件中进行配置(@ConfigurationProperties 绑定)
// druidDataSource.setUrl("jdbc:mysql://127.0.0.1:3306/jdbc");
// druidDataSource.setUsername("root");
// druidDataSource.setPassword("root");
// druidDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
return druidDataSource;
}
}

在配置文件 application.yml 中添加以下数据源配置,它们会与与 Druid 数据源中的属性进行绑定:

1
2
3
4
5
6
7
#数据源连接信息
spring:
datasource:
username: root
password: root
url: jdbc:mysql://127.0.0.1:3306/jdbc
driver-class-name: com.mysql.cj.jdbc.Driver

至此,我们就已经将数据源从 HikariCP 切换到了 Druid 了。

通过starter整合Druid

Druid 可以说是国内使用最广泛的数据源连接池产品,但到目前为止 Spring Boot 官方只对 Hikari、Tomcat、Dbcp2 和 OracleUcp 等 4 种数据源产品提供了自动配置支持,对于其他的数据源连接池产品(包括 Druid),则并没有提供自动配置支持。这就导致用户只能通过自定义的方式整合 Druid,非常繁琐。

为了解决这一问题,于是阿里官方提供了 Druid Spring Boot Starter,它可以帮助我们在 Spring Boot 项目中,轻松地整合 Druid 的数据库连接池和监控功能。

引入依赖:

1
2
3
4
5
6
<!--添加 druid 的 starter-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.17</version>
</dependency>

配置属性:

在 Spring Boot 配置文件中配置以下内容:

  • JDBC 通用配置
  • Druid 数据源连接池配置
  • Druid 监控配置
  • Druid 内置 Filter 配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
##################### JDBC 通用配置  ####################
spring:
datasource:
username: root #数据库登陆用户名
password: root #数据库登陆密码
url: jdbc:mysql://127.0.0.1:3306/jdbc #数据库url
driver-class-name: com.mysql.cj.jdbc.Driver #数据库驱动

##################### Druid连接池的配置 ##################
spring:
datasource:
druid:
initial-size: 5 #初始化连接大小
min-idle: 5 #最小连接池数量
max-active: 20 #最大连接池数量
max-wait: 60000 #获取连接时最大等待时间,单位毫秒
time-between-eviction-runs-millis: 60000 #配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
min-evictable-idle-time-millis: 300000 #配置一个连接在池中最小生存的时间,单位是毫秒
validation-query: SELECT 1 FROM DUAL #测试连接
test-while-idle: true #申请连接的时候检测,建议配置为true,不影响性能,并且保证安全性
test-on-borrow: false #获取连接时执行检测,建议关闭,影响性能
test-on-return: false #归还连接时执行检测,建议关闭,影响性能
pool-prepared-statements: false #是否开启PSCache,PSCache对支持游标的数据库性能提升巨大,oracle建议开启,mysql下建议关闭
max-pool-prepared-statement-per-connection-size: 20 #开启poolPreparedStatements后生效
filters: stat,wall #配置扩展插件,常用的插件有=>stat:监控统计 wall:防御sql注入
connection-properties: 'druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000' #通过connectProperties属性来打开mergeSql功能;慢SQL记录

###################### Druid 监控配置信息 ##################
spring:
datasource:
druid:
# StatViewServlet配置,说明请参考Druid Wiki,配置_StatViewServlet配置
stat-view-servlet:
enabled: true #是否开启内置监控页面,默认值为 false
url-pattern: '/druid/*' #StatViewServlet 的映射路径,即内置监控页面的访问地址
reset-enable: true #是否启用重置按钮
login-username: admin #内置监控页面的登录页用户名 username
login-password: admin #内置监控页面的登录页密码 password
# WebStatFilter配置,说明请参考Druid Wiki,配置_配置WebStatFilter
web-stat-filter:
enabled: true #是否开启内置监控中的 Web-jdbc 关联监控的数据
url-pattern: '/*' #匹配路径
exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*' #排除路径
session-stat-enable: true #是否监控session
# Spring监控配置,说明请参考Druid Github Wiki,配置_Druid和Spring关联监控配置
aop-patterns: com.heavytiger.jdbc.* #Spring监控AOP切入点,如x.y.z.abc.*,配置多个英文逗号分隔

###################### Druid 监控配置信息 ####################
spring:
datasource:
druid:
#对配置已开启的 filters 即 stat(sql 监控) wall(防火墙)
filter:
#配置StatFilter (SQL监控配置)
stat:
enabled: true #开启 SQL 监控
slow-sql-millis: 1000 #慢查询
log-slow-sql: true #记录慢查询 SQL
#配置WallFilter (防火墙配置)
wall:
enabled: true #开启防火墙
config:
update-allow: true #允许更新操作
drop-table-allow: false #禁止删表操作
insert-allow: true #允许插入操作
delete-allow: true #删除数据操作

在配置 Druid 内置 Filter 时,需要先将对应 Filter 的 enabled 设置为 true,否则内置 Filter 的配置不会生效。

整合MyBatis

引入依赖
1
2
3
4
5
6
<!--引入 mybatis-spring-boot-starter 的依赖-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
配置MyBatis
1
2
3
4
5
6
7
8
9
###################################### MyBatis 配置######################################
mybatis:
# 指定 mapper.xml 的位置
mapper-locations: classpath:mybatis/mapper/*.xml
#扫描实体类的位置,在此处指明扫描实体类的包,在 mapper.xml 中就可以不写实体类的全路径名
type-aliases-package: com.heavytiger.pojo
configuration:
#默认开启驼峰命名法,可以不用设置该属性
map-underscore-to-camel-case: true
创建实体类及接口

实体类不再占用位置书写

Mapper接口需要进行标记:@Mapper如:

1
2
3
4
5
@Mapper
public interface UserMapper {
//通过用户名密码查询用户数据
User getByUserNameAndPassword(User user);
}

当 mapper 接口较多时,我们可以在 Spring Boot 主启动类上使用 @MapperScan 注解扫描指定包下的 mapper 接口,而不再需要在每个 mapper 接口上都标注 @Mapper 注解。

参考资料

[1] 尚硅谷雷神SpringBoot2零基础入门springboot全套完整版(spring boot2)_哔哩哔哩_bilibili

[2] SpringBoot2核心技术与响应式编程 · 语雀 (yuque.com)


-------------本文到此结束 感谢您的阅读-------------
谢谢你请我喝肥宅快乐水(๑>ڡ<) ☆☆☆