SpringBoot笔记


一、SpringBoot基础
1、什么是SpringBoot
为了简化Spring繁琐的配置,快速创建出生产级别的Spring应用的快速开发脚手架。

2、SpringBoot优缺点
优点:

创建独立 Spring 应用

内嵌 Web 服务器(默认使用 Tomcat)

自动starter依赖,简化构建配置

自动配置 Spring 以及第三方功能

提供生产级别的监控、健康检查及外部化配置

无代码生成、无需编写 XML

缺点:

版本迭代快,需要时刻关注变化

封装太深,内部原理复杂,不容易精通

3、微服务与分布式
微服务

James Lewis and Martin Fowler (2014) 提出微服务完整概念。

In short, the microservice architectural style is an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms, often an HTTP resource API. These services are built around business capabilities and independently deployable by fully automated deployment machinery. There is a bare minimum of centralized management of these services, which may be written in different programming languages and use different data storage technologies.-- [James Lewis and Martin Fowler (2014)](

微服务是一种架构风格

一个应用拆分为一组小型服务

每个服务运行在自己的进程内,也就是可独立部署和升级

服务之间使用轻量级 HTTP 交互

服务围绕业务功能拆分

可以由全自动部署机制独立部署

去中心化,服务自治。服务可以使用不同的语言、不同的存储技术

分布式

将大型应用拆分成多个小型服务势必会产生分布式,这也有就带来了响应的问题:

配置管理

服务发现

远程调用

负载均衡

服务容错

服务监控

链路追踪

日志管理

任务调度

分布式的解决: SpringBoot + SpringCloud

4、系统要求
针对于Spring Boot 2.6.5版本

Java 8

Maven 3.5+

因为Spring Boot版本迭代快,不同版本对系统要求不一致,尤其是Maven版本,具体版本要求参见官网。

5、Hello SpringBoot
创建默认的Maven项目

image-20220329164733919

添加依赖

spring-boot-starter-parent 是Spring Boot专用启动器,提供有用的Maven默认值。它还提供了一个依赖管理部分,以便省略版本标签以获取对应依赖项(Spring Boot的其他依赖不需要注明版本号)。

image-20220329165756986

tree命令打印项目依赖项的树表示。可以看到Spring-Boot-Starter-Parent本身不提供依赖项。

加入spring-boot-starter-web依赖

因为需要使用Spring Boot创建一个Web项目,因此在pom.xml中添加Spring Boot的web模块依赖。

org.springframework.boot spring-boot-starter-web 再次查看Maven已经引入了logback、tomcat、spring等相关依赖。

image-20220329171444554

编写代码

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@EnableAutoConfiguration
public class MyApplication {

@RequestMapping("/")
String home() {
return "Hello World!";
}

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

}
@RestController和@RequestMapping注解是SpringMVC的注解。

@EnableAutoConfiguration注解作用是:根据添加的jar依赖项猜测如何配置Spring。因为Spring -boot-start -web添加了Tomcat和Spring MVC,所以自动配置成一个web应用程序,并相应地设置Spring。

Main方法通过调用run来委托Spring Boot的SpringApplication类。SpringApplication启动应用程序,而Spring又启动自动配置的Tomcat web服务器。我们需要将MyApplication.class作为参数传递给run方法,以告诉SpringApplication哪个组件是Spring的主组件。args数组也被传递来公开任何命令行参。

运行:控制台

$ mvn spring-boot:run

. ____ _ __ _ _
/\ / ' __ _ () __ __ _ \ \ \
( ( )_
_ | '_ | '| | ' / ` | \ \ \
\/ )| |)| | | | | || (| | ) ) ) )
' |
| .__|| ||| |_, | / / / /
=========|
|==============|/=////
:: Spring Boot :: (v2.6.5)
....... . . .
....... . . . (log output here)
....... . . .
........ Started MyApplication in 2.222 seconds (JVM running for 6.514)

浏览器中访问http://localhost:8080/ 后页面显示“Hello World!”,SpringBoot项目创建成功。

打包部署【非必须】

要创建一个可执行的jar包,需要将spring-boot-maven-plugin添加到pom.xml中。

org.springframework.boot spring-boot-maven-plugin 在项目根目录下,使用mvn package命令将项目打成jar包

PS E:\IDEA_Projects\SpringBootHelloWorld> mvn package
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< com.th:SpringBootHelloWorld >---------------------
[INFO] Building SpringBootHelloWorld 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
......
[INFO] Building jar: E:\IDEA_Projects\SpringBootHelloWorld\target\SpringBootHelloWorld-1.0-SNAPSHOT.jar
[INFO] --- spring-boot-maven-plugin:2.6.5:repackage (repackage) @ SpringBootHelloWorld ---
[INFO] Replacing main artifact with repackaged archive
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 19.707 s
[INFO] Finished at: 2022-03-29T17:35:23+08:00
此时在项目的target目录下会生成一个SpringBootHelloWorld-1.0-SNAPSHOT.jar文件和SpringBootHelloWorld-1.0-SNAPSHOT.jar.original文件。而这个SpringBootHelloWorld-1.0-SNAPSHOT.jar.original是Maven在Spring Boot重新打包之前创建的原始jar包。

image-20220329174007409

使用java -jar命令运行jar包

PS E:\IDEA_Projects\SpringBootHelloWorld\target> java -jar .\SpringBootHelloWorld-1.0-SNAPSHOT.jar

. ____ _ __ _ _
/\ / ' __ _ () __ __ _ \ \ \
( ( )_
_ | '_ | '| | ' / ` | \ \ \
\/ )| |)| | | | | || (| | ) ) ) )
' |
| .__|| ||| |_, | / / / /
=========|
|==============|/=////
:: Spring Boot :: (v2.6.5)

同样可以访问http://localhost:8080/ 并显示"Hello World!"。

小彩蛋

项目启动时控制台会打印“Spring”,这个输出内容是可以定制,只需要在resource资源目录下存放一个banner.txt文件,这个文件保存ASCII文字可在BootSchool网站定制并下载,然后拷贝到resource资源目录下即可。

[INFO] Attaching agents: []


|__ | | | __
| | | |
_ __ ___ | |) | _ _ __ ___
| | | '
| '/ _ / _ \ / | | | '/ _
| | | | | | | | __/ __/ | | |
| | | | /
|| || ||| _
|_
|| _,|| ___|
6、开发技巧
lombok简化开发
简化实体类开发

简化debug

@Slf4j
@RestController
public class HelloController {
@RequestMapping("/hello")
public String handle01(@RequestParam("name") String name){
log.info("请求进来了....");
return "Hello, Spring Boot 2!"+"你好:"+name;
}
}
dev-tools 自动重启

org.springframework.boot
spring-boot-devtools
true

修改代码后使用 Ctrl+F9 重新构建项目,类似于重写部署。

Spring Initailizr(项目初始化向导)
Idea集成的一个简化创建SpringBoot项目的工具,创建过程中可选择需要的模块,Spring Initailizr会自动引入相关依赖,并配置相关功能。

二、注解开发
1、组件添加
@Configuration 【来源Spring】
@Configuration:是Spring注解,用于类表示这个类是一个配置类。

与SpringBoot 1不同的是,在SpringBoot2中,这个注解有了一个新的属性proxyBeanMethods(),表示代理bean的方法。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
@AliasFor(
annotation = Component.class
)
String value() default "";

boolean proxyBeanMethods() default true;
}
Full(proxyBeanMethods = true)(保证每个@Bean方法被调用多少次返回的组件都是单实例的)(默认)

Lite(proxyBeanMethods = false)(每个@Bean方法被调用多少次返回的组件都是新创建的)

最佳实战

配置类组件之间无依赖关系(类中属性为一个类的对象)用Lite模式加速容器启动过程,减少判断。

配置类组件之间有依赖关系,方法会被调用得到之前单实例组件,用Full模式(默认)。

@Bean
@Component
@Controller
@Service
@Repository
@ComponentScan
@Import 【均来源Spring】
@Conditional
条件装配:满足Conditional指定的条件,则进行组件注入。@Conditional是一个根注解,针对不同情况有不同的条件注解:

image-20220330174914218

这些条件装配注解作用于类或者方法,当作用于类时表示该注解条件满足时,该类内所有组件进行装配,否则不装配;当作用于方法时,只有该注解条件满足时,这个方法返回的组件才会被注册到容器中,否则不装配。

@ConditionalOnBean:容器中有某个bean时。

@ConditionalOnMissingBean:容器中没有某个bean时。

2、原生配置文件引入
@ImportResource
将原生Spring配置文件中对象引入到SpringBoot项目中。












在配置类上使用注解将以上配置文件中th、dog对象导入

@Configuration
@ImportResource("classpath:beans.xml")
public class MyConfig {

@Bean
public User user(){
return new User("ThreePure", 18);
}

@Bean
public Pet pet(){
return new Pet();
}
}
测试类:

public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(MyApplication.class, args);

boolean user = run.containsBean("user");
System.out.println("user:"+user);
boolean pet = run.containsBean("pet");
System.out.println("pet:"+pet);
boolean th = run.containsBean("th");
System.out.println("th:"+th);
boolean dog = run.containsBean("dog");
System.out.println("dog:"+dog);

}

=打印输出=
user:true
pet:true
th:true
dog:true
3、配置绑定
如何使用Java读取到properties文件中的内容,并且把它封装到JavaBean中,以供随时使用、传统方法:

enum
public class getProperties {
public static void main(String[] args) throws FileNotFoundException, IOException {
Properties pps = new Properties();
pps.load(new FileInputStream("a.properties"));
Enumeration enum1 = pps.propertyNames();//得到配置文件的名字
while(enum1.hasMoreElements()) {
String strKey = (String) enum1.nextElement();
String strValue = pps.getProperty(strKey);
System.out.println(strKey + "=" + strValue);
//封装到JavaBean。
}
}
}
在SpringBoot中有两种配置配置绑定方案:

@ConfigurationProperties + @Component

通过将实体类注入到容器,以及为实体类配置properties文件前缀,SpringBoot将properties文件中配置进行绑定。

@Component
@ConfigurationProperties(prefix = "mycar")
public class Car {
...
}
在properties文件(application.properties)中设置对象值,前缀就是实体类中配置的前缀mycar:

mycar.brand = BYD
mycar.price = 100000
通过容器获取的Car对象将会有 Car{brand='BYD', price=100000} 属性。

@EnableConfigurationProperties + @ConfigurationProperties

实体类:

@ConfigurationProperties(prefix = "mycar")
public class Car {
...
}
配置类:

@Configuration
@EnableConfigurationProperties(Car.class)
public class MyConfig {
}
@EnableConfigurationProperties(Car.class)注解的作用是:

开启Car配置绑定功能。

把这个Car这个组件自动注册到容器中 ,所以实体类中并不需要@Component注解。

【注意】与@configurationProperties类似的注解还有@PropertySource。

@PropertySource :加载指定的配置文件;

对于Person类的对应的person.properties配置文件可以使用@PropertySource(value = "classpath:person.properties")引入。

@configurationProperties:默认从全局配置文件中获取值;

三、自动装配原理
1、依赖管理
1.1 版本仲裁机制
创建的SpringBoot项目均依赖一个父项目spring-boot-starter-parent,主要是负责资源过滤及插件管理!

org.springframework.boot spring-boot-starter-parent 2.6.5 而spring-boot-starter-parent又依赖于它的父项目spring-boot-dependencies,是SpringBoot的版本控制中心。 org.springframework.boot spring-boot-dependencies 2.6.5 spring-boot-dependencies几乎声明了所有开发中常用的依赖的版本号,其子项目均会默认使用规定的版本。这就是自动版本仲裁机制。因此导入依赖默认是不需要写版本;但是如果导入的包没有在依赖中管理,或者使用不同版本依赖就需要手动配置版本了,如在该版本中默认的mysql为8.0.28,但是实际开发使用的数据库为5.7,那么需要手动配置;方法如下:

查看spring-boot-dependencies里面规定当前依赖的版本用的 key。

在当前项目里面重写配置,如下面的代码。

5.1.43 1.2 starter场景启动器 springboot-boot-starter-xxx:就是SpringBoot官方的场景启动器。

SpringBoot创建的web项目依赖于spring-boot-starter-web,其导入了web模块正常运行所依赖的组件;spring-boot-starter-web父项目spring-boot-starter。spring-boot-starter所有场景启动器最底层的依赖。

org.springframework.boot spring-boot-starter 2.6.5 compile SpringBoot将所有的功能场景都抽取出来,做成一个个的starter(启动器),只需要在项目中引入这些starter即可,所有相关的依赖都会导入进来 , 我们要用什么功能就导入什么样的场景启动器即可 ;

也可以引入第三方自定义场景启动器,第三方场景启动器往往是*-spring-boot-starter。

更多SpringBoot所有支持的场景

2、自动配置
如根据spring-boot-starter-web配置相关:

自动配置相关依赖或场景启动器,如自动配置Tomcat场景启动器。

org.springframework.boot spring-boot-starter-tomcat 2.6.5 compile 自动配好SpringMVC

引入SpringMVC全套组件

自动配好SpringMVC常用组件(功能)

自动配好Web常见功能,如:字符编码问题

public static void main(String[] args) {
//1、返回我们IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MyApplication.class, args);
//2、查看容器里面的组件
String[] names = run.getBeanDefinitionNames();
for (String name : names) {
System.out.println("【Bean】"+name);
}
}
通过获取上下文对象,获取其全部组件信息,SpringBoot已经创建了web相关的组件:dispatcherServlet、mvcViewResolver、multipartResolver、characterEncodingFilter等其他组件。

image-20220330091415136

默认的包结构

主程序所在包及其下面的所有子包里面的组件都会被默认扫描进来

无需以前的包扫描配置

可自定义扫描路径

各种配置拥有默认值

默认配置最终都是映射到某个类上,如:MultipartProperties

配置文件的值最终会绑定每个类上,这个类会在容器中创建对象

按需加载所有自动配置项

非常多的starter

引入了哪些场景这个场景的自动配置才会开启

SpringBoot所有的自动配置功能都在 spring-boot-autoconfigure包里面

3、自动配置原理
SpringBoot自动配置根据添加的jar依赖项自动配置Spring应用程序,选择性在@Configuration类中添加@EnableAutoConfiguration或@SpringBootApplication注释来选择自动配置。@SpringBootApplication源码如下:

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
……
}
@EnableAutoConfiguration: 启用Spring Boot的自动配置机制。

@ComponentScan: 在应用程序所在的包(Main方法所在包同级目录下的所有包)上启用组件扫描

@SpringBootConfiguration: 允许在上下文中注册额外的bean或导入额外的配置类。

由此一个@SpringBootApplication注释来等同于@EnableAutoConfiguration、@ComponentScan、@SpringBootConfiguration这三个注解。分析这三个注解对应功能了解自动配置原理。

@ComponentScan
扫描组件,无其他相关自动配置功能。

@SpringBootConfiguration
@Configuration
@Indexed
public @interface SpringBootConfiguration {
@AliasFor(
annotation = Configuration.class
)
boolean proxyBeanMethods() default true;
}
被@Configuration标识,表示其本身也是一个配置类。

@EnableAutoConfiguration [重点]
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

Class<?>[] exclude() default {};

String[] excludeName() default {};
}
包含了两个相关的注解@AutoConfigurationPackage,@Import({AutoConfigurationImportSelector.class})。

@AutoConfigurationPackage 自动包规则原理
@Import(AutoConfigurationPackages.Registrar.class) ////给容器中导入一个组件
public @interface AutoConfigurationPackage {
String[] basePackages() default {};

Class<?>[] basePackageClasses() default {};
}
其中@AutoConfigurationPackage。完成自动配置包并指定了默认的包规则,这也就是SpringBoot的自动包规则原理。

通过查看Registrar.class源码,它有一个方法registerBeanDefinitions,这个方法的功能是:利用Registrar给容器中导入一系列组件。

public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
}
image-20220331175347689

metadata:表示的是标识这个注解的位置,也就是主类,最后通过

new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames()方法获取主类所在的包路径下。这就解释SpringBoot默认的包路径是主类所在的包下。最后通过register()方法将这个包下所有组件导入。

@Import({AutoConfigurationImportSelector.class})
在AutoConfigurationImportSelector.class中,

利用getAutoConfigurationEntry(annotationMetadata)给容器中批量导入一些组件.

调用List configurations = getCandidateConfigurations(annotationMetadata, attributes)获取到所有需要导入到容器中的配置类

利用工厂加载 Map<String, List> loadSpringFactories(@Nullable ClassLoader classLoader);得到所有的组件。

从META-INF/spring.factories位置来加载一个文件。

默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件

spring-boot-autoconfigure-*.jar包里面也有META-INF/spring.factories,而这个文件就写死了SpringBoot一启动就要给容器中加载的所有配置类。

按需开启自动配置项
虽然场景的所有自动配置启动的时候默认全部加载,但是xxxxAutoConfiguration按照条件装配规则(通过@Conditional),最终会按需配置,如:AopAutoConfiguration类:

@Configuration(
proxyBeanMethods = false
)
@ConditionalOnProperty(
prefix = "spring.aop",
name = "auto",
havingValue = "true",
matchIfMissing = true
)
public class AopAutoConfiguration {
public AopAutoConfiguration() {
}
...
}
【面试题_SpringBoot的自动配置原理?】

image-20220525102148044

SpringBootConfiguration相当于@Configuration,表示一个配置类。

@ComponentScan定义扫描路径。

@EnableAutoConfiguration表示启用Spring Boot的自动配置机制。

@AutoConfigurationPackage。完成自动配置包并指定了默认的包规则。

Import注解导入的Registrar可以获取标识@SpringBootApplication注解的位置,从而获取包路径。

@Import(AutoConfigurationImportSelector.class)自动配置核心。

AutoConfigurationImportSelector完成对组件注册

自动配置类由各个starter提供,使用@Configuration+@Bean定义配置类,放到META-lNF/spring.factories下扫描META-INF/spring.factories下的配置类
使用@Import导入自动配置类
4、自动配置流程
SpringBoot先加载所有的自动配置类 xxxxxAutoConfiguration。

每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。(xxxxProperties里面读取,xxxProperties和配置文件进行了绑定)。

生效的配置类就会给容器中装配很多组件,只要容器中有这些组件,相当于这些功能就有了。

定制化配置

用户直接自己@Bean替换底层的组件。

用户去看这个组件是获取的配置文件什么值就去修改。 server.servlet.encoding.charset=UTF-8

xxxxxAutoConfiguration---> 组件 ---> xxxxProperties里面拿值 ----> application.properties

SpringBoot默认会在底层配好所有的组件,但是如果用户自己配置了以用户的优先。

5、最佳实战
引入场景依赖 官方文档

查看自动配置了哪些(选做)

自己分析,引入场景对应的自动配置一般都生效了

配置文件中添加debug=true开启自动配置报告,在控制台中 Negative表示不生效组件,Positive表示生效组件。

是否需要修改

参照文档修改配置项

查看 官方文档

分析xxxxProperties绑定了配置文件的哪些。

自定义加入或者替换组件

@Bean、@Component...

自定义器 XXXXXCustomizer;

四、配置文件
SpringBoot使用一个全局的配置文件 , 配置文件名称是固定的。

application.properties

语法结构 :key=value

spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=100MB
application.yml 这种语言以数据作为中心,而不是以标记语言为重点!

语法结构 :key:空格 value

spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/xiaomissm?useSSL=true&useUnicode=true&characterEncoding=UTF-8
username: root
password: mysqlpw
配置文件的作用 :修改SpringBoot自动配置的默认值,因为SpringBoot在底层都给我们自动配置好了;

yaml / yml配置文件
1、基本语法
key: value;k v之间有空格

大小写敏感

使用缩进表示层级关系

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

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

'#'表示注释

字符串无需加引号,如果要加,单引号''、双引号""表示字符串内容会被转义、不转义

“ ” 双引号,不会转义字符串里面的特殊字符 , 特殊字符会作为本身想表示的意思;

比如 :name: "T \n H" 输出 :T 换行 H

'' 单引号,会转义特殊字符 , 特殊字符最终会变成和普通字符一样输出

比如 :name: ‘T \n H’ 输出 :T \n H

2、数据类型
字面量:单个的、不可再分的值。date、boolean、string、number、null

k: v
对象:键值对的集合。map、hash、set、object

行内写法:

k: {k1:v1,k2:v2,k3:v3}

k:
k1: v1
k2: v2
k3: v3
数组:一组按次序排列的值。array、list、queue

行内写法:

k: [v1,v2,v3]

或者

k:

  • v1
  • v2
  • v3
    3、实例
    实体类

@Component //注册bean
@ConfigurationProperties(prefix = "person")
public class Person {
private String userName;
private Boolean boss;
private Date birth;
private Integer age;
private Pet pet;
private String[] interests;
private List animal;
private Map<String, Object> score;
private Set salarys;
private Map<String, List> allPets;
//有参无参构造、get、set方法、toString()方法
}

@Component
public class Pet {
private String name;
private Double weight;
//有参无参构造、get、set方法、toString()方法
}
用yaml配置以上对象

person:
userName: zhangsan
boss: false
birth: 2019/12/12 20:12:33
age: 18
pet:
name: tomcat
weight: 23.4
interests: [篮球,游泳]
animal:
- jerry
- mario
score:
english:
first: 30
second: 40
third: 50
math: [131,140,148]
chinese: {first: 128,second: 136}
salarys: [3999,4999.98,5999.99]
allPets:
sick:
- {name: tom}
- {name: jerry,weight: 47}
health: [{name: mario,weight: 47}]
4、配置提示
自定义的类和配置文件绑定一般没有提示,为了能够提示,可以通过spring-boot-configuration-processor进行配置,需要导入依赖:

org.springframework.boot spring-boot-configuration-processor true spring-boot-configuration-processor在实际项目运行中并没有用,所以可以在打包时可以将其移除: org.springframework.boot spring-boot-maven-plugin org.springframework.boot spring-boot-configuration-processor 通过配置提示,发现对于形如lastName含有大写字母的字符,提示会显示last-name,这种用-小写字母表示大写字母就是松散绑定。

5、yaml占位符
在yaml文件中,可以使用占位符为属性赋值:

person:
name: qinjiang${random.uuid} # 随机uuid
age: ${random.int} # 随机int
dog:
name: ${person.hello:other}_旺财
age: 1
6、@ConfigurationProperties与@Value
@ConfigurationProperties @Value
功能 批量注入配置文件的属性 一个一个指定属性值
松散绑定 支持 不支持
SpEL 不支持 支持
JSR303数据校验 支持 不支持
复杂类型封装 支持 不支持
1、@ConfigurationProperties只需要写一次即可 ,@Value则需要每个字段都添加。

2、松散绑定: yml中的last-name,对于实体类lastName属性, - 后面跟着的字母默认是大写的。这就是松散绑定。

3、JSR303数据校验 , 这个就是我们可以在字段是增加一层过滤器验证 , 可以保证数据的合法性。

4、复杂类型封装,yml中可以封装对象 , 使用value就不支持。

7、JSR303数据校验
JSR303数据校验类似于表单提交中只能限制输入内容为邮件格式等,在注入属性的时候要求其注入的值为被要求的值。Springboot中可以用@validated来校验数据,如果数据异常则会统一抛出异常,方便异常中心统一处理。我们这里来写个注解让我们的name只能支持Email格式;

@Component //注册bean
@ConfigurationProperties(prefix = "person")
@Validated //数据校验
public class Person {
@Email(message="邮箱格式错误") //name必须是邮箱格式
private String name;
}
在数据校验中,除了@Email之外,还有以下校验格式:

@NotNull(message="名字不能为空")
private String userName;
@Max(value=120,message="年龄最大不能查过120")
private int age;
@Email(message="邮箱格式错误")
private String email;

空检查
@Null 验证对象是否为null
@NotNull 验证对象是否不为null, 无法查检长度为0的字符串
@NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
@NotEmpty 检查约束元素是否为NULL或者是EMPTY.

Booelan检查
@AssertTrue 验证 Boolean 对象是否为 true
@AssertFalse 验证 Boolean 对象是否为 false

长度检查
@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内
@Length(min=, max=) string is between min and max included.

日期检查
@Past 验证 Date 和 Calendar 对象是否在当前时间之前
@Future 验证 Date 和 Calendar 对象是否在当前时间之后
@Pattern 验证 String 对象是否符合正则表达式的规则
8、多环境切换
在现实开发中,往往开发过程中需要使用不同的环境,比如开发环境,测试环境以及最终的线上环境,SpringBoot允许配置不同的环境并且支持环境的切换。

多配置文件 文件名可以是 application-{profile}.properties/yml

application-test.properties 代表测试环境配置

application-dev.properties 代表开发环境配置

但是Springboot并不会直接启动这些配置文件,它默认使用application.properties主配置文件/yml;我们需要通过一个配置来选择需要激活的环境:

spring.profiles.active=dev
yaml多文件块

properties配置文件要求每一个环境都是一个properties文件,而在yaml中允许通过文件块的形式,将多个环境写在同一个yaml文件中:

server:
port: 8081

选择要激活那个环境块

spring:
profiles:
active: prod # 使用prod环境


server:
port: 8083
spring:
profiles: dev #配置环境(dev)的名称


server:
port: 8084
spring:
profiles: prod #配置环境(prod)的名称
在yaml文件块中,使用---分割每一个配置文件块,通过profiles来配置环境名称,通过这个环境名称就可以在profiles.active被指定。

9、配置文件加载位置
SpringBoot允许配置文件放于:

file:./config/ 优先级1

file:./ 优先级2

classpath:/config/ 优先级3

classpath:/ 优先级4

优先级由高到底,高优先级的配置会覆盖低优先级的配置;SpringBoot会从这四个位置全部加载主配置文件;互补配置;

五、Web开发
1、简单功能分析
1、静态资源访问
静态资源目录

默认路径【类路径下】:called /static (or /public or /resources or /META-INF/resources

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

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

改变默认的静态资源路径:

spring:
web:
resources:
static-locations: [classpath:/hh/]

访问http://localhost:8080/img.png 【不需要hh/】

修改默认静态资源目录后默认的所有静态资源目录将会失效。

静态资源访问前缀 :所有访问静态资源名前加res。

spring:
mvc:
static-path-pattern: /res/**

访问:当前项目 + static-path-pattern + 静态资源名 = 静态资源文件夹下找

http://localhost:8080/res/img.png

webjar : 以jar方式添加css,js等资源文件

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

2、欢迎页支持
Spring Boot支持静态html和模板化 (controller能处理/index) 的欢迎页面。它首先在配置的静态内容位置中查找index.html文件。如果没有找到,它就会查找索引模板。如果找到其中任何一个,则自动将其用作应用程序的欢迎页面。

【配置静态资源访问前缀会导致欢迎页功能失效】

3、自定义Favicon
favicon.ico 放在静态资源目录下即可。

【配置静态资源访问前缀会导致网站图标功能失效】

4、静态资源配置原理
2、请求参数处理
1、RESTful映射及源码解析
基础RESTful内容参见SpringMVC笔记。

用法

在SpringBoot中开启RESTful功能

spring:
mvc:
hiddenmethod:
filter:
enabled: true
开启页面表单的Rest功能

页面 form的属性method=post,隐藏域 _method=put、delete等(如果直接get或post,无需隐藏域)

编写请求映射

Rest原理(表单提交要使用REST的时候)

表单提交会带上_method=PUT

请求过来被HiddenHttpMethodFilter拦截

首先请求是否正常,并且是POST请求

获取到_method的值。

兼容以下请求;PUT、DELETE、PATCH

原生request(post),包装模式requesWrapper重写了getMethod方法,返回的是传入的值。

过滤器链放行的时候用wrapper。以后的方法调用getMethod是调用requesWrapper的。

public class HiddenHttpMethodFilter extends OncePerRequestFilter {
private static final List ALLOWED_METHODS;
public static final String DEFAULT_METHOD_PARAM = "_method";
……

protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    HttpServletRequest requestToUse = request;
    if ("POST".equals(request.getMethod()) && request.getAttribute("javax.servlet.error.exception") == null) {
        String paramValue = request.getParameter(this.methodParam);
        if (StringUtils.hasLength(paramValue)) {
            String method = paramValue.toUpperCase(Locale.ENGLISH);
            if (ALLOWED_METHODS.contains(method)) {
                requestToUse = new HiddenHttpMethodFilter.HttpMethodRequestWrapper(request, method);
            }
        }
    }
    filterChain.doFilter((ServletRequest)requestToUse, response);
}

……
private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {
private final String method;
public HttpMethodRequestWrapper(HttpServletRequest request, String method) {
super(request);
this.method = method;
}
public String getMethod() {
return this.method;
}
}
}
Rest使用客户端工具。

如PostMan可直接发送put、delete等方式请求。

更改默认的_method

根据源码@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)意味着在没有HiddenHttpMethodFilter时,才执行hiddenHttpMethodFilter()。因此,我们可以自定义filter,改变默认的_method。例如:

@Configuration(proxyBeanMethods = false)
public class WebConfig{
//自定义filter
@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
methodFilter.setMethodParam("_m");
return methodFilter;
}
}
将_method改成_m。

请求映射原理

所有的请求映射都在HandlerMapping中:

SpringBoot自动配置欢迎页的 WelcomePageHandlerMapping 。访问 /能访问到index.html;

SpringBoot自动配置了默认 的 RequestMappingHandlerMapping

请求进来,挨个尝试所有的HandlerMapping看是否有请求信息。

如果有就找到这个请求对应的handler

如果没有就是下一个 HandlerMapping

我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping。自定义 HandlerMapping

2、普通参数与基本注解
注解:

@PathVariable 路径变量

@RequestHeader 获取请求头

@RequestParam 获取请求参数(指问号后的参数,url?a=1&b=2)

@CookieValue 获取Cookie值

@RequestBody 获取请求体[POST] 【以上注解参见SpringMVC笔记】

@RequestAttribute 获取request域属性

@GetMapping("/goto")
public String goToPage(HttpServletRequest request){

request.setAttribute("msg","成功了...");
request.setAttribute("code",200);
return "forward:/success";  //转发到  /success请求

}

//===================================================
@ResponseBody
@GetMapping("/success")
public Map success(@RequestAttribute(value = "msg",required = false) String msg,
@RequestAttribute(value = "code",required = false)Integer code,
HttpServletRequest request){
Object msg1 = request.getAttribute("msg");
Map<String,Object> map = new HashMap<>();

    map.put("reqMethod_msg",msg1);
    map.put("annotation_msg",msg);
    map.put("annotation_code",code);
    return map;
}

@MatrixVariable 矩阵变量

什么是MatrixVariable矩阵变量

queryString请求方式
/request?username=admin&password=123456&age=20

rest风格请求
/request/admin/123456/20

MatrixVariable矩阵变量
/request;username=admin;password=123456;age=20
矩阵变量可以理解是像Rest风格请求外的另一种请求风格,严格来说矩阵变量的请求需要用到rest风格但是又不同于rest。

使用前注意点

SpringBoot默认是禁用了矩阵变量的功能。

矩阵变量必须有url路径变量才能被解析。

手动开启矩阵变量:

手动开启:原理。对于路径的处理。UrlPathHelper的removeSemicolonContent设置为false,让其支持矩阵变量的。

【方式一】配置类中创建返回WebMvcConfigurer的Bean:

@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
// 不移除;后面的内容。矩阵变量功能就可以生效
urlPathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(urlPathHelper);
}
};
}
【方式二】实现WebMvcConfigurer接口:

@Configuration(proxyBeanMethods = false)
public class WebConfig implements WebMvcConfigurer {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {

    UrlPathHelper urlPathHelper = new UrlPathHelper();
    // 不移除;后面的内容。矩阵变量功能就可以生效
    urlPathHelper.setRemoveSemicolonContent(false);
    configurer.setUrlPathHelper(urlPathHelper);
}

}
使用案例

///cars/sell;low=34;brand=byd,audi,yd
@GetMapping("/cars/{path}")
public Map carsSell(@MatrixVariable("low") Integer low,
@MatrixVariable("brand") List brand,
@PathVariable("path") String path){
Map<String,Object> map = new HashMap<>();

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

}

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

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

}
//=效果===
// http://localhost:8080/cars/sell;low=34;brand=byd,audi,yd
{
"path": "sell",
"low": 34,
"brand": [
"byd",
"audi",
"yd"
]
}

// http://localhost:8080/boss/1;age=20/2;age=10
{
"bossAge": 20,
"empAge": 10
}
pathvar属性: 当多级路径中出现了相同的参数可以用pathvar指定绑定哪一级路径,然后再获取该级下的参数值。

@ModelAttribute

3、参数处理原理
3、数据响应与内容协商
image-20220404175036660

1、响应JSON
2、内容协商
根据客户端接收能力不同,返回不同媒体类型的数据。

4、视图解析与模板引擎
1、视图解析
image-20220404180547336

SpringBoot默认打包方式为Jar包,而JSP不支持在Jar包中编译,所以SpringBoot默认不支持JSP,因此需要引入第三方模板引擎实现页面渲染。

2、模板引擎-Thymeleaf
Thymeleaf是一个现代化的、服务端的Java模板引擎,其语法简单,缺点是它并不是一个高性能引擎,对于高并发应用并不适合。主要用于一个单体应用。

基本语法
表达式

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

文本值: 'one text' , 'Another one!' ,…

数字: 0 , 34 , 3.0 , 12.3 ,…

布尔值: true , false

空值: null

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

文本操作

字符串拼接: +

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

数学运算

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

布尔运算

运算符: and , or

一元运算: ! , not

比较运算

比较: > , < , >= , <= ( gt , lt , ge , le )

等式: == , != ( eq , ne )

条件运算

If-then: (if) ? (then)

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

Default: (value) ?: (defaultvalue)

特殊操作

无操作: _

设置属性值-th:attr
设置单个值

设置多个值


官方文档 - 5 Setting Attribute Values

迭代

Onions 2.41 yes Onions 2.41 yes 条件运算 view

User is an administrator

User is a manager

User is some other thing

3、SpringBoot使用thymeleaf 引入Starter org.springframework.boot spring-boot-starter-thymeleaf 了解thymeleaf自动配策略

@Configuration(
proxyBeanMethods = false
)
@EnableConfigurationProperties({ThymeleafProperties.class})
@ConditionalOnClass({TemplateMode.class, SpringTemplateEngine.class})
@AutoConfigureAfter({WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class})
@Import({ReactiveTemplateEngineConfiguration.class, DefaultTemplateEngineConfiguration.class})
public class ThymeleafAutoConfiguration {
……
}
在ThymeleafProperties中配置了thymeleaf的前后缀规则:

public class ThymeleafProperties {
public static final String DEFAULT_PREFIX = "classpath:/templates/";
public static final String DEFAULT_SUFFIX = ".html";
……
}
开发页面

html文件 【需要引入thymeleaf的命名空间】

Title

nice

去百度
去百度

Controller处理

@Controller
public class ThymeleafTest {
@GetMapping("/thy")
public String hello(Model model){
//model中的数据会被放在请求域中 request.setAttribute("a",aa)
model.addAttribute("msg","Hello Thymeleaf");
model.addAttribute("link","http://www.baidu.com");
return "success";
}
}
结果

image-20220418170213858

5、拦截器
1、编写拦截器HandlerInterceptor接☐
public class LoginInterceptor implements HandlerInterceptor {

/**目标方法执行前执行 */
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    //登录检查逻辑
    HttpSession session = request.getSession();
    Object loginUser = session.getAttribute("loginUser");
    if (loginUser != null){
        return true;       //放行
    }else {
        request.setAttribute("msg", "请先登录"); //拦截
        request.getRequestDispatcher("/").forward(request, response);
        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);
}

}
HandlerInterceptor接口的preHandle、postHandle、afterCompletion分别表示在目标方法执行前执行,目标方法执行后执行 、方法渲染后执行。

2、配置拦截器
配置拦截器主要进行拦截器拦截规则的制定以及将拦截器注册到容器中,将拦截器注册到容器中通过配置类实现WebMvcConfigurer接口,重写addInterceptors方法实现。

@Configuration
public class AdminWebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
//添加登录拦截器 /** 静态资源也会被拦截,注意要排除静态资源
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/")
.excludePathPatterns("/", "/login", "/css/
", "/fonts/", "/images/", "/js/");
}
}
/
规则表示拦截所有的请求,其中包括静态资源也会被拦截,注意要排除静态资源。

3、拦截器原理
根据当前请求,找到HandlerExecutionChain(可以处理请求的handler以及handler的所有拦截器)

image-20220419112412922

先来顺序执行所有拦截器的 preHandle()方法。

如果当前拦截器preHandle()返回为true。则执行下一个拦截器的preHandle()

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

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

所有拦截器都返回true,才执行目标方法。

倒序执行所有拦截器的postHandle()方法。

前面的步骤有任何异常都会直接倒序触发 afterCompletion()。

页面成功渲染完成以后,也会倒序触发 afterCompletion()。

image-20220419115627904

4、Filter、Interceptor 区别
几乎拥有相同的功能

Filter是Servlet定义的原生组件,它的好处是脱离Spring应用也能使用。

Interceptor是Spring定义的接口,可以使用Spring的自动装配等功能。

6、文件上传
一个文件上传的过程如下图所示:

image-20220419163309904

浏览器发起 HTTP POST 请求,指定请求头:
Content-Type: multipart/form-data

服务端解析请求内容,执行文件保存处理,返回成功消息。

1、页面表单

2、文件上传 MultipartFile类 @PostMapping("/upload") public String upload(@RequestParam("email") String email, @RequestParam("username") String username, @RequestPart("headerImg") MultipartFile headerImg, @RequestPart("photos") MultipartFile[] photos) throws IOException {
//上传头像
if (!headerImg.isEmpty()){
    //保存到文件服务器,OSS服务器
    String originalFilename = headerImg.getOriginalFilename();
    headerImg.transferTo(new File("E:\\IDEA_Projects\\SpringBoot_Admin\\FileServer\\"+originalFilename));
}
//上传生活照
if (photos.length > 0){
    for (MultipartFile photo: photos) {
        if (!photo.isEmpty()){
            String originalFilename = photo.getOriginalFilename();
            photo.transferTo(new File("E:\\IDEA_Projects\\SpringBoot_Admin\\FileServer\\"+originalFilename));
        }
    }
}

return "index.html";

}
3、自动配置原理
文件上传相关的配置类:

org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration

org.springframework.boot.autoconfigure.web.servlet.MultipartProperties

文件上传自动配置类 MultipartAutoConfiguration 自动配置好了文件上传解析器 StandardServletMultipartResolver(使用 Servlet 所提供的功能支持,不需要依赖任何其他的项目) 。

原理步骤:

request 请求进来使用文件上传解析器判断(isMultipart(request)并封装(resolveMultipart(request)),返回(MultipartHttpServletRequest)文件上传请求;

参数解析器 RequestPartMethodArgumentResolver 来解析请求中的文件内容并封装成 MultipartFile(上传文件的详细信息,如原始文件名、内容类型、大小等等);

将 request 中的文件信息封装为一个 MultiValueMap<String, MultipartFile>;

遍历 MultiValueMap 对于每一个 MultipartFile 调用 FileCopyUtils.copy() 实现文件流的拷贝。

7、异常处理
1、默认错误处理
默认规则:

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

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

{
"timestamp": "2020-11-22T05:53:28.416+00:00",
"status": 404,
"error": "Not Found",
"message": "No message available",
"path": "/abc"
}
要对其进行自定义,添加View解析为error。

要完全替换默认行为,可以实现 ErrorController并注册该类型的Bean定义,或添加ErrorAttributes类型的组件以使用现有机制但替换其内容。

/templates/error/下的4xx,5xx (500)页面会被自动解析。

2、定制错误处理逻辑
image-20220419171006002

3、错误处理底层组件
ErrorMvcAutoConfiguration 自动配置异常处理规则

容器中的组件:类型:DefaultErrorAttributes -> id:errorAttributes

public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver

DefaultErrorAttributes:定义错误页面中可以包含数据(异常明细,堆栈信息等)。

容器中的组件:类型:BasicErrorController --> id:basicErrorController(json+白页 适配响应)

处理默认 /error 路径的请求,页面响应 new ModelAndView("error", model);

容器中有组件 View -> id是error;(响应默认错误页)

容器中放组件 BeanNameViewResolver(视图解析器);按照返回的视图名作为组件的id去容器中找View对象。

容器中的组件:类型:DefaultErrorViewResolver -> id:conventionErrorViewResolver

如果发生异常错误,会以HTTP的状态码 作为视图页地址(viewName),找到真正的页面(主要作用)。

error/404、5xx.html

如果想要返回页面,就会找error视图(StaticView默认是一个白页)。

4、异常处理步骤
1、执行目标方法,目标方法运行期间有任何异常都会被catch、而且标志当前请求结束;并且用 dispatchException

2、进入视图解析流程(页面渲染?)

processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

3、mv = processHandlerException;处理handler发生的异常,处理完成返回ModelAndView;

1、遍历所有的 handlerExceptionResolvers,看谁能处理当前异常【HandlerExceptionResolver处理器异常解析器】

image-20220419233221932

2、系统默认的 异常解析器;

image-20220419233259950

DefaultErrorAttributes先来处理异常。把异常信息保存到rrequest域,并且返回null;

默认没有任何人能处理异常,所以异常会被抛出

1、如果没有任何人能处理最终底层就会发送 /error 请求。会被底层的BasicErrorController处理

2、解析错误视图;遍历所有的 ErrorViewResolver 看谁能解析。

image-20220419233423618

3、默认的 DefaultErrorViewResolver ,作用是把响应状态码作为错误页的地址,error/500.html

4、模板引擎最终响应这个页面 error/500.html

5、定制错误处理逻辑
自定义错误页

error/404.html error/5xx.html;有精确的错误状态码页面就匹配精确,没有就找 4xx.html;如果都没有就触发白页。

@ControllerAdvice+@ExceptionHandler处理全局异常;底层是 ExceptionHandlerExceptionResolver 支持的。【推荐】

@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler({ArithmeticException.class,NullPointerException.class}) //处理异常
public String handleArithException(Exception e){
log.error("异常是:{}",e);
return "login"; //视图地址
}
}
@ResponseStatus+自定义异常 ;底层是 ResponseStatusExceptionResolver ,把responseStatus注解的信息底层调用 response.sendError(statusCode, resolvedReason),tomcat发送的/error。

if(users.size()>3){
throw new UserTooManyException();//抛出自定义异常
}
DefaultHandlerExceptionResolver:Spring底层的异常,如 参数类型转换异常;使用DefaultHandlerExceptionResolver 处理框架底层的异常。

自定义实现 HandlerExceptionResolver 处理异常;可以作为默认的全局异常处理规则

@Order(value = Ordered.HIGHEST_PRECEDENCE) //设置优先级,数字越小优先级越高
@Component
public class CustomerHandlerExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
try {
response.sendError(511,"我喜欢的错误");
} catch (IOException e) {
e.printStackTrace();
}
return new ModelAndView();
}
}
image-20220420102314822

ErrorViewResolver 实现自定义处理异常 【一般不使用】

response.sendError(),error请求就会转给controller。

你的异常没有任何人能处理,tomcat底层调用response.sendError(),error请求就会转给controller。

basicErrorController 要去的页面地址是 ErrorViewResolver 。

8、Web原生组件注入(Servlet、Filter、Listener)
1、使用Servlet API 【推荐使用】
Servlet注入 @WebServlet注解

@WebServlet(urlPatterns = "/myservlet")
public class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("66666");
}
}
Filter注入 @WebFilter注解

@Slf4j
@WebFilter(urlPatterns = {"/css/*"})
public class MyFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
log.info("MyFilter工作");
filterChain.doFilter(servletRequest, servletResponse);
}

@Override
public void init(FilterConfig filterConfig) throws ServletException {
    log.info("doFilter初始化完成");
}

@Override
public void destroy() {
    log.info("doFilter销毁");
}

}
Listener注入 @WebListener注解

@Slf4j
@WebListener
public class MyServletContextListener implements ServletContextListener {

@Override
public void contextInitialized(ServletContextEvent sce) {
    log.info("【MySwervletContextListener监听到项目初始化完成】");
}

@Override
public void contextDestroyed(ServletContextEvent sce) {
    log.info("【MySwervletContextListener监听到项目销毁】");
}

}
ServletComponentScan

主启动类添加注解@ServletComponentScan,完成对Servlet的扫描才能使Servlet、Filter、Listener有效。

@SpringBootApplication
@ServletComponentScan(basePackages = "com.th.admin")
public class SpringBootAdminApplication {
}
2、使用RegistrationBean
在配置类中添加ServletRegistrationBean, FilterRegistrationBean, 和ServletListenerRegistrationBean实例对象,使用普通的自定义Servlet,Filter和Listener而不需要使用 @WebServlet、@WebFilter、 @WebListener和ServletComponentScan注解。

@Configuration(proxyBeanMethods = true)
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();

// return new FilterRegistrationBean(myFilter,myServlet());
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(myFilter);
filterRegistrationBean.setUrlPatterns(Arrays.asList("/my","/css/*"));
return filterRegistrationBean;
}

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

}
3、DispatcherServlet注入原理
DispatcherServletAutoConfiguration中为容器中自动配置了DispatcherServlet,属性绑定到webMvcProperties,对应的配置文件配置项为spring.mvc。

通过ServletRegistrationBean把DispatcherServlet配置进来。

默认路径是“/”整个也是配置在webMvcProperties中。

根据最佳匹配原则,对于“/”下的请求都会进行由DispatcherServlet进行处理,而对于“/my”不匹配的路径由自定义Servlet处理,这也是对于过滤器无法过滤“/my”的原因。

9、嵌入式Servlet容器
默认支持的WebServer:Tomcat, Jetty, Undertow。

1、原理
SpringBoot应用启动发现当前是Web应用,因为web场景包-导入tomcat。

因此创建一个web版的IOC容器 ServletWebServerApplicationContext 。

ServletWebServerApplicationContext 启动的时候寻找 ServletWebServerFactory (Servlet 的web服务器工厂——>Servlet 的web服务器)。

SpringBoot底层默认有很多的WebServer工厂(ServletWebServerFactoryConfiguration内创建WebServer工厂Bean),如:

TomcatServletWebServerFactory

JettyServletWebServerFactory

UndertowServletWebServerFactory

底层直接有一个自动配置类ServletWebServerFactoryAutoConfiguration。

ServletWebServerFactoryAutoConfiguration导入了ServletWebServerFactoryConfiguration(配置类)。

ServletWebServerFactoryConfiguration根据动态判断系统中到底导入了哪个Web服务器的包。(默认是web-starter导入tomcat包),容器中就有 TomcatServletWebServerFactory

TomcatServletWebServerFactory创建出Tomcat服务器并启动;TomcatWebServer 的构造器拥有初始化方法initialize——this.tomcat.start();

内嵌服务器,与以前手动把启动服务器相比,改成现在使用代码启动(tomcat核心jar包存在)。

2、切换嵌入式Servlet容器

org.springframework.boot
spring-boot-starter-web


org.springframework.boot
spring-boot-starter-tomcat


org.springframework.boot spring-boot-starter-jetty SpringBoot默认使用Tomcat服务器,若需更改其他服务器,如上(Jetty)则在工程pom.xml中排除spring-boot-starter-tomcat后引入spring-boot-starter-jetty。根据原理可知ServletWebServerFactoryConfiguration根据动态判断系统中到底导入了哪个Web服务器的包,然后创建对应的服务器工厂Bean。

3、定制Servlet容器
实现WebServerFactoryCustomizer

把配置文件的值和ServletWebServerFactory进行绑定。

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

修改配置文件 server.xxx

直接自定义 ConfigurableServletWebServerFactory

10、定制化原理
1、定制化的常见方式
修改配置文件

xxxxxCustomizer

编写自定义的配置类 xxxConfiguration + @Bean替换、增加容器中默认组件,视图解析器

Web应用 编写一个配置类实现 WebMvcConfigurer接口。 即可定制化web功能 + @Bean给容器中再扩展一些组件

@Configuration
public class AdminWebConfig implements WebMvcConfigurer{
}
@EnableWebMvc + WebMvcConfigurer — @Bean 可以全面接管SpringMVC【慎用】,所有规则全部自己重新配置; 实现定制和扩展功能。

原理:

WebMvcAutoConfiguration默认的SpringMVC的自动配置功能类,如静态资源、欢迎页等。

一旦使用 @EnableWebMvc ,会@Import(DelegatingWebMvcConfiguration.class)。

DelegatingWebMvcConfiguration的作用,只保证SpringMVC最基本的使用

自动配置了一些非常底层的组件,这些组件依赖的组件都是从容器中获取如。

把所有系统中的WebMvcConfigurer拿过来,所有功能的定制都是这些WebMvcConfigurer合起来一起生效。

public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport。

WebMvcAutoConfiguration里面的配置要能生效必须

@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)。

@EnableWebMvc 导致了WebMvcAutoConfiguration 没有生效。

2、原理分析套路
场景starter - xxxxAutoConfiguration - 导入xxx组件 - 绑定xxxProperties - 绑定配置文件项。

六、数据访问
1、数据源的自动配置
6.1.1 导入JDBC场景

org.springframework.boot
spring-boot-starter-data-jdbc

image-20220421080353153

除此之外,还需要导入数据库驱动包(MySQL为例)。因为SpringBoot的版本仲裁机制,在SpringBoot的spring-boot-dependencies-2.6.6.pom中配置了mysql的具体版本为mysql.version>8.0.28</mysql.version>,而项目本地的数据库为MySQL版本为5.7,因此需要修改版本号,具体方法有两种:

直接引入具体版本依赖(maven的就近依赖原则)

mysql mysql-connector-java 5.1.47 重新声明版本(maven的属性的就近优先原则) 1.8 5.1.47 6.1.2 分析自动配置项 导入JDBC场景后SpringBoot默认HikariDataSource数据源。

相关数据源配置类:org\springframework\boot\spring-boot-autoconfigure\2.6.6\spring-boot-autoconfigure-2.6.6.jar!\org\springframework\boot\autoconfigure\jdbc\。

DataSourceAutoConfiguration :

修改数据源相关的配置:spring.datasource。

数据库连接池的配置,是自己容器中没有DataSource才自动配置的。

底层配置好的连接池是:HikariDataSource。

DataSourceTransactionManagerAutoConfiguration: 事务管理器的自动配置。

JdbcTemplateAutoConfiguration: JdbcTemplate的自动配置,可以来对数据库进行CRUD。

可以修改前缀为spring.jdbc的配置项来修改JdbcTemplate。

@Bean @Primary JdbcTemplate:Spring容器中有这个JdbcTemplate组件,使用@Autowired。

JndiDataSourceAutoConfiguration: JNDI的自动配置。

XADataSourceAutoConfiguration: 分布式事务相关的。

6.1.3 修改配置项[配置基本数据库信息]
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/xiaomissm?useSSL=true&useUnicode=true&characterEncoding=UTF-8
username: root
password: mysqlpw
6.1.4 测试
@SpringBootTest
@Slf4j
class SpringBootAdminApplicationTests {

@Autowired
JdbcTemplate jdbcTemplate;

@Test
void contextLoads() {
    Long count = jdbcTemplate.queryForObject("select count(*) from users", Long.class);
    log.info("users表中记录总数:{}",count);
    //SpringBootAdminApplicationTests    : users表中记录总数:2
}

}
2、使用Druid数据源
6.2.1 自定义方式
添加依赖

com.alibaba druid ${druid-version} 创建数据源

按照Spring可以使用配置文件添加标签的方式创建dataSource,可以结合SpringBoot的配置文件中spring:datasource与配置类绑定创建数据源对象。如:

spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/xiaomissm?useSSL=true&useUnicode=true&characterEncoding=UTF-8
username: root
password: mysqlpw
@Configuration
public class DataSourceConfig {

@Bean
@ConfigurationProperties("spring.datasource")  //复用配置文件的数据源配置
public DataSource createDataSource(){
    DruidDataSource druidDataSource = new DruidDataSource();

// druidDataSource.setUrl(); 使用配置文件中 spring.datasource.url代替
// druidDataSource.setUsername();
// druidDataSource.setPassword();
return druidDataSource;
}
}
StatViewServlet 内置监控页

StatViewServlet用于展示Druid的统计信息。

提供监控信息展示的html页面

提供监控信息的JSON API

配置类中添加以下代码在容器中添加StatViewServlet对象。

/**

  • 配置 druid的监控页功能
    */
    @Bean
    public ServletRegistrationBean statViewServlet(){
    StatViewServlet statViewServlet = new StatViewServlet();

    ServletRegistrationBean registrationBean = new ServletRegistrationBean(statViewServlet,"/druid/*");
    //监控页账号密码:
    registrationBean.addInitParameter("loginUsername","admin");
    registrationBean.addInitParameter("loginPassword","123456");
    return registrationBean;
    }
    http://110.76.43.235:9000/druid/index.html可以访问到监控页面。

image-20220421211245003

StatFilter

StatFilter用于统计监控信息,如SQL监控、URI监控。对于以上使用StatViewServlet只是开启了监控信息的展示页面,但是并没有真正的监控数据,要获取监控数据,需要对DruidDataSource对象进行配置一个StatFilter对象.属性值为stat。

DruidDataSource druidDataSource = new DruidDataSource();
//添加监控功能
druidDataSource.setFilters("stat");
image-20220421210452675

WebStatFilter

WebStatFilter用于采集web-jdbc关联监控的数据。

@Bean
public FilterRegistrationBean webStatFilter(){
WebStatFilter webStatFilter = new WebStatFilter();

FilterRegistrationBean<WebStatFilter> filterRegistrationBean = new FilterRegistrationBean<>(webStatFilter);
filterRegistrationBean.setUrlPatterns(Arrays.asList("/*"));
//排除一些不必要的url请求
filterRegistrationBean.addInitParameter("exclusions","*.js,*.gif,*.jpg,*.png,*.css,*.ico,*.woff,/druid/*");

return filterRegistrationBean;

}
另外Druid提供了WallFilter等其他功能,它是基于SQL语义分析来实现防御SQL注入攻击的。

6.2.2 官方starter方式
引入druid-starter

com.alibaba druid-spring-boot-starter 1.1.17 分析自动配置

扩展配置项 spring.datasource.druid

自动配置类DruidDataSourceAutoConfigure

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的配置, stat、slf4j、wall、log4j等

配置实例

spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/xiaomissm?useSSL=true&useUnicode=true&characterEncoding=UTF-8
username: root
password: mysqlpw

druid:
  aop-patterns: com.th.admin.*       #监控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

3、整合Mybatis
6.3.1 引入依赖

org.mybatis.spring.boot
mybatis-spring-boot-starter
2.1.4

image-20220421223245110

自动配置分析

@Configuration
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties({MybatisProperties.class})
@AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class})
public class MybatisAutoConfiguration implements InitializingBean {
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {……}

@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {……}

@Configuration
@Import({MybatisAutoConfiguration.AutoConfiguredMapperScannerRegistrar.class})
@ConditionalOnMissingBean({MapperFactoryBean.class, MapperScannerConfigurer.class})
public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {……}
……

}
全局配置文件 MybatisProperties.class ===> prefix = "mybatis"

SqlSessionFactory:自动配置好了

SqlSession:自动配置了SqlSessionTemplate 其中组合了SqlSession

@Import(AutoConfiguredMapperScannerRegistrar.class) 扫描接口相关

Mapper: MyBatis的接口标注了@Mapper就会被自动扫描进来

6.3.2 配置模式
实体类

mybatis-config.xml

yaml配置文件配置Mybatis的配置文件以及mapper映射文件

mybatis:
config-location: classpath:mybatis/mybatis-config.xml #全局配置文件位置
mapper-locations: classpath:mybatis/mapper/*.xml #sql映射文件位置
在yaml中配置mybatis.configuration相关的,就是相当于改mybatis全局配置文件中的值。(也就是说配置了mybatis.configuration,就不需配置mybatis全局配置文件mybatis-config.xml了)。如果两种方式都进行了配置则会冲突报错。

编写mapper接口。必须标注@Mapper注解

@Mapper
public interface UsersMapper {
public Users getUsers(Long id);
}
编写sql映射文件并绑定mapper接口

Service层以及Controller层

@Service
public class UsersService {
@Autowired
private UsersMapper usersMapper;

public Users getUsers(Long id){
    return usersMapper.getUsers(id);
}

}

@Controller
public class MybatisTestController {
@Autowired
private UsersService usersService;

@ResponseBody
@GetMapping("/users/{id}")
public Users getUsers(@PathVariable("id") Long id){
    return usersService.getUsers(id);
}

}
测试结果:

image-20220422000551027

6.3.3 注解模式| 混合模式
@Mapper
public interface CityMapper {
@Select("SELECT id, name, state, country FROM city WHERE id = #{id}")
public City findById(long id);
}
参见Mybatis部分。

小结

简单DAO方法就写在注解上。复杂的就写在配置文件里。

使用@MapperScan("com.lun.boot.mapper") 简化,Mapper接口就可以不用标注@Mapper注解。

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

6.4.2 整合Mybatis-Plus
引入依赖

com.baomidou mybatis-plus-boot-starter 3.5.1 image-20220422213220917

Mybatis-Plus以及自动引入了Mybatis包以及spring-boot-start-jdbc,所以不再需要重复引入这些包了。

自动配置分析

MybatisPlusAutoConfiguration配置类,MybatisPlusProperties配置项绑定。 prefix = "mybatis-plus"

SqlSessionFactory自动配置,使用容器中默认的数据源。

mapperLocations 自动配置,有默认值classpath:/mapper/**/.xml,这表示类路径下的所有mapper文件夹下任意路径下的所有xml都是sql映射文件。 【建议使用】

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

@Mapper 标注的接口也会被自动扫描,建议直接 @MapperScan("com.th.admin.mapper")批量扫描。

MyBatis Plus优点之一:只需要我们的Mapper继承MyBatisPlus的BaseMapper 就可以拥有CRUD能力,减轻开发工作。

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.th.admin.bean.User;
public interface UserMapper extends BaseMapper {
}
image-20220422220415860

6.4.3 CRUD 分页
Service层

public interface UserService extends IService {
/* 使用Mybatis-Plus后只需要让接口继承IService,即可Service的CRUD也不用写了/
}
++++++++++++++接口实现类+++++++++++++++++++
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
/
使用Mybatis-Plus后只需要让实现类继承ServiceImpl,即可省略实现类方法*/
}
Controller层

@GetMapping("/dynamic_table")
public String dynamic_table(@RequestParam(value = "pn",defaultValue = "1")Integer pn, Model model){
Page userPage = new Page<>(pn, 2);
Page page = userService.page(userPage, null);
model.addAttribute("page", page);
return "table/dynamic_table";
}
//删除数据 获取当前页,以便删除后重定向到当前页
@GetMapping("/user/delete/{id}")
public String deleteUser(@PathVariable("id") Long id,
@RequestParam(value = "pn",defaultValue = "1")Integer pn,
RedirectAttributes ra){

    userService.removeById(id);
    ra.addAttribute("pn",pn);
    return "redirect:/dynamic_table";
}

分页插件

Mybatis-Plus必须配置一个分页插件才能有效。

@Configuration
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor paginationInterceptor() {
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
//这是分页拦截器
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
paginationInnerInterceptor.setOverflow(true);
paginationInnerInterceptor.setMaxLimit(500L);
mybatisPlusInterceptor.addInnerInterceptor(paginationInnerInterceptor);
return mybatisPlusInterceptor;
}
}
前端展示

ID 张三 0 123@163.com 删除 ………… 5、Redis Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。Redis中文网

6.5.1 Redis自动配置
引入依赖

org.springframework.boot spring-boot-starter-data-redis image-20220423094109736

自动配置分析

RedisAutoConfiguration自动配置类,RedisProperties 属性类 --> prefix = "spring.redis"。

连接工厂LettuceConnectionConfiguration、JedisConnectionConfiguration两种客户端连接是准备好的。

自动注入了RedisTemplate<Object, Object>,key,value均可Object。

自动注入了StringRedisTemplate,key,value都是String

底层只要我们使用StringRedisTemplate、RedisTemplate就可以操作Redis。

外网Redis环境搭建:

阿里云按量付费Redis,其中选择经典网络。

申请Redis的公网连接地址。

修改白名单,允许0.0.0.0/0访问。

6.5.2 RedisTemplate与Lettuce
配置文件

redis:
host: r-bp1itgbd3b8ijve0jipd.redis.rds.aliyuncs.com
port: 6379
password: th:th123456@
redis的密码为账号:密码。host、port、password组合使用为url。

测试使用

@Autowired
StringRedisTemplate redisTemplate;

@Autowired
RedisConnectionFactory redisConnectionFactory;

@Test
void testRedis(){
log.info("【RedisConnectionFactory】:=>{}",redisConnectionFactory.getClass());
ValueOperations<String, String> operations = redisTemplate.opsForValue();
operations.set("hello", "world");
String hello = operations.get("hello");
log.info("hello的值为{}",hello);
}
默认使用Lettuce,

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisClient.class)
@ConditionalOnProperty(name = "spring.redis.client-type", havingValue = "lettuce", matchIfMissing = true)
class LettuceConnectionConfiguration extends RedisConnectionConfiguration {
……
}
6.5.3 切换至jedis
导入包

redis.clients jedis 配置redis的client-type 为jedis

redis:
host: r-bp1itgbd3b8ijve0jipd.redis.rds.aliyuncs.com
port: 6379
password: th:th123456@
client-type: jedis
可以使用spring.redis.jedis.进行jedis的配置。

七、单元测试
1、JUnit5 的变化
Spring Boot 2.2.0 版本开始引入 JUnit 5 作为单元测试默认库

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

JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage

JUnit Platform:不仅支持Junit自制的测试引擎,其他测试引擎也都可以接入。

JUnit Jupiter: 提供了JUnit5的新的编程模型,是JUnit5新特性的核心。内部包含了一个测试引擎,用于在Junit Platform上运行。

JUnit Vintage: 提供了兼容JUnit4.x,Junit3.x的测试引擎。

JUnit5的使用:

导入依赖

org.springframework.boot spring-boot-starter-test test Spring的JUnit 5的基本单元测试模板

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class SpringBootAdminApplicationTests {

@Test
void testRedis(){
    ……
}

}
SpringBoot的JUnit4的是@SpringBootTest+@RunWith(SpringRunner.class),而在JUnit5只需要@SpringBootTest。另外在测试类中也可以使用一些SpringBoot其他注解,如@Autowired。

【注意】

SpringBoot 2.4 以上版本已经从spring-boot-starter-test移除了默认对 Vintage 的依赖。如果需要兼容JUnit4需要自行引入(不能使用JUnit4的功能 @Test)

org.junit.vintage junit-vintage-engine test org.hamcrest hamcrest-core 2、JUnit5 常用注解 @Test:表示方法是测试方法。但是与JUnit4的@Test不同,他的职责非常单一不能声明任何属性,拓展的测试将会由Jupiter提供额外测试

@ParameterizedTest:表示方法是参数化测试。

@ValueSource注释指定一个String数组作为参数的源。

@RepeatedTest:表示方法可重复执行。

@DisplayName:为测试类或者测试方法设置展示名称。

@BeforeEach:表示在每个单元测试之前执行。

@AfterEach:表示在每个单元测试之后执行。

@BeforeAll:表示在所有单元测试之前执行。

@AfterAll:表示在所有单元测试之后执行。

@Tag:表示单元测试类别,类似于JUnit4中的@Categories。

@Disabled:表示测试类或测试方法不执行,类似于JUnit4中的@Ignore。

@Timeout:表示测试方法运行如果超过了指定时间将会返回错误。

@ExtendWith:为测试类或测试方法提供扩展类引用。

3、断言(assertions)
断言(assertions)是测试方法中的核心部分,用来对测试需要满足的条件进行验证。这些断言方法都是 org.junit.jupiter.api.Assertions 的静态方法。使用Maven工具可以在所有的测试运行结束以后,产生一个详细的测试报告;

1、简单断言
对单个值进行简单的验证

方法 说明
assertEquals 判断两个对象或两个原始类型是否相等
assertNotEquals 判断两个对象或两个原始类型是否不相等
assertSame 判断两个对象引用是否指向同一个对象
assertNotSame 判断两个对象引用是否指向不同的对象
assertTrue 判断给定的布尔值是否为 true
assertFalse 判断给定的布尔值是否为 false
assertNull 判断给定的对象引用是否为 null
assertNotNull 判断给定的对象引用是否不为 null
@Test
@DisplayName("simple assertion")
public void simple() {
assertEquals(3, 1 + 2, "simple math");
assertNotEquals(3, 1 + 1);

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

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

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

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

@Test
@DisplayName("array assertion")
public void array() {
assertArrayEquals(new int[]{1, 2}, new int[] {1, 2});
}
3、组合断言
assertAll()方法接受多个 org.junit.jupiter.api.Executable 函数式接口的实例作为要验证的断言,可以通过 lambda 表达式很容易的提供这些断言。

@Test
@DisplayName("assert all")
public void all() {
assertAll("Math",
() -> assertEquals(2, 1 + 1),
() -> assertTrue(1 > 0)
);
}
4、异常断言
在JUnit4时期,想要测试方法的异常情况时,需要用@Rule注解的ExpectedException变量还是比较麻烦的。而JUnit5提供了一种新的断言方式Assertions.assertThrows(),配合函数式编程就可以进行使用。

@Test
@DisplayName("异常测试")
public void exceptionTest() {
ArithmeticException exception = Assertions.assertThrows(
//扔出断言异常
ArithmeticException.class, () -> System.out.println(1 % 0));
}
5、超时断言
JUnit5还提供了Assertions.assertTimeout()为测试方法设置了超时时间。

@Test
@DisplayName("超时测试")
public void timeoutTest() {
//如果测试方法时间超过1s将会异常
Assertions.assertTimeout(Duration.ofMillis(1000), () -> Thread.sleep(500));
}
6、快速失败
通过 fail 方法直接使得测试失败。

@Test
@DisplayName("fail")
public void shouldFail() {
fail("This should fail");
}
4、前置条件(assumption)
Unit 5 中的前置条件(assumptions【假设】)类似于断言,不同之处在于不满足的断言assertions会使得测试方法失败,而不满足的前置条件只会使得测试方法的执行终止。前置条件可以看成是测试方法执行的前提,当该前提不满足时,就没有继续执行的必要。

@Test
@DisplayName("assume then do")
public void testAssumption() {
Assumptions.assumeTrue(true, "条件不为true");
System.out.println("111");
}
Assumptions类主要有assumeTrue和 assumFalse 以及assumingThat 三个方法,前两者确保给定的条件为true 或 false,不满足条件会使得测试执行终止。assumingThat的参数是表示条件的布尔值和对应的 Executable接口的实现对象。只有条件满足时,Executable 对象才会被执行;当条件不满足时,测试执行并不会终止。

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

也就是在嵌套测试情况下,外层Test不能驱动内层方法,让内层方法提前执行;而内层方法可以驱动外层方法,让外层方法提前执行。

代码参见官网。

6、参数化测试
一次性传入多个参数进行测试:

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

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

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

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

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

@ParameterizedTest
@ValueSource(strings = {"one", "two", "three"})
@DisplayName("参数化测试")
public void parameterizedTest1(String string) {
System.out.println(string);
Assertions.assertTrue(StringUtils.isNotBlank(string));
}
7、迁移指南
JUnit4---> JUnit5

注解在 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。

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

2、1.x 与2.x版本区别
image-20220423180514958

3、如何使用

org.springframework.boot spring-boot-starter-actuator 两种方式查看

HTTP:默认只暴露health Endpoint

JMX: cmd窗口使用jconsole打开Java监控和管理控制台,查看响应监控数据。默认暴露所有Endpoint

HTTP方式:访问 http://localhost:8080/actuator/**

HTTP方式默认只会开启health端点,需要在配置文件中开启以HTTP方式开启所有端点。

management:
endpoints:
enabled-by-default: true #暴露所有端点信息
web:
exposure:
include: '*' #以web方式暴露
4、可视化
https://github.com/codecentric/spring-boot-admin

2、Actuator Endpoint
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:日志记录

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

重要的几点:

health endpoint返回的结果,应该是一系列健康检查后的一个汇总报告。

默认不显示详细信息,需要配置 management:endpoint:health:show-details: always

很多的健康检查默认已经自动配置好了,比如:数据库、redis等。

可以很容易的添加自定义的健康检查机制。

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

通过Metrics对接多种监控系统。

简化核心Metrics开发。

添加自定义Metrics或者扩展已有Metrics。

4、管理Endpoint
1、开启与禁用Endpoint
默认所有的Endpoint除过shutdown都是开启的。

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

开启全部的Endpoint,配置management: endpoints:enabled-by-default: true。不建议

management:
endpoints:
enabled-by-default: true #暴露所有端点信息
web:
exposure:
include: '*' #以web方式暴露
2、暴露Endpoint
由于端点可能包含敏感信息,默认并不是全部公开:具体公开如下:

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 No
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
quartz Yes No
scheduledtasks Yes No
sessions Yes No
shutdown Yes No
startup Yes No
threaddump Yes No
要更改公开哪些端点,请使用以下特定于技术的include和exclude属性:

Property Default
management.endpoints.jmx.exposure.exclude
management.endpoints.jmx.exposure.include *
management.endpoints.web.exposure.exclude
management.endpoints.web.exposure.include health
3、定制Endpoint
1、定制Health信息
方式一 继承AbstractHealthIndicator类

@Component
public class MyHealthIndicator extends AbstractHealthIndicator {
@Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
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);
}
}
方式二 实现HealthIndicator接口

//@Component
public class MyHeHealthIndicator 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();

}
2、定制info信息
编写InfoContribute子类

@Component
public class MyInfoContributor implements InfoContributor {
@Override
public void contribute(Info.Builder builder) {
builder.withDetail("example",
Collections.singletonMap("key", "value"));
}
}
http://localhost:8080/actuator/info 展示响应的info信息。

3、定制Metrics信息
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tags;
import org.springframework.stereotype.Component;

@Component
public class MyBean {
private final Dictionary dictionary;
public MyBean(MeterRegistry registry) {
this.dictionary = Dictionary.load();
registry.gauge("dictionary.size", Tags.empty(), this.dictionary.getWords().size());
}
}

//=第二种方式===
public class MyMeterBinderConfiguration {
@Bean
public MeterBinder queueSize(Queue queue) {
return (registry) -> Gauge.builder("queueSize", queue::size).register(registry);
}
}
4、定制Endpoint
@Component
@Endpoint(id = "aa")
public class MyAAAEndpoint {
@ReadOperation
public Map getAaaInfo(){
return Collections.singletonMap("info","AAA started...");
}

@WriteOperation
private void restartAaa(){
    System.out.println("AAA restarted....");
}

}
九、原理解析
1、Profile功能
为了方便多环境适配,Spring Boot简化了profile功能。

application-profile功能

默认配置文件application.yaml任何时候都会加载。

指定环境配置文件application-{env}.yaml,env通常替代为test,

激活指定环境

配置文件激活:spring.profiles.active=prod

命令行激活:java -jar xxx.jar --spring.profiles.active=prod --person.name=haha(修改配置文件的任意值,命令行优先)

默认配置与环境配置同时生效

同名配置项,profile配置优先

一般结合@ConfigurationProperties("prefix")一起使用。

@Profile条件装配功能

@Configuration(proxyBeanMethods = false)
@Profile("production")
public class ProductionConfiguration {
// ...
}
@Profile不仅可以修饰在类上还可以修饰在方法上。

profile分组

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

使用:--spring.profiles.active=production 激活production组所包含的proddb和prodmq个配置。
2、外部化配置
1、常用外部配置源
SpringBoot支持14种外部配置源方式,但是常用的为:Java属性文件、YAML文件、环境变量、命令行参数;

2、配置文件查找位置
(1) classpath 根路径

(2) classpath 根路径下config目录

(3) jar包当前目录

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

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

3、配置文件加载顺序:
当前jar包内部的application.properties和application.yml

当前jar包内部的application-{profile}.properties 和 application-{profile}.yml

引用的外部jar包的application.properties和application.yml

引用的外部jar包的application-{profile}.properties 和 application-{profile}.yml

4、指定环境优先,外部优先,后面的可以覆盖前面的同名配置项
3、自定义starter
1、starter启动原理
starter的pom.xml引入autoconfigure依赖

starter
autoconfigure
spring-boot-starter
autoconfigure包中配置使用META-INF/spring.factories中EnableAutoConfiguration的值,使项目启动加载指定的自动配置类

编写自动配置类 xxxAutoConfiguration -> xxxxProperties

@Configuration

@Conditional

@EnableConfigurationProperties

@Bean

......

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

2、自定义starter
创建一个空项目,在这个空项目中分别命名为hello-spring-boot-starter(普通Maven工程),hello-spring-boot-starter-autoconfigure两个模块(需用用到Spring Initializr创建的Maven工程)。

hello-spring-boot-starter无需编写什么代码,只需让该工程引入hello-spring-boot-starter-autoconfigure依赖:

com.lun
hello-spring-boot-starter
1.0.0-SNAPSHOT

com.lun hello-spring-boot-starter-autoconfigure 1.0.0-SNAPSHOT hello-spring-boot-starter-autoconfigure的pom.xml如下: org.springframework.boot spring-boot-starter hello-spring-boot-starter-autoconfigure模块创建4个文件:

com/lun/hello/auto/HelloServiceAutoConfiguration

@Configuration
@EnableConfigurationProperties(HelloProperties.class)//默认HelloProperties放在容器中
public class HelloServiceAutoConfiguration {

@Bean
@ConditionalOnMissingBean(HelloService.class)
public HelloService helloService(){
    return new HelloService();
}

}
com/lun/hello/bean/HelloProperties

@ConfigurationProperties("hello")
public class HelloProperties {
private String prefix;
private String suffix;

//getter and setter method

}
com/lun/hello/service/HelloService

public class HelloService {

@Autowired
private HelloProperties helloProperties;

public String sayHello(String userName){
    return helloProperties.getPrefix() + ": " + userName + " > " + helloProperties.getSuffix();
}

}
src/main/resources/META-INF/spring.factories

Auto Configure

org.springframework.boot.autoconfigure.EnableAutoConfiguration=
com.lun.hello.auto.HelloServiceAutoConfiguration
用maven插件,分别将两工程install到本地。

接下来,测试使用自定义starter,用Spring Initializr创建名为工程,引入自定义hello-spring-boot-starter依赖:

com.lun hello-spring-boot-starter 1.0.0-SNAPSHOT 添加配置文件application.properties:

hello.prefix=hello
hello.suffix=666
添加单元测试类:

@SpringBootTest
class HelloSpringBootStarterTestApplicationTests {

@Autowired
private HelloService helloService;

@Test
void contextLoads() {
    // System.out.println(helloService.sayHello("lun"));
    Assertions.assertEquals("hello: lun > 666", helloService.sayHello("lun"));
}

3、SpringApplication创建初始化流程
创建 SpringApplication
主要分析SpringApplication.java有参数构造方法。

保存一些信息。比如当前SpringBoot类

this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
判定当前应用的类型。SERVLET or REACTIVE

this.webApplicationType = WebApplicationType.deduceFromClasspath();
BootstrapRegistryInitializer:初始启动引导器(List):去spring.factories文件中找 org.springframework.boot.BootstrapRegistryInitializer

this.bootstrapRegistryInitializers = new ArrayList<>(
getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
找 ApplicationContextInitializer;去spring.factories ApplicationContextInitializer

List<ApplicationContextInitializer<?>> initializers

setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
找 ApplicationListener ;应用监听器。去spring.factories ApplicationListener

List<ApplicationListener<?>> listeners

setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
运行 SpringApplication
public ConfigurableApplicationContext run(String... args) {……}方法
记录应用的启动时间

创建引导上下文(Context环境)createBootstrapContext()

DefaultBootstrapContext bootstrapContext = createBootstrapContext();
获取到所有之前的bootstrapRegistryInitializers 挨个执行intitialize()来完成对引导启动器上下文环境设置。

让当前应用进入headless模式。java.awt.headless。

configureHeadlessProperty();
获取所有 RunListener(运行监听器)【为了方便所有Listener进行事件感知】

SpringApplicationRunListeners listeners = getRunListeners(args);
getSpringFactoriesInstances去spring.factories找 SpringApplicationRunListener.

getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args)
遍历 SpringApplicationRunListener 调用 starting 方法;

listeners.starting(bootstrapContext, this.mainApplicationClass);
相当于通知所有感兴趣系统正在启动过程的人,项目正在 starting。

保存命令行参数;ApplicationArguments

ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
准备环境 prepareEnvironment();

ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
返回或者创建基础环境信息对象。StandardServletEnvironment。

ConfigurableEnvironment environment = getOrCreateEnvironment();
配置环境信息对象。

configureEnvironment(environment, applicationArguments.getSourceArgs());
读取所有的配置源的配置属性值。

configurePropertySources(environment, args);
绑定环境信息

ConfigurationPropertySources.attach(environment);
监听器调用 listener.environmentPrepared();通知所有的监听器当前环境准备完成

listeners.environmentPrepared(bootstrapContext, environment);
打印banner信息

Banner printedBanner = printBanner(environment);
创建IOC容器(createApplicationContext())

context = createApplicationContext();
根据项目类型(Servlet)创建容器,

当前会创建 AnnotationConfigServletWebServerApplicationContext

switch (webApplicationType) {
case SERVLET:
return new AnnotationConfigServletWebServerApplicationContext();
……
准备ApplicationContext IOC容器的基本信息 prepareContext()

prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
保存环境信息 context.setEnvironment(environment);

IOC容器的后置处理流程。postProcessApplicationContext(context);

应用初始化器;applyInitializers;applyInitializers(context);

遍历所有的 ApplicationContextInitializer 。调用 initialize.。来对ioc容器进行初始化扩展功能

遍历所有的 listener 调用 contextPrepared。EventPublishRunListenr;通知所有的监听器contextPrepared

所有的监听器调用 contextLoaded。通知所有的监听器 contextLoaded;listeners.contextLoaded(context);

刷新IOC容器。refreshContext

refreshContext(context);
创建容器中的所有组件

容器刷新完成后工作 afterRefresh`

所有监听 器 调用 listeners.started(context); 通知所有的监听器 started

listeners.started(context, timeTakenToStartup);
调用所有runners;callRunners()

callRunners(context, applicationArguments);
获取容器中的 ApplicationRunner

获取容器中的 CommandLineRunner

合并所有runner并且按照@Order进行排序

遍历所有的runner。调用 run 方法

如果以上有异常,

调用Listener 的 failed

调用所有监听器的 ready方法

listeners.ready(context, timeTakenToReady);
running如果有问题。继续通知 failed 。调用所有 Listener 的 failed;通知所有的监听器 failed

十、技术整合
1、Spring Security
Spring Security是一个强大的、高度可定制的身份验证和访问控制框架。

Spring Security是Spring Boot底层安全模块默认的技术选型,他可以实现强大的Web安全控制。

Spring Security的两个主要目标是“认证Authentication"和“授权Authorization”(访问控制)

引入依赖

org.springframework.boot spring-boot-starter-security 编写 Spring Security 配置类

@EnableWebSecurity
public class Config extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.apply(customDsl())
.flag(true)
.and()
...;
}
}
2、Shiro
虚拟化技术
安全控制
缓存技术
消息中间件
对象存储
定时调度
异步任务
分布式系统

十一、相关命令
mvn dependency:tree: 打印项目依赖关系树表示

mvn spring-boot:run:运行当前SpringBoot项目

mvn package:将web项目打成jar包

java -jar ....jar:运行jar包

posted @ 2022-06-07 11:13  三淳  阅读(69)  评论(0编辑  收藏  举报