SpringBoot入门-02

SpringBoot入门-02

热部署

SpringBoot为我们提供了一个方便我们开发测试的工具dev-tools,使用后可以实现热部署的效果

当我们运行了程序后对程序进行了修改,程序会自动重启

原理是使用了两个ClassLoder,一个ClassLoader加载哪些不会改变的类(第三方jar包),另一个ClassLoader加载会更改的类.称之为Restart ClassLoader,这样在有代码更改的时候,原来的Restart Classloader被丢弃,重新创建一个Restart ClassLoader,由于需要加载的类相比较少,所以实现了较快的重启

准备工作

①设置IDEA自动编译

在idea中的setting做下面配置

image-20231112142820027

②设置允许程序运行时自动启动

ctrl + shift + alt + / 这组快捷键后会有一个小弹窗,点击Registry 就会进入下面的界面,找到下面的配置项并勾选,勾选后直接点close

image-20231112150148312

image-20231112150256731

image-20231112150345758

使用

①添加依赖

1
2
3
4
5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>

②触发热部署

当我们在修改完代码或者静态资源后可以切换到其它软件,让IDEA自动进行编译,自动编译后就会触发热部署

或者使用Ctrl+F9手动触发重新编译

单元测试

我们可以使用SpringBoot整合Junit进行单元测试

Spring Boot 2.2.0 版本开始引入 JUnit 5 作为单元测试默认库

使用

①添加依赖

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

②编写测试类

1
2
3
4
5
6
7
8
9
10
11
12
@SpringBootTest
public class ApplicationTest {

@Autowired
private HelloController helloController;

@Test
public void testJunit(){
System.out.println(1);
System.out.println(helloController);
}
}

注意:测试类所在的包需要和启动类是在同一个包下,否则就要使用如下写法指定启动类

1
2
3
4
5
6
7
8
9
10
11
12
13
//classes属性来指定启动类
@SpringBootTest(classes = HelloApplication.class)
public class ApplicationTest {

@Autowired
private HelloController helloController;

@Test
public void testJunit(){
System.out.println(1);
System.out.println(helloController);
}
}

兼容老版本

如果是对老项目中的SpringBoot进行了版本升级会发现之前的单元测试代码出现了一些问题

因为Junit5和之前的Junit4有比较大的不同

先看一张图:

junit5

从上图可以看出 JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage

  • JUnit Platform: 这是Junit提供的平台功能模块,通过它,其它的测试引擎也可以接入
  • JUnit JUpiter:这是JUnit5的核心,是一个基于JUnit Platform的引擎实现,它包含许多丰富的新特性来使得自动化测试更加方便和强大。
  • JUnit Vintage:这个模块是兼容JUnit3、JUnit4版本的测试引擎,使得旧版本的自动化测试也可以在JUnit5下正常运行。

虽然Junit5包含了JUnit Vintage来兼容JUnit3和Junit4,但是SpringBoot 2.4 以上版本对应的spring-boot-starter-test移除了默认对 **Vintage 的依赖,所以当我们仅仅依赖spring-boot-starter-test时会发现之前我们使用的@Test注解和@RunWith注解都不能使用了

我们可以单独在依赖vintage来进行兼容

1
2
3
4
5
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<scope>test</scope>
</dependency>

注意:org.junit.Test对应的是Junit4的版本,就搭配@RunWith注解来使用。

SpringBoot2.2.0之前版本的写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import com.sin.controller.HelloController;
//import org.junit.jupiter.api.Test;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

//classes属性来指定启动类
@SpringBootTest
@RunWith(SpringRunner.class)
public class ApplicationTest {

@Autowired
private HelloController helloController;

@Test
public void testJunit(){
System.out.println(1);
System.out.println(helloController);
}
}

整合mybatis

准备工作

①数据准备

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
CREATE DATABASE /*!32312 IF NOT EXISTS*/`springboot_db` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci */ /*!80016 DEFAULT ENCRYPTION='N' */;

USE `springboot_db`;

/*Table structure for table `user` */

DROP TABLE IF EXISTS `user`;

CREATE TABLE `user` (
`id` int NOT NULL AUTO_INCREMENT,
`username` varchar(50) DEFAULT NULL,
`age` int DEFAULT NULL,
`address` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb3;

/*Data for the table `user` */

insert into `user`(`id`,`username`,`age`,`address`) values (2,'pdd',25,'上海'),(3,'UZI',19,'上海11'),(4,'RF',19,NULL),(6,'胡桃',16,'往生堂'),(8,'test1',11,'a'),(9,'test2',12,'b');

/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;

②实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Integer id;
private String username;
private Integer age;
private String address;
}

整合步骤

项目结构:

image-20231112154008749

依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!--mybatis启动器-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.2</version>
</dependency>

<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
<scope>runtime</scope>
</dependency>

配置数据库信息

1
2
3
4
5
6
spring:
datasource:
url: jdbc:mysql://localhost:3306/springboot_db?characterEncoding=utf-8&serverTimezone=UTC
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver

配置mybatis相关配置

1
2
3
mybatis:
mapper-locations: classpath:mapper/*Mapper.xml # mapper映射文件路径
type-aliases-package: com.sin.pojo # 配置哪个包下的类有默认的别名

编写Mapper接口

注意在接口上加上@Mapper 和@Repository 注解

1
2
3
4
5
@Repository
@Mapper
public interface UserMapper {
public List<User> findAll();
}

编写mapper接口对应的xml文件

1
2
3
4
5
6
7
<?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="com.sin.mapper.UserMapper">
<select id="findAll" resultType="com.sin.pojo.User">
select * from user
</select>
</mapper>

测试

1
2
3
4
5
6
7
8
9
10
11
@SpringBootTest(classes = HelloApplication.class)
public class SpringMyTest {

@Autowired
UserMapper userMapper;

@Test
public void tesMapper(){
System.out.println(userMapper.findAll());
}
}

Web开发

静态资源访问

由于SpringBoot的项目是打成jar包的所以没有之前web项目的那些web资源目录(webapps)

那么我们的静态资源要放到哪里呢?

从SpringBoot官方文档中我们可以知道,我们可以把静态资源放到 resources/static (或者 resources/public 或者resources/resources 或者 resources/META-INF/resources) 中即可

静态资源放完后,

例如我们想访问文件:resources/static/index.html 只需要在访问时资源路径写成/index.html即可

例如我们想访问文件:resources/static/pages/login.html 访问的资源路径写成: /pages/login.html

修改静态资源访问路径

SpringBoot默认的静态资源路径匹配为/** ,如果想要修改可以通过 spring.mvc.static-path-pattern 这个配置进行修改

例如想让访问静态资源的url必须前缀有/res,例如/res/index.html 才能访问到static目录中的,我们可以修改如下:

在application.yml中

1
2
3
spring:
mvc:
static-path-pattern: /res/** #修改静态资源访问路径

修改静态资源存放目录

我们可以修改 spring.web.resources.static-locations 这个配置来修改静态资源的存放目录

例如:

1
2
3
4
5
6
spring:
web:
resources:
static-locations:
- classpath:/sgstatic/
- classpath:/static/

设置请求映射规则@RequestMapping

该注解可以加到方法上或者是类上

我们可以用其来设定所能匹配请求的要求,只有符合了设置的要求,请求才能被加了该注解的方法或类处理

指定请求路径

path或者value属性都可以用来指定请求路径

例如:

我们期望让请求的资源路径为**/test/testPath的请求能够被testPath**方法处理则可以写如下代码

1
2
3
4
5
6
7
8
@RestController
@RequestMapping("/test")
public class HelloController {
@RequestMapping("/testPath")
public String testPath(){
return "testPath";
}
}
1
2
3
4
5
6
7
8
@RestController
public class HelloController {

@RequestMapping("/test/testPath")
public String testPath(){
return "testPath";
}
}

指定请求方式

method属性可以用来指定可处理的请求方式

例如:

我们期望让请求的资源路径为**/test/testMethodPOST请求能够被testMethod**方法处理,则可以写如下代码

1
2
3
4
5
6
7
8
9
10
@RestController
@RequestMapping("/test")
public class TestController {

@RequestMapping(value = "/testMethod",method = RequestMethod.POST)
public String testMethod(){
System.out.println("testMethod处理了请求");
return "testMethod";
}
}

注意:我们可以也可以运用如下注解来进行替换

  • ​ @PostMapping 等价于 @RequestMapping(method = RequestMethod.POST)

  • ​ @GetMapping 等价于 @RequestMapping(method = RequestMethod.GET)

  • ​ @PutMapping 等价于 @RequestMapping(method = RequestMethod.PUT)

  • ​ @DeleteMapping 等价于 @RequestMapping(method = RequestMethod.DELETE)

例如:上面的需求我们可以使用下面的写法实现

1
2
3
4
5
6
7
8
9
10
@RestController
@RequestMapping("/test")
public class TestController {

@PostMapping(value = "/testMethod")
public String testMethod(){
System.out.println("testMethod处理了请求");
return "testMethod";
}
}

指定请求参数

我们可以使用params属性来对请求参数进行一些限制,可以要求必须具有某些参数,或者是某些参数必须是某个值,或者是某些参数必须不是某个值

例如:我们期望让请求的资源路径为**/test/testParamsGET请求,并且请求参数中具有code参数**的请求能够被testParams方法处理,则可以写如下代码

1
2
3
4
5
6
7
8
9
@RestController
@RequestMapping("/test")
public class TestController {
@RequestMapping(value = "/testParams",method = RequestMethod.GET,params = "code")
public String testParams(){
System.out.println("testParams处理了请求");
return "testParams";
}
}

如果是要求不能有code这个参数可以把改成如下形式

1
2
3
4
5
6
7
8
9
@RestController
@RequestMapping("/test")
public class TestController {
@RequestMapping(value = "/testParams",method = RequestMethod.GET,params = "!code")
public String testParams(){
System.out.println("testParams处理了请求");
return "testParams";
}
}

如果要求有code这参数,并且这参数值必须是某个值可以改成如下形式

1
2
3
4
5
6
7
8
9
@RestController
@RequestMapping("/test")
public class TestController {
@RequestMapping(value = "/testParams",method = RequestMethod.GET,params = "code=sin")
public String testParams(){
System.out.println("testParams处理了请求");
return "testParams";
}
}

如果要求有code这参数,并且这参数值必须不是某个值可以改成如下形式

1
2
3
4
5
6
7
8
9
@RestController
@RequestMapping("/test")
public class TestController {
@RequestMapping(value = "/testParams",method = RequestMethod.GET,params = "code!=sin")
public String testParams(){
System.out.println("testParams处理了请求");
return "testParams";
}
}

指定请求头

我们可以使用headers属性来对请求头进行一些限制

例如:我们期望让请求的资源路径为**/test/testHeaders的GET请求,并且请求头中具有deviceType**的请求能够被testHeaders方法处理,则可以写如下代码

1
2
3
4
5
6
7
8
9
10
@RestController
@RequestMapping("/test")
public class TestController {

@RequestMapping(value = "/testHeaders",method = RequestMethod.GET,headers = "deviceType")
public String testHeaders(){
System.out.println("testHeaders处理了请求");
return "testHeaders";
}
}

如果是要求不能有deviceType这个请求头可以把改成如下形式

1
2
3
4
5
6
7
8
9
10
@RestController
@RequestMapping("/test")
public class TestController {

@RequestMapping(value = "/testHeaders",method = RequestMethod.GET,headers = "!deviceType")
public String testHeaders(){
System.out.println("testHeaders处理了请求");
return "testHeaders";
}
}

如果要求有deviceType这个请求头,并且其值必须是某个值可以改成如下形式

1
2
3
4
5
6
7
8
9
10
@RestController
@RequestMapping("/test")
public class TestController {

@RequestMapping(value = "/testHeaders",method = RequestMethod.GET,headers = "deviceType=ios")
public String testHeaders(){
System.out.println("testHeaders处理了请求");
return "testHeaders";
}
}

如果要求有deviceType这个请求头,并且其值必须不是某个值可以改成如下形式

1
2
3
4
5
6
7
8
9
10
@RestController
@RequestMapping("/test")
public class TestController {

@RequestMapping(value = "/testHeaders",method = RequestMethod.GET,headers = "deviceType!=ios")
public String testHeaders(){
System.out.println("testHeaders处理了请求");
return "testHeaders";
}
}

指定请求头Content-Type

我们可以使用consumes属性来对Content-Type这个请求头进行一些限制

范例一

我们期望让请求的资源路径为**/test/testConsumes**的POST请求,并且请求头中的Content-Type头必须为 multipart/from-data 的请求能够被testConsumes方法处理,则可以写如下代码

1
2
3
4
5
@RequestMapping(value = "/testConsumes",method = RequestMethod.POST,consumes = "multipart/from-data")
public String testConsumes(){
System.out.println("testConsumes处理了请求");
return "testConsumes";
}
范例二

如果我们要求请求头Content-Type的值必须不能为某个multipart/from-data,则可以改成如下形式

1
2
3
4
5
@RequestMapping(value = "/testConsumes",method = RequestMethod.POST,consumes = "!multipart/from-data")
public String testConsumes(){
System.out.println("testConsumes处理了请求");
return "testConsumes";
}

获取请求参数

获取路径参数

RestFul风格的接口一些参数是在请求路径上的。类似: /user/1 这里的1就是id

如果我们想获取这种格式的数据可以使用**@PathVariable**来实现

范例一

要求定义个RestFul风格的接口,该接口可以用来根据id查询用户。请求路径要求为 /user ,请求方式要求为GET

而请求参数id要写在请求路径上,例如 /user/1 这里的1就是id

我们可以定义如下方法,通过如下方式来获取路径参数:

1
2
3
4
5
6
7
8
9
10
@RestController
public class UserController {

@RequestMapping(value = "/user/{id}",method = RequestMethod.GET)
public String findUserById( @PathVariable("id")Integer id){
System.out.println("findUserById");
System.out.println(id);
return "findUserById";
}
}
范例二

如果这个接口,想根据id和username查询用户。请求路径要求为 /user ,请求方式要求为GET

而请求参数id和name要写在请求路径上,例如 /user/1/zs 这里的1就是id,zs是name

我们可以定义如下方法,通过如下方式来获取路径参数:

1
2
3
4
5
6
7
8
9
10
@RestController
public class UserController {
@RequestMapping(value = "/user/{id}/{name}",method = RequestMethod.GET)
public String findUser(@PathVariable("id") Integer id,@PathVariable("name") String name){
System.out.println("findUser");
System.out.println(id);
System.out.println(name);
return "findUser";
}
}

获取请求体中的Json格式参数

RestFul风格的接口一些比较复杂的参数会转换成Json通过请求体传递过来,这种时候我们可以使用**@RequestBody**注解获取请求体中的数据

配置

SpringBoot的web启动器已经默认导入了jackson的依赖,不需要再额外导入依赖了

使用
范例一

要求定义个RestFul风格的接口,该接口可以用来新建用户。请求路径要求为 /user ,请求方式要求为POST

用户数据会转换成json通过请求体传递
​请求体数据

1
{"name":"sin","age":15}
  1. 获取参数封装成实体对象

​ 如果我们想把Json数据获取出来封装User对象,我们可以这样定义方法:

1
2
3
4
5
6
7
8
9
@RestController
public class UserController {
@RequestMapping(value = "/user",method = RequestMethod.POST)
public String insertUser(@RequestBody User user){
System.out.println("insertUser");
System.out.println(user);
return "insertUser";
}
}

​ User实体类如下:

1
2
3
4
5
6
7
8
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Integer id;
private String name;
private Integer age;
}

  1. 获取参数封装成Map集合

​ 也可以把该数据获取出来封装成Map集合:

1
2
3
4
5
6
@RequestMapping(value = "/user",method = RequestMethod.POST)
public String insertUser(@RequestBody Map map){
System.out.println("insertUser");
System.out.println(map);
return "insertUser";
}
范例二

如果请求体传递过来的数据是一个User集合转换成的json,Json数据可以这样定义:

1
[{"name":"sin1","age":14},{"name":"sin2","age":15},{"name":"sin3","age":16}]

​ 方法定义:

1
2
3
4
5
6
@RequestMapping(value = "/users",method = RequestMethod.POST)
public String insertUsers(@RequestBody List<User> users){
System.out.println("insertUsers");
System.out.println(users);
return "insertUser";
}

注意事项:如果需要使用**@RequestBody**来获取请求体中Json并且进行转换,要求请求头 Content-Type 的值要为: application/json

获取QueryString格式参数

如果接口的参数是使用QueryString的格式的话,我们也可以使用SpringMVC快速获取参数

我们可以使用**@RequestParam**来获取QueryString格式的参数

使用

范例

要求定义个接口,该接口请求路径要求为 /testRequestParam,请求方式无要求。参数为id和name和likes。使用QueryString的格式传递

  1. 参数单独的获取

​ 如果我们想把id,name,likes单独获取出来可以使用如下写法:

​ 在方法中定义方法参数,方法参数名要和请求参数名一致,这种情况下我们可以省略**@RequestParam**注解

1
2
3
4
5
6
7
8
@RequestMapping("/testRquestParam")
public String testRquestParam(Integer id, String name, String[] likes){
System.out.println("testRquestParam");
System.out.println(id);
System.out.println(name);
System.out.println(Arrays.toString(likes));
return "testRquestParam";
}

​ 如果方法参数名和请求参数名不一致,我们可以加上**@RequestParam**注解例如:

1
2
3
4
5
6
7
8
@RequestMapping("/testRquestParam")
public String testRquestParam(@RequestParam("id") Integer uid,@RequestParam("name") String name, @RequestParam("likes")String[] likes){
System.out.println("testRquestParam");
System.out.println(uid);
System.out.println(name);
System.out.println(Arrays.toString(likes));
return "testRquestParam";
}
  1. 获取参数封装成实体对象

​ 如果我们想把这些参数封装到一个User对象中可以使用如下写法:

1
2
3
4
5
6
@RequestMapping("/testRquestParam")
public String testRquestParam(User user){
System.out.println("testRquestParam");
System.out.println(user);
return "testRquestParam";
}

​ User类定义如下:

1
2
3
4
5
6
7
8
9
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Integer id;
private String name;
private Integer age;
private String[] likes;
}

​ 测试时请求url如下:

1
http://localhost:8080/testRquestParam?id=1&name=sinarcsinx&likes=编程&likes=学习&likes=游戏

注意:实体类中的成员变量要和请求参数名对应上。并且要提供对应的set/get方法。

相关注解其他属性

required

代表是否必须,默认值为true也就是必须要有对应的参数,如果没有就会报错

如果对应的参数可传可不传则可以把其设置为fasle

例如:

1
2
3
4
5
6
7
8
@RequestMapping("/testRquestParam")
public String testRquestParam(@RequestParam(value = "id",required = false) Integer uid,@RequestParam("name") String name, @RequestParam("likes")String[] likes){
System.out.println("testRquestParam");
System.out.println(uid);
System.out.println(name);
System.out.println(Arrays.toString(likes));
return "testRquestParam";
}
defaultValue

如果对应的参数没有,我们可以用defaultValue属性设置默认值

例如:

1
2
3
4
5
6
7
8
@RequestMapping("/testRquestParam")
public String testRquestParam(@RequestParam(value = "id",required = false,defaultValue = "777") Integer uid,@RequestParam("name") String name, @RequestParam("likes")String[] likes){
System.out.println("testRquestParam");
System.out.println(uid);
System.out.println(name);
System.out.println(Arrays.toString(likes));
return "testRquestParam";
}

响应体响应数据

无论是RestFul风格还是我们之前web阶段接触过的异步请求,都需要把数据转换成Json放入响应体中

数据放到响应体

我们的SpringMVC为我们提供了**@ResponseBody**来非常方便的把Json放到响应体中

@ResponseBody可以加在哪些东西上面?类上和方法上

数据转换成Json

配置

SpringBoot项目中使用了web的start后,不需要进行额外的依赖和配置

使用

只要把要转换的数据直接作为方法的返回值返回即可,SpringMVC会帮我们把返回值转换成json

范例

要求定义个RestFul风格的接口,该接口可以用来根据id查询用户。请求路径要求为 /response/user ,请求方式要求为GET

而请求参数id要写在请求路径上,例如 /response/user/1 这里的1就是id

要求获取参数id,去查询对应id的用户信息(模拟查询即可,可以选择直接new一个User对象),并且转换成json响应到响应体中

1
2
3
4
5
6
7
8
9
10
11
@Controller
@RequestMapping("/response")
public class ResponseController {

@RequestMapping("/user/{id}")
@ResponseBody
public User findById(@PathVariable("id") Integer id){
User user = new User(id, "sinarcsinx", 15, null);
return user;
}
}

跨域请求

什么是跨域

浏览器出于安全的考虑,使用 XMLHttpRequest对象发起 HTTP请求时必须遵守同源策略,否则就是跨域的HTTP请求,默认情况下是被禁止的。 同源策略要求源相同才能正常进行通信,即协议、域名、端口号都完全一致。

CORS解决跨域

CORS是一个W3C标准,全称是”跨域资源共享”(Cross-origin resource sharing),允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制

它通过服务器增加一个特殊的Header[Access-Control-Allow-Origin]来告诉客户端跨域的限制,如果浏览器支持CORS、并且判断Origin通过的话,就会允许XMLHttpRequest发起跨域请求

SpringBoot使用CORS解决跨域

使用@CrossOrigin

可以在支持跨域的方法上或者是Controller上加上@CrossOrigin注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@RestController
@RequestMapping("/user")
@CrossOrigin
public class UserController {

@Autowired
private UserServcie userServcie;

@RequestMapping("/findAll")
public ResponseResult findAll(){
//调用service查询数据 ,进行返回
List<User> users = userServcie.findAll();

return new ResponseResult(200,users);
}
}
使用 WebMvcConfigurer 的 addCorsMappings 方法配置CorsInterceptor
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Configuration
public class CorsConfig implements WebMvcConfigurer {

@Override
public void addCorsMappings(CorsRegistry registry) {
// 设置允许跨域的路径
registry.addMapping("/**")
// 设置允许跨域请求的域名
.allowedOriginPatterns("*")
// 是否允许cookie
.allowCredentials(true)
// 设置允许的请求方式
.allowedMethods("GET", "POST", "DELETE", "PUT")
// 设置允许的header属性
.allowedHeaders("*")
// 跨域允许时间
.maxAge(3600);
}
}

统一响应格式(可以直接用,需要 Jackson 依赖)

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
/**
* @author sinarcsinx
*/

//枚举响应

public enum AppHttpCodeEnum {
// 成功
SUCCESS(200,"操作成功"),
// 登录
NEED_LOGIN(401,"需要登录后操作"),
NO_OPERATOR_AUTH(403,"无权限操作"),
SYSTEM_ERROR(500,"出现错误"),
USERNAME_EXIST(501,"用户名已存在"),
PHONE_NUMBER_EXIST(502,"手机号已存在"),
EMAIL_EXIST(503, "邮箱已存在"),
REQUIRE_USERNAME(504, "必需填写用户名"),
LOGIN_ERROR(505,"用户名或密码错误"),
CONTENT_NOT_NULL(506, "内容不能为空"),
FILE_TYPE_ERROR(507, "文件类型错误"),
USERNAME_NOT_NULL(508,"用户名不能为空"),
PASSWORD_NOT_NULL(509,"密码不能为空"),
EMAIL_NOT_NULL(510,"密码不能为空"),
NICKNAME_NOT_NULL(511,"昵称不能为空"),
TAG_NAME_NOT_NULL(512,"标签名称不能为空");
int code;
String msg;

AppHttpCodeEnum(int code, String errorMessage){
this.code = code;
this.msg = errorMessage;
}

public int getCode() {
return code;
}

public String getMsg() {
return msg;
}
}
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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ResponseResult<T> implements Serializable {
private Integer code;
private String msg;
private T data;

public ResponseResult() {
this.code = AppHttpCodeEnum.SUCCESS.getCode();
this.msg = AppHttpCodeEnum.SUCCESS.getMsg();
}

public ResponseResult(Integer code, T data) {
this.code = code;
this.data = data;
}

public ResponseResult(Integer code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}

public ResponseResult(Integer code, String msg) {
this.code = code;
this.msg = msg;
}

public static ResponseResult errorResult(int code, String msg) {
ResponseResult result = new ResponseResult();
return result.error(code, msg);
}
public static ResponseResult okResult() {
ResponseResult result = new ResponseResult();
return result;
}
public static ResponseResult okResult(int code, String msg) {
ResponseResult result = new ResponseResult();
return result.ok(code, null, msg);
}

public static ResponseResult okResult(Object data) {
ResponseResult result = setAppHttpCodeEnum(AppHttpCodeEnum.SUCCESS, AppHttpCodeEnum.SUCCESS.getMsg());
if(data!=null) {
result.setData(data);
}
return result;
}

public static ResponseResult errorResult(AppHttpCodeEnum enums){
return setAppHttpCodeEnum(enums,enums.getMsg());
}

public static ResponseResult errorResult(AppHttpCodeEnum enums, String msg){
return setAppHttpCodeEnum(enums,msg);
}

public static ResponseResult setAppHttpCodeEnum(AppHttpCodeEnum enums){
return okResult(enums.getCode(),enums.getMsg());
}

private static ResponseResult setAppHttpCodeEnum(AppHttpCodeEnum enums, String msg){
return okResult(enums.getCode(),msg);
}

public ResponseResult<?> error(Integer code, String msg) {
this.code = code;
this.msg = msg;
return this;
}

public ResponseResult<?> ok(Integer code, T data) {
this.code = code;
this.data = data;
return this;
}

public ResponseResult<?> ok(Integer code, T data, String msg) {
this.code = code;
this.data = data;
this.msg = msg;
return this;
}

public ResponseResult<?> ok(T data) {
this.data = data;
return this;
}

public Integer getCode() {
return code;
}

public void setCode(Integer code) {
this.code = code;
}

public String getMsg() {
return msg;
}

public void setMsg(String msg) {
this.msg = msg;
}

public T getData() {
return data;
}

public void setData(T data) {
this.data = data;
}

}

拦截器

登录案例

image-20231112195936105

思路分析

在前后端分离的场景中,很多时候会采用token的方案进行登录校验

登录成功时,后端会根据一些用户信息生成一个token字符串返回给前端

前端会存储这个token。以后前端发起请求时如果有token就会把token放在请求头中发送给后端

后端接口就可以获取请求头中的token信息进行解析,如果解析不成功说明token超时了或者不是正确的token,相当于是未登录状态。

如果解析成功,说明前端是已经登录过的

Token生成方案-JWT

本案例采用目前企业中运用比较多的JWT来生成token

使用时先引入相关依赖

1
2
3
4
5
6
7
8
9
10
11
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>

<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>

然后可以使用下面的工具类来生成和解析token

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
74
75
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.Date;

/**
* JWT工具类
*/
public class JwtUtil {

//有效期为
public static final Long JWT_TTL = 60 * 60 *1000L;// 60 * 60 *1000 一个小时
//设置秘钥明文
public static final String JWT_KEY = "Sin";

/**
* 创建token
* @param id
* @param subject
* @param ttlMillis
* @return
*/
public static String createJWT(String id, String subject, Long ttlMillis) {

SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
if(ttlMillis==null){
ttlMillis=JwtUtil.JWT_TTL;
}
long expMillis = nowMillis + ttlMillis;
Date expDate = new Date(expMillis);
SecretKey secretKey = generalKey();

JwtBuilder builder = Jwts.builder()
.setId(id) //唯一的ID
.setSubject(subject) // 主题 可以是JSON数据
.setIssuer("Sin") // 签发者
.setIssuedAt(now) // 签发时间
.signWith(signatureAlgorithm, secretKey) //使用HS256对称加密算法签名, 第二个参数为秘钥
.setExpiration(expDate);// 设置过期时间
return builder.compact();
}

/**
* 生成加密后的秘钥 secretKey
* @return
*/
public static SecretKey generalKey() {
byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
return key;
}

/**
* 解析
*
* @param jwt
* @return
* @throws Exception
*/
public static Claims parseJWT(String jwt) throws Exception {
SecretKey secretKey = generalKey();
return Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(jwt)
.getBody();
}

}
登录接口实现

数据准备

1
2
3
4
5
6
7
8
9
10
11
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) DEFAULT NULL,
`password` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

/*Data for the table `sys_user` */

insert into `sys_user`(`id`,`username`,`password`) values (1,'root','root'),(2,'sin','master');

实体类

1
2
3
4
5
6
7
8
9
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SystemUser {

private Integer id;
private String username;
private String password;
}

SystemUserController

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("/sys_user")
public class SystemUserController {
@Resource
private SystemUserService userService;

@PostMapping("/login")
public ResponseResult login(@RequestBody SystemUser user) {
//校验用户名密码是否正确
SystemUser loginUser = userService.login(user);
Map<String, Object> map;
if (loginUser != null) {
//如果正确 生成token返回
map = new HashMap<>();
String token = JwtUtil.createJWT(UUID.randomUUID().toString(), String.valueOf(loginUser.getId()), null);
map.put("token", token);
} else {
//如果不正确 给出相应的提示
return new ResponseResult(300, "用户名或密码错误,请重新登录");
}
return new ResponseResult(200, "登录成功", map);
}
}

Service

1
2
3
public interface SystemUserService {
SystemUser login(SystemUser user);
}

ServiceImpl

1
2
3
4
5
6
7
8
9
10
11
@Service
public class SystemUserServcieImpl implements SystemUserService {
@Autowired
private SystemUserMapper systemUserMapper;

@Override
public SystemUser login(SystemUser user) {
SystemUser loginUser = systemUserMapper.login(user);
return loginUser;
}
}

dao

1
2
3
4
5
@Mapper
@Repository
public interface SystemUserMapper {
SystemUser login(SystemUser user);
}
1
2
3
4
5
6
7
<?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="com.sin.dao.SystemUserMapper">
<select id="login" resultType="com.sin.pojo.SystemUser">
select * from sys_user where username = #{username} and password = #{password}
</select>
</mapper>

可以使用postman测试

结果:

1
2
3
4
5
6
7
{
"code": 200,
"msg": "登录成功",
"data": {
"token": "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJjMTIwNzdmNC1kYzg0LTRhOWItYjg4Yy0zNzBkY2M4OTM4OWUiLCJzdWIiOiIxIiwiaXNzIjoiU2luIiwiaWF0IjoxNjk5NzkyODkyLCJleHAiOjE2OTk3OTY0OTJ9.Uw_L68BNS41C_dEAHnJ0cIU61g6V5r9qzUU_7MSZzqI"
}
}

拦截器的概念

如果我们想在多个Handler方法执行之前或者之后都进行一些处理,甚至某些情况下需要拦截掉,不让Handler方法执行,那么可以使用SpringMVC为我们提供的拦截器

使用步骤

创建类实现HandlerInterceptor接口

1
2
public class LoginInterceptor implements 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
@Component
public class LoginInterceptor implements HandlerInterceptor {

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//获取请求头中的token
String token = request.getHeader("token");
//判断token是否为空,如果为空也代表未登录 提醒重新登录(401)
if(!StringUtils.hasText(token)){
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
//解析token看看是否成功
try {
Claims claims = JwtUtil.parseJWT(token);
String subject = claims.getSubject();
System.out.println(subject);
} catch (Exception e) {
e.printStackTrace();
//如果解析过程中没有出现异常说明是登录状态
//如果出现了异常,说明未登录,提醒重新登录(401)
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
return true;
}
}

配置拦截器

1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration
public class LoginConfig implements WebMvcConfigurer {

@Autowired
private LoginInterceptor loginInterceptor;

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)//添加拦截器
.addPathPatterns("/**") //配置拦截路径
.excludePathPatterns("/sys_user/login");//配置排除路径
}
}

异常统一处理

创建类加上@ControllerAdvice注解进行标识

1
2
3
@ControllerAdvice
public class MyControllerAdvice {
}

定义异常处理方法

定义异常处理方法,使用**@ExceptionHandler**标识可以处理的异常

1
2
3
4
5
6
7
8
9
10
11
12
@ControllerAdvice
public class MyControllerAdvice {
@ExceptionHandler(RuntimeException.class)
@ResponseBody
public ResponseResult handlerException(Exception e){
//获取异常信息,存放如ResponseResult的msg属性
String message = e.getMessage();
ResponseResult result = new ResponseResult(300,message);
//把ResponseResult作为返回值返回,转换成json存入响应体中
return result;
}
}

获取web原生对象

我们之前在web阶段我们经常要使用到request对象,response,session对象等。我们也可以通过SpringMVC获取到这些对象(不过在MVC中我们很少获取这些对象,因为有更简便的方式,避免了我们使用这些原生对象相对繁琐的API)

我们只需要在方法上添加对应类型的参数即可,但是注意数据类型不要写错了,SpringMVC会把我们需要的对象传给我们的形参

1
2
3
4
5
6
7
8
9
@RestController
public class TestController {

@RequestMapping("/getRequestAndResponse")
public ResponseResult getRequestAndResponse(HttpServletRequest request, HttpServletResponse response, HttpSession session){
System.out.println(request);
return new ResponseResult(200,"成功");
}
}

自定义参数解析

如果我们想实现像获取请求体中的数据那样,在Handler方法的参数上增加一个@RepuestBody注解就可以获取到对应的数据的话

可以使用HandlerMethodArgumentResolver来实现自定义的参数解析

①定义用来标识的注解

1
2
3
4
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface CurrentUserId {
}

②创建类实现HandlerMethodArgumentResolver接口并重写其中的方法

注意加上@Component注解注入Spring容器

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
@Component
public class UserIdArgumentResolver implements HandlerMethodArgumentResolver {

//判断方法参数使用能使用当前的参数解析器进行解析
@Override
public boolean supportsParameter(MethodParameter parameter) {
//如果方法参数有加上CurrentUserId注解,就能把被我们的解析器解析
return parameter.hasParameterAnnotation(CurrentUserId.class);
}

//进行参数解析的方法,可以在方法中获取对应的数据,然后把数据作为返回值返回。方法的返回值就会赋值给对应的方法参数
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
//获取请求头中的token
String token = webRequest.getHeader("token");
if(StringUtils.hasText(token)){
//解析token,获取userId
Claims claims = JwtUtil.parseJWT(token);
String userId = claims.getSubject();
//返回结果
return userId;
}
return null;
}
}

③配置参数解析器

1
2
3
4
5
6
7
8
9
10
11
@Configuration
public class ArgumentResolverConfig implements WebMvcConfigurer {

@Autowired
private UserIdArgumentResolver userIdArgumentResolver;

@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(userIdArgumentResolver);
}
}

④测试

在需要获取UserId的方法中增加对应的方法参数然后使用@CurrentUserId进行标识即可获取到数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@RestController
@RequestMapping("/user")
//@CrossOrigin
public class UserController {

@Autowired
private UserServcie userServcie;

@RequestMapping("/findAll")
public ResponseResult findAll(@CurrentUserId String userId) throws Exception {
System.out.println(userId);
//调用service查询数据 ,进行返回s
List<User> users = userServcie.findAll();

return new ResponseResult(200,users);
}
}

声明式事务

直接在需要事务控制的方法上加上对应的注解**@Transactional**

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Service
public class UserServiceImpl implements UserServcie {

@Autowired
private UserMapper userMapper;

@Override
public List<User> findAll() {
return userMapper.findAll();
}

@Override
@Transactional
public void insertUser() {
//添加2个用户到数据库
User user = new User(null,"胡桃",16,"Sin家");
User user2 = new User(null,"Sin",18,"Sin家");
userMapper.insertUser(user);
userMapper.insertUser(user2);
}
}

AOP

1
2
3
spring:
aop:
auto: false

使用步骤

①添加依赖

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

②自定义注解

1
2
3
4
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface InvokeLog {
}

③定义切面类

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
@Aspect  //标识这是一个切面类
@Component
public class InvokeLogAspect {

//确定切点
@Pointcut("@annotation(com.sin.aop.InvokeLog)")
public void pt(){
}

@Around("pt()")
public Object printInvokeLog(ProceedingJoinPoint joinPoint){
//目标方法调用前
Object proceed = null;
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
String methodName = signature.getMethod().getName();
System.out.println(methodName+"即将被调用");
try {
proceed = joinPoint.proceed();
//目标方法调用后
System.out.println(methodName+"被调用完了");
} catch (Throwable throwable) {
throwable.printStackTrace();
//目标方法出现异常了
System.out.println(methodName+"出现了异常");
}
return proceed;
}
}

④在需要正确的地方增加对应的注解

1
2
3
4
5
6
7
8
9
10
11
12
@Service
public class UserServiceImpl implements UserServcie {

@Autowired
private UserMapper userMapper;

@Override
@InvokeLog //需要被增强方法需要加上对应的注解
public List<User> findAll() {
return userMapper.findAll();
}
}

切换动态代理

有的时候我们需要修改AOP的代理方式

我们可以使用以下方式修改:

在配置文件中配置spring.aop.proxy-target-class为false这为使用jdk动态代理。该配置默认值为true,代表使用cglib动态代理

1
2
3
4
5
6
7
@SpringBootApplication
@EnableAspectJAutoProxy(proxyTargetClass = false)//修改代理方式
public class WebApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(WebApplication.class, args);
}
}

如果想生效还需要在配置文件中做如下配置

1
2
3
spring:
aop:
proxy-target-class: false #切换动态代理的方式

模板引擎相关-Thymeleaf

快速入门

依赖

1
2
3
4
5
<!--thymeleaf依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

定义Controller

在controller中往域中存数据,并且跳转

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Controller
public class ThymeleafController {

@Autowired
private UserServcie userServcie;

@RequestMapping("/thymeleaf/users")
public String users(Model model){
//获取数据
List<User> users = userServcie.findAll();
//望域中存入数据
model.addAttribute("users",users);
model.addAttribute("msg","hello thymeleaf");
//页面跳转
return "table-standard";
}
}

htmL

resources\templates下存放模板页面。

在html标签中加上 xmlns:th=”http://www.thymeleaf.org

获取域中的name属性的值可以使用: ${name} 注意要在th开头的属性中使用

1
2
3
<html lang="en" class="no-ie" xmlns:th="http://www.thymeleaf.org">
.....
<div class="panel-heading" th:text="${msg}">Kitchen Sink</div>

如果需要引入静态资源,需要使用如下写法

1
2
3
4
5
6
7
8
9
10
11
12
13
<link rel="stylesheet" th:href="@{/app/css/bootstrap.css}">
<!-- Vendor CSS-->
<link rel="stylesheet" th:href="@{/vendor/fontawesome/css/font-awesome.min.css}">
<link rel="stylesheet" th:href="@{/vendor/animo/animate+animo.css}">
<link rel="stylesheet" th:href="@{/vendor/csspinner/csspinner.min.css}">
<!-- START Page Custom CSS-->
<!-- END Page Custom CSS-->
<!-- App CSS-->
<link rel="stylesheet" th:href="@{/app/css/app.css}">
<!-- Modernizr JS Script-->
<script th:src="@{/vendor/modernizr/modernizr.js}" type="application/javascript"></script>
<!-- FastClick for mobiles-->
<script th:src="@{/vendor/fastclick/fastclick.js}" type="application/javascript"></script>

遍历语法:遍历的语法 th:each=”自定义的元素变量名称 : ${集合变量名称}”

1
2
3
4
5
6
<tr th:each="user:${users}">
<td th:text="${user.id}"></td>
<td th:text="${user.username}"></td>
<td th:text="${user.age}"></td>
<td th:text="${user.address}"></td>
</tr>

整合Redis

依赖

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

配置Redis地址和端口号

1
2
3
4
spring:
redis:
host: 127.0.0.1 #redis服务器ip地址
port: 6379 #redis端口号

注入RedisTemplate使用

1
2
3
4
5
6
7
@Autowired
private StringRedisTemplate redisTemplate;

@Test
public void testRedis(){
redisTemplate.opsForValue().set("name","sin");
}

环境切换

为什么要使用profile

在实际开发环境中,我们存在开发环境的配置,部署环境的配置,测试环境的配置等等,里面的配置信息很多时,例如:端口、上下文路径、数据库配置等等

若每次切换环境时,我们都需要进行修改这些配置信息时,会比较麻烦,profile的出现就是为了解决这个问题,它可以让我们针对不同的环境进行不同的配置,然后可以通过激活、指定参数等方式快速切换环境

使用

创建profile配置文件

我们可以用application-xxx.yml的命名方式 创建配置文件,其中xxx可以根据自己的需求来定义

例如

​ 我们需要一个测试环境的配置文件,则可以命名为:application-test.yml

​ 需要一个生产环境的配置文件,可以命名为:application-prod.yml

我们可以不同环境下不同的配置放到对应的profile文件中进行配置。然后把不同环境下都相同的配置放到application.yml文件中配置

激活环境

我们可以再application.yml文件中使用spring.profiles.active属性来配置激活哪个环境

也可以使用虚拟机参数来指定激活环境。例如 : -Dspring.profiles.active=test

也可以使用命令行参数来激活环境。例如: –spring.profiles.active =test

日志

开启日志

1
2
3
4
debug: true #开启日志
logging:
level:
com.sinarcsinx: debug #设置日志级别

指标监控

我们在日常开发中需要对程序内部的运行情况进行监控, 比如:健康度、运行指标、日志信息、线程状况等等 ,而SpringBoot的监控Actuator就可以帮我们解决这些问题

使用

①添加依赖

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

②访问监控接口

http://localhost:81/actuator

③配置启用监控端点

1
2
3
4
5
6
management:
endpoints:
enabled-by-default: true #配置启用所有端点
web:
exposure:
include: "*" #web端暴露所有端点

常用端点

端点名称 描述
beans 显示应用程序中所有Spring Bean的完整列表。
health 显示应用程序运行状况信息。
info 显示应用程序信息。
loggers 显示和修改应用程序中日志的配置。
metrics 显示当前应用程序的“指标”信息。
mappings 显示所有@RequestMapping路径列表。
scheduledtasks 显示应用程序中的计划任务。

图形化界面 SpringBoot Admin

①创建SpringBoot Admin Server应用

要求引入spring-boot-admin-starter-server依赖

1
2
3
4
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-server</artifactId>
</dependency>

然后在启动类上加上@EnableAdminServer注解

②配置SpringBoot Admin client应用

在需要监控的应用中加上spring-boot-admin-starter-client依赖

1
2
3
4
5
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
<version>2.3.1</version>
</dependency>

然后配置SpringBoot Admin Server的地址

1
2
3
4
5
spring:
boot:
admin:
client:
url: http://localhost:8888 #配置 Admin Server的地址