HelloWorld

引入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.cuc</groupId>
<artifactId>boot-01-helloworld</artifactId>
<version>1.0-SNAPSHOT</version>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
</parent>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>

</project>

创建主程序

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 主程序类
*/
// 这是一个SpringBoot应用
@SpringBootApplication
public class MainApplication {

public static void main(String[] args) {
// 固定写法,将SpringBoot应用跑起来
SpringApplication.run(MainApplication.class,args);
}
}

编写业务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 1 @RestController@Controller@ResponseBody的合体
* 2 @ResponseBody 标注在类上表示所有方法都有@ResponseBody注解
*/
// @Controller
// @ResponseBody
@RestController
public class HelloController {

@RequestMapping("/hello")
public String handler01(){
return "Hello World!";
}
}

启动

无需再配置tomcat。

image-20220706013803956

简化配置

新建配置文件,所有的配置都可以在里面更改。

image-20220706014636734

简化部署

SpringBoot不需要设置打包方式

1)引入插件

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>

2)执行clean、package打包

image-20220706015925246

3)运行jar包

image-20220706020313274

4)效果

image-20220706020408059

SpringBoot特点

依赖管理

  • 父项目做依赖管理
1
2
3
4
5
6
7
<!-- 依赖管理 -->  
<!--几乎声明了所有开发中常用的依赖的版本号,自动版本仲裁机制-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
</parent>
  • 无需关注版本号,自动版本仲裁
1
2
1、引入依赖默认都可以不写版本
2、引入非版本仲裁的jar,要写版本号。
  • 修改默认版本号
1
2
3
4
5
1、查看spring-boot-dependencies里面规定当前依赖的版本 用的 key。
2、在当前项目里面重写配置
<properties>
<mysql.version>5.1.43</mysql.version>
</properties>

自动配置

  1. 自动配置Tomcat

    • 引入Tomcat依赖
    • 配置Tomcat
  2. 自动配置SpringMVC

    • 引入SpringMVC全套组件
    • 自动配置常用组件
  3. 自动配好web常用功能

    • 自动配好SpringMVC常见问题:如字符编码问题
  4. 默认的包结构

    • 主程序所在包及其下面的所有子包里面的组件都会被默认扫描进来,所以项目结构如下图所示:

      image-20220706233131680

    • 无需以前的包扫描配置

    • 想要改变扫描路径,@SpringBootApplication(scanBasePackages=**"com.cuc"**)或@ComponentScan 指定扫描路径

      1
      2
      3
      4
      5
      @SpringBootApplication
      等同于
      @SpringBootConfiguration
      @EnableAutoConfiguration
      @ComponentScan("com.cuc.boot")
  5. 各种配置拥有默认值

    • 默认配置最终都是映射到某个类上,如:MultipartProperties
    • 配置文件的值最终会绑定每个类上,这个类会在容器中创建对象
  6. 按需加载所有自动配置项

    • 非常多的starter
    • 引入了哪些场景这个场景的自动配置才会开启
    • SpringBoot所有的自动配置功能都在 spring-boot-autoconfigure 包里面
  7. … …

底层注解

@Configuration

主要理解proxyBeanMethods的使用,但是我没理解。

1)配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 1、配置类里使用@Bean给容器注册组件
* 2、配置类本身也是一个组件
* 3、proxyBeanMethods: 代理bean的方法,默认为单实例
* Full(proxyBeanMethods = true) 单例,每次需要查看容器是否已经存在实例,存在则不再创建实例
* Lite(proxyBeanMethods = false) 多例,每次不查看容器是否已经存在实例,直接创建实例,无需判断,加速容器启动
*/

// @Configuration(proxyBeanMethods = false)
@Configuration // 这是一个配置类,相当于配置文件
public class MyConfig {

@Bean // 给容器添加组件:以方法名作为组件的id。返回类型就是组件类型
public User user01(){
User zs = new User("ZhangSan",18);
zs.setPet(pet01());
return zs;
}

@Bean
public Pet pet01(){
return new Pet("tomcat");
}
}

2)主程序类

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
/**
* 主程序类
*/
// 这是一个SpringBoot应用
// @SpringBootApplication
// 等同于
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.cuc.boot")
public class MainApplication {

public static void main(String[] args) {
// 固定写法,将SpringBoot应用跑起来
// SpringApplication.run(MainApplication.class,args);

// 1、返回IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class,args);

// 2、查看容器里的组件
String[] names = run.getBeanDefinitionNames();
for(String name:names){
System.out.println(name);
}

// 3、从容器中获取组件
Pet tom01 = run.getBean("pet01",Pet.class);
Pet tom02 = run.getBean("pet01",Pet.class);
System.out.println(tom01==tom02); // 不管单例还是多例结果都为true,不知道为什么

User user = run.getBean("user01",User.class);
System.out.println(user.getPet()==tom01); // 单例结果为true 多例结果为false

// 配置类本身也是一个组件
MyConfig bean = run.getBean(MyConfig.class);
System.out.println(bean);

// 也可以通过配置类获取组件
User user1 = bean.user01();
User user2 = bean.user01();
System.out.println(user1 == user2); // 单例结果为true 多例结果为false

}
}

@Import

给容器导入组件,其它高级用法

1
2
3
@Configuration // 这是一个配置类,相当于配置文件
@Import({User.class, DBHelper.class}) // 会调用无参构造创建对应的实例放进容器中,名称默认为全类名com.cuc.boot.pojo.User
public class MyConfig { ... }

@Conditional

按条件装配。满足指定条件则进行组件注入

image-20220706221953182

示例:

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

// 不注册pet01组件
// @Bean
public Pet pet01(){
return new Pet("tomcat");
}

// 代表存在组件pet01时才为将user01注册成组件,注意组件的注册顺序,如果pet01在user01底下,则不管有没有注册pet01,user01组件都不会创建,因为判断的时候pet01还没注册
@ConditionalOnBean(name = "pet01") // 也可以注解在类上,如果容器没有pet01组件,则当前类底下所有组件都不注册
@Bean
public User user01(){
User zs = new User("ZhangSan",18);
zs.setPet(pet01());
return zs;
}
}

@ImportResource

导入配置文件。例如自己写的xml配置文件,可以使用@ImportResource使之生效

1)首先创建一个spring配置文件

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="dog" class="com.cuc.boot.pojo.Pet">
<property name="name" value="dog" />
</bean>

</beans>

2)使用注解导入配置文件

1
2
3
@Configuration
@ImportResource("classpath:bean.xml") // 导入配置文件中的组件
public class MyConfig { ... }

3)测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 主程序类
*/
@SpringBootApplication
public class MainApplication {

public static void main(String[] args) {

// 返回IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class,args);

System.out.println(run.containsBean("dog")); // 结果为true

}
}

@ConfigurationProperties

配置绑定,读取properties文件中内容,并且把它封装到JavaBean中,以供随时使用。

1)填写properties文件内容

image-20220706230412776

2)封装容器中

  • 第一种方式:@Component+@ConfigurationProperties
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
@Component // 需将当前类加到容器中
@ConfigurationProperties(prefix = "mycar")
public class Car {
private String brand;
private Integer price;

public Car() {
}

public Car(String brand, Integer price) {
this.brand = brand;
this.price = price;
}

public String getBrand() {
return brand;
}

public void setBrand(String brand) {
this.brand = brand;
}

public Integer getPrice() {
return price;
}

public void setPrice(Integer price) {
this.price = price;
}
}
  • 第二种方式 :@ConfigurationProperties+@EnableConfigurationProperties
1
2
3
4
5
6
7
@ConfigurationProperties(prefix = "mycar")
public class Car {
private String brand;
private Integer price;

...
}
1
2
3
4
5
@Configuration 
@EnableConfigurationProperties(Car.class)
// 1、开启Car配置绑定功能
// 2、将Car注册到容器中
public class MyConfig { ... }

3)编写controller类

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

@Autowired
private Car car;

@RequestMapping("/car")
public Car handler02(){
return car;
}
}

4)测试

image-20220706230652459

SpringBoot开发

引入依赖

地址,查看SpringBoot支持的依赖。

修改配置项

例如修改端口号等

地址,查看SpringBoot的配置项。

自定义加入或替换组件

@Bean、@Component等

自定义器

XXXXXCustomizer;有待深入

开发小技巧

LomBok

简化JavaBean开发,以及日志功能

  1. 引入依赖

    1
    2
    3
    4
    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    </dependency>
  2. 安装插件

    image-20220707005452388

  3. 使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @Data // 生成get、set、toString、equals、hashCode方法
    @Getter // get方法
    @Setter // set方法
    @ToString // 生成toString方法
    @NoArgsConstructor // 无参构造
    @AllArgsConstructor // 有参构造(全参)
    @EqualsAndHashCode // 生成equals和hashCode方法
    public class Emp {
    private String name;
    private Integer age;
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @Slf4j // 日志
    @RestController
    public class HelloController {

    @RequestMapping("/hello")
    public String handler01(){
    log.info("请求进来了");
    return "Hello World!";
    }
    }

    image-20220707010915074

Spring Initailizr

SpringBoot初始化向导,帮助快速创建SpringBoot应用

image-20220707013457084

image-20220707014148083

先删除没用的东西

image-20220709212812345

自动生成目录结构

image-20220707014645179

dev-tools

  1. 引入依赖

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

    当更改了项目内容,执行Ctrl+F9实现自动重启。

配置文件

配置文件:properties、yaml,会先读取properties然后再读取yaml

yaml

才发现hexo静态博客的配置文件就是yaml

  1. 基本语法

    • key: value;kv之间有空格

    • 大小写敏感

    • 使用缩进表示层级关系

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

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

    • ‘#’表示注释

    • 字符串无需加引号,如果要加,’’ 和 “” 表示字符串内容,以及单引号里写\n仅仅代表字符串,而双引号则换行

      所以:单引号不会转义,双引号会转义。

  2. 数据类型

    • 字面量:单个的、不可再分的值。date、boolean、string、number、null

      1
      k: v
    • 对象:map、hash、set、object

      1
      2
      3
      4
      5
      6
      行内写法:  k: {k1:v1,k2:v2,k3:v3}
      #或
      k:
      k1: v1
      k2: v2
      k3: v3
    • 数组:array、list、queue

      1
      2
      3
      4
      5
      6
      行内写法:  k: [v1,v2,v3]
      #或者
      k:
      - v1
      - v2
      - v3
  3. 示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    @Component
    @ConfigurationProperties(prefix = "person")
    @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;
    }
    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
    # yaml表示以上对象
    # 创建application.yml配置文件并写入
    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}
    health: [{name: mario,weight: 47}]
    1
    2
    3
    4
    5
    6
    7
    @Autowired
    private Person person;

    @RequestMapping("/yaml")
    public Person handler03(){
    return person;
    }

    image-20220707021219068

配置提示

自定义的类和配置文件绑定一般没有提示。

  1. 引入依赖

    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
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
    </dependency>

    <!-- 以及 -->

    <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>
  2. 使用

    image-20220707023555247

Web场景

简单功能

静态资源访问

  1. 静态资源目录

    SpringBoot默认静态资源目录为 /static or /public or /resources or /META-INF/resources

    也就是在resources(即类目录)下创建对应名称的文件夹,SpringBoot就会自动将它识别为静态资源的存储位置

    访问: 当前项目根路径/ + 静态资源名

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

    改变默认的静态资源路径

    1
    2
    3
    spring:
    resources:
    static-locations: classpath:/haha # 可以配置多个 [classpath:/haha,...]

    image-20220707191030799

    效果

    image-20220707191407466

    image-20220707191425899

    image-20220707191449129

    image-20220707191509970

  2. 静态资源访问前缀

    修改访问前缀(默认无前缀)访问:当前项目根路径 + static-path-pattern + 静态资源名

    1
    2
    3
    spring:
    mvc:
    static-path-pattern: /res/**

    image-20220707192039883

  3. webjars(了解)

    自动映射 /webjars/**

    https://www.webjars.org/

    1
    2
    3
    4
    5
    6
    7
    <!-- 举例 -->
    <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
spring:
# mvc:
# static-path-pattern: /res/** 这个会导致welcome page功能失效
resources:
static-locations: [classpath:/haha/]

image-20220707194431058

image-20220707194439576

自定义Favicon

favicon.ico 放在静态资源目录下即可。同样访问前缀不能有

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

image-20220707195242202

image-20220707195057949

请求处理

请求映射

@xxxMapping,支持Rest风格,需手动配置开启HiddenHttpMethodFilter,才能支持PUT、DELETE

1
2
3
4
5
6
# 开启
spring:
mvc:
hiddenmethod:
filter:
enabled: true
1
2
3
4
5
6
7
8
9
10
<!-- 1、method必须为post -->
<form action="/user" method="post">
<!-- 2、使用隐藏域 -->
<input name="_method" type="hidden" value="PUT" />
<input value="PUT提交" type="submit" />
</form>
<form action="/user" method="post">
<input name="_method" type="hidden" value="DELETE" />
<input value="DELETE提交" type="submit" />
</form>
1
2
3
4
5
6
7
8
9
10
11
12
13
@RestController
public class HelloController {

@PutMapping("/user")
public String putUser(){
return "PUT";
}

@DeleteMapping("/user")
public String deleteUser(){
return "DELETE";
}
}

拓展:自定义_method名称

1
2
3
4
5
6
7
8
9
10
@Configuration
public class WebConfig {

@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
methodFilter.setMethodParam("_m");
return methodFilter;
}
}
1
<input  name="_m" type="hidden" value="PUT" />

参数与注解

  • 注解

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    //  http://localhost:8080/car/2/owner/admin?age=18&inters=aa&inters=bb
    @GetMapping("/car/{id}/owner/{username}")
    public Object getCar(@PathVariable("id") Integer id,
    @PathVariable("username") String name,
    @PathVariable Map<String,String> pv, // {id=2, username=admin}
    @RequestHeader("User-Agent") String userAgent,
    @RequestHeader Map<String,String> header, // 所有请求头信息
    @RequestParam("age") Integer age,
    @RequestParam("inters") List<String> inters, // [aa, bb]
    @RequestParam Map<String,String> params, // {age=18, inters=aa}
    @CookieValue("_ga") String _ga,
    @CookieValue("_ga") Cookie cookie)
    {
    return null;
    }

    获取请求体,即post请求,获取kv值。

    1
    2
    3
    4
    5
    <form action="/save" method="post">
    <input name="username" value="admin" />
    <input name="root" value="root" />
    <input value="提交" type="submit" />
    </form>
    1
    2
    3
    4
    5
    6
    7
    @PostMapping("/save")
    public Object postMethod(@RequestBody String content){
    Map<String,Object> map = new HashMap<>();
    map.put("content",content);
    System.out.println(map); // {content=username=admin&root=root}
    return null;
    }

    @RequestAttribute获取请求域数据,@ModelAttribute没学

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

    @GetMapping("/goto")
    public String gotoPage(HttpServletRequest request){
    request.setAttribute("msg","成功...");
    return "forward:/success";
    }

    @ResponseBody
    @GetMapping("/success")
    public String success(@RequestAttribute("msg") String msg,HttpServletRequest request){
    System.out.println(msg);
    // 等价
    System.out.println(request.getAttribute("msg"));
    return null;
    }
    }

    矩阵变量

    1. 语法:
      • 矩阵变量应当绑定在路径变量中
      • 若有多个矩阵变量,矩阵变量之间用;分隔
      • 若一个矩阵变量有多个值,值之间使用,分隔,或者命名多个重复key
    2. SpringBoot默认禁用了矩阵变量的功能,需手动开启
    3. 矩阵变量必须有url路径变量才能被解析
    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
    @Configuration
    public class WebConfig implements WebMvcConfigurer {


    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {

    UrlPathHelper urlPathHelper = new UrlPathHelper();
    // 取消自动移除路径;后面的内容
    urlPathHelper.setRemoveSemicolonContent(false);
    configurer.setUrlPathHelper(urlPathHelper);
    }

    // 使用@Bean注入或者通过实现WebMvcConfigurer

    // @Bean
    // public WebMvcConfigurer webMvcConfigurer(){
    // return new WebMvcConfigurer() {
    // @Override
    // public void configurePathMatch(PathMatchConfigurer configurer) {
    // UrlPathHelper urlPathHelper = new UrlPathHelper();
    // urlPathHelper.setRemoveSemicolonContent(false);
    // configurer.setUrlPathHelper(urlPathHelper);
    // }
    // };
    // }

    }
    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
    // http://localhost:8080/cars/sell;low=34;brand=byd,audi
    @GetMapping("/cars/{path}") // {path}是必须的,不能写成sell
    public Object 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);
    System.out.println(map); // {path=sell, low=34, brand=[byd, audi]}
    return null;
    }

    // http://localhost:8080/boss/1;age=20/2;age=10
    @GetMapping("/boss/{bossId}/{empId}")
    public Object 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);
    System.out.println(map); // {bossAge=20, empAge=10}
    return null;
    }
  • 将参数封装到pojo

    使用GET或POST都可以

    1
    2
    3
    4
    5
    6
    7
    8
    <form action="/saveUser" method="post">
    姓名:<input name="userName" value="张三" /><br>
    年龄:<input name="age" value="18" /><br>
    生日:<input name="birth" value="2022/07/08" /><br>
    宠物姓名:<input name="pet.name" value="阿猫" /><br>
    宠物年龄:<input name="pet.age" value="5" /><br>
    <input value="保存" type="submit" />
    </form>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // User
    @Data
    @ToString
    public class User {
    private String userName;
    private Integer age;
    private Date birth;
    private Pet pet;
    }

    // Pet
    @Data
    public class Pet {
    private String name;
    private Integer age;
    }
    1
    2
    3
    4
    5
    6
    @PostMapping("/saveUser")
    public Object saveUser(User user){
    System.out.println(user);
    // User(userName=张三, age=18, birth=Fri Jul 08 00:00:00 CST 2022, pet=Pet(name=阿猫, age=5))
    return null;
    }
  • 自定义Converter

    自定义参数封装到pojo的规则

    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
    @Configuration
    public class WebConfig {

    // 大体步骤基本跟开启矩阵变量一致
    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
    return new WebMvcConfigurer() {

    @Override
    public void addFormatters(FormatterRegistry registry) {
    // 实现将字符串转成Pet
    registry.addConverter(new Converter<String, Pet>() {
    @Override
    public Pet convert(String s) { // s 就是页面提交过来的值
    // 阿猫,3
    if(!StringUtils.isEmpty(s)){
    Pet pet = new Pet();
    String[] split = s.split(",");
    pet.setName(split[0]);
    pet.setAge(Integer.parseInt(split[1]));
    return pet;
    }
    return null;
    }
    });
    }
    };
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    <form action="/saveUser" method="post">
    姓名:<input name="userName" value="张三" /><br>
    年龄:<input name="age" value="18" /><br>
    生日:<input name="birth" value="2022/07/08" /><br>
    <!--宠物姓名:<input name="pet.name" value="阿猫" /><br>-->
    <!--宠物年龄:<input name="pet.age" value="5" /><br>-->
    宠物:<input name="pet" value="阿猫,5" /><br>
    <input value="保存" type="submit" />
    </form>
    1
    2
    // 结果是正确的
    User(userName=张三, age=18, birth=Fri Jul 08 00:00:00 CST 2022, pet=Pet(name=阿猫, age=5))

数据响应与内容协商

响应JSON

jackson.jar+@ResponseBody,spring-boot-starter-web依赖就包含了jackson

内容协商

根据客户端接收能力的不同,返回不同媒体类型的数据(xml、json)。其中优先接收xml。控制器方法直接通过@ResponseBody返回数据即可。

  1. 引入依赖

    1
    2
    3
    4
    <dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-xml</artifactId>
    </dependency>
  2. 接收数据(根据请求头响应相应格式数据)

    image-20220708135308822

    image-20220708135335351

  3. 开启浏览器参数方式内容协商功能

    为了方便内容协商,开启基于请求参数的内容协商功能。因为浏览器默认优先接收xml,开启后,浏览器可根据需要获取指定格式内容。

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

    发请求: http://localhost:8080/xmlOrJson?format=jsonhttp://localhost:8080/xmlOrJson?format=xml

  4. 自定义MessageConverter

    返回自定义协议数据,例如请求头为application/x-test ,其中x-test自定义

    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
    /**
    * 自定义Converter
    */
    public class TestConverter implements HttpMessageConverter<User> {


    /**
    * 读功能,即能够接收对应格式数据
    */
    @Override
    public boolean canRead(Class<?> aClass, MediaType mediaType) {
    return false;
    }

    /**
    * 写功能,即能够返回对应格式数据
    */
    @Override
    public boolean canWrite(Class<?> aClass, MediaType mediaType) {
    return aClass.isAssignableFrom(User.class);
    }

    /**
    * 服务器统计所有MessageConverter都能写出哪些内容类型(xml、json...)
    * application/x-test
    */
    @Override
    public List<MediaType> getSupportedMediaTypes() {
    return MediaType.parseMediaTypes("application/x-test");
    }

    @Override
    public User read(Class<? extends User> aClass, HttpInputMessage httpInputMessage) throws IOException, HttpMessageNotReadableException {
    return null;
    }

    @Override
    public void write(User user, MediaType mediaType, HttpOutputMessage httpOutputMessage) throws IOException, HttpMessageNotWritableException {
    // 自定义协议数据的写出
    String data = user.getUserName()+";"+user.getBirth();

    // 写出去
    OutputStream body = httpOutputMessage.getBody();
    body.write(data.getBytes());
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    @Configuration
    public class WebConfig {

    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
    return new WebMvcConfigurer() {

    // 自定义内容协商(extend表示扩展,并不会把已支持的覆盖掉)
    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
    converters.add(new TestConverter());
    }
    };
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    @GetMapping("/xtest")
    @ResponseBody
    public User returnXTest(){
    System.out.println("ss");
    User user = new User();
    user.setUserName("admin");
    user.setBirth(new Date());
    return user;
    }

    image-20220708151128818

  5. 自定义参数内容协商

    http://localhost:8080/xmlOrJson?format=gg

    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
    @Configuration
    public class WebConfig {

    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
    return new WebMvcConfigurer() {

    // 自定义内容协商(extend表示扩展,并不会把已支持的覆盖掉)
    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
    converters.add(new TestConverter());
    }

    // 自定义参数内容协商
    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
    Map<String, MediaType> mediaTypes = new HashMap<>();
    // 指定支持解析哪些参数对应哪些媒体类型
    mediaTypes.put("json",MediaType.APPLICATION_JSON);
    mediaTypes.put("xml",MediaType.APPLICATION_XML);
    // 前提是之前已经自定义了converter
    mediaTypes.put("gg",MediaType.parseMediaType("application/x-test"));
    ParameterContentNegotiationStrategy strategy = new ParameterContentNegotiationStrategy(mediaTypes);
    configurer.strategies(Arrays.asList(strategy));
    }
    };
    }
    }

    image-20220708152957734

  6. 完善自定义参数内容协商

    使用自定义参数内容协商,通过请求头内容协商的方法将失效,即默认返回json数据,而不是根据请求头返回对应格式数据。

    可如下解决

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // 自定义参数内容协商
    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
    Map<String, MediaType> mediaTypes = new HashMap<>();
    mediaTypes.put("json",MediaType.APPLICATION_JSON);
    mediaTypes.put("xml",MediaType.APPLICATION_XML);
    mediaTypes.put("gg",MediaType.parseMediaType("application/x-test"));
    // 基于请求参数
    ParameterContentNegotiationStrategy ParameterStrategy = new ParameterContentNegotiationStrategy(mediaTypes);
    // 基于请求头
    HeaderContentNegotiationStrategy headerStrategy = new HeaderContentNegotiationStrategy();

    configurer.strategies(Arrays.asList(ParameterStrategy,headerStrategy));
    }

    之后不管用请求头还是请求参数都可以,请求参数的优先级好像更高。

    image-20220708154344057

视图解析与模板引擎

SpringBoot默认不支持JSP,需要引入第三方模板引擎技术实现页面渲染

视图解析

转发、重定向、自定义视图

模板引擎-Thymeleaf

先了解

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1 th:text="${msg}">哈哈</h1>
<h2>
<a href="www.aaa.com" th:href="${link}">去百度</a> <br/>
<a href="www.bbb.com" th:href="@{link}">去百度2</a>
</h2>
</body>
</html>

拦截器

登录检查与静态资源放行

  • 拦截器类
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
/**
* 登录检查
*/
public class LoginInterceptor implements HandlerInterceptor {

/**
* 目标方法执行前
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 检查逻辑
HttpSession session = request.getSession();
Object user = session.getAttribute("user");
if (user!=null){
// 已经登录
return true;
}
// 未登录
response.sendRedirect("/login");
return false;
}

/**
* 目标方法执行后
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}

/**
* 页面渲染后
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
  • 配置拦截器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Configuration
public class WebConfig {

@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {

// 拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**") // 拦截所有路径(注意包括静态资源访问也会被拦截,不放行页面样式会消失)
.excludePathPatterns("/","/login","/css"); // 不拦截哪些路径
}
};
}
}
  • 控制器方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 需先访问一次/login再访问/main才能去往首页
@RestController
public class InterceptorController {

@GetMapping("/login")
public String login(HttpSession session){
session.setAttribute("user","xxx");
return "登录页";
}

@GetMapping("/main")
public String main(){
return "首页";
}
}

拦截器原理

  1. 根据当前请求,找到HandlerExecutionChain
  2. 顺序执行所有拦截器的 preHandle方法
    • 1、如果当前拦截器prehandler返回为true。则执行下一个拦截器的preHandle
    • 2、如果当前拦截器返回为false。直接倒序执行所有已经执行了的拦截器的afterCompletion;
  3. 如果任何一个拦截器返回false。直接跳出不执行目标方法
  4. 所有拦截器都返回True。执行目标方法
  5. 倒序执行所有拦截器的postHandle方法。
  6. 前面的步骤有任何异常都会直接倒序触发 afterCompletion
  7. 页面成功渲染完成以后,也会倒序触发 afterCompletion

文件上传

单文件上传与多文件上传

  1. 页面表单

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <form method="post" action="/upload" enctype="multipart/form-data">
    <input type="email" name="email"><br>
    <input type="text" name="username"><br>
    <!-- 单文件 -->
    <input type="file" name="headerImg"><br>
    <!-- 多文件 -->
    <input type="file" name="files" multiple><br>
    <input type="submit" value="提交">
    </form>
  2. 文件上传代码

    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
    /**
    * MultipartFile 自动封装上传过来的文件
    */
    @PostMapping("/upload")
    public String upload(@RequestParam("email") String email,
    @RequestParam("username") String username,
    @RequestPart("headerImg") MultipartFile headerImg,
    @RequestPart("files") MultipartFile[] files) throws IOException {

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

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

    if(files.length > 0){
    for (MultipartFile file : files) {
    if(!file.isEmpty()){
    String originalFilename = file.getOriginalFilename();
    file.transferTo(new File("D:\\fileUpload\\"+originalFilename));
    }
    }
    }
    return "success";
    }

异常处理

默认规则

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

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

    image-20220708173801233

    image-20220708173846232

  • templates/error/下的4xx5xx名称页面会被自动解析;前提得引入thymeleaf依赖。需要注意例如发生400错误,它会自动去找400.html页面,如果没有则找4xx.html页面,如果再没有就是用默认的错误页面。

    image-20220708190423281

    image-20220708190448547

自定义异常处理

  1. @ControllerAdvice+@ExceptionHandler处理全局异常(推荐使用)

    • 异常处理类

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      /**
      * 处理整个web controller的异常
      */
      @Slf4j
      @ControllerAdvice
      public class GlobalExceptionHandler {

      // 处理数学运算以及空指针异常
      @ExceptionHandler({ArithmeticException.class,NullPointerException.class})
      public String handleException01(Exception e){
      log.error("异常:{}",e.getMessage());
      return "error/404"; // 视图地址
      }
      }
    • 控制器方法

      1
      2
      3
      4
      5
      @GetMapping("/error01")
      public String exception01(){
      int i = 10/0;
      return "";
      }
    • 测试

      image-20220708193808885

  2. @ResponseStatus+自定义异常

    • 异常处理类

      1
      2
      3
      @ResponseStatus(value = HttpStatus.FORBIDDEN,reason = "错误原因")
      public class TooManyException extends RuntimeException{
      }
    • 控制器方法

      1
      2
      3
      4
      5
      6
      7
      8
      9
      @GetMapping("/error02")
      @ResponseBody
      public String exception02(){
      int i = 101;
      if (i>100){
      throw new TooManyException();
      }
      return "";
      }
    • 测试

      image-20220708195123725

      image-20220708195130821

  3. Spring底层的异常,如参数类型转换异常

  4. 自定义实现HandlerExceptionResolver处理异常

  5. ErrorViewResolver实现自定义异常处理

原生组件注入

Servlet、Filter、Listener

注意:使用以下方式注入的Servlet不会被Spring的拦截器拦截!!!!

解释:DispatcherServlet默认处理/后任意路径,若定义原生Tomcat-Servlet处理路径为/my,因为精确优先原则,请求如果为/my直接走Tomcat-Servlet,如果不是,再走DispatcherServlet的Spring流程。

使用Servlet API

1
2
3
4
5
6
7
8
9
10
11
/**
* 主程序类
**/
@ServletComponentScan // 指定原生组件放在哪里,不指定默认扫描所有
@SpringBootApplication
public class Boot02HelloworldApplication {

public static void main(String[] args) {
SpringApplication.run(Boot02HelloworldApplication.class, args);
}
}
  • Servlet

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    /**
    * 需要在主程序类使用@ServletComponentScan注解才能使用
    */
    @WebServlet(urlPatterns = "/myServlet")
    public class MyServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    resp.getWriter().write("666");
    }
    }

    image-20220708200210638

  • Filter

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    // 同样需要在主程序类使用@ServletComponentScan注解才能使用
    @WebFilter(urlPatterns = {"/css/*","/images/*"}) // 拦截路径
    public class MyFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    System.out.println("初始化方法");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

    // 逻辑

    filterChain.doFilter(servletRequest,servletResponse); // 放行
    }

    @Override
    public void destroy() {
    System.out.println("销毁");
    }
    }
  • Listener

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // 主程序类同样需要@ServletComponentScan注解
    @WebListener
    public class MyListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
    System.out.println("监听到项目初始化完成");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
    System.out.println("监听到项目销毁");
    }
    }

使用RegistrationBean

将上面的@WebServlet、@WebFilter、@WebListener注解删掉,使用如下代替

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
@Configuration
public class MyRegisterConfig {

@Bean
public ServletRegistrationBean myServlet(){
MyServlet myServlet = new MyServlet();
return new ServletRegistrationBean(myServlet,"/myServlet1","/myServlet2"); // 可以有多个请求地址
}

@Bean
public FilterRegistrationBean myFilter(){
MyFilter myFilter = new MyFilter();

// return new FilterRegistrationBean(myFilter,myServlet()); // 拦截路径为/myServlet1、/myServlet2
// 或者
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(myFilter);
filterRegistrationBean.setUrlPatterns(Arrays.asList("/my1","/my2"));
return filterRegistrationBean;
}

@Bean
public ServletListenerRegistrationBean myListener(){
MyListener myListener = new MyListener();
return new ServletListenerRegistrationBean(myListener);
}
}

嵌入式Web容器

了解下

  • 默认支持的webServer

    • TomcatJettyUndertow
    • ServletWebServerApplicationContext 容器启动寻找ServletWebServerFactory 并引导创建服务器
  • 切换服务器

    默认为Tomcat

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
    <!-- 将tomcat排除掉 -->
    <exclusion>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-tomcat</artifactId>
    </exclusion>
    </exclusions>
    </dependency>
    <!-- 切换 -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-undertow</artifactId>
    </dependency>

数据访问

SQL

配置

  1. 导入JDBC场景(自动配置事务、数据源-HikariDataSource、jdbc、jndi、以及分布式事务相关的依赖)

    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jdbc</artifactId>
    </dependency>
  2. 导入MySQL驱动

    1
    2
    3
    4
    5
    <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.11</version>
    </dependency>
  3. 添加配置项

    1
    2
    3
    4
    5
    6
    spring:
    datasource:
    url: jdbc:mysql://localhost:3306/db_springboot?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
  4. 测试

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

    @Autowired
    JdbcTemplate jdbcTemplate;

    @Test
    void contextLoads() {
    Long aLong = jdbcTemplate.queryForObject("select count(*) from t_user", Long.class);
    System.out.println(aLong);
    }

    }

使用Druid数据源

druid官方地址

自定义方式

  1. 引入依赖

    1
    2
    3
    4
    5
    <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.17</version>
    </dependency>
  2. 配置数据源

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    @Configuration
    public class MyDatasourceConfig {

    // 配置
    @ConfigurationProperties(prefix = "spring.datasource")
    @Bean
    public DataSource dataSource(){
    DruidDataSource druidDataSource = new DruidDataSource();

    // druidDataSource.setUrl("jdbc:mysql://localhost:3306/db_springboot?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8");
    // druidDataSource.setUsername("root");
    // druidDataSource.setPassword("root");
    // druidDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");

    // 或者使用@ConfigurationProperties注解绑定配置文件中的配置

    // 还可以配置很多druid配置
    druidDataSource.setFilters("stat"); // 加入监控功能,同样能将配置写在配置文件中

    return druidDataSource;
    }
    }
  3. 测试

    1
    2
    3
    4
    5
    6
    7
    @Autowired
    DataSource dataSource;

    @Test
    void test2(){
    System.out.println("数据源类型:"+dataSource.getClass());
    }

    image-20220708223826764

  4. StatViewServlet

    StatViewServlet的用途包括:

    • 提供监控信息展示的html页面
    • 提供监控信息的JSON API
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @Configuration
    public class MyDatasourceConfig {

    // 配置
    ...

    // 配置druid的监控页
    @Bean
    public ServletRegistrationBean statViewServlet(){
    StatViewServlet statViewServlet = new StatViewServlet();
    ServletRegistrationBean<StatViewServlet> registrationBean = new ServletRegistrationBean<>(statViewServlet, "/druid/*");
    return registrationBean;
    }
    }

    image-20220708225158640

  5. StatFilter

    用于统计监控信息;如SQL监控、URI监控

    1
    druidDataSource.setFilters("stat"); // 加入监控功能

    image-20220708230021394

  6. 其它功能去看官方文档

官方starter方式

  1. 引入依赖

    1
    2
    3
    4
    5
    <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.17</version>
    </dependency>
  2. 示例配置

    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
    spring:
    datasource:
    url: jdbc:mysql://localhost:3306/db_springboot?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver

    druid:
    aop-patterns: com.cuc.boot.* #监控SpringBean
    filters: stat,wall # 底层开启功能,stat(sql监控),wall(防火墙)

    stat-view-servlet: # 配置监控页功能
    enabled: true
    login-username: admin
    login-password: admin
    resetEnable: false

    web-stat-filter: # 监控web
    enabled: true
    urlPattern: /*
    exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'


    filter:
    stat: # 对上面filters里面的stat的详细配置
    slow-sql-millis: 1000
    logSlowSql: true
    enabled: true
    wall:
    enabled: true
    config:
    drop-table-allow: false

整合MyBatis

搭建

文档

starter

SpringBoot官方的Starter:spring-boot-starter-*

第三方的: *-spring-boot-starter

建议安装插件

image-20220709191932523

1
2
3
4
5
6
<!-- 会自动引入jdbc,可以把之前引入的jdbc注释掉 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>

以前配置的大体操作步骤

  1. 全局配置文件
  2. SqlSessionFactory:自动配置了
  3. SqlSession:自动配置了SqlSessionTemplate,里面组合了SqlSession
  4. Mapper:只要在自己写的mapper接口标注了@Mapper就会被自动扫描(当然也可以在主程序类使用@MapperScan指定扫描包而不用@Mapper注解)
1
2
3
4
5
6
# 配置mybatis规则
mybatis:
# config-location: classpath:mybatis/mybatis-config.xml #全局配置文件位置
mapper-locations: classpath:mybatis/mapper/*.xml #sql映射文件位置
configuration: # 在这里配置相当于在全局文件配置(注意:如果配置了configuration就不能有config-location)
map-underscore-to-camel-case: true # 驼峰命名
1
2
3
4
5
6
7
8
9
10
<!-- 全局配置文件 -->
<?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>

<!-- 如果不在SpringBoot配置文件中配置,在这里也可以进行配置 -->

</configuration>

配置模式

1
2
3
4
5
// mapper接口
@Mapper
public interface AccountMapper {
public Account getAccById(int id);
}
1
2
3
4
5
6
7
8
9
10
11
<!-- mapper映射文件 -->
<?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.cuc.boot.mapper.AccountMapper">
<!-- public Account getAccById(int id); -->
<select id="getAccById" resultType="com.cuc.boot.pojo.Account">
select * from t_account where id=#{id}
</select>
</mapper>
1
2
3
4
5
6
7
8
9
// 测试
@Autowired
AccountMapper accountMapper;

@Test
void test3(){
Account acc = accountMapper.getAccById(1);
System.out.println(acc); // Account(id=1, userName=111, password=111)
}

注解模式

使用@Select、@Insert、@Update、@Delete、@Options等注解就可以不用在mapper映射文件中写

1
2
3
4
5
6
7
8
9
10
@Mapper
public interface CityMapper {

@Select("select * from t_city where id=#{id}")
public City getById(int id);

@Insert("insert into t_city(name,state,country) values(#{name},#{state},#{country})")
@Options(useGeneratedKeys = true,keyProperty = "id") // 返回自增后的id给city
public void insert(City city);
}

image-20220709013729948

混合模式

即注解和配置模式可以混合使用。

整合Mybatis-Plus

自动配置

  • 配置项绑定:mybatis-plus:xxx 就是对mybatis-plus的定制

  • SqlSessionFactory 自动配置好了。底层是容器中默认的数据源

  • mapperLocations 自动配置好的。有默认值。classpath*:/mapper/**/*.xml

    image-20220709202004134

  • 容器中也自动配置好了 SqlSessionTemplate

  • @Mapper 标注的接口也会被自动扫描;建议直接 @MapperScan("com.cuc.boot.mapper") 批量扫描就行

  1. 引入依赖

    1
    2
    3
    4
    5
    6
    <!-- 会自动引入jdbc和mybatis,所以可以将之前引入的注释掉  -->
    <dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.1</version>
    </dependency>
  2. 数据源配置

    之前已经配置过了,即spring.datasource下的配置

  3. 使用

    基础的用法都跟mybatis差不多,优点:可以继承BaseMapper,就不用写一些基本的crud,升入请学习mybatis-plus

    1
    2
    3
    4
    5
    6
    7
    8
    @Data
    @TableName("t_account")
    public class Account {
    // 属性名需跟字段名一样
    private int id;
    private String username;
    private String password;
    }
    1
    2
    3
    4
    @Mapper
    public interface AccountMapper extends BaseMapper<Account> {

    }
    1
    2
    3
    4
    5
    @Test
    void test3(){
    Account acc = accountMapper.selectById(1);
    System.out.println(acc);
    }

    image-20220709204220938

NoSQL

Redis

配置

  1. 引入依赖

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

    默认已经搭建或者购买了现成reids,并且做了相关配置。

  3. 配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    spring:
    redis:
    host: 120.48.54.126 # Redis服务器地址
    port: 6379 #Redis服务器连接端口
    password: root # 密码
    database: 0 #Redis数据库索引(默认为0)
    timeout: 1800000 #连接超时时间(毫秒)
    lettuce: # 默认使用Lettuce
    pool:
    max-active: 20 #连接池最大连接数(使用负值表示没有限制)
    max-wait: 1 #最大阻塞等待时间(负数表示没限制)
    max-idle: 5 #连接池中的最大空闲连接
    min-idle: 0 #连接池中的最小空闲连接
  4. 测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @Autowired
    private StringRedisTemplate redisTemplate;

    @Test
    void testRedis() {
    //设置值到redis
    redisTemplate.opsForValue().set("name","lucy");
    //从redis获取值
    String name = (String)redisTemplate.opsForValue().get("name");
    System.out.println(name); // lucy
    }

Jedis

  1. 引入依赖

    1
    2
    3
    4
    5
    <!--导入jedis-->
    <dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    </dependency>
  2. 配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    spring:
    redis:
    host: 120.48.54.126
    port: 6379
    password: root
    client-type: jedis # 指定客户端类型
    database: 0
    timeout: 1800000
    jedis: # 同样修改
    pool:
    max-active: 20
    max-wait: 1
    max-idle: 5
    min-idle: 0
  3. 查看是否切换

    1
    2
    3
    4
    5
    6
    7
    8
    @Autowired
    RedisConnectionFactory redisConnectionFactory;

    @Test
    void testJedis(){
    System.out.println(redisConnectionFactory.getClass());
    // class org.springframework.data.redis.connection.jedis.JedisConnectionFactory
    }

单元测试

JUnit5简介与引入

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

JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage

注意:

SpringBoot 2.4 以上版本移除了默认对 Vintage 的依赖。如果需要兼容junit4需要自行引入

1
2
3
4
5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
<!-- 如果需要使用junit4 -->
<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>

现在版本:

1
2
3
4
5
6
7
@SpringBootTest
class Boot02HelloworldApplicationTests {

@Test
void contextLoads() {
}
}

以前:@SpringBootTest + @RunWith(SpringTest.class)

常用注解

  • @Test : 表示方法是测试方法。但是与JUnit4的@Test不同,他的职责非常单一不能声明任何属性,拓展的测试将会由Jupiter提供额外测试
  • @ParameterizedTest : 表示方法是参数化测试
  • @RepeatedTest : 表示方法可重复执行
  • @DisplayName : 为测试类或者测试方法设置展示名称
  • @BeforeEach : 表示在每个单元测试之前执行
  • @AfterEach : 表示在每个单元测试之后执行
  • @BeforeAll : 表示在所有单元测试之前执行
  • @AfterAll : 表示在所有单元测试之后执行
  • @Tag : 表示单元测试类别,类似于JUnit4中的@Categories
  • @Disabled : 表示测试类或测试方法不执行,类似于JUnit4中的@Ignore
  • @Timeout : 表示测试方法运行如果超过了指定时间将会返回错误
  • @ExtendWith : 为测试类或测试方法提供扩展类引用
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
@DisplayName("JUnit5测试类")
// @SpringBootTest // 有整合@ExtendWith注解
public class TestDemo {

@Test
@DisplayName("测试DisplayName注解")
void test1(){
System.out.println(1);
}

@Test
@Disabled // 类测试时不执行
void test2(){
System.out.println(2);
}

@Test
@Timeout(value = 500,unit = TimeUnit.MILLISECONDS)
void test3() throws InterruptedException {
Thread.sleep(500);
System.out.println(3);
}

@RepeatedTest(2) // 重复次数
void test4(){
System.out.println(4);
}

@BeforeEach
void test5(){
System.out.println("测试开始了");
}

@AfterEach
void test6(){
System.out.println("测试结束了");
}

@BeforeAll
static void test7(){
System.out.println("所有测试要开始了");
}

@AfterAll
static void test8(){
System.out.println("所有测试都结束了");
}
}

image-20220709222211966

断言

断言(assertions)是测试方法中的核心部分,用来对测试需要满足的条件进行验证。

即断定某件事情发生,如果没发生就认为出错。

简单断言

用来对单个值进行简单的验证。如:

方法 说明
assertEquals 判断两个对象或两个原始类型是否相等
assertNotEquals 判断两个对象或两个原始类型是否不相等
assertSame 判断两个对象引用是否指向同一个对象
assertNotSame 判断两个对象引用是否指向不同的对象
assertTrue 判断给定的布尔值是否为 true
assertFalse 判断给定的布尔值是否为 false
assertNull 判断给定的对象引用是否为 null
assertNotNull 判断给定的对象引用是否不为 null
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
// 以下断言都是正确的
@Test
void test1(){
assertEquals(3, 1 + 2, "断言失败的输出信息");
assertNotEquals(3, 1 + 1);

assertNotSame(new Object(), new Object());
Object obj = new Object();
assertSame(obj, obj);

assertFalse(1 > 2);
assertTrue(1 < 2);

assertNull(null);
assertNotNull(new Object());
}

@Test
void test2(){
assertEquals(5,call(2,3));
}

int call(int i,int j){
return i+j;
}

数组断言

通过 assertArrayEquals 方法来判断两个对象或原始类型的数组是否相等

1
2
3
4
@Test
public void array() {
assertArrayEquals(new int[]{1, 2}, new int[]{1, 2});
}

组合断言

1
2
3
4
5
6
7
@Test
public void all() {
assertAll("Math", // 哪个组
() -> assertEquals(2, 1 + 1),
() -> assertTrue(1 > 0)
);
}

异常断言

1
2
3
4
5
6
7
@Test
public void exceptionTest() {
ArithmeticException exception = Assertions.assertThrows(
//扔出断言异常
ArithmeticException.class, () -> System.out.println(1 % 0));
System.out.println(exception.getMessage()); // / by zero
}

超时断言

1
2
3
4
5
@Test
public void timeoutTest() {
//如果测试方法时间超过1s将会异常
Assertions.assertTimeout(Duration.ofMillis(1000), () -> Thread.sleep(500));
}

快速失败

通过 fail 方法直接使测试失败

1
2
3
4
@Test
public void shouldFail() {
fail("This should fail");
}

前置条件

JUnit 5 中的前置条件类似于断言,不同之处在于不满足的断言会使得测试方法失败,而不满足的前置条件只会使得测试方法的执行终止。前置条件可以看成是测试方法执行的前提,当该前提不满足时,就没有继续执行的必要。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// assumeTrue 和 assumFalse 确保给定的条件为 true 或 false,不满足条件会使得测试执行终止。
// assumingThat 的参数是表示条件的布尔值和对应的 Executable 接口的实现对象。
// 只有条件满足时,Executable 对象才会被执行;当条件不满足时,测试执行并不会终止。
public class AssumptionsTest {
private final String environment = "DEV";

@Test
public void simpleAssume() {
assumeTrue(Objects.equals(this.environment, "DEV"));
assumeFalse(() -> Objects.equals(this.environment, "PROD"));
}

@Test
public void assumeThenDo() {
assumingThat(
Objects.equals(this.environment, "DEV"), // 为true才输出In DEV
() -> System.out.println("In DEV")
);
}
}

嵌套测试

JUnit 5 可以通过 Java 中的内部类和@Nested 注解实现嵌套测试,从而可以更好的把相关的测试方法组织在一起。嵌套的层次没有限制。

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
class TestingAStackDemo {

Stack<Object> stack;

@Test
void isInstantiatedWithNew() {
new Stack<>();
}

@Nested
class WhenNew {

@BeforeEach
void createNewStack() {
stack = new Stack<>();
}

@Test
void isEmpty() {
assertTrue(stack.isEmpty());
}

@Test
void throwsExceptionWhenPopped() {
assertThrows(EmptyStackException.class, stack::pop);
}

@Test
void throwsExceptionWhenPeeked() {
assertThrows(EmptyStackException.class, stack::peek);
}

@Nested
class AfterPushing {

String anElement = "an element";

@BeforeEach
void pushAnElement() {
stack.push(anElement);
}

@Test
void isNotEmpty() {
assertFalse(stack.isEmpty());
}

@Test
void returnElementWhenPopped() {
assertEquals(anElement, stack.pop());
assertTrue(stack.isEmpty());
}

@Test
void returnElementWhenPeeked() {
assertEquals(anElement, stack.peek());
assertFalse(stack.isEmpty());
}
}
}
}

参数化测试

参数化测试是JUnit5很重要的一个新特性,它使得用不同的参数多次运行测试成为了可能,也为我们的单元测试带来许多便利。

强大之处:如:CSV,YML,JSON 文件甚至方法的返回值也可以作为入参。只需要去实现ArgumentsProvider接口,任何外部文件都可以作为它的入参。

  • @ValueSource: 为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型
  • @NullSource: 表示为参数化测试提供一个null的入参
  • @EnumSource: 表示为参数化测试提供一个枚举入参
  • @CsvFileSource:表示读取指定CSV文件内容作为参数化测试入参
  • @MethodSource:表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@ParameterizedTest
@ValueSource(strings = {"one", "two", "three"})
public void parameterizedTest1(String string) {
System.out.println(string);
Assertions.assertTrue(StringUtils.isNotBlank(string));
}


@ParameterizedTest
@MethodSource("method") //指定方法名
public void testWithExplicitLocalMethodSource(String name) {
System.out.println(name);
Assertions.assertNotNull(name);
}

static Stream<String> method() {
return Stream.of("apple", "banana"); // 返回顺序流
}

image-20220709232128039

指标监控

SpringBoot Actuator

简介

未来每一个微服务在云上部署以后,我们都需要对其进行监控、追踪、审计、控制等。SpringBoot就抽取了Actuator场景,使得我们每个微服务快速引用即可获得生产级别的应用监控、审计等功能。

使用

  1. 引入场景

    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
  2. 暴露所有监控信息为HTTP

    1
    2
    3
    4
    5
    6
    management:
    endpoints:
    enabled-by-default: true #暴露所有端点信息
    web:
    exposure:
    include: '*' #以web方式暴露
  3. 测试

    http://localhost:8080/actuator/beans

    http://localhost:8080/actuator/configprops

    http://localhost:8080/actuator/metrics

    http://localhost:8080/actuator/metrics/jvm.gc.pause

    http://localhost:8080/actuator/endpointName/detailPath
    … …

  4. 效果

    image-20220709235914199

  5. 谷歌安装json插件后

    image-20220710000004853

可视化

https://github.com/codecentric/spring-boot-admin

Actuator Endpoint

端点

最常用的Endpoint

  • Health:监控状况
  • Metrics:运行时指标
  • Loggers:日志记录
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.namelogging.file.path属性)。支持使用HTTPRange标头来检索部分日志文件的内容。
prometheus 以Prometheus服务器可以抓取的格式公开指标。需要依赖micrometer-registry-prometheus

Health Endpoint

健康检查端点,我们一般用于在云平台,平台会定时的检查应用的健康状况,我们就需要Health Endpoint可以为平台返回当前应用的一系列组件健康状况的集合。

  • health endpoint返回的结果,是一系列健康检查后的一个汇总报告
  • 很多的健康检查默认已经自动配置好了,比如:数据库、redis等
  • 可以很容易的添加自定义的健康检查机制

Metrics Endpoint

提供详细的、层级的、空间指标信息,这些信息可以被pull(主动推送)或者push(被动获取)方式得到

  • 通过Metrics对接多种监控系统
  • 简化核心Metrics开发
  • 添加自定义Metrics或者扩展已有Metrics

管理Endpoints

开启与禁用Endpoints

  • 默认所有的Endpoint除过shutdown都是开启的。

  • 需要开启或者禁用某个Endpoint。配置模式为 management.endpoint.endpointName.enabled = true

    1
    2
    3
    4
    management:
    endpoint:
    beans:
    enabled: true
  • 或者禁用所有的Endpoint然后手动开启指定的Endpoint

    1
    2
    3
    4
    5
    6
    7
    8
    management:
    endpoints:
    enabled-by-default: false
    endpoint:
    beans:
    enabled: true
    health:
    enabled: true

暴露Endpoints

支持的暴露方式

  • HTTP:默认只暴露healthinfo Endpoint
  • JMX:默认暴露所有Endpoint
  • 除过health和info,剩下的Endpoint都应该进行保护访问。如果引入SpringSecurity,则会默认配置安全访问规则
ID JMX Web
auditevents Yes No
beans Yes No
caches Yes No
conditions Yes No
configprops Yes No
env Yes No
flyway Yes No
health Yes Yes
heapdump N/A No
httptrace Yes No
info Yes Yes
integrationgraph Yes No
jolokia N/A No
logfile N/A No
loggers Yes No
liquibase Yes No
metrics Yes No
mappings Yes No
prometheus N/A No
scheduledtasks Yes No
sessions Yes No
shutdown Yes No
startup Yes No
threaddump Yes No

定制 Endpoint

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Component
public class MyHealthIndicator implements HealthIndicator {

@Override
public Health health() {
int errorCode = check(); // perform some specific health check
if (errorCode != 0) {
return Health.down().withDetail("Error Code", errorCode).build();
}
return Health.up().build();
}

}

// 构建Health
Health build = Health.down()
.withDetail("msg", "error service")
.withDetail("code", "500")
.withException(new RuntimeException())
.build();
1
2
3
4
management:
health:
enabled: true
show-details: always #总是显示详细信息。可显示每个模块的状态信息
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
@Component
public class MyComHealthIndicator extends AbstractHealthIndicator {

/**
* 真实的检查方法
* @param builder
* @throws Exception
*/
@Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
//mongodb。 获取连接进行测试
Map<String,Object> map = new HashMap<>();
// 检查完成
if(1 == 2){
// builder.up(); //健康
builder.status(Status.UP);
map.put("count",1);
map.put("ms",100);
}else {
// builder.down();
builder.status(Status.OUT_OF_SERVICE);
map.put("err","连接超时");
map.put("ms",3000);
}


builder.withDetail("code",100)
.withDetails(map);

}
}

常用两种方式,http://localhost:8080/actuator/info 会输出以下方式返回的所有info信息

  1. 编写配置文件

    1
    2
    3
    4
    5
    info:
    appName: boot-admin
    version: 2.0.1
    mavenProjectName: @project.artifactId@ #使用@@可以获取maven的pom文件值
    mavenProjectVersion: @project.version@
  2. 编写InfoContributor

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Component
    public class ExampleInfoContributor implements InfoContributor {

    @Override
    public void contribute(Info.Builder builder) {
    builder.withDetail("example",
    Collections.singletonMap("key", "value"));
    }
    }
  1. 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
  2. 增加定制Metrics

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    class MyService{
    Counter counter;
    public MyService(MeterRegistry meterRegistry){
    counter = meterRegistry.counter("myservice.method.running.counter");
    }

    public void hello() {
    counter.increment();
    }
    }


    //也可以使用下面的方式
    @Bean
    MeterBinder queueSize(Queue queue) {
    return (registry) -> Gauge.builder("queueSize", queue::size).register(registry);
    }

场景:开发ReadinessEndpoint来管理程序是否就绪,或者LivenessEndpoint来管理程序是否存活;

当然,也可以直接使用 https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-features.html#production-ready-kubernetes-probes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Component
@Endpoint(id = "container")
public class DockerEndpoint {


@ReadOperation
public Map getDockerInfo(){
return Collections.singletonMap("info","docker started...");
}

@WriteOperation
private void restartDocker(){
System.out.println("docker restarted....");
}

}