SpringBoot
SpringBoot2
简介
简化Spring应用的厨师搭建和开发过程
是高层框架,底层是SpringFramework
是整合Spring技术栈的一站式框架
是简化Spring技术栈的快速开发脚手架
@SpringBootApplication:主程序类,这是一个SpringBoot应用
@RestController:@Controller + @ResponseBody
优点
- 创建独立Spring应用
- 内嵌web服务器
- 自动starter依赖,简化构建配置
- 自动配置Spring以及第三方功能
- 提供生产级别的监控、健康检查及外部化配置
- 无代码生成、无需编写XML
缺点
- 迭代快,需要时刻关注变化
- 封装太深,内部原理复杂,不容易精通
环境要求
- Java8及以上
- Maven3.3及以上
背景
微服务
- 微服务是一种架构风格
- 一个应用拆分为一组小型服务
- 每个服务运行在自己的进程内,也就是可独立部署和升级
- 服务之间使用轻量级HTTP交互
- 服务围绕业务功能拆分
- 可以由全自动部署机制独立部署
- 去中心化,服务自治。服务可以使用不同的语言、不同的存储技术
分布式
- SpringBoot + SpringCloud
云原生
- 原生应用如何上云:Cloud Native
特点
依赖管理
父项目做依赖管理parent:可以避免多个依赖使用相同技术时出现依赖版本冲突
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15<!--依赖管理-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
</parent>
<!--他的父项目-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.3.4.RELEASE</version>
</parent>
<!--几乎声明了所有开发中常用的依赖的版本号,自动版本仲裁机制-->开发导入场景启动器starter
spring-boot-starter-*
:*代表某种场景*-spring-boot-starter
: 第三方提供的简化开发的场景启动器只要引入starter这个场景的所有常规需要的依赖我们都自动引入
SpringBoot所有支持的场景:https://docs.spring.io/spring-boot/docs/current/reference/html/using-spring-boot.html#using-boot-starter
所有场景启动器最底层的依赖
1
2
3
4
5
6<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.3.4.RELEASE</version>
<scope>compile</scope>
</dependency>无需关注版本号,自动版本仲裁
- 引入依赖默认都可以不写版本
- 引入非版本仲裁的jar要写版本号
可以修改默认版本号
查看spring-boot-dependencies里面规定当前依赖的版本用的key
在当前项目里面重写配置
1
2
3<properties>
<mysql.version>5.1.43</mysql.version>
</properties>
自动配置
Tomcat
引入Tomcat依赖
配置Tomcat
1
2
3
4
5
6
7<!--Spring Boot 内嵌Tomcat服务器:将Tomcat服务器作为对象运行,并将该对象交给Spring容器管理-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>2.3.4.RELEASE</version>
<scope>compile</scope>
</dependency>1
2
3# Tomcat配置,yaml版
server:
port: 811
2# Tomcat配置,properties版
server.port=80
SpringMVC
- 引入SpringMVC全套组件
- 自动配好SpringMVC常用组件(功能)
自动配好Web常见功能,如:字符编码问题
- SpringBoot帮我们配置好了所有web开发的常见场景
默认的包结构
主程序所在包及所有子包里的组件都会被默认扫描进来
无需以前的包扫描配置
要改变扫描路径:
- @SpringBootApplication(scanBasePackages=”cc.mousse”)
- @ComponentScan
1
2
3
4
5@SpringBootApplication
// 等同于
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("cc.mousse.boot")
各种配置拥有默认值
- 默认配置最终都是映射到某个类上,如:MultipartProperties
- 配置文件的值最终会绑定每个类上,这个类会在容器中创建对象
按需加载所有自动配置项
- 非常多的starter
- 引入了哪些场景这个场景的自动配置才会开启
- SpringBoot所有的自动配置功能都在spring-boot-autoconfigure里
底层注解
引导类
@SpringBootApplication:启动程序(创建并初始化Spring容器)
- @SpringBootConfigration
- @Configration
- @ComponentScan:扫描当前包所在包及其子包
组件添加
@Configuration
- 配置类组件之间无依赖关系用Lite模式加速容器启动过程,减少判断
- 配置类组件之间有依赖关系,方法会被调用得到之前单实例组件,用Full模式
1 |
|
@Bean/@Component/@Controller/@Service/@Repository
@ComponentScan/@Import
- @Import高级用法: https://www.bilibili.com/video/BV1gW411W7wy?p=8
1 |
|
@Conditional
- 条件装配:满足Conditional指定的条件,则进行组件注入
1 |
|
配置引入
@ImportResource
1 |
|
1 |
|
配置绑定
使用Java读取到properties文件中的内容,并且把它封装到JavaBean中,以供随时使用
@ConfigurationProperties
@EnableConfigurationProperties + @ConfigurationProperties
1 |
|
@Component + @ConfigurationProperties
1 |
|
最佳实践
查看自动配置了哪些(选做)
自己分析,引入场景对应的自动配置一般都生效了
配置文件中debug=true开启自动配置报告。Negative(不生效)\Positive(生效)
是否需要修改
- 参照文档修改配置项:https://docs.spring.io/spring-boot/docs/current/reference/html/appendix-application-properties.html#common-application-properties
- 自己分析xxxxProperties绑定了配置文件的哪些
- 自定义加入或者替换组件:@Bean、@Component等
- 自定义器 XXXXXCustomizer
开发技巧
Lombok
简化JavaBean开发
1 |
|
1 |
|
dev-tools
项目或者页面修改以后:Ctrl+F9
1 |
|
Spring Initailizr
项目初始化向导
resources/static:静态资源
resources/templates:页面
resourves/application.properties:配置文件
核心
配置文件
优先级
- properties > yml > yaml
- 属性共存叠加并相互覆盖
properties
同以前的properties用法
yaml/yml
YAML Ain’t Markup Language(YAML不是一种标记语言)的递归缩写。在开发的这种语言时YAML的意思其实是:”Yet Another Markup Language”(仍是一种标记语言)。
非常适合用来做以数据为中心的配置文件
基本语法
key: value
,kv之间有空格- 大小写敏感
- 使用缩进表示层级关系
- 缩进不允许使用tab,只允许空格
- 缩进的空格数不重要,只要相同层级的元素左对齐即可
- ‘#’表示注释
- 字符串无需加引号,如果要加,’’与””表示字符串内容 会被 转义/不转义
数据类型
字面量:单个的、不可再分的值
- date、boolean、string、number、null
1
k: v
对象:键值对的集合
- map、hash、set、object
1
2
3
4
5
6
7#行内写法:
k: {k1:v1,k2:v2,k3:v3}
#或
k:
k1: v1
k2: v2
k3: v3数组:一组按次序排列的值
- array、list、queue
1
2
3
4
5
6
7#行内写法
k: [v1,v2,v3]
#或者
k:
- v1
- v2
- v3
1 |
|
1 |
|
1 |
|
配置提示
- 自定义的类和配置文件绑定一般没有提示
1 |
|
web开发
SpringMVC
多场景我们都无需自定义配置:
内容协商视图解析器和BeanName视图解析器
静态资源(包括webjars)
自动注册
Converter,GenericConverter,Formatter
支持
HttpMessageConverters
(后来我们配合内容协商理解原理)自动注册
MessageCodesResolver
(国际化用)静态index.html 页支持
自定义
Favicon
自动使用
ConfigurableWebBindingInitializer
,(DataBinder负责将请求数据绑定到JavaBean上)
不用@EnableWebMvc注解。使用
@Configuration
+WebMvcConfigurer
自定义规则声明
WebMvcRegistrations
改变默认底层组件@EnableWebMvc+@Configuration+DelegatingWebMvcConfiguration全面接管SpringMVC
简单功能
静态资源访问
静态资源目录
放在类路径下: called
/static
(or/public
or/resources
or/META-INF/resources
访问: 当前项目根路径/ + 静态资源名
原理: 静态映射/**
- 请求进来,先去找Controller看能不能处理。不能处理的所有请求又都交给静态资源处理器。静态资源也找不到则响应404页面
改变默认的静态资源路径
1
2
3
4
5spring:
web:
resources:
static-locations:
[ classpath:/pz/ ]
静态资源访问前缀
- 默认无前缀
- 当前项目 + static-path-pattern + 静态资源名 = 静态资源文件夹下找
1
2
3
4spring:
mvc:
# 会导致welcome page功能失效
static-path-pattern: /res/**webjars
- 自动映射 /webjars/**
- https://www.webjars.org/
- 访问地址:http://localhost:8080/webjars/jquery/3.5.1/jquery.js后面地址要按照依赖里面的包路径
1
2
3
4
5<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.5.1</version>
</dependency>
欢迎页支持
- 方法1:静态资源路径下index.html
- 可以配置静态资源路径
- 不可以配置静态资源的访问前缀,否则导致index.html不能被默认访问
- 方法2:controller能处理/index
1 |
|
自定义Favicon
- 网站图标
- favicon.ico放在静态资源目录下即可
1 |
|
请求参数处理
注解与参数
基本注解
@PathVariable:获取路径变量
- 若有Map<String, String>:把所有的变量放进此Map中
@RequestHeader:获取请求头
- 若有Map<String, String>:把所有的请求头放进此Map中
@RequestParam:获取请求参数
- 若有Map<String, String>:把所有的请求头放进此Map中
@CookieValue:获取Cookie值
- String:获取Cookie的值
- Cookie:获取Cookie对象
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@GetMapping("/car/{id}/owner/{username}")
public Map<String, Object> getCar(
// 获取路径变量
@PathVariable Integer id,
@PathVariable String username,
@PathVariable Map<String, String> pathVariable,
// 获取请求头
@RequestHeader("User-Agent") String userAgent,
@RequestHeader Map<String, String> headers,
// 获取请求参数
@RequestParam("age") Integer age,
@RequestParam("interests") List<String> interests,
@RequestParam Map<String, String> params,
// 获取Cookie值
@CookieValue("_ga") String _ga,
// 获取Cookie对象
@CookieValue("_ga") Cookie cookie
) {
Map<String, Object> map = new HashMap<>();
map.put("id", id);
map.put("username", username);
map.put("pathVariable", pathVariable);
map.put("userAgent", userAgent);
map.put("headers", headers);
map.put("age", age);
map.put("interests", interests);
map.put("params", params);
map.put("_ga", _ga);
System.out.println(cookie.getName() + " " + cookie.getValue());
return map;
}@RequestBody:获取请求体(POST)
1
2
3
4
5
6@PostMapping("/save")
public Map<String, Object> postMethod(@RequestBody String responseBody) {
Map<String, Object> map = new HashMap<>();
map.put("responseBody", responseBody);
return map;
}@RequestAttribute:获取request域属性
- 若有HttpServletRequest:把域属性放入此HttpServletRequest中
1
2
3
4
5
6
7
8
9
10
11
12@ResponseBody
@GetMapping("/success")
public Map<String, Object> success(
@RequestAttribute("msg") String msg,
HttpServletRequest request
) {
Map<String, Object> map = new HashMap<>();
Object code = request.getAttribute("code");
map.put("msg", msg);
map.put("code", code);
return map;
}@MatrixVariable:矩阵变量
矩阵变量需要在SpringBoot中手动开启
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@Configuration
public class WebConfig implements WebMvcConfigurer {
// 方式1, 需实现WebMvcConfigurer
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
// 不移除分号后面内容, 矩阵变量才能生效
urlPathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(urlPathHelper);
}
// 方式2
@Bean
public WebMvcConfigurer webMvcConfigurer() {
return new WebMvcConfigurer() {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
urlPathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(urlPathHelper);
}
};
}
}根据RFC3986的规范,矩阵变量应当绑定在路径变量中
若是有多个矩阵变量,应当使用英文符号
;
进行分隔若是一个矩阵变量有多个值,应当使用英文符号
,
进行分隔或为之命名多个重复的key如:
- /cars/sell;low=34;brand=byd,audi,yd
- 页面开发cookie被禁用时获取session里面内容:把Cookie的值使用矩阵变量的方式进行传递
- url重写:/abc;jsesssionid=xxxx
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// /cars/sell;low=34;brand=byd,audi,yd
@GetMapping("/cars/{path}")
public Map<String, Object> carsSell(
// 获取矩阵变量
@MatrixVariable Integer low,
@MatrixVariable List<String> brand,
// 获取路径: sell
@PathVariable 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
// 矩阵变量必须有url路径变量才能被解析
@GetMapping("/boss/{bossId}/{empId}")
public Map<String, Object> boss(
@MatrixVariable(pathVar = "bossId", value = "age") Integer bossAge,
@MatrixVariable(pathVar = "empId", value = "age") 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
复杂参数
- Map、Model(map、model里面的数据会被放在request的请求域 request.setAttribute)、Errors/BindingResult、RedirectAttributes(重定向携带数据)、ServletResponse(response)、SessionStatus、UriComponentsBuilder、ServletUriComponentsBuilder
自定义对象参数
- 可以自动类型转换与格式化,可以级联封装
模版引擎
SpringBoot默认不支持JSP,需要引入第三方模板引擎技术实现页面渲染
Thymeleaf
现代化、服务端Java模板引擎
基本语法
表达式
表达式名字 语法 用途 变量取值 ${…} 获取请求域、session域、对象等值 选择变量 *{…} 获取上下文对象值 消息 #{…} 获取国际化等值 链接 @{…} 生成链接 片段表达式 ~{…} jsp:include 作用,引入公共页面片段 字面量:文本值/数字/布尔值/空值(null)/变量(不能有空格)
文本操作
- 字符串拼接:+
- 变量替换:|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)
- If-then:
特殊操作
- 无操作: _
设置属性值
th:attr
1
2
3
4
5
6
7
8
9
10
11
12
13
14<!--设置单个值-->
<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>
<!--设置多个值-->
<img src="../../images/gtvglogo.png" th:attr="src=@{/images/gtvglogo.png},title=#{logo},alt=#{logo}" />
<!--以上两个的代替写法-->
<input type="submit" value="Subscribe!" th:value="#{subscribe.submit}"/>
<form action="subscribe.html" th:action="@{/subscribe}">迭代
1
2
3
4
5
6
7
8
9
10
11<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>
<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>
使用
引入Starter
1
2
3
4<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>自动配置好了thymeleaf
所有thymeleaf的配置值都在 ThymeleafProperties
配置好了SpringTemplateEngine
配好了ThymeleafViewResolver
我们只需要直接开发页面
1
2
3public static final String DEFAULT_PREFIX = "classpath:/templates/";
public static final String DEFAULT_SUFFIX = ".html"; //xxx.html
开发
1
2
3
4server:
servlet:
# 设置项目访问路径
context-path: /world1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Success</title>
<link rel="stylesheet" type="text/css" th:href="@{/css/style.css}">
</head>
<body>
<h1>Success</h1>
<h2 th:text="${msg}"></h2>
<!-- 地址为: http://mousse.cc -->
<h2><a th:href="${link}" th:text="${link}"></a></h2>
<!-- 地址为: link -->
<h2><a th:href="@{link}" th:text="${link}"></a></h2>
</body>
</html>
<a href="#" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
<img src="images/photos/user-avatar.png" alt=""/>
<!-- thymeleaf行内写法 -->
[[${session.loginUser.username}]]
<span class="caret"></span>
</a>1
2
3
4
5
6
7
8
9
10
11
12
13@Controller
public class ViewController {
@RequestMapping("/success")
public String success(Model model) {
// model中的数据会被放在请求域中, 相当于request.setAttribute()
model.addAttribute("msg", "Hello World");
// 若只写mousse.cc, 链接地址为: localhost:8080/mousse.cc
model.addAttribute("link", "http://mousse.cc");
return "success";
}
}
构建后台管理系统
项目创建
- thymeleaf、web-starter、devtools、lombok
静态资源处理
- 自动配置好,我们只需要把所有静态资源放到 static 文件夹下
路径构建
- th:action=”@{/login}”
模板抽取
- th:insert/replace/include
页面跳转
1
2
3
4
5
6
7
8
9
10
11
12@PostMapping("/index")
public String index(User user, HttpSession session, Model model) {
if (StringUtils.hasLength(user.getUsername()) && StringUtils.hasLength(user.getPassword())) {
// 把登录成功的用户保存起来
session.setAttribute("loginUser", user);
// 登录成功后重定向至main.html, 防止表单重新提交
return "redirect:/main.html";
} else {
model.addAttribute("msg", "账号密码错误");
return "login";
}
}数据渲染
1
2
3
4
5
6
7
8
9
10@GetMapping("/dynamic_table")
public String dynamicTable(Model model) {
// 动态遍历表格内容
List<User> users = new ArrayList<>();
for (int i = 0; i < 100; i++) {
users.add(new User("user" + (i + 1), UUID.randomUUID().toString().substring(0, 5)));
}
model.addAttribute("users", users);
return "table/dynamic_table";
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18<table class="display table table-bordered table-striped" id="dynamic-table">
<thead>
<tr>
<th>#</th>
<th>用户名</th>
<th>密码</th>
</tr>
</thead>
<tbody>
<!-- 使用thymeleaf遍历 -->
<tr class="gradeX" th:each="user,stats:${users}">
<!-- 获取遍历状态 -->
<td>[[${stats.count}]]</td>
<td th:text="${user.username}">用户名</td>
<td th:text="${user.password}">密码</td>
</tr>
</tbody>
</table>
拦截器
实现HandlerInterceptor接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20// 登录检查
public class LoginInterceptor implements HandlerInterceptor {
// 目标方法执行之前
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 登录检查逻辑
HttpSession session = request.getSession();
User loginUser = (User) session.getAttribute("loginUser");
if (loginUser != null) {
return true;
} else {
// 未登录时跳转到登录页面
request.setAttribute("msg", "请先登录");
request.getRequestDispatcher("/login").forward(request, response);
return false;
}
}
}配置拦截器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16@Controller
// 实现WebMvcConfigurer接口定制功能
public class AdminWebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
// 所有请求都会被拦截, 包括静态资源
.addPathPatterns("/**")
// 放行登录页面
.excludePathPatterns("/", "/login", "/index")
// 放行静态资源
.excludePathPatterns("/css/**", "/fonts/**", "/images/**", "/js/**");
}
}
文件上传
页面表单
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<form role="form" th:action="@{/upload}" method="post" enctype="multipart/form-data">
<div class="form-group">
<label for="exampleInputEmail1">邮箱</label>
<input type="email" name="email" class="form-control" id="exampleInputEmail1"
placeholder="Enter email">
</div>
<div class="form-group">
<label for="exampleInputPassword1">名字</label>
<input type="text" name="username" class="form-control" id="exampleInputPassword1"
placeholder="Password">
</div>
<!-- 单文件上传 -->
<div class="form-group">
<label for="exampleInputFile">头像</label>
<input type="file" name="profileImg" id="exampleInputFile">
</div>
<!-- 多文件上传 -->
<div class="form-group">
<label for="exampleInputFile">生活照</label>
<input type="file" name="photos" multiple>
</div>
<div class="checkbox">
<label>
<input type="checkbox"> Check me out
</label>
</div>
<button type="submit" class="btn btn-primary">提交</button>
</form>代码
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@Slf4j
@Controller
public class FormController {
@GetMapping("/form_layouts")
public String formLayouts() {
return "form/form_layouts";
}
@PostMapping("/upload")
public String upload(
@RequestParam String email,
@RequestParam String username,
// MultipartFile自动封装上传的文件
@RequestPart MultipartFile profileImg,
@RequestPart MultipartFile[] photos
) throws IOException {
log.info("email: {}", email);
log.info("username: {}", username);
log.info("profileImgSize: {}", profileImg.getSize());
log.info("photosCount: {}", photos.length);
// 保存文件
if (!profileImg.isEmpty()) {
String originalFilename = profileImg.getOriginalFilename();
profileImg.transferTo(new File(originalFilename));
}
if (photos.length > 0) {
for (MultipartFile photo : photos) {
String originalFilename = photo.getOriginalFilename();
photo.transferTo(new File(originalFilename));
}
}
return "main";
}
}
异常处理
默认规则
- 默认情况下Spring Boot提供/error处理所有错误的映射
- 对于机器客户端,它将生成JSON响应,其中包含错误,HTTP状态和异常消息的详细信息
- 对于浏览器客户端,响应一个“whitelabel”错误视图,以HTML格式呈现相同的数据
- 要对其进行自定义,添加View解析为error
- 要完全替换默认行为,可以实现ErrorController并注册该类型的Bean定义,或添加ErrorAttributes类型的组件以使用现有机制但替换其内容
- error/下的4xx,5xx页面会被自动解析
1 |
|
定制错误处理逻辑
- 自定义错误页
- error/404.html,error/5xx.html
- 有精确的错误状态码页面就匹配精确,没有就找4xx.html,如果都没有就触发白页
原生Servlet组件
使用Servlet API
@ServletComponentScan(basePackages = “cc.mousse”)
- 指定原生Servlet组件都放在哪
1
2
3
4
5
6
7
8
9@ServletComponentScan(basePackages = "cc.mousse")
@SpringBootApplication
public class ManagementSystemApplication {
public static void main(String[] args) {
SpringApplication.run(ManagementSystemApplication.class, args);
}
}@WebServlet(urlPatterns = “/my”)
- 效果:直接响应,没有经过Spring的拦截器
1
2
3
4
5
6
7
8
9@WebServlet(urlPatterns = "/my")
public class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("Hello World");
}
}@WebFilter(urlPatterns={“/css/“,”/images/“})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23@Slf4j
// /**为Spring写法, /*为Servlet写法
@WebFilter(urlPatterns = {"/css/*", "/images/*"})
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.info("MyFilter初始化");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
log.info("MyFilter运作");
// 放行
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
log.info("MyFilter销毁");
}
}@WebListener
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15@Slf4j
@WebListener
public class MyServletContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
log.info("MyServletContextListener: 项目初始化");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
log.info("MyServletContextListener: 项目销毁");
}
}
使用RegistrationBean
- ServletRegistrationBean,FilterRegistrationBean,ServletListenerRegistrationBean
1 |
|
定制化原理
常见方式
修改配置文件
xxxxxCustomizer
编写自定义的配置类:xxxConfiguration+ @Bean替换、增加容器中默认组件,视图解析器
Web应用编写一个配置类实现WebMvcConfigurer即可定制化web功能 + @Bean给容器中再扩展一些组件
1
2@Configuration
public class AdminWebConfig implements WebMvcConfigurer@EnableWebMvc + WebMvcConfigurer + @Bean可以全面接管SpringMVC,所有规则全部自己重新配置,实现定制和扩展功能
1
2
3@EnableWebMvc
@Configuration
public class AdminWebConfig implements WebMvcConfigurer
原理分析套路
- 场景starter
- xxxxAutoConfiguration
- 导入xxx组件
- 绑定xxxProperties
- 绑定配置文件项
数据访问
SQL
自动配置
HikariDataSource
导入JDBC场景
1
2
3
4<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>导入数据库驱动
官方不知道我们接下要操作什么数据库
数据库版本和驱动版本对应
- 若要修改版本:
- 直接依赖引入具体版本(maven的就近依赖原则)
- 重新声明版本(maven的属性的就近优先原则)
1
2
3
4
5
6<!-- MySQL驱动已经有版本仲裁 -->
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>- 若要修改版本:
分析自动配置
DataSourceAutoConfiguration: 数据源的自动配置
- 修改数据源相关的配置:spring.datasource
- 数据库连接池的配置,是自己容器中没有DataSource才自动配置的
- 底层配置好的连接池是:HikariDataSource
1
2
3
4
5
6
7@Configuration(proxyBeanMethods = false)
@Conditional(PooledDataSourceCondition.class)
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
@Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class,
DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.OracleUcp.class,
DataSourceConfiguration.Generic.class, DataSourceJmxConfiguration.class })
protected static class PooledDataSourceConfigurationDataSourceTransactionManagerAutoConfiguration: 事务管理器的自动配置
JdbcTemplateAutoConfiguration: JdbcTemplate的自动配置,可以来对数据库进行crud
- 可以修改这个配置项@ConfigurationProperties(prefix = “spring.jdbc”) 来修改JdbcTemplate
- @Bean@Primary JdbcTemplate;容器中有这个组件
JndiDataSourceAutoConfiguration: jndi的自动配置
XADataSourceAutoConfiguration: 分布式事务相关的
修改配置项
1
2
3
4
5
6spring:
datasource:
url: jdbc:mysql://localhost:3306/springboot
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
1 |
|
Druid
druid官方github地址:https://github.com/alibaba/druid
整合第三方技术的方式
自定义方式
创建数据源
1
2
3
4
5
6<!-- https://mvnrepository.com/artifact/com.alibaba/druid-spring-boot-starter -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.8</version>
</dependency>1
2
3
4
5
6
7
8
9
10// 默认自动配置是判断容器中没有才会配置HikariDataSource
// @ConditionalOnMissingBean(DataSource.class)
@Bean
@ConfigurationProperties("spring.datasource")
public DataSource dataSource() throws SQLException {
DruidDataSource dataSource = new DruidDataSource();
// 加入监控, 防火墙功能, 也可以在配置文件中实现
dataSource.setFilters("stat,wall");
return dataSource;
}1
2
3
4
5spring:
datasource:
# 设置防火墙等参数
filters: stat,wall
max-active: 10StatViewServlet
- StatViewServlet的用途包括:
- 提供监控信息展示的html页面
- 提供监控信息的JSON API
1
2
3
4
5
6
7
8
9
10
11// 配置Druid的监控页功能
@Bean
public ServletRegistrationBean<StatViewServlet> statViewServlet() {
// 配置拦截路径
ServletRegistrationBean<StatViewServlet> registrationBean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
// 设置登录账户
registrationBean.addInitParameter("loginUsername","admin");
registrationBean.addInitParameter("loginPassword","123456");
return registrationBean;
}- StatViewServlet的用途包括:
StatFilter
用于统计监控信息:如SQL监控、URI监控
系统中所有filter
别名 Filter类名 default com.alibaba.druid.filter.stat.StatFilter stat com.alibaba.druid.filter.stat.StatFilter mergeStat com.alibaba.druid.filter.stat.MergeStatFilter encoding com.alibaba.druid.filter.encoding.EncodingConvertFilter log4j com.alibaba.druid.filter.logging.Log4jFilter log4j2 com.alibaba.druid.filter.logging.Log4j2Filter slf4j com.alibaba.druid.filter.logging.Slf4jLogFilter commonlogging com.alibaba.druid.filter.logging.CommonsLogFilter
1
2
3
4
5
6
7
8
9
10// WebStatFilter用于采集web-jdbc关联监控的数据
@Bean
public FilterRegistrationBean<WebStatFilter> webStatFilter() {
FilterRegistrationBean<WebStatFilter> filterRegistrationBean = new FilterRegistrationBean<>(new WebStatFilter());
// 设置拦截路径为/*
filterRegistrationBean.setUrlPatterns(List.of("/*"));
// 设置排除拦截的路径
filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
return filterRegistrationBean;
}
starter方式
引入druid-starter
1
2
3
4
5<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.6</version>
</dependency>分析自动配置
- 扩展配置项:spring.datasource.druid
- DruidSpringAopConfiguration.class:监控SpringBean
- 配置项:spring.datasource.druid.aop-patterns
- DruidStatViewServletConfiguration.class:监控页的配
- spring.datasource.druid.stat-view-servlet,默认开启
- DruidWebStatFilterConfiguration.class, web监控配置;
- spring.datasource.druid.web-stat-filter,默认开启
- DruidFilterConfiguration.class}):所有Druid自己filter的配置
1
2
3
4
5
6
7
8private static final String FILTER_STAT_PREFIX = "spring.datasource.druid.filter.stat";
private static final String FILTER_CONFIG_PREFIX = "spring.datasource.druid.filter.config";
private static final String FILTER_ENCODING_PREFIX = "spring.datasource.druid.filter.encoding";
private static final String FILTER_SLF4J_PREFIX = "spring.datasource.druid.filter.slf4j";
private static final String FILTER_LOG4J_PREFIX = "spring.datasource.druid.filter.log4j";
private static final String FILTER_LOG4J2_PREFIX = "spring.datasource.druid.filter.log4j2";
private static final String FILTER_COMMONS_LOG_PREFIX = "spring.datasource.druid.filter.commons-log";
private static final String FILTER_WALL_PREFIX = "spring.datasource.druid.filter.wall";配置
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
36spring:
datasource:
url: jdbc:mysql://localhost:3306/springboot
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
# 设置防火墙等参数
# filters: stat,wall
# max-active: 10
druid:
# 监控页配置
stat-view-servlet:
enabled: true
login-username: admin
login-password: pswd
# 关闭重置按钮
reset-enable: false
# 监控web应用
web-stat-filter:
enabled: true
url-pattern: /*
# 配置排除路径, 默认就有不配也可
exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'
# 配置防火墙等
# filters: stat,wall
# 监控此包下的所有内容
aop-patterns: cc.mousse.*
# 单独详细配置过滤器
filter:
stat:
enabled: true
# 配置慢查询
slow-sql-millis: 1000
log-slow-sql: true
wall:
enabled: true
MyBatis
starter
- 官方starter:spring-boot-starter-*
- 第三方starter:*-spring-boot-starter
1
2
3
4
5<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
8mybatis:
# 配置全局配置文件位置
# config-location: classpath:mybatis/mybatis-3-config.xml
# 配置SQL映射文件位置
mapper-locations: classpath:mybatis/mapper/*.xml
# 指定mybatis全局配置文件中的相关配置项, 它和config-location二选一
configuration:
map-underscore-to-camel-case: true全局配置文件:建议配置在mybatis.configuration
1
2
3
4
5
6
7
8
9<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- <settings>-->
<!-- <setting name="mapUnderscoreToCamelCase" value="true"/>-->
<!-- </settings>-->
</configuration>
混合模式
SQL映射文件位置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19<?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">
<mapper namespace="cc.mousse.managementsystem.mapper.AccountMapper">
<!-- Account getById(Integer id) -->
<select id="getById" resultType="cc.mousse.managementsystem.bean.Account">
select *
from account_tbl
where id = #{id};
</select>
<insert id="addCity" keyProperty="id" useGeneratedKeys="true">
insert into city(name, state, country)
values (#{name}, #{state}, #{country});
</insert>
</mapper>Mapper接口
1
2
3
4
5
6
7
8
9
10
11
12
13@Mapper
public interface AccountMapper {
Account getById(Integer id);
}
@Mapper
public interface CityMapper {
void addCity(City city);
}
注解模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16@Mapper
public interface CityMapper {
@Select("select * from city where id = #{id}")
City getById(Integer id);
}
@Mapper
public interface CityMapper {
@Select("insert into city(name, state, country) values (#{name}, #{state}, #{country})")
@Options(useGeneratedKeys = true, keyProperty = "id")
void addCity(City city);
}总结
- 引入mybatis-starter
- 配置application.yaml中,指定mapper-location位置即可
- 编写Mapper接口并标注@Mapper注解
- 简单方法直接注解方式
- 复杂方法编写mapper.xml进行绑定映射
- @MapperScan(“com.atguigu.admin.mapper”) 简化,其他的接口就可以不用标注@Mapper注解
MyBatis-Plus
MyBatis-Plus(简称 MP)是一个MyBatis的增强工具,在MyBatis的基础上只做增强不做改变,为简化开发、提高效率而生
建议安装MybatisX插件
引入依赖
1
2
3
4
5
6<!-- 已包含jdbc和mybatis -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3.4</version>
</dependency>- 自动配置
- MybatisPlusAutoConfiguration配置类,MybatisPlusProperties配置项绑定
- mybatis-plus:xxx就是对mybatis-plus的定制
- SqlSessionFactory自动配置好,底层是容器中默认的数据源
- mapperLocations自动配置好
- 默认值:classpath*:/mapper/**/*.xml
- 任意包的类路径下的所有mapper文件夹下任意路径下的所有xml都是sql映射文件(建议以后sql映射文件放在mapper下)
- 容器中自动配置好了SqlSessionTemplate
- @Mapper标注的接口也会被自动扫描,建议直接**@MapperScan**(“cc .mousse.mapper”) 批量扫描
- MybatisPlusAutoConfiguration配置类,MybatisPlusProperties配置项绑定
- 优点:Mapper继承BaseMapper就可以拥有crud能力
- 自动配置
Mapper接口继承BasicMapper
1
2public interface UserMapper extends BaseMapper<User> {
}CRUD功能
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
// 若表名与类名一致则不用添加
@TableName("user_tbl")
public class User {
// 登录用
// 所有的属性都应该在数据库中, 不在数据库中的属性需要使用@TableField(exist = false): 表中不存在
@TableField(exist = false)
private String username;
@TableField(exist = false)
private String password;
//数据库用
private Long id;
private String name;
private Integer age;
private String email;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17@Configuration
public class MyBatisConfig {
// 分页插件
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
// 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false
paginationInnerInterceptor.setOverflow(true);
// 设置最大单页限制数量,默认 500 条,-1 不受限制
paginationInnerInterceptor.setMaxLimit(500L);
interceptor.addInnerInterceptor(paginationInnerInterceptor);
return interceptor;
}
}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@GetMapping("/dynamic_table")
public String dynamicTable(
Model model,
@RequestParam(value = "page", defaultValue = "1") Integer id
) {
// 分页查询数据
Page<User> userPage = new Page<>(id, 2);
// 分页查询的结果
Page<User> page = userService.page(userPage, null);
// page里包含所要查询的信息
model.addAttribute("page", page);
return "table/dynamic_table";
}
@GetMapping("/user/delete/{id}")
public String deleteUser(
@PathVariable Integer id,
@RequestParam Integer page,
RedirectAttributes redirectAttributes
) {
userService.removeById(id);
// 设置重定向参数
redirectAttributes.addAttribute("page", page);
return "redirect:/dynamic_table";
}1
2public interface UserMapper extends BaseMapper<User> {
}1
2public interface UserService extends IService<User> {
}1
2
3@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}1
2
3
4
5
6
7
8
9
10@MapperScan("cc.mousse.managementsystem.mapper")
@ServletComponentScan(basePackages = "cc.mousse")
@SpringBootApplication
public class ManagementSystemApplication {
public static void main(String[] args) {
SpringApplication.run(ManagementSystemApplication.class, args);
}
}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<table class="display table table-bordered table-striped" id="dynamic-table">
<thead>
<tr>
<th>#</th>
<th>编号</th>
<th>用户名</th>
<th>年龄</th>
<th>邮箱</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<!-- 使用thymeleaf遍历 -->
<tr class="gradeX" th:each="user,stats:${page.records}">
<!-- 获取遍历状态 -->
<td>[[${stats.count}]]</td>
<td th:text="${user.id}">编号</td>
<td th:text="${user.name}">用户名</td>
<td th:text="${user.age}">年龄</td>
<td th:text="${user.email}">邮箱</td>
<td>
<a class="btn btn-danger btn-sm" type="button"
th:href="@{/user/delete/{id}(id=${user.id},page=${page.current})}">
删除
</a>
</td>
</tr>
</tbody>
</table>
<div class="row-fluid">
<div class="span6">
<div class="dataTables_info" id="dynamic-table_info">
当前第 [[${page.current}]] 页
总计 [[${page.pages}]] 页
共 [[${page.total}]] 记录
</div>
</div>
<div class="span6">
<div class="dataTables_paginate paging_bootstrap pagination">
<ul>
<li class="prev disabled"><a href="#">← 上一页</a></li>
<li th:class="${num==page.current?'active':''}"
th:each="num:${#numbers.sequence(1,page.pages)}">
<a th:href="@{/dynamic_table(page=${num})}">[[${num}]]</a>
</li>
<li class="next"><a href="#">下一页 → </a></li>
</ul>
</div>
</div>
</div>
</div>
NoSQL
Redis自动配置
- RedisAutoConfiguration自动配置类
- RedisProperties属性类:spring.redis.xxx是对redis的配置
- 连接工厂是准备好的LettuceConnectionConfiguration、JedisConnectionConfiguration
- 自动注入了RedisTemplate<Object, Object>:xxxTemplate
- 自动注入了StringRedisTemplate:k/v都是String
- key : value
- 底层只要使用StringRedisTemplate、RedisTemplate就可以操作redis
1
2
3
4<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>1
2
3
4
5spring:
# 配置Redis
redis:
host: mousse.cc
password: root- RedisAutoConfiguration自动配置类
RedisTemplate与Lettuce
1
2
3
4
5
6@Test
void redisTest() {
ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
ops.set("hello", "world");
log.info("hello {}", ops.get("hello"));
}切换至jedis
1
2
3
4
5<!-- 导入jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>1
2
3
4
5
6spring:
# 配置Redis
redis:
host: mousse.cc
password: root
client-type: jedis1
2
3
4
5
6
7@Test
void redisTest() {
ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
ops.set("hello", "world");
log.info("hello {}", ops.get("hello"));
System.out.println(redisConnectionFactory.getClass());
}
1 |
|
1 |
|
1 |
|
单元测试
JUnit5
Spring Boot 2.2.0版本开始引入JUnit 5作为单元测试默认库
JUnit Platform:Junit Platform是在JVM上启动测试框架的基础,不仅支持Junit自制的测试引擎,其他测试引擎也都可以接入
JUnit Jupiter:JUnit Jupiter提供了JUnit5的新的编程模型,是JUnit5新特性的核心。内部包含了一个测试引擎,用于在Junit Platform上运行
JUnit Vintage:由于JUint已经发展多年,为了照顾老的项目JUnit Vintage提供了兼容JUnit4.x,Junit3.x的测试引擎
SpringBoot 2.4以上版本移除了默认对Vintage的依赖,如果需要兼容junit4需要自行引入(不能使用junit4的功能 @Test)
1
2
3
4
5
6
7
8
9
10
11<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
</exclusion>
</exclusions>
</dependency>
注解
- @Test:表示方法是测试方法,但是与JUnit4的@Test不同,他的职责非常单一不能声明任何属性,拓展的测试将会由Jupiter提供额外测试
- @ParameterizedTest:表示方法是参数化测试,下方会有详细介绍
- @RepeatedTest:表示方法可重复执行,下方会有详细介绍
- @DisplayName:为测试类或者测试方法设置展示名称
- @BeforeEach:表示在每个单元测试之前执行
- @AfterEach:表示在每个单元测试之后执行
- @BeforeAll:表示在所有单元测试之前执行
- @AfterAll:表示在所有单元测试之后执行
- @Tag:表示单元测试类别,类似于JUnit4中的@Categories
- @Disabled:表示测试类或测试方法不执行,类似于JUnit4中的@Ignore
- @Timeout:表示测试方法运行如果超过了指定时间将会返回错误
- @ExtendWith:为测试类或测试方法提供扩展类引用
1 |
|
断言
断言(assertions)是测试方法中的核心部分,用来对测试需要满足的条件进行验证。这些断言方法都是org.junit.jupiter.api.Assertions的静态方法检查业务逻辑返回的数据是否合理,所有的测试运行结束以后会有一个详细的测试报告
简单断言
方法 说明 assertEquals 判断两个对象或两个原始类型是否相等 assertNotEquals 判断两个对象或两个原始类型是否不相等 assertSame 判断两个对象引用是否指向同一个对象 assertNotSame 判断两个对象引用是否指向不同的对象 assertTrue 判断给定的布尔值是否为 true assertFalse 判断给定的布尔值是否为 false assertNull 判断给定的对象引用是否为 null assertNotNull 判断给定的对象引用是否不为 null 1
2
3
4
5
6
7
8
9
10
11@DisplayName("简单断言测试")
@Test
void simpleAssertionTest() {
int cal = cal(2, 3);
// message: 自定义失败信息
// 前面断言失败, 后面的代码都不运行
assertEquals(5, cal, "业务逻辑失败");
Object o1 = new Object();
Object o2 = new Object();
assertSame(o1, o2, "两个对象不一样");
}数组断言
1
2
3
4
5@Test
@DisplayName("数组断言测试")
public void arrayAssertionTest() {
assertArrayEquals(new int[]{1, 2}, new int[] {1, 2});
}组合断言
1
2
3
4
5
6
7
8@Test
@DisplayName("组合断言测试")
public void allAssertionTest() {
assertAll("Math",
() -> assertEquals(2, 1 + 1, "结果不相等"),
() -> assertTrue(1 > 0, "结果不为true")
);
}异常断言
1
2
3
4
5
6
7
8@Test
@DisplayName("异常断言测试")
public void exceptionAssertionTest() {
ArithmeticException exception = Assertions.assertThrows(
//扔出断言异常
ArithmeticException.class, () -> System.out.println(1 % 0)
);
}超时断言
1
2
3
4
5
6@Test
@DisplayName("超时断言测试")
public void timeoutAssertionTest() {
//如果测试方法时间超过1s将会异常
Assertions.assertTimeout(Duration.ofMillis(1000), () -> Thread.sleep(500));
}快速失败
1
2
3
4
5@Test
@DisplayName("快速失败断言测试")
public void shouldAssertionFail() {
fail("This should fail");
}
前置条件
JUnit 5中的前置条件(assumptions【假设】)类似于断言,不同之处在于不满足的断言会使得测试方法失败,而不满足的前置条件只会使得测试方法的执行终止。前置条件可以看成是测试方法执行的前提,当该前提不满足时,就没有继续执行的必要
1 |
|
嵌套测试
JUnit 5可以通过Java中的内部类和@Nested注解实现嵌套测试,从而可以更好的把相关的测试方法组织在一起。在内部类中可以使用@BeforeEach 和@AfterEach注解,而且嵌套的层次没有限制
1 |
|
参数化测试
参数化测试是JUnit5很重要的一个新特性,它使得用不同的参数多次运行测试成为了可能,也为我们的单元测试带来许多便利
利用@ValueSource等注解,指定入参,我们将可以使用不同的参数进行多次单元测试,而不需要每新增一个参数就新增一个单元测试,省去了很多冗余代码
@ValueSource:为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型
@NullSource:表示为参数化测试提供一个null的入参
@EnumSource:表示为参数化测试提供一个枚举入参
@CsvFileSource:表示读取指定CSV文件内容作为参数化测试入参
@MethodSource:表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流)
支持外部的各类入参。如CSV,YML,JSON 文件甚至方法的返回值也可以作为入参。只需要去实现ArgumentsProvider接口,任何外部文件都可以作为它的入参
1 |
|
迁移指南
- 注解在org.junit.jupiter.api包中,断言在org.junit.jupiter.api.Assertions中,前置条件org.junit.jupiter.api.Assumptions类中
- 把@Before和@After替换成@BeforeEach和@AfterEach
- 把@BeforeClass和@AfterClass 替换成@BeforeAll和@AfterAll
- 把@Ignore替换成@Disabled
- 把@Category替换成@Tag
把@RunWith、@Rule 和@ClassRule替换成@ExtendWith - 把@RunWith、@Rule 和@ClassRule替换成@ExtendWith
指标监控
SpringBoot Actuator
SpringBoot就抽取了Actuator场景,使得我们每个微服务快速引用即可获得生产级别的应用监控、审计等功能
使用
引入场景
1
2
3
4<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>暴露所有监控信息为HTTP
1
2
3
4
5
6
7
8
9# 配置监控: management是所有actuator的配置
management:
endpoints:
# 默认开启所有监控端点
enabled-by-default: true
web:
exposure:
# 以web方式暴露所有端点
include: '*'测试
可视化
Actuator Endpoint
最常使用的端点
ID | 描述 |
---|---|
auditevents |
暴露当前应用程序的审核事件信息。需要一个AuditEventRepository组件 。 |
beans |
显示应用程序中所有Spring Bean的完整列表。 |
caches |
暴露可用的缓存。 |
conditions |
显示自动配置的所有条件信息,包括匹配或不匹配的原因。 |
configprops |
显示所有@ConfigurationProperties 。 |
env |
暴露Spring的属性ConfigurableEnvironment |
flyway |
显示已应用的所有Flyway数据库迁移。 需要一个或多个Flyway 组件。 |
health |
显示应用程序运行状况信息。 |
httptrace |
显示HTTP跟踪信息(默认情况下,最近100个HTTP请求-响应)。需要一个HttpTraceRepository 组件。 |
info |
显示应用程序信息。 |
integrationgraph |
显示Spring integrationgraph 。需要依赖spring-integration-core 。 |
loggers |
显示和修改应用程序中日志的配置。 |
liquibase |
显示已应用的所有Liquibase数据库迁移。需要一个或多个Liquibase 组件。 |
metrics |
显示当前应用程序的“指标”信息。 |
mappings |
显示所有@RequestMapping 路径列表。 |
scheduledtasks |
显示应用程序中的计划任务。 |
sessions |
允许从Spring Session支持的会话存储中检索和删除用户会话。需要使用Spring Session的基于Servlet的Web应用程序。 |
shutdown |
使应用程序正常关闭。默认禁用。 |
startup |
显示由ApplicationStartup 收集的启动步骤数据。需要使用SpringApplication 进行配置BufferingApplicationStartup 。 |
threaddump |
执行线程转储。 |
如果您的应用程序是Web应用程序(Spring MVC,Spring WebFlux或Jersey),则可以使用以下附加端点:
ID 描述 heapdump
返回 hprof
堆转储文件。jolokia
通过HTTP暴露JMX bean(需要引入Jolokia,不适用于WebFlux)。需要引入依赖 jolokia-core
。logfile
返回日志文件的内容(如果已设置 logging.file.name
或logging.file.path
属性)。支持使用HTTPRange
标头来检索部分日志文件的内容。prometheus
以Prometheus服务器可以抓取的格式公开指标。需要依赖 micrometer-registry-prometheus
。最常用的Endpoint
- Health:监控状况
- Metrics:运行时指标
- Loggers:日志记录
Health Endpoint
- 健康检查端点,我们一般用于在云平台,平台会定时的检查应用的健康状况,我们就需要Health Endpoint可以为平台返回当前应用的一系列组件健康状况的集合
- 重要的几点:
- health endpoint返回的结果,应该是一系列健康检查后的一个汇总报告
- 很多的健康检查默认已经自动配置好了,比如:数据库、redis等
- 可以很容易的添加自定义的健康检查机制
1 |
|
Metrics Endpoint
- 提供详细的、层级的、空间指标信息,这些信息可以被pull(主动推送)或者push(被动获取)方式得到
- 通过Metrics对接多种监控系统
- 简化核心Metrics开发
- 添加自定义Metrics或者扩展已有Metrics
管理Endpoints
开启与禁用Endpoints
默认所有的Endpoint除过shutdown都是开启的
需要开启或者禁用某个Endpoint,配置模式为:
- management.endpoint.
.enabled = true
1
2
3
4management:
endpoint:
beans:
enabled: true- management.endpoint.
或者禁用所有的Endpoint然后手动开启指定的Endpoint
1
2
3
4
5
6
7
8management:
endpoints:
enabled-by-default: false
endpoint:
beans:
enabled: true
health:
enabled: true
暴露Endpoints
- 支持的暴露方式
- HTTP:默认只暴露health和info Endpoint
- JMX:默认暴露所有Endpoint
- 除过health和info,剩下的Endpoint都应该进行保护访问。如果引入SpringSecurity,则会默认配置安全访问规则
ID JMX auditevents
Yes beans
Yes caches
Yes conditions
Yes configprops
Yes env
Yes flyway
Yes health
Yes heapdump
N/A httptrace
Yes info
Yes integrationgraph
Yes jolokia
N/A logfile
N/A loggers
Yes liquibase
Yes metrics
Yes mappings
Yes prometheus
N/A scheduledtasks
Yes sessions
Yes shutdown
Yes startup
Yes threaddump
Yes - 支持的暴露方式
定制Endpoint
定制Health信息
1 |
|
1 |
|
定制info信息
方式1:编写配置文件
1
2
3
4
5
6info:
appName: boot-admin
version: 2.3.3
# 使用@@可以获取maven的pom文件值
mavenProjectName: @project.artifactId@
mavenProjectVersion: @project.version@方式2:编写InfoContributor
1
2
3
4
5
6
7
8
9@Component
public class AppInfoContributor implements InfoContributor {
@Override
public void contribute(Info.Builder builder) {
builder.withDetail("msg", "hello world").withDetails(Collections.singletonMap("code", 100));
}
}
定制Metrics信息
SpringBoot支持自动适配的Metrics
- JVM metrics, report utilization of
- Various memory and buffer pools
- Statistics related to garbage collection
- Threads utilization
- Number of classes loaded/unloaded
- CPU metrics
- File descriptor metrics
- Kafka consumer and producer metrics
- Log4j2 metrics: record the number of events logged to Log4j2 at each level
- Logback metrics: record the number of events logged to Logback at each level
- Uptime metrics: report a gauge for uptime and a fixed gauge representing the application’s absolute start time
- Tomcat metrics (server.tomcat.mbeanregistry.enabled must be set to true for all Tomcat metrics to be registered)
- Spring Integration metrics
- JVM metrics, report utilization of
增加定制Metrics
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@Service
public class CityServiceImpl implements CityService {
protected Counter counter;
@Autowired
private CityMapper cityMapper;
public CityServiceImpl(MeterRegistry meterRegistry) {
counter = meterRegistry.counter("cityService.addCity.count");
}
public City getCityById(Integer id) {
return cityMapper.getById(id);
}
public City addCity(City city) {
counter.increment();
cityMapper.addCity(city);
return city;
}
}
//也可以使用下面的方式
@Bean
MeterBinder queueSize(Queue queue) {
return (registry) -> Gauge.builder("queueSize", queue::size).register(registry);
}
定制Endpoint
- 场景:开发ReadinessEndpoint来管理程序是否就绪,或者LivenessEndpoint来管理程序是否存活,也可以直接使用 https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-features.html#production-ready-kubernetes-probes
1 |
|
高级特性
Profile功能
为了方便多环境适配springboot简化了profile功能
application-profile功能
默认配置文件:application.yaml,任何时候都会加载
指定环境配置文件:application-{env}.yaml
激活指定环境
配置文件激活
1
spring.profiles.actice=test
命令行激活:
java -jar xxx.jar --spring.profiles.active=prod --person.name=haha
修改配置文件的任意值,命令行优先
默认配置与环境配置同时生效
同名配置项,profile配置优先
@Profile条件装配功能
1 |
|
profile分组
1 |
|
外部化配置
外部配置源
- 常用:Java属性文件、YAML文件、环境变量、命令行参数
配置文件查找位置
- classpath根路径
- classpath根路径下config目录
- jar包当前目录
- jar包当前目录的config目录
- /config子目录的直接子目录
配置文件加载顺序
- 当前jar包内部的application.properties和application.yml
- 当前jar包内部的application-{profile}.properties和application-{profile}.yml
- 引用的外部jar包的application.properties和application.yml
- 引用的外部jar包的application-{profile}.properties和application-{profile}.yml
指定环境优先,外部优先,后面的可以覆盖前面的同名配置项
自定义starter
starter启动原理
- starter-pom引入autoconfigurer包
- autoconfigure包中配置使用 META-INF/spring.factories中 EnableAutoConfiguration的值,使得项目启动加载指定的自动配置类
- 编写自动配置类 xxxAutoConfiguration -> xxxxProperties
- @Configuration
- @Conditional
- @EnableConfigurationProperties
- @Bean
- ……
- 引入starter — xxxAutoConfiguration — 容器中放入组件 —- 绑定xxxProperties —- 配置项
自定义starter
- atguigu-hello-spring-boot-starter(启动器)
- atguigu-hello-spring-boot-starter-autoconfigure(自动配置包)
运维
打包运行
package,会执行一遍maven生命周期,可能需要跳过test阶段
jar支持命令行启动需要依赖maven插件支持
1
2
3
4
5
6
7
8<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>widows
1
2
3
4
5
6
7
8
9
10
11
12java –jar springboot.jar
# 若端口被占用
# 查询端口
netstat -ano
# 查询指定端口
netstat -ano |findstr "端口号"
# 根据进程PID查询进程名称
tasklist |findstr "进程PID号"
# 根据PID杀死任务
taskkill /F /PID "进程PID号"
# 根据进程名称杀死任务
taskkill -f -t -im "进程名称"linux
- 基于Linux(CenterOS7)
- 安装JDK,且版本不低于打包时使用的JDK版本
- 安装包保存在/usr/local/自定义目录中或$HOME下
- 其他操作参照Windows版进行
1
2
3
4
5
6# 后台启动
nohup java -jar springboot.jar > server.log 2>&1 &
# server.log为日志保存文件
# 查询进程
ps -ef | grep "java -jar"
高级配置
SpringBoot在开发和运行环境均支持使用临时参数修改工程配置
SpringBoot支持4级配置文件,应用于开发与线上环境进行配置的灵活设置
SpringBoot支持使用自定义配置文件的形式修改配置文件存储位置
基于微服务开发时配置文件将使用配置中心进行管理
临时属性设置
1
2java –jar springboot.jar –-server.port=80
# 携带多个属性启动SpringBoot,属性间使用空格分隔开发环境
方式1:带属性启动SpringBoot程序,为程序添加运行属性:Program arguments
方式2:通过编程形式带参数启动SpringBoot程序,为程序添加运行参数
1
2
3
4
5
6
7
8
9
10
11@SpringBootApplication
public class SsmpApplication {
public static void main(String[] args) {
args[0] = "--server.port=80";
SpringApplication.run(SsmpApplication.class, args);
// 也可以不带args参数
// SpringApplication.run(SsmpApplication.class);
}
}
配置文件分类(优先级从高到低)
- file:config/application.yml
- 工程路径config目录中配置文件:运维经理整体调控
- file:application.yml
- 工程路径配置文件:运维人员配置涉密线上环境
- classpath:config/application.yml
- 项目类路径config目录中配置文件:项目经理整体调控
- classpath:application.yml
- 项目类路径配置文件:开发人员本机开发与测试
- 多层级配置文件间的属性采用叠加并覆盖的形式作用于程序
- file:config/application.yml
自定义配置文件
通过启动参数加载指定文件路径下的配置文件
1
Program arguments:--spring.config.location=classpath:/ebank.properties
- properties与yml文件格式均支持
通过启动参数加载指定文件路径下的配置文件时可以加载多个配置
1
2Program arguments:--spring.config.location=classpath:/ebank.properties, classpath:/ebank-server.properties
# 后面的配置文件会叠加并覆盖前面的- 多配置文件常用于将配置进行分类,进行独立管理,或将可选配置单独制作便于上线更新维护
重要说明
- 单服务器项目:使用自定义配置文件需求较低
- 多服务器项目:使用自定义配置文件需求较高,将所有配置放置在一个目录中,统一管理
- 基于SpringCloud技术,所有的服务器将不再设置配置文件,而是通过配置中心进行设定,动态加载配置信息
多环境开发
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
29#应用环境, 放公共配置
spring:
profiles:
active: dev
---
#设置环境, 用---分隔
#开发环境
spring:
config:
activate:
on-profile: dev
server:
port: 82
---
#测试环境
spring:
config:
activate:
on-profile: test
server:
port: 81
---
#生产环境
spring:
config:
activate:
on-profile: pro
server:
port: 80多配置文件
1
2
3
4#application.yaml
spring:
profiles:
active: dev1
2
3#application-dev.yaml
server:
port: 821
2
3#application-test.yaml
server:
port: 811
2
3#application-pro.yaml
server:
port: 80根据功能对配置文件中的信息进行拆分,并制作成独立的配置文件
配置文件
- application-devDB.yml
- application-devRedis.yml
- application-devMVC.yml
使用include属性在激活指定环境的情况下,同时对多个环境进行加载使其生效,多个环境间使用逗号分隔
1
2
3
4
5spring:
profiles:
active: dev
include: devDB,devRedis,devMVC
# 后面的配置文件会叠加并覆盖前面的]从Spring2.4版开始使用group属性替代include属性,降低了配置书写量
1
2
3
4
5
6
7
8#使用group属性定义多种主环境与子环境的包含关系
spring:
profiles:
active: dev
group:
"dev": devDB,devRedis,devMVC
"pro": proDB,proRedis,proMVC
"test": testDB,testRedis,testMVC
Properties版
- properties文件多环境配置仅支持多文件格式
1
2#主启动配置文件application.properties
spring.profiles.active=pro1
2#application-dev.properties
server.port=821
2#application-test.properties
server.port=811
2#application-pro.properties
server.port=80多环境开发控制
当Maven与SpringBoot同时对多环境进行控制时,以Mavn为主,SpringBoot使用
@..@
占位符读取Maven对应的配置属性值基于SpringBoot读取Maven配置属性的前提下,如果在Idea下测试 工程时pom.xml每次更新需要手动compile方可生效
Maven
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21<!-- 设置多环境 -->
<profiles>
<profile>
<id>env_dev</id>
<!-- 配置属性, 配置文件会读取此变量 -->
<properties>
<profile.active>dev</profile.active>
</properties>
<!-- 设定活动环境 -->
<activation>
<activeByDefault>true</activeByDefault>
</activation>
</profile>
<profile>
<id>env_pro</id>
<properties>
<profile.active>pro</profile.active>
</properties>
</profile>
</profiles>SpringBoot
1
2
3spring:
profiles:
active: @profile.active@
日志
日志基础
作用
- 编程期调试代码
- 运营期记录信息
- 记录日常运营重要信息(峰值流量、平均响应时长)
- 记录应用报错信息(错误堆栈)
- 记录运维过程数据(扩容、宕机、报警)
级别
TRACE:运行堆栈信息,使用率低
DEBUG:程序员调试代码使用
INFO:记录运维过程数据
WARN:记录运维过程报警数据
ERROR:记录错误堆栈信息
FATAL:灾难信息,合并计入ERROR
启动默认为INFO级别,INFO以下的看不到
方法1:Program argument:–debug
方法2:application.yaml
1
debug: true
添加日志记录操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23/**
* 设置同一的返回值结果类型便于前端开发读取数据
*/
@RestController
@RequestMapping("/books")
public class BookController {
// 创建记录日志的对象
private static final Logger log = LoggerFactory.getLogger(BookController.class);
@Autowired
private IBookService bookService;
@GetMapping("/{id}")
public Result getById(@PathVariable Integer id) {
log.debug("debug");
log.info("info");
log.warn("warn");
log.error("error");
return new Result(true, bookService.getById(id));
}
}设置日志输出级别
1
2
3
4
5
6
7# 开启debug模式,输出调试信息,常用于检查系统运行状况
debug: true
# 设置日志级别,root表示根节点,即整体应用日志级别
logging:
level:
root: debug设置日志组,控制指定包对应的日志输出级别,也可以直接控制指定包对应的日志输出级别
1
2
3
4
5
6
7
8
9
10
11logging:
# 设置日志组
group:
# 自定义组名,设置当前组中所包含的包
ebank: com.itheima.controller
level:
root: warn
# 为对应组设置日志级别
ebank: debug
# 为对包设置日志级别
com.itheima.controller: debug使用lombok提供的注解@Slf4j简化开发,减少日志对象的声明操作
1
@Slf4j
日志输出格式控制
默认格式
1
2
3时间 级别 PID --- [所属线程] 所属类/接口名 : 日志信息
# PID: 进程ID,用于表明当前操作所处的进程,当多服务同时记录日志时,该值可用于协助程序员调试程序
# 所属类/接口名: 当前显示信息为SpringBoot重写后的信息,名称过长时,简化包名书写为首字母,甚至直接删除格式控制
1
2
3
4
5
6
7logging:
pattern:
console: "%d - %m%n"
# %d:日期 %m:消息 %n:换行
logging:
pattern:
console: "%d %clr(%p) --- [%16t] %clr(%-40.40c){cyan} : %m %n"
日志文件
1
2
3
4
5
6
7
8logging:
file:
# 设置日志文件
name: server.log
logback:
rollingpolicy:
max-file-size: 3KB
file-name-pattern: server.%d{yyyy-MM-dd}.%i.log