springboot3学习笔记
1 SpringBoot简介
雷丰阳老师https://www.yuque.com/leifengyang/springboot3/wuil67bq85d88gso
SpringBoot 是框架的框架,能够帮我们简单、快速地创建一个独立的、生产级别的 Spring 应用。它能够简化开发,简化配置,简化整合,简化部署,简化监控,简化运维。
- SpringBoot根据不同的场景提供了不同的starter,例如
web-starter
。mybatis-starter
。它能够将场景所需的依赖自动进行导入并进行版本控制。 - SpringBoot能够按需自动配置 Spring 以及 第三方库。
约定大于配置:每个场景都有很多默认配置。如需自定义,可利用application.properties 配置文件对整个项目进行统一配置。
1.1 自动配置机制
1.导入场景启动器,会将该场景所需的所有依赖进行导入(maven的依赖传递)。
2.所有的场景启动器都引入了一个spring-boot-starter,核心场景启动器,而spring-boot-starter
引入了spring-boot-autoconfigure
包。
3.spring-boot-autoconfigure
里面囊括了所有场景的所有配置(通过配置类实现)。只要该配置类生效,则自动配置生效。
4.按需配置原理:主程序:@SpringBootApplication,该注解主要由三个注解组成:@SpringBootConfiguration
、@EnableAutoConfiguratio
、@ComponentScan
。
@ComponentScan
:开启组件扫描,默认只扫描主程序所在包及子包。@EnableAutoConfiguration
:SpringBoot 开启自动配置的核心。是由@Import(AutoConfigurationImportSelector.class)
提供功能:批量给容器中导入组件。项目启动的时候利用@Import
批量导入组件机制把autoconfigure
包下的自动配置类导入进来。而组件扫描扫描不到,再利用条件注解使得自动配置类生效。自动配置类中通过@bean给容器中提供相关的组件。
5.组件默认值:容器中放的所有组件的一些核心参数,通过@EnableConfigurationPropertie
s注解与xxxProperties.class绑定,而xxxProperties类中有组件的默认值,且类中的属性通过@ConfigurationProperties( prefix = "xxx")
注解和配置文件绑定(换句话说,只有xxxProperties.class类中的可以修改的属性才能再配置文件中进行重新配置)。只需要改配置文件的值,核心组件的底层参数都能修改
(修改方法:在配置文件中直接写xxx.属性名=所需值)
效果:开发者只需导入starter、修改配置文件,即可定制所需开发场景。
6.版本控制:每个boot项目都有一个父项目spring-boot-starter-parentparent的父项目是spring-boot-dependencies。 父项目是版本仲裁中心,把所有常见的jar的依赖版本都声明好了。如果需要切换版本,利用maven的就近原则,直接添加所需依赖即可。
1.2 YAML配置文件
由于要使用一个配置文件对整个项目进行配置,properties文件层级不够清晰,也可以使用application.yml
进行配置文件的编写。
1.基本语法:
- 大小写敏感
- 使用缩进表示层级关系,k: v,使用空格分割k,v
- 缩进时不允许使用Tab键,只允许使用空格。换行
- 缩进的空格数目不重要,只要相同层级的元素左侧对齐即可
- #表示注释,从这个字符一直到行尾,都会被解析器忽略。
person:
name: 张三
age: 18
birthDay: 2010/10/10 12:12:12
like: true
child:
name: 李四
age: 20
birthDay: 2018/10/10
text: ["abc","def"]
dogs:
- name: 小黑
age: 3
- name: 小白
age: 2
cats:
c1:
name: 小蓝
age: 3
c2: {name: 小绿,age: 2} #对象也可用{}表示
1.3 日志功能
1. SpringBoot如何实现日志的默认配置
- 1、每个starter场景,都会导入一个核心场景
spring-boot-starter
- 2、核心场景引入了日志的所用功能spring-boot-starter-logging
- 3、默认使用了logback + slf4j 组合作为默认底层日志
- 4、日志是系统一启动就要用,xxxAutoConfiguration是系统启动好了以后放好的组件,后来用的。
- 5、日志是利用监听器机制配置好的。
ApplicationListener
。 - 6、日志所有的配置都可以通过修改配置文件实现。以logging开始的所有配置。
2. 日志级别
- 由低到高:ALL,TRACE, DEBUG, INFO, WARN, ERROR,FATAL,OFF
- 不指定级别的所有类,都使用root指定的级别作为默认级别
- SpringBoot日志默认级别是 INFO
3. 日志配置
logging.level.<logger-name>=<level>
指定日志级别
logger-name若为root则表示所有日志,也可指定包或指定类(也可以将相同配置的logger分组进行统一设置,SpringBoot 预定义两个组:sql 和 web)
level指定日志级别logging.file.name
指定日志文件输出的路径及文件名- 文件归档与滚动切割
每天的日志应该独立分割出来存档。如果使用logback,可以通过application.properties/yaml文件指定日志滚动规则。
如果是其他日志系统,需要自行配置(添加log4j2-spring.xml) - 切换日志组合
利用maven的就近原则和<exclusion>
标签屏蔽掉原本的日志依赖并引入所需依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
2.Web开发
2.1 WebMvcAutoConfiguration原理
- 生效条件
@AutoConfiguration(after = { DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
ValidationAutoConfiguration.class }) //在这些自动配置之后
@ConditionalOnWebApplication(type = Type.SERVLET) //如果是web应用就生效,类型SERVLET、REACTIVE 响应式web
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class) //容器中没有这个Bean才生效
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)//优先级
@ImportRuntimeHints(WebResourcesRuntimeHints.class)
public class WebMvcAutoConfiguration {
}
- 默认效果
参照WebMvcAutoConfiguration配置类源码,其内部使用@bean注解添加了各种组件
- 两个过滤器组件
a. HiddenHttpMethodFilter;页面表单提交Rest请求(GET、POST、PUT、DELETE)
b. FormContentFilter: 表单内容Filter,GET(数据放URL后面)、POST(数据放请求体)请求可以携带数据,PUT、DELETE 的请求体数据会被忽略 - 一个WebMvcConfigurer组件(WebMvcAutoConfigurationAdapter implements WebMvcConfigurer接口)
@Configuration(
proxyBeanMethods = false
)
@Import({EnableWebMvcConfiguration.class})
@EnableConfigurationProperties({WebMvcProperties.class, WebProperties.class})
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware {
}
该组件给SpringMVC添加各种定制功能,所有的功能最终会和配置文件进行绑定(WebMvcProperties.class:spring.mvc 配置文件, WebProperties.class:spring.web 配置文件)
-
WebMvcConfigurer接口
提供了配置SpringMVC底层的所有组件入口
-
配置SpringMVC底层组件的方法:
1.利用配置文件
2.自己写一个配置类,添加@Configuration
注解并实现WebMvcConfigurer
接口,内部通过重写接口方法或通过@Bean
放入组件来实现组件配置
注意,配置类若添加@EnableWebMvc注解,会禁用webmvc的自动配置,观察@EnableWebMvc源码
,有@Import({DelegatingWebMvcConfiguration.class})
,而DelegatingWebMvcConfiguration
继承了WebMvcConfigurationSupport
,
所以该配置类相当于导入了WebMvcConfigurationSupport组件,而webmvc默认配之类的生效条件之一就是没有这个组件。
-
为什么容器中放一个WebMvcConfigurer就能配置底层行为?
WebMvcAutoConfiguration
是一个自动配置类,它里面有一个EnableWebMvcConfiguration
组件,继承于DelegatingWebMvcConfiguration
,并与WebProperties.class
绑定,DelegatingWebMvcConfiguration
利用 依赖注入DI 把容器中 所有的WebMvcConfigurer
注入进来,别人调用DelegatingWebMvcConfiguration
的方法配置底层规则,而它调用所有WebMvcConfigurer
的配置底层方法。
WebMvcConfigurationSupport
提供了很多的默认设置和组件。
-
静态资源访问规则
规则一:访问: /webjars/** 路径就去 classpath:/META-INF/resources/webjars/ 下找资源
规则二:访问: /** 路径就去 静态资源默认的四个位置找资源
a. classpath:/META-INF/resources/
b. classpath:/resources/
c. classpath:/static/
d. classpath:/public/
规则三:静态资源默认都有缓存规则的设置
a. 所有缓存的设置,直接通过配置文件:spring.web
b. cachePeriod: 缓存周期; 多久不用找服务器要新的。 默认没有,以s为单位
c. cacheControl: HTTP缓存控制;https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Caching
d. useLastModified:是否使用最后一次修改。配合HTTP Cache规则 -
欢迎页与favicon.ico
欢迎页规则:在WebMvcAutoConfiguration
中进行了定义,在静态资源目录下找 index.html,没有就在 templates下找index.html
favicon.ico:页面访问时的小图标,在静态资源目录下找(一般在前端进行设置)
2.2 自定义静态资源规则
#1、spring.web:静态资源策略(开启、处理链、缓存)
#开启静态资源映射规则
spring.web.resources.add-mappings=true
#设置缓存
spring.web.resources.cache.period=3600
##缓存详细合并项控制,覆盖period配置:
## 浏览器第一次请求服务器,服务器告诉浏览器此资源缓存7200秒,7200秒以内的所有此资源访问不用发给服务器请求,7200秒以后发请求给服务器
spring.web.resources.cache.cachecontrol.max-age=7200
## 共享缓存
spring.web.resources.cache.cachecontrol.cache-public=true
#使用资源 last-modified 时间,来对比服务器和浏览器的资源是否相同没有变化。相同返回 304
spring.web.resources.cache.use-last-modified=true
#自定义静态资源文件夹位置
spring.web.resources.static-locations=classpath:/a/,classpath:/b/,classpath:/static/
#2、 spring.mvc
## 2.1. 自定义webjars路径前缀
spring.mvc.webjars-path-pattern=/wj/**
## 2.2. 静态资源访问路径前缀
spring.mvc.static-path-pattern=/static/**
- 采取配置类的方法自定义静态资源规则
@Configuration //这是一个配置类
public class MyConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
//保留以前规则
//自己写新的规则。
registry.addResourceHandler("/static/**")
.addResourceLocations("classpath:/a/","classpath:/b/")
.setCacheControl(CacheControl.maxAge(1180, TimeUnit.SECONDS));
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**")
.addResourceLocations("classpath:/a/", "classpath:/b/")
.setCacheControl(CacheControl.maxAge(1180, TimeUnit.SECONDS));
}
}
2.3 路径匹配
- springboot现在默认使用
PathPatternParser
路径匹配策略,它与Ant风格的路径匹配语法上区别不大,性能更好,但是 ** 只允许出现在路径的结尾处。 - 通过
spring.mvc.pathmatch.matching-strategy=ant_path_matcher
可以实现路径匹配策略的转换
2.4 内容协商
2.4.1 默认规则
a.基于请求头内容协商:(默认开启)
客户端向服务端发送请求,携带HTTP标准的Accept请求头(Accept: application/json、text/xml、text/yaml
),服务端根据客户端请求头期望的数据类型进行动态返回。
b.基于请求参数内容协商:(需要开启)
发送请求 GET /projects/spring-boot?format=json
匹配到 @GetMapping("/projects/spring-boot")
根据参数协商,优先返回 json 类型数据【需要开启参数匹配设置】
发送请求 GET /projects/spring-boot?format=xml,优先返回 xml 类型数据
# 开启基于请求参数的内容协商功能。 默认参数名:format。 默认此功能不开启
spring.mvc.contentnegotiation.favor-parameter=true
# 指定内容协商时使用的参数名。默认是 format
spring.mvc.contentnegotiation.parameter-name=type
若要能够返回xml格式,需要导入相关的依赖
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
2.4.2 自定义内容返回(yaml为例)
-
- 增加yaml返回的依赖
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
</dependency>
-
- 编写配置
#新增一种媒体类型
spring.mvc.contentnegotiation.media-types.yaml=text/yaml
-
- 增加
HttpMessageConverter
组件,专门负责把对象写出为yaml格式
- 增加
public class MyYamlHttpMessageConverter extends AbstractHttpMessageConverter<Object> {
private ObjectMapper objectMapper = null; //把对象转成yaml
public MyYamlHttpMessageConverter(){
//告诉SpringBoot这个MessageConverter支持哪种媒体类型 //媒体类型
super(new MediaType("text", "yaml", Charset.forName("UTF-8")));
YAMLFactory factory = new YAMLFactory()
.disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER);
this.objectMapper = new ObjectMapper(factory);
}
@Override
protected boolean supports(Class<?> clazz) {
//只要是对象类型,不是基本类型
return true;
}
@Override //@RequestBody
protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
return null;
}
@Override //@ResponseBody 把对象怎么写出去
protected void writeInternal(Object methodReturnValue, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
//try-with写法,自动关流
try(OutputStream os = outputMessage.getBody()){
this.objectMapper.writeValue(os,methodReturnValue);
}
}
}
-
- 将
HttpMessageConverter
组件通过配置类放进容器中
- 将
@Configuration //这是一个配置类
public class MyConfig implements WebMvcConfigurer{
@Override //配置一个能把对象转为yaml的messageConverter
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new MyYamlHttpMessageConverter());
}
}
2.4.3 内容协商原理
- 如果controller方法的返回值标注了
@ResponseBody
注解
1.1. 请求进来先来到DispatcherServlet
的doDispatch()
进行处理
1.2. 找到一个HandlerAdapter
适配器。利用适配器执行目标方法
1.3.RequestMappingHandlerAdapter
来执行,调用invokeHandlerMethod()
来执行目标方法
1.4. 目标方法执行之前,准备好两个东西
1.4.1.HandlerMethodArgumentResolver
:参数解析器,确定目标方法每个参数值
1.4.2.HandlerMethodReturnValueHandler
:返回值处理器,确定目标方法的返回值改怎么处理
1.5.RequestMappingHandlerAdapter
里面的invokeAndHandle()
真正执行目标方法
1.6. 目标方法执行完成,会返回返回值对象
1.7. 找到一个合适的返回值处理器HandlerMethodReturnValueHandler
(默认有多种,遍历选择)
1.8. 最终找到RequestResponseBodyMethodProcessor
能处理 标注了@ResponseBody
注解的方法
1.9.RequestResponseBodyMethodProcessor
调用writeWithMessageConverters
,利用MessageConverter
把返回值写出去 HttpMessageConverter
会先进行内容协商
2.1. 遍历所有的MessageConverter
看谁支持这种内容类型的数据(有默认的几种 MessageConverter,遍历选择)
2.2 根据需求选择对应的MessageConverter
2.3 通过MessageConverter
中对应的方法把内容按要求写出去
2.5 模板引擎
现在多用前后端分离的架构, 模板引擎部分知识等需要了再补。
2.6 错误处理机制
错误处理的自动配置都在ErrorMvcAutoConfiguration
中,两大核心机制:
● 1. SpringBoot 会自适应处理错误,响应页面或JSON数据
● 2. SpringMVC的错误处理机制依然保留,MVC处理不了,才会交给boot进行处理
spring容器中有一个默认的名为 error 的 view; 提供了默认白页功能。
- 在前后端分离的场景下,一般不返回视图信息,而返回json
此时,后台发生的所有错误,使用 @ControllerAdvice + @ExceptionHandler进行统一异常处理。
//举例
@ResponseBody
@ControllerAdvice//标记为一个全局的异常处理组件
public class MyGlobalExceptionHandler
{
// 专门用来捕获和处理Controller层的异常
@ExceptionHandler(Exception.class)
public ModelAndView customException(Exception e)
{
ModelAndView mv = new ModelAndView();
mv.addObject("message", e.getMessage());
mv.setViewName("myerror");
return mv;
}
// 专门用来捕获和处理Controller层的空指针异常
@ExceptionHandler(NullPointerException.class)
public ModelAndView nullPointerExceptionHandler(NullPointerException e)
{
ModelAndView mv = new ModelAndView(new MappingJackson2JsonView());
mv.addObject("success",false);
mv.addObject("mesg","请求发生了空指针异常,请稍后再试");
return mv;
}
}
https://www.cnblogs.com/xd502djj/p/9873172.html
2.7 嵌入式容器
servlet容器,默认为tomcat
自动配置原理与之前类似,通过看源码得知,直接在配置文件中修改server下的相关配置就可以修改服务器参数
- 通过给容器中放一个
ServletWebServerFactory
,来禁用掉SpringBoot
默认放的服务器工厂,实现自定义嵌入任意服务器。 - 全面接管SpringMVC,可在自定义的配置类上加入@EnableWebMvc,原理前面讲过。
2.8 Web新特性
1.Problemdetails
ProblemDetailsExceptionHandler
是一个 @ControllerAdvice
,集中处理系统异常
处理以下异常。如果系统出现以下异常,会被SpringBoot支持以 RFC 7807规范方式返回错误数据.
2.函数式Web
将路由与业务分离。
- 核心类
● RouterFunction 定义路由信息(发什么请求,谁来处理)
● RequestPredicate 定义请求方式
● ServerRequest 封装请求
● ServerResponse 封装响应 - 示例
//写法固定
@Configuration(proxyBeanMethods = false)
public class MyRoutingConfiguration {
//accept是RequestPredicates工具类的一个静态方法,传入MediaType,返回对应的 AcceptPredicate ;
private static final RequestPredicate ACCEPT_JSON = accept(MediaType.APPLICATION_JSON);
@Bean
public RouterFunction<ServerResponse> routerFunction(MyUserHandler userHandler) {
return route()
.GET("/{user}", ACCEPT_JSON, userHandler::getUser)
.GET("/{user}/customers", ACCEPT_JSON, userHandler::getUserCustomers)
.DELETE("/{user}", ACCEPT_JSON, userHandler::deleteUser)
.build();
}
}
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.function.ServerRequest;
import org.springframework.web.servlet.function.ServerResponse;
//在handler中做具体实现,返回ServerResponse
@Component
public class MyUserHandler {
public ServerResponse getUser(ServerRequest request) {
...//具体的处理语句(从request中取出所需参数做对应处理后,调用ServerResponse中的方法将响应封装入ServerResponse)
return ServerResponse.ok().build();
}
public ServerResponse getUserCustomers(ServerRequest request) {
...
//body中的对象会利用HttpMessageConvertor将对象转换为json写入(类似于@ResponceBody的原理)
return ServerResponse.ok().body(Person);
}
public ServerResponse deleteUser(ServerRequest request) {
...
return ServerResponse.ok().build();
}
}
3.数据访问
SpringBoot 整合 Spring、SpringMVC、MyBatis 进行数据访问场景开发。
3.1 项目创建
导入MyBatis、和mysql-connector相关的starter
<!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
3.2 配置数据源
spring.datasource.url=jdbc:mysql://192.168.200.100:3306/demo
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
3.3 在配置文件中配置Mybatis
#指定mapper映射的xml文件位置
mybatis.mapper-locations=classpath:/mapper/*.xml
#开启驼峰命名
mybatis.configuration.map-underscore-to-camel-case=true
3.4 CRUD编写
● 编写Bean
● 编写Mapper
● 使用mybatisx插件,快速生成MapperXML,在XML文件中编写具体的sql语句
● 配置类上使用注解 @MaperScan("包名"),告诉springboot 接口的位置
● 测试CRUD
3.5 整合其他数据源
默认为HikariDataSource,也可整合其他数据源比如阿里的druid
- 导入druid-starter,在配置文件中更改配置即可
4.基础特性
4.1 SpringApplication
- 自定义 SpringApplication
在application中调用相应的set方法也能实现底层配置,若与外部配置文件重复,以配置文件为准。
import org.springframework.boot.Banner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(MyApplication.class);
application.setBannerMode(Banner.Mode.OFF);
application.run(args);
}
}
4.2 Profiles
- Spring Profiles 提供一种隔离配置的方式,使其仅在特定环境生效;
- 任何@Component, @Configuration 或 @ConfigurationProperties 可以使用 @Profile{"环境名"} 标记,来指定在那种环境下被加载。【容器中的组件都可以被 @Profile标记】
环境激活
- 在配置文件中加入
spring.profiles.active=production,myenv
- 也可以使用命令行激活。--spring.profiles.active=dev,myenv
- 还可以配置默认环境; 不标注@Profile 的组件永远都存在。spring.profiles.default=test(spring.profiles.default默认值为default)
环境包含
spring.profiles.include[0]=common
spring.profiles.include[1]=local
- 任何环境被激活,common和local都会存在
Profile分组
spring.profiles.group.prod[0]=db
spring.profiles.group.prod[1]=mq
- 使用
spring.profiles.active=prod
激活prod就会激活prod,db,mq配置文件
最终效果
- 生效的环境 = 激活的环境/默认环境 + 包含的环境
- 基础的配置mybatis、log、xxx:写到包含环境中
- 需要动态切换变化的 db、redis:写到激活的环境中
Profile 配置文件
- application-{profile}.properties可以作为指定环境的配置文件(命名方式固定)
- 激活这个环境,配置就会生效。
效果
application.properties:主配置文件,任意时候都生效
application-{profile}.properties:指定环境配置文件,激活指定环境生效
profile优先级 > application
4.3 外部化配置
SpringBoot 应用启动时会自动寻找application.properties和application.yaml位置,进行加载。顺序如下:
- 类路径: 内部
a. 类根路径
b. 类下/config包 - 当前路径(项目所在的位置)
a. 当前路径
b. 当前下/config子目录
c. /config目录的直接子目录
生效规律:最外层的最优先。
命令行 > 所有,包外 > 包内,config目录 > 根目录,profile > application 。
4.4 导入配置
使用spring.config.import可以导入额外的配置文件
spring.config.import=my.properties
4.5 属性占位符
配置文件中可以使用 ${name:default}形式取出之前配置过的值。
app.name=MyApp
app.description=${app.name} is a Spring Boot application written by ${username:Unknown}
4.6 单元测试
spring-boot-test提供核心测试能力,spring-boot-test-autoconfigure 提供测试的一些自动配置。
我们只需要导入spring-boot-starter-test 即可整合测试.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
注解
● @Test :表示方法是测试方法。
● @ParameterizedTest :表示方法是参数化测试,使得对于不同参数的同一方法不需重复编写测试的代码。
● @RepeatedTest :表示方法可重复执行
● @DisplayName :为测试类或者测试方法设置展示名称
● @BeforeEach :表示在每个单元测试之前执行
● @AfterEach :表示在每个单元测试之后执行
● @BeforeAll :表示在所有单元测试之前执行
● @AfterAll :表示在所有单元测试之后执行
● @Tag :表示单元测试类别,类似于JUnit4中的@Categories
● @Disabled :表示测试类或测试方法不执行,类似于JUnit4中的@Ignore
● @Timeout :表示测试方法运行如果超过了指定时间将会返回错误
● @ExtendWith :为测试类或测试方法提供扩展类引用
断言
assertEquals | 判断两个对象或两个原始类型是否相等 |
---|---|
assertNotEquals | 判断两个对象或两个原始类型是否不相等 |
assertSame | 判断两个对象引用是否指向同一个对象 |
assertNotSame | 判断两个对象引用是否指向不同的对象 |
assertTrue | 判断给定的布尔值是否为 true |
assertFalse | 判断给定的布尔值是否为 false |
assertNull | 判断给定的对象引用是否为 null |
assertNotNull | 判断给定的对象引用是否不为 null |
参数化测试
参数化测试是JUnit5很重要的一个新特性,它使得用不同的参数多次运行测试成为了可能,也为我们的单元测试带来许多便利。
利用@ValueSource等注解,指定入参,我们将可以使用不同的参数进行多次单元测试,而不需要每新增一个参数就新增一个单元测试,省去了很多冗余代码。
@ValueSource: 为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型
@NullSource: 表示为参数化测试提供一个null的入参
@EnumSource: 表示为参数化测试提供一个枚举入参
@CsvFileSource:表示读取指定CSV文件内容作为参数化测试入参
@MethodSource:表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流)
@ParameterizedTest
@ValueSource(strings = {"one", "two", "three"})
@DisplayName("参数化测试1")
public void parameterizedTest1(String string) {
System.out.println(string);
Assertions.assertTrue(StringUtils.isNotBlank(string));
}
@ParameterizedTest
@MethodSource("method") //指定方法名
@DisplayName("方法来源参数")
public void testWithExplicitLocalMethodSource(String name) {
System.out.println(name);
Assertions.assertNotNull(name);
}
static Stream<String> method() {
return Stream.of("apple", "banana");
}
嵌套测试
JUnit 5 可以通过 Java 中的内部类和@Nested 注解实现嵌套测试,从而可以更好的把相关的测试方法组织在一起。在内部类中可以使用@BeforeEach 和@AfterEach 注解,而且嵌套的层次没有限制。
5.核心原理
5.1 事件和监听器
生命周期监听
1.监听器-SpringApplicationRunListener,能够监听应用的生命周期
- 监听器定义方法
a.创建监听器类,实现SpringApplicationRunListener接口(可重写接口的七种方法,分别在不同的生命周期中调用)
b.在 META-INF/spring.factories 中配置 org.springframework.boot.SpringApplicationRunListener=自己的Listener的全类名
/**
* Listener先要从 META-INF/spring.factories 读到
*
* 1、引导: 利用 BootstrapContext 引导整个项目启动
* starting: 应用开始,SpringApplication的run方法一调用,只要有了 BootstrapContext 就执行
* environmentPrepared: 环境准备好(把启动参数等绑定到环境变量中),但是ioc还没有创建;【调一次】
* 2、启动:
* contextPrepared: ioc容器创建并准备好,但是sources(主配置类)没加载。并关闭引导上下文;组件都没创建 【调一次】
* contextLoaded: ioc容器加载。主配置类加载进去了。但是ioc容器还没刷新(bean还没创建)。
* =======截止以前,ioc容器里面还没造bean=======
* started: ioc容器刷新了(所有bean造好了),但是 runner 没调用。
* ready: ioc容器刷新了(所有bean造好了),所有 runner 调用完了。
* 3、运行
* 以前步骤都正确执行,代表容器running。
*/
2.生命周期
事件触发时机
1.各种回调监听器
● BootstrapRegistryInitializer: 感知特定阶段:感知引导初始化
创建引导上下文bootstrapContext的时候触发。
创建方法:
META-INF/spring.factories
application.addBootstrapRegistryInitializer();
场景:进行密钥校对授权。
● ApplicationContextInitializer: 感知特定阶段: 感知ioc容器初始化
创建方法:
META-INF/spring.factories
application.addInitializers();
● ApplicationListener: 感知全阶段:基于事件机制,感知事件。 到了哪个阶段可以做自定义的事
@Bean或@EventListener: 事件驱动
创建方法:
SpringApplication.addListeners(…)或 SpringApplicationBuilder.listeners(…)
META-INF/spring.factories
● SpringApplicationRunListener: 感知全阶段生命周期 + 各种阶段都能自定义操作; 功能更完善。
创建方法:
META-INF/spring.factories
● ApplicationRunner: 感知特定阶段:感知应用就绪Ready。卡死应用,就不会就绪
创建方法:
@Bean (因为此时容器已经refresh完成,该类型的监听器就是直接从容器中取得的)
● CommandLineRunner: 感知特定阶段:感知应用就绪Ready。卡死应用,就不会就绪
创建方法:
@Bean(因为此时容器已经refresh完成,该类型的监听器就是直接从容器中取得的)
- 最佳实战:
● 如果项目启动前做事: BootstrapRegistryInitializer 和 ApplicationContextInitializer
● 如果想要在项目启动完成后做事:ApplicationRunner和 CommandLineRunner
● 如果要干涉生命周期做事:SpringApplicationRunListener
● 如果想要用事件机制:ApplicationListener
2.完整触发流程
9大事件触发顺序&时机
- ApplicationStartingEvent:应用启动但未做任何事情, 除过注册listeners and initializers.
- ApplicationEnvironmentPreparedEvent: Environment 准备好,但context 未创建.
- ApplicationContextInitializedEvent: ApplicationContext 准备好,ApplicationContextInitializers 调用,但是任何bean未加载
- ApplicationPreparedEvent: 容器刷新之前,bean定义信息加载
- ApplicationStartedEvent: 容器刷新完成, runner未调用
=以下就开始插入了探针机制==== - AvailabilityChangeEvent: LivenessState.CORRECT应用存活; 存活探针
- ApplicationReadyEvent: 任何runner被调用
- AvailabilityChangeEvent:ReadinessState.ACCEPTING_TRAFFIC就绪探针,可以接请求
- ApplicationFailedEvent :启动出错
- 感知应用是否存活了:可能植物状态,虽然活着但是不能处理请求。
- 应用是否就绪了:能响应请求,说明确实活的比较好。
- 探针机制主要在后面在云上的部署有很大作用。
3.SpringBoot事件驱动开发
应用启动过程生命周期事件感知(9大事件)、应用运行中事件感知(自定义)。
● 事件发布:ApplicationEventPublisherAware或注入:ApplicationEventMulticaster
● 事件监听:组件 + @EventListener
- 示例场景:
- 示例代码:
1)创建自定义的事件,继承ApplicationEvent,并可重写其有参构造方法,使事件可携带参数
class LoginSuccessEvent entends ApplicationEvent{
public LoginSuccessEvent(User user){
super(user);
}
}
2)创建事件发布者
@Service
public class EventPublisher implements ApplicationEventPublisherAware {
/**
* 底层发送事件用的组件,SpringBoot会通过ApplicationEventPublisherAware接口自动注入给我们
* 事件是广播出去的。所有监听这个事件的监听器都可以收到
*/
ApplicationEventPublisher applicationEventPublisher;
/**
* 所有事件都可以发
* @param event
*/
public void sendEvent(ApplicationEvent event) {
//调用底层API发送事件
applicationEventPublisher.publishEvent(event);
}
/**
* 会被自动调用,把真正发事件的底层组组件给我们注入进来
* @param applicationEventPublisher event publisher to be used by this object
*/
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
}
3)事件订阅者
@Service
public class CouponService {
@Order(1) //控制执行优先级(监听同一时间的事件订阅者方法,Order越小执行越晚)
@EventListener //标记为事件监听器
public void onEvent(LoginSuccessEvent loginSuccessEvent){ //传入要监听的事件,若全部监听,就传ApplicationEvent类型
System.out.println("===== CouponService ====感知到事件"+loginSuccessEvent);
UserEntity source = (UserEntity) loginSuccessEvent.getSource(); //取出事件中携带的数据
sendCoupon(source.getUsername()); //调用服务方法
}
public void sendCoupon(String username){
System.out.println(username + " 随机得到了一张优惠券");
}
}
也可使Service类直接实现ApplicationListener接口,接口的泛型为我们要监听的事件(没有以上方法方便)
@Service
public class CouponService implements ApplicationListener<LoginSuccessEvent>{
public void sendCoupon(String username){
System.out.println(username + " 随机得到了一张优惠券");
}
public void onApplicationEvent(LoginSuccessEvent loginSuccessEvent){
System.out.println("===== CouponService ====感知到事件"+loginSuccessEvent);
UserEntity source = (UserEntity) loginSuccessEvent.getSource();
sendCoupon(source.getUsername());
}
}
5.2 自动配置原理
- 前两章已介绍过了了,此处不再赘述
SPI机制
SPI机制
● Java中的SPI(Service Provider Interface)是一种软件设计模式,用于在应用程序中动态地发现和加载组件。SPI的思想是,定义一个接口或抽象类,然后通过在classpath中定义实现该接口的类来实现对组件的动态发现和加载。
● SPI的主要目的是解决在应用程序中使用可插拔组件的问题。例如,一个应用程序可能需要使用不同的日志框架或数据库连接池,但是这些组件的选择可能取决于运行时的条件。通过使用SPI,应用程序可以在运行时发现并加载适当的组件,而无需在代码中硬编码这些组件的实现类。
● 在Java中,SPI的实现方式是通过在META-INF/spring/services目录下创建一个以服务接口全限定名为名字的文件,文件中包含实现该服务接口的类的全限定名。当应用程序启动时,Java的SPI机制会自动扫描classpath中的这些文件,并根据文件中指定的类名来加载实现类。
● 通过使用SPI,应用程序可以实现更灵活、可扩展的架构,同时也可以避免硬编码依赖关系和增加代码的可维护性。
5.3 自定义starter
1.定义自己的starter包,引入spring-boot-starter基础依赖
2.开发主要功能,并引入相关依赖
2.1 将属性类与配置文件前缀绑定
@ConfigurationProperties(prefix = "robot") //此属性类和配置文件指定前缀绑定
@Component
@Data
public class RobotProperties {
private String name;
private String age;
private String email;
}
2.2 导入配置处理器,配置文件自定义的properties配置都会有提示
<!-- 导入配置处理器,配置文件自定义的properties配置都会有提示-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
3.编写xxxAutoConfiguration
自动配置类,利用@Import
导入这个模块需要的所有组件
4.利用使用@EnableXxx机制,定义@EnableXxx注解,@import(xxxAutoConfiguration.class)
,别人引入starter可以使用 @EnableRobot开启功能
5.利用SPI机制,完全自动配置
● META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中编写好我们自动配置类的全类名即可
● 项目启动,自动加载我们的自动配置类
这样别的项目只需要导入我们自定义的starter,就会导入该自动配置类从而import相关的所有组件进入容器。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!