SpringBoot

环境要求

学习资料

一、Spring&SpringBoot

1.1 Spring的能力

Spring官网

1.2 Spring的生态

覆盖了:

web开发

数据访问

安全控制

分布式

消息服务

移动开发

批处理

......

1.3 Spring5重大升级

1.3.1 响应式编程

1.3.2 内部源码设计

基于Java8的一些新特性,如:接口默认实现。重新设计源码架构。

二、SpringBoot入门

官方文档

优点:

  • 快速搭建独立运行项目
  • 简化版本依赖
  • 自动配置Spring及第三方功能
  • 简化部署
  • 提供监控健康检查

2.1 系统要求

2.1.1 系统要求

  • Java 8 & 兼容java14 .
  • Maven 3.3+
  • idea 2019.1.2

2.1.2 maven设置

<mirrors>
      <mirror>
        <id>nexus-aliyun</id>
        <mirrorOf>central</mirrorOf>
        <name>Nexus aliyun</name>
        <url>http://maven.aliyun.com/nexus/content/groups/public</url>
      </mirror>
  </mirrors>
 
  <profiles>
         <profile>
              <id>jdk-1.8</id>
              <activation>
                <activeByDefault>true</activeByDefault>
                <jdk>1.8</jdk>
              </activation>
              <properties>
                <maven.compiler.source>1.8</maven.compiler.source>
                <maven.compiler.target>1.8</maven.compiler.target>
                <maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
              </properties>
         </profile>
  </profiles>

2.2 HelloWorld工程

2.2.1 创建maven工程

2.2.2 引入依赖

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

2.2.3 创建主程序

@SpringBootApplication
public class ApplicationMain {
    public static void main(String[] args) {
        SpringApplication.run(ApplicationMain.class, args);
    }
}

2.2.4 编写业务

@RestController
public class HelloController {
    @GetMapping("hello")
    public String hello(){
        return "Hello world !";
    }
}

2.2.5 测试

直接运行main方法

2.2.6 简化配置

application.properties

server.port=8888

2.2.7 简化部署

引入springboot打包插件

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

打成jar包并运行

三 自动配置原理

3.1 依赖管理

1、springboot项目要声明父项目是spring-boot-starter-parent

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

2、而spring-boot-starter-parent的父项目是spring-boot-dependencies

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

3、spring-boot-dependencies中声明了所有可能用到的依赖的版本号

4、springboot项目导入starter场景启动器

  • spring提供的官方启动器: spring-boot-starter-* (文档说明
  • *-spring-boot-starter: 第三方为我们提供的简化开发的场景启动器。
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

此处无需关注版本号,根据父项目版本号自动引入

5、可以修改默认版本号

<!-- 查看spring-boot-dependencies里面规定当前依赖的版本 用的 key。在当前项目里面重写配置 -->
<properties>
    <mysql.version>5.1.43</mysql.version>
</properties>

3.2 自动配置

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

声明父项目并配置此启动器后,springboot做了许多自动配置

  • 自动配好SpringMVC
    • 引入SpringMVC全套组件
    • 自动配好SpringMVC常用组件(功能)
  • 自动配好Web常见功能,如:字符编码问题
    • SpringBoot帮我们配置好了所有web开发的常见场景
  • 默认的包结构
    • 主程序所在包及其下面的所有子包里面的组件都会被默认扫描进来
    • 无需以前的包扫描配置
    • 想要改变扫描路径,@SpringBootApplication(scanBasePackages="com.atguigu") 或者@ComponentScan 指定扫描路径
@SpringBootApplication等同于:
------------------------------------
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.atguigu.boot")
  • 各种配置拥有默认值
    • 默认配置最终都是映射到某个类上,如:MultipartProperties
    • 配置文件的值最终会绑定每个类上,这个类会在容器中创建对象
  • 按需加载所有自动配置项
    • 非常多的starter
    • 引入了哪些场景这个场景的自动配置才会开启
    • SpringBoot所有的自动配置功能都在 spring-boot-autoconfigure 包里面
  • ......

3.3 容器注解

3.3.1 组件添加

1、@Configuration

此注解声明类为配置项类,类中声明的@Bean等都会注册成为Spring的bean

/**
 * 1、配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单实例的
 * 2、配置类本身也是组件
 * 3、proxyBeanMethods:代理bean的方法
 *      Full(proxyBeanMethods = true)、【保证每个@Bean方法被调用多少次返回的组件都是单实例的】
 *      Lite(proxyBeanMethods = false)【每个@Bean方法被调用多少次返回的组件都是新创建的】
 *      组件依赖必须使用Full模式默认。其他默认是否Lite模式
 */
@Configuration(proxyBeanMethods = false) //告诉SpringBoot这是一个配置类 == 配置文件
public class MyConfig {

    @Bean
    public Person person(){
        return new Person("xiang", 18, pet());
    }

    @Bean
    public Pet pet(){
        return new Pet("cat");
    }
}

示例:

@SpringBootApplication
public class ApplicationMain {
    public static void main(String[] args) {
        ConfigurableApplicationContext applicationContext = SpringApplication.run(ApplicationMain.class, args);

        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        for (String beanDefinition : beanDefinitionNames) {
            System.out.println(beanDefinition);
        }

        //通过@Bean注解添加的bean
        Person person = applicationContext.getBean("person", Person.class);
        Pet pet = applicationContext.getBean("pet", Pet.class);
        System.out.println(person.getPet() == pet);
    }
}

2、@Component、@Controller、@Service、@Repository

声明对象或类注册为bean

3、@Import

@Import导入其他配置类

@Configuration
public class MyImport {
    @Bean
    public Family family(){
        return new Family();
    }
}

@Import(MyImport.class)
@Configuration
public class MyConfig {

}
@SpringBootApplication
public class ApplicationMain {
    public static void main(String[] args) {
        ConfigurableApplicationContext applicationContext = SpringApplication.run(ApplicationMain.class, args);

        //通过@Import添加的bean
        Family family = applicationContext.getBean(Family.class);
        System.out.println(family);
    }
}

4、@Conditional

@Conditional为注册bean添加了些条件限制

@ConditionalOnMissingBean(name = "personCondition")
@Configuration
public class MyConditionalConfig {
    @Bean
    public Person personCondition(){
        return new Person("xiang", 23, null);
    }
}
@SpringBootApplication
public class ApplicationMain {
    public static void main(String[] args) {
        ConfigurableApplicationContext applicationContext = SpringApplication.run(ApplicationMain.class, args);

        //通过@Configuration 和 @Conditional 注解添加的bean
        Person personCondition = applicationContext.getBean("personCondition", Person.class);
        System.out.println(personCondition);
    }
}

3.3.2 原生配置文件引入

@ImportResource

已有spring配置文件beans.xml, 若想将其配置注册到springboot中,则使用@ImportSource

beans.xml

<?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="person3" class="com.xiang.entity.Person" />
</beans>
@ImportResource("classpath:beans.xml")
@Configuration
public class MyImportResource {
}
@SpringBootApplication
public class ApplicationMain {
    public static void main(String[] args) {
        ConfigurableApplicationContext applicationContext = SpringApplication.run(ApplicationMain.class, args);

        //通过@ImportResource添加的bean
        boolean person3 = applicationContext.containsBean("person3");
        System.out.println("person3:" + person3);
    }
}

3.3.3 配置绑定

@ConfigurationProperties

@ConfigurationPropertiesScan

若想为一个组件赋值并注册,则使用这两个注解,@ConfigurationProperties负责为组件赋值,@ConfigurationPropertiesScan则负责扫描包将其注册为spring组件

car.name=BYD
car.price=10000
@Data
@ConfigurationProperties(prefix = "car")
public class Car {
    private String name;
    private int price;
}
@Configuration
@ConfigurationPropertiesScan(basePackages = "com.xiang")
public class MyPropertiesConfig {
}
@SpringBootApplication
public class ApplicationMain {
    public static void main(String[] args) {
        ConfigurableApplicationContext applicationContext = SpringApplication.run(ApplicationMain.class, args);
        //通过@COnfigurationProperties注册的bean
        String[] carNames = applicationContext.getBeanNamesForType(Car.class);
        System.out.println(Arrays.toString(carNames));
    }
}

3.4 自动配置原理入门

@SpringBoootApplication 注解内容

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication{}

3.4.1 @SpringBootConfiguration

还是一个@Configuration,代表当前是一个配置类。

3.4.2 @ComponentScan

指定扫描哪些Spring注解。

3.4.3 @EnableAutoConfiguration

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {}

1. @AutoConfigurationPackage

自动配置包,指定了默认的包规则。

@Import(AutoConfigurationPackages.Registrar.class)  //给容器中导入一个组件
public @interface AutoConfigurationPackage {}

//利用Registrar给容器中导入一系列组件
//将指定的一个包下的所有组件导入进来?MainApplication 所在包下。

2. AutoCongigurationImportSelector

1、利用getAutoConfigurationEntry(annotationMetadata);给容器中批量导入一些组件
2、调用List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes)获取到所有需要导入到容器中的配置类
3、利用工厂加载 Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader);得到所有的组件
4、从META-INF/spring.factories位置来加载一个文件。
	默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件
    spring-boot-autoconfigure-2.3.4.RELEASE.jar包里面也有META-INF/spring.factories

// 文件里面写死了spring-boot一启动就要给容器中加载的所有配置类
// spring-boot-autoconfigure-2.3.4.RELEASE.jar/META-INF/spring.factories
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
# ...

3.4.4 按需开启自动配置项

虽然我们127个场景的所有自动配置启动的时候默认全部加载。

xxxxAutoConfiguration按照条件装配规则(@Conditional),最终会按需配置。

3.4.5 修改默认配置

@Bean
@ConditionalOnMissingBean
public CharacterEncodingFilter characterEncodingFilter() {
}

总结

  • SpringBoot先加载所有的自动配置类 xxxxxAutoConfiguration
  • 每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。xxxxProperties里面拿。xxxProperties和配置文件进行了绑定
  • 生效的配置类就会给容器中装配很多组件
  • 只要容器中有这些组件,相当于这些功能就有了
  • 定制化配置
    • 用户直接自己@Bean替换底层的组件
    • 用户去看这个组件是获取的配置文件什么值就去修改:xxxxxAutoConfiguration ---> 组件 ---> xxxxProperties里面拿值 ----> application.properties

引入场景依赖: SpringBoot场景

参照文档修改配置项:配置项文档

3.5 开发小技巧

3.5.1、Lombok

简化JavaBean开发

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>

idea中搜索安装lombok插件

===============================简化JavaBean开发===================================
@NoArgsConstructor
//@AllArgsConstructor
@Data
@ToString
@EqualsAndHashCode
public class User {

    private String name;
    private Integer age;

    private Pet pet;

    public User(String name,Integer age){
        this.name = name;
        this.age = age;
    }

}
================================简化日志开发===================================
@Slf4j
@RestController
public class HelloController {
    @RequestMapping("/hello")
    public String handle01(@RequestParam("name") String name){
        
        log.info("请求进来了....");
        
        return "Hello, Spring Boot 2!"+"你好:"+name;
    }
}

3.5.2、dev-tools

 <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-devtools</artifactId>
      <optional>true</optional>
 </dependency>

项目或者页面修改以后:Ctrl+F9;实际上是重新启动。

要想真正热部署,还需要依赖Jrebel等插件。

3.5.3、Spring Initailizr(项目初始化向导)

1、选择我们需要的开发场景

 2、自动依赖引入

3、自动创建项目结构

4、自动编写好主配置类

四、配置文件

4.1 文件类型

4.1.1 propertes

同之前properties用法

server.port=8088

4.1.2 yaml

YAML 是 "YAML Ain't Markup Language"(YAML 不是一种标记语言)的递归缩写。

在开发的这种语言时,YAML 的意思其实是:"Yet Another Markup Language"(仍是一种标记语言)。

4.1.2.1 基本语法

  • key: value;kv之间有空格
  • 大小写敏感
  • 使用缩进表示层级关系
  • 缩进不允许使用tab,只允许空格
  • 缩进的空格数不重要,只要相同层级的元素左对齐即可
  • '#'表示注释
  • 字符串无需加引号,如果要加,''与""表示字符串内容。单引号禁止转义,双引号允许转义。

4.1.2.2 数据类型

1、字面量:

单个的、不可再分的值。比如int,double,String

k: v

2、对象:键值对的集合。map,hash,object等

#行内写法
k: {k1: v1, k2: v2}

# 分行写法
k:
   k1: v1
   k2: v2
   k3: v3

3、数组:一组数据,list、set、array

# 行内写法
k: [k1,k2,k3]

# 分行写法
k:
  -k1
  -k2
  -k3

4.1.2.3 示例

@Data
@ToString
@Component
@ConfigurationProperties(prefix = "person")
public class Person {
    private String name;
    private int age;
    private String desc;
    private List<String> hobbies;
    private Set<String> addresses;
    private List<Pet> pets;
    private Map<String, List<Pet>> group;
}

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

application.yml

##双引号允许转译,单引号禁用转译,不加引号等同于单引号
person:
  name: xiang
  age: 18
  desc: 大家好 \n 我是希昂
  hobbies: [打游戏,跑步]
  addresses:
    - 连云港
    - 南京
    - '江苏'
    - "中国"
  pets: [{name: 哈巴狗, weight: 23.33}]
  group:
    good:
      - name: 哈巴狗
        weight: 23.33
      - name: 金毛
        weight: 34.33
    bad: [{name: 泰迪, weight: 3.33},{name: 博美, weight: 3.33}]

4.2 配置提示

自定义的类和配置文件绑定一般没有提示。最好导入配置提示依赖。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>

五、Web开发

5.1 SpringMVC自动配置概览

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

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

  • 内容协商视图解析器和BeanName视图解析器
  • 静态资源(包括webjars)
  • 自动注册 Converter,GenericConverter,Formatter
  • 支持 HttpMessageConverters (后来我们配合内容协商理解原理)
  • 自动注册 MessageCodesResolver (国际化用)
  • 静态index.html 页支持
  • 自定义 Favicon
  • 自动使用 ConfigurableWebBindingInitializer ,(DataBinder负责将请求数据绑定到JavaBean上)

5.2 简单功能分析

5.2.1 静态资源访问

1、静态资源目录

静态资源目录默认为:【 "classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/" 】

即放在如下目录下的文件都会被认为是静态资源。

也可以通过如下配置项更改静态资源目录:

spring:
  ## 设置静态资源存放路径(只有这些路径下的资源才会被认为是静态资源)
  web:
    resources:
      static-locations: ["classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"]

2、静态资源访问前缀

静态资源访问前缀默认为:/**

即所有请求在匹配不到控制器层路径时,都会来查询静态资源。

注意当路径匹配得到控制器层路径时,优先通过控制器层处理。

可以通过如下参数设置静态资源路径。

spring:
  ## 设置静态资源请求路径(只有这些请求才会被认为是静态资源请求)
  mvc:
    static-path-pattern: /static/**

3、webjar

webjar里的资源也可以当作静态资源访问。具体访问方式见上图。

其中webjar引入方式:

<dependency>
    <groupId>org.webjars.npm</groupId>
    <artifactId>jquery</artifactId>
    <version>3.6.0</version>
</dependency>

webjar的目录结构:

 4、自定义Favicon

自定义网站图标。

favicon.ico放在静态目录下即可。

​​​​​​​

 

5.3 请求参数处理

5.3.1 请求映射

Rest接口

SpringBoot符合REST风格,针对同一资源,增删改查分别对应如下的method

  • GET - 查
  • POST - 增
  • PUT - 改
  • DELETE - 删

示例如下:

@RestController
public class UserController {
    @GetMapping("/user")
    public String getUser(){
        return "getUser";
    }

    @PostMapping("/user")
    public String postUser(){
        return "postUser";
    }

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

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

表单隐藏方法

表单提交时默认不支持DELETE和PUT方法,若想支持REST,则需要开启HiddenHttpMethodFilter

1.开启HiddenHttpMethodFilter

spring:
  mvc:
    hiddenmethod:
      filter:
        # 开启hidden方法处理
        enabled: true

2.表单中添加隐藏参数_method

<form action="/user" method="post">
    <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. 项目启动时初始化Spring容器及Web组件:HttpServletBean.init() -> HttpServletBean.initServletBean() -> FrameworkServlet.initWebApplicationContext()
  2. Servlet接到请求时进入DispatcherServlet.dispatch(): HttpServletBean.doService() -> FrameworkServlet.processRequest() -> DispatcherServlet.doService() -> DispatcherServlet.dispatch()
  3. 从hanlerMappings中找到最匹配的Handler
  4. 使用适配器组装hanler
  5. 通过反射执行具体Controller的方法

5.3.2 普通参数与基本注解

1.注解

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

@RestController
@RequestMapping("/param")
public class ParamController {
    
    @GetMapping("/pathVariable/{id}/{name}")
    public Map<String, Object> pathTest(@PathVariable String id, @PathVariable String name,
                                        @PathVariable Map<String, Object> map) {
        HashMap<String, Object> result = new HashMap<>();
        result.put("id", id);
        result.put("name", name);
        result.put("map", map);
        return result;
    }

    @GetMapping("/requestParam")
    public Map<String, Object> requestParam(@RequestParam String name,
                                            @RequestParam Map<String, Object> map) {
        HashMap<String, Object> result = new HashMap<>();
        result.put("name", name);
        result.put("map", map);
        return result;
    }

    @GetMapping("/requestHeader")
    public Map<String, Object> requestHeader(@RequestHeader("host") String host,
                                             @RequestHeader Map<String, Object> map) {
        HashMap<String, Object> result = new HashMap<>();
        result.put("host", host);
        result.put("map", map);
        return result;
    }

    @GetMapping("/cookie")
    public Map<String, Object> cookie(@CookieValue Cookie map) {
        HashMap<String, Object> result = new HashMap<>();
        result.put("map", map);
        return result;
    }

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

2.自定义对象参数

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

/**
 *     姓名: <input name="userName"/> <br/>
 *     年龄: <input name="age"/> <br/>
 *     生日: <input name="birth"/> <br/>
 *     宠物姓名:<input name="pet.name"/><br/>
 *     宠物年龄:<input name="pet.age"/>
 */
@Data
public class Person {
    
    private String userName;
    private Integer age;
    private Date birth;
    private Pet pet;
    
}

@Data
public class Pet {

    private String name;
    private String age;

}



5.3.3 POJO封装过程

  • ServletModelAttributeMethodProcessor

5.3.4 参数处理原理

  1. 接口由handlerMapping找到对应的HandlerAdapter(RequestMappingHandlerAdapter)
  2. 进入到RequestMappingHandlerAdapter的handle方法。
  3. handle方法中遍历所有的参数解析器argumentResolvers,找到对应的解析器,解析获取方法的每一个入参
  4. 获取到入参后,即通过反射调用方法获。

​​​​​​​

 

5.4 返回结果处理

5.4.1 响应JSON

5.4.1.1、jackson.jar + @ResponseBody

web场景自动引入了json依赖

 ​​​​​​​

 给前端自动返回json数据步骤:

1、返回解析器

try {
    this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
    HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
    if (handler == null) {
        throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
    }
    handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}

RequestResponseBodyMethodProcessor

@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
    mavContainer.setRequestHandled(true);
    ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
    ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
    
    // Try even with null return value. ResponseBodyAdvice could get involved.
        // 使用消息转换器进行写出操作
    writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}

2、返回解析器原理

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

5.4.1.2 SpringMVC支持哪些返回值

ModelAndView
Model
View
ResponseEntity 
ResponseBodyEmitter
StreamingResponseBody
HttpEntity
HttpHeaders
Callable
DeferredResult
ListenableFuture
CompletionStage
WebAsyncTask
@ModelAttribute 且为对象类型的
@ResponseBody注解 ---> RequestResponseBodyMethodProcessor;

5.4.2 内容协商

根据客户端接收能力不同,返回不同媒体类型的数据。

5.4.2.1 引入xml依赖

 <dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-xml</artifactId>
</dependency>

5.4.2.2 postman分别测试返回json和xml

只需要改变请求头中Accept字段。Http协议中规定的,告诉服务器本客户端可以接收的数据类型。

 

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

为了方便内容协商,开启基于请求参数的内容协商功能。

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

发请求:

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

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

确定客户端接收什么样的内容类型;

1、Parameter策略优先确定是要返回json数据(获取请求头中的format的值)

2、最终进行内容协商返回给客户端json即可。

5.4.2.4 内容协商原理

  1. 判断当前响应头中是否已经有确定的媒体类型。MediaType
  2. 获取客户端(PostMan、浏览器)支持接收的内容类型。(获取客户端Accept请求头字段)【application/xml】
    1. contentNegotiationManager 内容协商管理器 默认使用基于请求头的策略
    2. HeaderContentNegotiationStrategy 确定客户端可以接收的内容类型
  3. 遍历循环所有当前系统的 MessageConverter,看谁支持操作这个对象(Person)
  4. 找到支持操作Person的converter,把converter支持的媒体类型统计出来。
  5. 客户端需要【application/xml】。服务端能力【10种、json、xml】
  6. 进行内容协商的最佳匹配媒体类型
  7. 用支持将对象转为最佳匹配媒体类型 的converter。调用它进行转化 。

导入了jackson处理xml的包,xml的converter就会自动进来

jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);

if (jackson2XmlPresent) {
    Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.xml();
    if (this.applicationContext != null) {
        builder.applicationContext(this.applicationContext);
    }
    messageConverters.add(new MappingJackson2XmlHttpMessageConverter(builder.build()));
}

5.4.2.5 自定义 MessageConverter

实现多协议数据兼容。json、xml

  1. @ResponseBody 响应数据。调用 RequestResponseBodyMethodProcessor 处理
  2. Processor 处理方法返回值。通过 MessageConverter 处理
  3. 所有 MessageConverter 合起来可以支持各种媒体类型数据的操作(读、写)
  4. 内容协商找到最终的 messageConverter;
 @Bean
public WebMvcConfigurer webMvcConfigurer(){
    return new WebMvcConfigurer() {
        @Override
        public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {

        }
    }
}

5.5 视图解析与模版引擎

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

5.5.1 视图解析

 视图解析原理流程

  1. 目标方法处理的过程中,所有数据都会被放在 ModelAndViewContainer 里面。包括数据和视图地址
  2. 方法的参数是一个自定义类型对象(从请求参数中确定的),把他重新放在 ModelAndViewContainer
  3. 任何目标方法执行完成以后都会返回 ModelAndView(数据和视图地址)。
  4. processDispatchResult 处理派发结果(页面改如何响应)
    1. render(mv, request, response); 进行页面渲染逻辑
      1. 根据方法的String返回值得到 View 对象【定义了页面的渲染逻辑】
        1. 所有的视图解析器尝试是否能根据当前返回值得到View对象
        2. 得到了 redirect:/main.html --> Thymeleaf new RedirectView()
        3. ContentNegotiationViewResolver 里面包含了下面所有的视图解析器,内部还是利用下面所有视图解析器得到视图对象。
        4. view.render(mv.getModelInternal(), request, response); 视图对象调用自定义的render进行页面渲染工作
          1. 获取目标url地址
          2. response.sendRedirect(encodedURL);

5.5.2 模板引擎-Thymeleaf

1、thymeleaf简介

Thymeleaf is a modern server-side Java template engine for both web and standalone environments, capable of processing HTML, XML, JavaScript, CSS and even plain text.

现代化、服务端Java模板引擎

2、基本语法

1、表达式

表达式名字

语法

用途

变量取值

${...}

获取请求域、session域、对象等值

选择变量

*{...}

获取上下文对象值

消息

#{...}

获取国际化等值

链接

@{...}

生成链接

片段表达式

~{...}

jsp:include 作用,引入公共页面片段

2、字面量

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

空值: null

变量: one,two,.... 变量不能有空格

3、文本操作

字符串拼接: +

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

4、数学运算

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

5、布尔运算

运算符: and , or

一元运算: ! , not

6、比较运算

比较: > , < , >= ,

7、条件运算

If-then: (if) ? (then)

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

Default: (value) ?: (defaultvalue)

8、特殊操作

无操作: _

3、设置属性值-th:attr

设置单个值

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

设置多个值

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

以上两个的代替写法 th:xxxx

<input type="submit" value="Subscribe!" th:value="#{subscribe.submit}"/>
<form action="subscribe.html" th:action="@{/subscribe}">

所有h5兼容的标签写法

https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#setting-value-to-specific-attributes

4、迭代

<tr th:each="prod : ${prods}">
        <td th:text="${prod.name}">Onions</td>
        <td th:text="${prod.price}">2.41</td>
        <td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
</tr>
<tr th:each="prod,iterStat : ${prods}" th:class="${iterStat.odd}? 'odd'">
  <td th:text="${prod.name}">Onions</td>
  <td th:text="${prod.price}">2.41</td>
  <td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
</tr>

5、条件运算

<a href="comments.html"
th:href="@{/product/comments(prodId=${prod.id})}"
th:if="${not #lists.isEmpty(prod.comments)}">view</a>
<div th:switch="${user.role}">
  <p th:case="'admin'">User is an administrator</p>
  <p th:case="#{roles.manager}">User is a manager</p>
  <p th:case="*">User is some other thing</p>
</div>

6、属性优先级

 

5.5.3 thymeleaf使用

1、引入Starter

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

2、自动配置好了thymeleaf

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(ThymeleafProperties.class)
@ConditionalOnClass({ TemplateMode.class, SpringTemplateEngine.class })
@AutoConfigureAfter({ WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class })
public class ThymeleafAutoConfiguration { }

自动配好的策略

  1. 所有thymeleaf的配置值都在 ThymeleafProperties
  2. 配置好了 SpringTemplateEngine
  3. 配好了 ThymeleafViewResolver
  4. 我们只需要直接开发页面
public static final String DEFAULT_PREFIX = "classpath:/templates/";
public static final String DEFAULT_SUFFIX = ".html";  //xxx.html

3、页面开发

<!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.atguigu.com" th:href="${link}">去百度</a>  <br/>
    <a href="www.atguigu.com" th:href="@{link}">去百度2</a>
</h2>
</body>
</html>

5.5.4 构建后台管理系统

1、项目创建

thymeleaf、web-starter、devtools、lombok

2、静态资源处理

自动配置好,我们只需要把所有静态资源放到 static 文件夹下

3、路径构建

th:action="@{/login}"

4、模板抽取

th:insert/replace/include

5、页面跳转

    @PostMapping("/login")
    public String main(User user, HttpSession session, Model model){

        if(StringUtils.hasLength(user.getUserName()) && "123456".equals(user.getPassword())){
            //把登陆成功的用户保存起来
            session.setAttribute("loginUser",user);
            //登录成功重定向到main.html;  重定向防止表单重复提交
            return "redirect:/main.html";
        }else {
            model.addAttribute("msg","账号密码错误");
            //回到登录页面
            return "login";
        }
    }

6、数据渲染

    @GetMapping("/dynamic_table")
    public String dynamic_table(Model model){
        //表格内容的遍历
        List<User> users = Arrays.asList(new User("zhangsan", "123456"),
                new User("lisi", "123444"),
                new User("haha", "aaaaa"),
                new User("hehe ", "aaddd"));
        model.addAttribute("users",users);

        return "table/dynamic_table";
    }
        <table class="display table table-bordered" id="hidden-table-info">
        <thead>
        <tr>
            <th>#</th>
            <th>用户名</th>
            <th>密码</th>
        </tr>
        </thead>
        <tbody>
        <tr class="gradeX" th:each="user,stats:${users}">
            <td th:text="${stats.count}">Trident</td>
            <td th:text="${user.userName}">Internet</td>
            <td >[[${user.password}]]</td>
        </tr>
        </tbody>
        </table>

5.6 拦截器

1、HandlerInterceptor 接口

/**
 * 登录检查
 * 1、配置好拦截器要拦截哪些请求
 * 2、把这些配置放在容器中
 */
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {

    /**
     * 目标方法执行之前
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

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

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

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

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

    /**
     * 目标方法执行完成以后
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.info("postHandle执行{}",modelAndView);
    }

    /**
     * 页面渲染以后
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        log.info("afterCompletion执行异常{}",ex);
    }
}

2、配置拦截器

/**
 * 1、编写一个拦截器实现HandlerInterceptor接口
 * 2、拦截器注册到容器中(实现WebMvcConfigurer的addInterceptors)
 * 3、指定拦截规则【如果是拦截所有,静态资源也会被拦截】
 */
@Configuration
public class AdminWebConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .addPathPatterns("/**")  //所有请求都被拦截包括静态资源
                .excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**","/js/**"); //放行的请求
    }
}

3、拦截器原理

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

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

  • 如果当前拦截器prehandler返回为true。则执行下一个拦截器的preHandle
  • 如果当前拦截器返回为false。直接 倒序执行所有已经执行了的拦截器的 afterCompletion;

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

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

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

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

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

 

5.7 文件上传

1、页面表单

<form method="post" action="/upload" enctype="multipart/form-data">
    <input type="file" name="file"><br>
    <input type="submit" value="提交">
</form>

2、文件上传代码

    /**
     * MultipartFile 自动封装上传过来的文件
     * @param email
     * @param username
     * @param headerImg
     * @param photos
     * @return
     */
    @PostMapping("/upload")
    public String upload(@RequestParam("email") String email,
                         @RequestParam("username") String username,
                         @RequestPart("headerImg") MultipartFile headerImg,
                         @RequestPart("photos") MultipartFile[] photos) throws IOException {

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

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

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

        return "main";
    }

3、自动配置原理

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

  • 自动配置好了 StandardServletMultipartResolver 【文件上传解析器】
  • 原理步骤:
  1. 请求进来使用文件上传解析器判断(isMultipart)并封装(resolveMultipart,返回MultipartHttpServletRequest)文件上传请求
  2. 参数解析器来解析请求中的文件内容封装成MultipartFile
  3. 将request中文件信息封装为一个Map;MultiValueMap

FileCopyUtils。实现文件流的拷贝

@PostMapping("/upload")
public String upload(@RequestParam("email") String email,
                     @RequestParam("username") String username,
                     @RequestPart("headerImg") MultipartFile headerImg,
                     @RequestPart("photos") MultipartFile[] photos)

5.8 异常处理

5.8.1 错误处理

1、默认规则

  • 默认情况下,Spring Boot提供/error处理所有错误的映射
  • 对于机器客户端,它将生成JSON响应,其中包含错误,HTTP状态和异常消息的详细信息。对于浏览器客户端,响应一个“ whitelabel”错误视图,以HTML格式呈现相同的数据
  • 要对其进行自定义,添加View解析为error
  • 要完全替换默认行为,可以实现 ErrorController 并注册该类型的Bean定义,或添加ErrorAttributes类型的组件以使用现有机制但替换其内容。
  • error/下的4xx,5xx页面会被自动解析

​​​​​​​

 

2、定制错误处理逻辑

  • 自定义错误页
    • error/404.html error/5xx.html;有精确的错误状态码页面就匹配精确,没有就找 4xx.html;如果都没有就触发白页
  • @ControllerAdvice + @ExceptionHandler处理全局异常;底层是 ExceptionHandlerExceptionResolver 支持的
  • @ResponseStatus+自定义异常 ;底层是 ResponseStatusExceptionResolver ,把responsestatus注解的信息底层调用 response.sendError(statusCode, resolvedReason);tomcat发送的/error
  • Spring底层的异常,如参数类型转换异常;DefaultHandlerExceptionResolver 处理框架底层的异常。
    • response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage());
  • 自定义实现 HandlerExceptionResolver 处理异常;可以作为默认的全局异常处理规则
  • ErrorViewResolver 实现自定义处理异常;
    • response.sendError 。error请求就会转给controller
    • 异常没有任何人能处理。tomcat底层 response.sendError。error请求就会转给controller
    • basicErrorController 要去的页面地址是 ErrorViewResolver ;

3、异常处理步骤流程

  1. 执行目标方法,目标方法运行期间有任何异常都会被catch、而且标志当前请求结束;并且用 dispatchException
  2. 进入视图解析流程:processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
  3. mv = processHandlerException;处理handler发生的异常,处理完成返回ModelAndView
    1. 遍历所有的 handlerExceptionResolvers,看谁能处理当前异常【HandlerExceptionResolver处理器异常解析器】

5.9 Web原生组件注入(Servlet、Filter、Listener)

5.9.1 使用Servlet API

@ServletComponentScan(basePackages = "com.atguigu.admin") : 指定原生Servlet组件都放在那里

@WebServlet(urlPatterns = "/my"):效果:直接响应,没有经过Spring的拦截器

@WebFilter(urlPatterns={"/css/*","/images/*"})

@WebListener

扩展:DispatchServlet 如何注册进来

容器中自动配置了 DispatcherServlet 属性绑定到 WebMvcProperties;对应的配置文件配置项是 spring.mvc。

通过 ServletRegistrationBean 把 DispatcherServlet 配置进来。

默认映射的是 / 路径。

 Tomcat-Servlet;

多个Servlet都能处理到同一层路径,精确优选原则

A: /my/

B: /my/1

5.9.2 使用RegistrationBean

ServletRegistrationBean, FilterRegistrationBean,  ServletListenerRegistrationBean

@Configuration
public class MyRegistConfig {

    @Bean
    public ServletRegistrationBean myServlet(){
        MyServlet myServlet = new MyServlet();
        return new ServletRegistrationBean(myServlet,"/my","/my02");
    }


    @Bean
    public FilterRegistrationBean myFilter(){
        MyFilter myFilter = new MyFilter();
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(myFilter);
        filterRegistrationBean.setUrlPatterns(Arrays.asList("/my","/css/*"));
        return filterRegistrationBean;
    }

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

5.10 嵌入式Servlet容器

5.10.1 切换嵌入式Servlet容器

  • 默认支持的webServer
    • Tomcat, Jetty, or Undertow
    • ServletWebServerApplicationContext 容器启动寻找ServletWebServerFactory 并引导创建服务器
  • 切换服务器

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <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>

原理

  • SpringBoot应用启动发现当前是Web应用。web场景包导入tomcat
  • web应用会创建一个web版的ioc容器ServletWebServerApplicationContext 
  • ServletWebServerApplicationContext  启动的时候寻找 ServletWebServerFactory(Servlet 的web服务器工厂---> Servlet 的web服务器) 
  • SpringBoot底层默认有很多的WebServer工厂;TomcatServletWebServerFactory, JettyServletWebServerFactory, or UndertowServletWebServerFactory
  • 底层直接会有一个自动配置类。ServletWebServerFactoryAutoConfiguration
  • ServletWebServerFactoryAutoConfiguration导入了ServletWebServerFactoryConfiguration(配置类)
  • ServletWebServerFactoryConfiguration 配置类 根据动态判断系统中到底导入了那个Web服务器的包。(默认是web-starter导入tomcat包),容器中就有 TomcatServletWebServerFactory
  • TomcatServletWebServerFactory 创建出Tomcat服务器并启动;TomcatWebServer 的构造器拥有初始化方法initialize---this.tomcat.start();
  • 内嵌服务器,就是手动把启动服务器的代码调用(tomcat核心jar包存在)

5.10.2 定制Servlet容器

  • 实现 WebServerFactoryCustomizer
    • 把配置文件的值和ServletWebServerFactory 进行绑定
  • 修改配置文件 server.xxx
  • 直接自定义 ConfigurableServletWebServerFactory

xxxxxCustomizer:定制化器,可以改变xxxx的默认规则

import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.stereotype.Component;

@Component
public class CustomizationBean implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {

    @Override
    public void customize(ConfigurableServletWebServerFactory server) {
        server.setPort(9000);
    }

}

5.11 定制化原理

5.11.1、定制化的常见方式

  • 修改配置文件
  • xxxxxCustomizer
  • 编写自定义的配置类 xxxConfiguration + @Bean替换、增加容器中默认组件、视图解析器
  • Web应用编写一个配置类实现WebMvcConfigurer即可定制化web功;+ @Bean给容器中再扩展一些组件
  • @EnableWebMvc + WebMvcConfigurer + @Bean 可以全面接管SpringMVC,所有规则全部自己重新配置实现定制和扩展功能
  • ... ...

5.11.2、原理分析套路

场景starter - xxxxAutoConfiguration - 导入xxx组件 - 绑定xxxProperties -- 绑定配置文件项

六、数据访问

6.1 SQL

6.1.1 数据源的自动配置-HikariDataSource

1、导入JDBC场景

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

 SpringBoot为什么不自动导入:

  1. 数据库很多,无法知道实际要用的数据库
  2. 数据库和驱动版本需要对应

覆盖修改JDBC版本

-- 方式一:直接使用对应版本
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>>5.1.49</version>
</dependency>

-- 方式二:覆盖父类版本
 <properties>
    <mysql.version>5.1.49</mysql.version>
</properties>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

2、分析自动配置

自动配置的类:

  • DataSourceAutoConfiguration 数据源自动配置
    • 修改数据源相关的配置:spring.datasource
    • 数据库连接池的配置,是容器中没有DataSource才会自动配置
    • 底层默认配置的连接池是:HakariDataSource
  • DataSourceTransactionManagerAutoConfiguration:事务管理器的自动配置
  • JdbcTemplateAutoConfiguration: JdbcTemplate的自动配置,可以对数据进行CRUD
    • 相关的配置:spring.jdbc
    • 会注入JdbcTemplate这个Bean
  • JndiDataSourceAutoConfiguration: jndi的自动配置
  • XADataSourceAutoConfiguration:分布式事务相关的

3、修改配置项

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/dbblog
    username: root
    password: 123456
    driver-class-name: com.mysql.jdbc.Driver

4、测试

@Slf4j
@SpringBootTest
public class JdbcTest {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    @Test
    public void test() {
        Long aLong = jdbcTemplate.queryForObject("select count(1) from article", Long.class);
        log.info("article总数:{}", aLong);
    }
}

6.1.2 使用Druid数据源

1、druid官方github地址

GitHub - alibaba/druid: 阿里云计算平台DataWorks(https://help.aliyun.com/document_detail/137663.html) 团队出品,为监控而生的数据库连接池

整合第三方技术的两种方式

  • 自定义
  • 官方starter

2、自定义方式

引入依赖

@Slf4j
@SpringBootTest
public class JdbcTest {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    @Test
    public void test() {
        Long aLong = jdbcTemplate.queryForObject("select count(1) from article", Long.class);
        log.info("article总数:{}", aLong);
    }
}

创建数据源

配置StatViewServlet

配置StatFilter

配置慢SQL记录

@Configuration
public class MyConfig {

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource dataSource() throws SQLException {
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setFilters("stat,wall");
        return druidDataSource;
    }

    @Bean
    public ServletRegistrationBean<StatViewServlet> statViewServlet() {
        StatViewServlet statViewServlet = new StatViewServlet();
        ServletRegistrationBean<StatViewServlet> registrationBean = new ServletRegistrationBean<>(statViewServlet, "/druid/*");
        registrationBean.addInitParameter("loginUsername","admin");
        registrationBean.addInitParameter("loginPassword","admin");
        return registrationBean;
    }

    @Bean
    public FilterRegistrationBean<Filter> webStatFilter() {
        WebStatFilter webStatFilter = new WebStatFilter();
        FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>(webStatFilter);
        filterRegistrationBean.setUrlPatterns(Collections.singletonList("/*"));
        filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
        return filterRegistrationBean;
    }
}

3、使用官方starter方式

引入druid-starter

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.17</version>
</dependency>

分析自动配置

  • 扩展配置项 spring.datasource.druid
  • DruidSpringAopConfiguration.class, 监控SpringBean的;配置项:spring.datasource.druid.aop-patterns
  • DruidStatViewServletConfiguration.class, 监控页的配置:spring.datasource.druid.stat-view-servlet;默认开启
  • DruidWebStatFilterConfiguration.class, web监控配置;spring.datasource.druid.web-stat-filter;默认开启
  • DruidFilterConfiguration.class 所有Druid自己filter的配置
private static final String FILTER_STAT_PREFIX = spring.datasource.druid.filter.stat;
private static final String FILTER_CONFIG_PREFIX = spring.datasource.druid.filter.config;
private static final String FILTER_ENCODING_PREFIX = spring.datasource.druid.filter.encoding;
private static final String FILTER_SLF4J_PREFIX = spring.datasource.druid.filter.slf4j;
private static final String FILTER_LOG4J_PREFIX = spring.datasource.druid.filter.log4j;
private static final String FILTER_LOG4J2_PREFIX = spring.datasource.druid.filter.log4j2;
private static final String FILTER_COMMONS_LOG_PREFIX = spring.datasource.druid.filter.commons-log;
private static final String FILTER_WALL_PREFIX = spring.datasource.druid.filter.wall;

4、配置示例

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/dbblog
    username: root
    password: 123456
    # druid
    druid:
      filters: wall,stat

      stat-view-servlet:
        enabled: true
        login-username: admin
        login-password: admin

      web-stat-filter:
        enabled: true
        url-pattern: /*
        exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'
mybatis:
#  config-location: classpath:mybatis/mybatis-config.xml
  mapper-locations: classpath:mybatis/mapper/*.xml
  configuration:
    map-underscore-to-camel-case: true

SpringBoot配置示例:druid/druid-spring-boot-starter at master · alibaba/druid · GitHub

配置项列表: https://github.com/alibaba/druid/wiki/DruidDataSource配置属性列表

6.1.3 整合Mybatis

官方地址:https://github.com/mybatis

引入starter

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.4</version>
</dependency>

 1、配置模式

  • 全局配置文件
  • SqlSessionFactory: 自动配置好了
  • SqlSession:自动配置了
  • SqlSessionTemplate 组合了SqlSession
  • @Import(AutoConfiguredMapperScannerRegistrar.class);
  • Mapper: 只要我们写的操作MyBatis的接口标准了 @Mapper 就会被自动扫描进来
@EnableConfigurationProperties(MybatisProperties.class) //MyBatis配置项绑定类。
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class })
public class MybatisAutoConfiguration{}

@ConfigurationProperties(prefix = mybatis)
public class MybatisProperties

Mapper接口--->绑定Xml

 <?xml version=1.0 encoding=UTF-8 ?>
<!DOCTYPE mapper
        PUBLIC -//mybatis.org//DTD Mapper 3.0//EN
        http://mybatis.org/dtd/mybatis-3-mapper.dtd>
<mapper namespace=com.atguigu.admin.mapper.AccountMapper>
<!--    public Account getAcct(Long id); -->
    <select id=getAcct resultType=com.atguigu.admin.bean.Account>
        select * from  account_tbl where  id=#{id}
    </select>
</mapper>

配置项值:mybatis

# 配置mybatis规则
mybatis:
  config-location: classpath:mybatis/mybatis-config.xml  #全局配置文件位置
  mapper-locations: classpath:mybatis/mapper/*.xml  #sql映射文件位置

配置 private Configuration configuration; mybatis.configuration下面的所有,就是相当于改mybatis全局配置文件中的值

# 配置mybatis规则
 #可以不写全局;配置文件,所有全局配置文件的配置都放在configuration配置项中即可
mybatis:
#  config-location: classpath:mybatis/mybatis-config.xml
  mapper-locations: classpath:mybatis/mapper/*.xml
  configuration:
    map-underscore-to-camel-case: true
  • 导入mybatis官方starter
  • 编写mapper接口。标准@Mapper注解
  • 编写sql映射文件并绑定mapper接口
  • 在application.yaml中指定Mapper配置文件的位置,以及指定全局配置文件的信息 (建议:配置在mybatis.configuration)

2、注解模式

@Mapper
public interface CityMapper {
    @Select("select * from city where id=#{id}")
    public City getById(Long id);

    public void insert(City city);
}

3、混合模式

@Mapper
public interface CityMapper {

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

    public void insert(City city);

}

6.1.4 整合Mybatis-Plus

1、是什么

MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。

mybatis plus 官网:https://baomidou.com/

建议安装 MybatisX 插件

2、怎么整合

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.1</version>
</dependency>

自动配置

  • MybatisPlusAutoConfiguration 配置类,MybatisPlusProperties 配置项绑定。mybatis-plus:xxx 就是对mybatis-plus的定制
  • SqlSessionFactory 自动配置好。底层是容器中默认的数据源
  • mapperLocations 自动配置好的。有默认值。classpath*:/mapper/**/*.xml;任意包的类路径下的所有mapper文件夹下任意路径下的所有xml都是sql映射文件。 建议以后sql映射文件,放在 mapper下
  • 容器中也自动配置好了 SqlSessionTemplate
  • @Mapper 标注的接口也会被自动扫描;建议直接 @MapperScan(com.atguigu.admin.mapper) 批量扫描就行

优点:

  • 只需要我们的Mapper继承 BaseMapper 就可以拥有crud能力

3、CRUD示例

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper,User> implements UserService {


}

public interface UserService extends IService<User> {

}

6.2 NoSQL

6.2.1 Redis自动配置

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

​​​​​​​

 自动配置:

  • RedisAutoConfiguration 自动配置类。RedisProperties 属性类 --> spring.redis.xxx是对redis的配置
  • 连接工厂是准备好的。LettuceConnectionConfiguration、JedisConnectionConfiguration
  • 自动注入了RedisTemplate : xxxTemplate;
  • 自动注入了StringRedisTemplate;k:v都是String
  • key:value
  • 底层只要我们使用 StringRedisTemplate、RedisTemplate就可以操作redis

6.2.2 RedisTemplate与Lettuce

@Test
void testRedis(){
    ValueOperations<String, String> operations = redisTemplate.opsForValue();

    operations.set("hello","world");

    String hello = operations.get("hello");
    System.out.println(hello);
}

6.2.3 切换至jedis

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!--        导入jedis-->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
</dependency>
spring:
  redis:
      host: localhost
      port: 6379
      client-type: jedis
      jedis:
        pool:
          max-active: 10

七、单元测试

7.1 Junit5的变化

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

作为最新版本的JUnit框架,JUnit5与之前版本的Junit框架有很大的不同。由三个不同子项目的几个不同模块组成。

JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage

  • JUnit Platform: Junit Platform是在JVM上启动测试框架的基础,不仅支持Junit自制的测试引擎,其他测试引擎也都可以接入。
  • JUnit Jupiter: JUnit Jupiter提供了JUnit5的新的编程模型,是JUnit5新特性的核心。内部 包含了一个测试引擎,用于在Junit Platform上运行。
  • JUnit Vintage: 由于JUint已经发展多年,为了照顾老的项目,JUnit Vintage提供了兼容JUnit4.x,Junit3.x的测试引擎。

​​​​​​​

 注意:

SpringBoot 2.4 以上版本移除了默认对 Vintage 的依赖。如果需要兼容junit4需要自行引入(不能使用junit4的功能 @Test)

<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>

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-test</artifactId>
  <scope>test</scope>
</dependency>

现在示例代码:

@SpringBootTest
class Boot05WebAdminApplicationTests {
    @Test
    void contextLoads() {
    }
}

以前:

@SpringBootTest + @RunWith(SpringTest.class)

SpringBoot整合Junit以后:

  • 编写测试方法:@Test标注(注意需要使用junit5版本的注解)
  • Junit类具有Spring的功能,@Autowired、比如 @Transactional 标注测试方法,测试完成后自动回滚

7.2 Junit5的常用注解

JUnit5的注解与JUnit4的注解有所变化

JUnit 5 User Guide

  • @Test :表示方法是测试方法。但是与JUnit4的@Test不同,他的职责非常单一不能声明任何属性,拓展的测试将会由Jupiter提供额外测试
  • @ParameterizedTest :表示方法是参数化测试,下方会有详细介绍
  • @RepeatedTest :表示方法可重复执行,下方会有详细介绍
  • @DisplayName :为测试类或者测试方法设置展示名称
  • @BeforeEach :表示在每个单元测试之前执行
  • @AfterEach :表示在每个单元测试之后执行
  • @BeforeAll :表示在所有单元测试之前执行
  • @AfterAll :表示在所有单元测试之后执行
  • @Tag :表示单元测试类别,类似于JUnit4中的@Categories
  • @Disabled :表示测试类或测试方法不执行,类似于JUnit4中的@Ignore
  • @Timeout :表示测试方法运行如果超过了指定时间将会返回错误
  • @ExtendWith :为测试类或测试方法提供扩展类引用

普通Test

@Slf4j
public class MySimpleTest {

    @BeforeAll
    static void beforeAll(){
        log.info("beforeAll execute...");
    }

    @BeforeEach
    public void beforeEach(){
        log.info("beforeEach execute...");
    }


    @DisplayName("simpleTest")
    @Test
    public void test(){
        log.info("test!");
    }

    @DisplayName("simpleTest2")
    @Test
    public void test2(){
        log.info("test2!");
    }

    @Timeout(value = 5, unit = TimeUnit.MILLISECONDS)
    @Test
    public void test3() throws InterruptedException {
        Thread.sleep(100);
        log.info("test3!");
    }
    
    @AfterEach
    public void afterEach(){
        log.info("afterEach execute...");
    }

    @AfterAll
    static void afterAll(){
        log.info("afterAll execute...");
    }
}

SpringBootTest

@Slf4j
@SpringBootTest
class MainApplicationTests {
    @Autowired
    StringRedisTemplate redisTemplate;

    @Test
    public void testRedis(){
        ValueOperations<String, String> operations = redisTemplate.opsForValue();
        operations.set("name1","test");
        log.info("names:{}", operations.get("name1"));
    }
}

7.3 断言

断言(assertions)是测试方法中的核心部分,用来对测试需要满足的条件进行验证。这些断言方法都是 org.junit.jupiter.api.Assertions 的静态方法。

7.3.1 简单断言

对单个值进行简单判断

方法

说明

assertEquals

判断两个对象或两个原始类型是否相等

assertNotEquals

判断两个对象或两个原始类型是否不相等

assertSame

判断两个对象引用是否指向同一个对象

assertNotSame

判断两个对象引用是否指向不同的对象

assertTrue

判断给定的布尔值是否为 true

assertFalse

判断给定的布尔值是否为 false

assertNull

判断给定的对象引用是否为 null

assertNotNull

判断给定的对象引用是否不为 null

简单判断

@Test
public void assertEqualsTest() {
    int sum = sum(5, 11);
    Assertions.assertEquals(16, sum, sum + "不等于" + 12);

    sum = sum(12, 13);
    Assertions.assertEquals(12, sum, sum + "不等于" + 12);
}

7.3.2 数组断言

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

@Test
@DisplayName("array assertion")
public void array() {
 	assertArrayEquals(new int[]{1, 2}, new int[] {1, 2});
}

7.3.3 组合断言

assertAll 方法接受多个 org.junit.jupiter.api.Executable 函数式接口的实例作为要验证的断言,可以通过 lambda 表达式很容易的提供这些断言

@Test
public void assertAllTest() {
    Assertions.assertAll("test",
            () -> Assertions.assertEquals(2, 1 + 1),
            () -> Assertions.assertEquals(4, 2 + 2));
    log.info("assert success");
}

7.3.4 异常断言

在JUnit4时期,想要测试方法的异常情况时,需要用@Rule注解的ExpectedException变量还是比较麻烦的。而JUnit5提供了一种新的断言方式Assertions.assertThrows() ,配合函数式编程就可以进行使用。

@Test
@DisplayName("异常测试")
public void exceptionTest() {
    ArithmeticException exception = Assertions.assertThrows(ArithmeticException.class, () -> System.out.println(1 % 0));

}

7.3.5 超时断言

Junit5还提供了Assertions.assertTimeout() 为测试方法设置了超时时间

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

7.3.6 快速失败

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

@Test
@DisplayName("fail")
public void shouldFail() {
    fail("This should fail");
}

7.4 前置条件

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

@Test
public void test(){
    int a = sum(5,5);
    int b = sum(1,2);
    Assumptions.assumeTrue(a == 10);
    Assumptions.assumeTrue(b == 4);
    log.info("success");
}

assumeTrue 和 assumFalse 确保给定的条件为 true 或 false,不满足条件会使得测试执行终止。

assumingThat 的参数是表示条件的布尔值和对应的 Executable 接口的实现对象。只有条件满足时,Executable 对象才会被执行;当条件不满足时,测试执行并不会终止。

7.5 嵌套测试

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

public class AStackTest {
    Stack<Object> stack;

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

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

7.6 参数化测试

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

利用@ValueSource等注解,指定入参,我们将可以使用不同的参数进行多次单元测试,而不需要每新增一个参数就新增一个单元测试,省去了很多冗余代码。

@ValueSource: 为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型

@NullSource: 表示为参数化测试提供一个null的入参

@EnumSource: 表示为参数化测试提供一个枚举入参

@CsvFileSource:表示读取指定CSV文件内容作为参数化测试入参

@MethodSource:表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流)

@Slf4j
public class ParamsTest {

    @ParameterizedTest
    @ValueSource(ints = {1, 2, 3, 4})
    public void testParameterized(int i) {
        log.info("param:{}", i);
    }

    @ParameterizedTest
    @MethodSource("strings")
    public void testParameterized2(String str) {
        log.info("param:{}", str);
    }

    static Stream<String> strings() {
        return Stream.of("apple", "banana");
    }
}

7.7 迁移指南

在进行迁移的时候需要注意如下的变化:

  • 注解在 org.junit.jupiter.api 包中,断言在 org.junit.jupiter.api.Assertions 类中,前置条件在 org.junit.jupiter.api.Assumptions 类中。
  • 把@Before 和@After 替换成@BeforeEach 和@AfterEach。
  • 把@BeforeClass 和@AfterClass 替换成@BeforeAll 和@AfterAll。
  • 把@Ignore 替换成@Disabled。
  • 把@Category 替换成@Tag。
  • 把@RunWith、@Rule 和@ClassRule 替换成@ExtendWith。

八、指标监控

8.1 SpringBoot Actuator

8.1.1 简介

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

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

8.1.2 1.x和2.x的不同

 

8.1.3 如何使用

  • 引入场景
  • 访问 http://localhost:8080/actuator/**
  • 暴露所有监控信息为HTTP
management:
  endpoints:
    enabled-by-default: true #暴露所有端点信息
    web:
      exposure:
        include: '*'  #以web方式暴露

测试

http://localhost:8080/actuator/beans

http://localhost:8080/actuator/configprops

http://localhost:8080/actuator/metrics

8.1.4 可视化

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

8.2 Actuator Endpoint

8.2.1 常使用的端点

ID

描述

auditevents

暴露当前应用程序的审核事件信息。需要一个AuditEventRepository组件。

beans

显示应用程序中所有Spring Bean的完整列表。

caches

暴露可用的缓存。

conditions

显示自动配置的所有条件信息,包括匹配或不匹配的原因。

configprops

显示所有@ConfigurationProperties。

env

暴露Spring的属性ConfigurableEnvironment

flyway

显示已应用的所有Flyway数据库迁移。需要一个或多个Flyway组件。

health

显示应用程序运行状况信息。

httptrace

显示HTTP跟踪信息(默认情况下,最近100个HTTP请求-响应)。需要一个HttpTraceRepository组件。

info

显示应用程序信息。

integrationgraph

显示Spring integrationgraph 。需要依赖spring-integration-core。

loggers

显示和修改应用程序中日志的配置。

liquibase

显示已应用的所有Liquibase数据库迁移。需要一个或多个Liquibase组件。

metrics

显示当前应用程序的“指标”信息。

mappings

显示所有@RequestMapping路径列表。

scheduledtasks

显示应用程序中的计划任务。

sessions

允许从Spring Session支持的会话存储中检索和删除用户会话。需要使用Spring Session的基于Servlet的Web应用程序。

shutdown

使应用程序正常关闭。默认禁用。

startup

显示由ApplicationStartup收集的启动步骤数据。需要使用SpringApplication进行配置BufferingApplicationStartup。

threaddump

执行线程转储。

如果您的应用程序是Web应用程序(Spring MVC,Spring WebFlux或Jersey),则可以使用以下附加端点:

ID

描述

heapdump

返回hprof堆转储文件。

jolokia

通过HTTP暴露JMX bean(需要引入Jolokia,不适用于WebFlux)。需要引入依赖jolokia-core。

logfile

返回日志文件的内容(如果已设置logging.file.name或logging.file.path属性)。支持使用HTTPRange标头来检索部分日志文件的内容。

prometheus

以Prometheus服务器可以抓取的格式公开指标。需要依赖micrometer-registry-prometheus。

最常用的Endpoint

  • Health:监控状况
  • Metrics:运行时指标
  • Loggers:日志记录

8.2.2 Health Endpoint

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

重要的几点:

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

8.2.3 Metrics Endpoint

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

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

 

8.2.4 管理Endpoints

1、开启与禁用Endpoints

  • 默认所有的Endpoint除过shutdown都是开启的。
  • 需要开启或者禁用某个Endpoint。配置模式为 management.endpoint..enabled = true
management:
  endpoint:
    beans:
      enabled: true
  • 或者禁用所有的Endpoint然后手动开启指定的Endpoint
management:
  endpoints:
    enabled-by-default: false
  endpoint:
    beans:
      enabled: true
    health:
      enabled: true

2、暴露Endpoints

支持的暴露方式

  • HTTP:默认只暴露health和info 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

8.3 定制Endpoint

8.3.1 定制Health信息

import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;

@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();
management:
    health:
      enabled: true
      show-details: always #总是显示详细信息。可显示每个模块的状态信息
@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);

    }
}

8.3.2 定制info信息

常用两种方式

1、编写配置文件

info:
  appName: boot-admin
  version: 2.0.1
  mavenProjectName: @project.artifactId@  #使用@@可以获取maven的pom文件值
  mavenProjectVersion: @project.version@

2、编写InfoContributor

import java.util.Collections;

import org.springframework.boot.actuate.info.Info;
import org.springframework.boot.actuate.info.InfoContributor;
import org.springframework.stereotype.Component;

@Component
public class ExampleInfoContributor implements InfoContributor {

    @Override
    public void contribute(Info.Builder builder) {
        builder.withDetail("example",
                Collections.singletonMap("key", "value"));
    }

}

http://localhost:8080/actuator/info 会输出以上方式返回的所有info信息

8.3.3 定制Metrics信息

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

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);
}

8.3.4 定制Endpoint

@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....");
    }

}

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

当然,这个也可以直接使用 Production-ready Features

九、原理解析

9.1 Profile功能

为了方便多环境适配,SpringBoot简化了profile功能

9.1.1 application-profile 功能

  • 默认配置文件 application.yaml;任何时候都会加载
  • 指定环境配置文件 application-{env}.yaml
  • 激活指定环境
    • 配置文件激活
    • 命令行激活:java -jar xxx.jar --spring.profiles.active=prod --person.name=haha
  • 默认配置与环境配置同时生效
  • 同名配置项,profile配置优先
java -jar boot-04-features-01-0.0.1-SNAPSHOT.jar --spring.profile
s.active=dev --person.name=王五

 

9.1.2 @Profile条件装配功能

@Configuration(proxyBeanMethods = false)
@Profile("production")
public class ProductionConfiguration {
    // ...
}

9.1.3 profile分组

spring.profiles.group.production[0]=proddb
spring.profiles.group.production[1]=prodmq

使用:--spring.profiles.active=production  激活

9.2 外部化配置

Core Features

9.2.1、外部配置源

常用:Java属性文件、YAML文件、环境变量、命令行参数;

9.2.2、配置文件查找位置

(1) classpath 根路径

(2) classpath 根路径下config目录

(3) jar包当前目录

(4) jar包当前目录的config目录

(5) /config子目录的直接子目录

9.2.3、配置文件加载顺序

  1.  当前jar包内部的application.properties和application.yml
  2.  当前jar包内部的application-{profile}.properties 和 application-{profile}.yml
  3.  引用的外部jar包的application.properties和application.yml
  4.  引用的外部jar包的application-{profile}.properties 和 application-{profile}.yml

9.2.4、指定环境优先、外部优先,后面的可以覆盖前面的同名配置项

9.3 自定义starter

9.3.1 starter启动原理

  • starter-pom引入 autoconfigurer 包

  • autoconfigure包中配置使用 META-INF/spring.factories 中 EnableAutoConfiguration 的值,使得项目启动加载指定的自动配置类
  • 编写自动配置类 xxxAutoConfiguration -> xxxxProperties
    • @Configuration
    • @Conditional
    • @EnableConfigurationProperties
    • @Bean
    • ......

引入starter --- xxxAutoConfiguration --- 容器中放入组件 ---- 绑定xxxProperties ---- 配置项

9.3.2 自定义starter

atguigu-hello-spring-boot-starter(启动器)

atguigu-hello-spring-boot-starter-autoconfigure(自动配置包)

9.4 SpringBoot原理

Spring原理【Spring注解】、SpringMVC原理、自动配置原理、SpringBoot原理

9.4.1 SpringBoot启动过程

  • 创建 SpringApplication
    • 保存一些信息。
    • 判定当前应用的类型:ClassUtils\Servlet
    • 找bootstrappers:初始启动引导器(List):去spring.factories文件中找 org.springframework.boot.Bootstrapper
    • 找 ApplicationContextInitializer:去spring.factories找 ApplicationContextInitializer
    • 找 ApplicationListener 应用监听器:去spring.factories找 ApplicationListener
  • 运行 SpringApplication
    • 记录应用的启动时间
    • 创建引导上下文createBootstrapContext():获取到所有之前的 bootstrappers 挨个执行 intitialize() 来完成对引导启动器上下文环境设置
    • 让当前应用进入headless模式:java.awt.headless
    • 获取所有 SpringApplicationRunListener(运行监听器):getSpringFactoriesInstances 去spring.factories找 SpringApplicationRunListener
    • 遍历 SpringApplicationRunListener 调用 starting 方法:通知所有感兴趣系统正在启动过程的人,项目正在 starting。
    • 保存命令行参数:ApplicationArguments
    • 准备环境 prepareEnvironment()
      • 返回或者创建基础环境信息对象。StandardServletEnvironment
      • 配置环境信息对象。读取所有的配置源的配置属性值。
      • 绑定环境信息
      • 监听器调用 listener.environmentPrepared();通知所有的监听器当前环境准备完成
    • 创建IOC容器(createApplicationContext()):根据项目类型创建容器
    • 准备ApplicationContext IOC容器的基本信息 prepareContext()
      • 保存环境信息
      • IOC容器的后置处理流程。
      • 应用初始化器;applyInitializers
        • 遍历所有的 ApplicationContextInitializer :调用 initialize,来对ioc容器进行初始化扩展功能
        • 遍历所有的 listener 调用 contextPrepared。EventPublishRunListenr;通知所有的监听器contextPrepared
      • 所有的监听器 调用 contextLoaded。通知所有的监听器 contextLoaded;
    • 刷新IOC容器:refreshContext创建容器中的所有组件(Spring注解)
    • 容器刷新完成后工作:afterRefresh
    • 所有监听器调用 listeners.started(context); 通知所有的监听器 started
    • 调用所有runners:callRunners()
      • 获取容器中的ApplicationRunner
      • 获取容器中的 CommandLineRunner
      • 合并所有runner并且按照@Order进行排序
      • 遍历所有的runner。调用 run方法
    • 如果以上有异常,调用Listener 的 failed
    • 调用所有监听器的 running 方法 listeners.running(context); 通知所有的监听器 running
    • running如果有问题,继续通知 failed ,调用所有 Listener 的 failed,通知所有的监听器 failed

9.4.2 Application Events and Listeners

Core Features

ApplicationContextInitializer

ApplicationListener

SpringApplicationRunListener

End

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值