尚硅谷SpringBoot2核心技术-核心功能(二)
配置文件
文件类型
properties
同以前的properties用法
比如给user下的name赋值
1 | xxx = |
yaml
简介
YAML是“YAML Ain’t Markup Language”(YAML不是一种标记语言)的递归缩写。
在开发这种语言时,YAML的意思其实是:“Yet Another Markup Language”(仍是一种标记语言)
非常时候用来做以数据为中心的配置文件
基本语法
key: value
kv之间有空格- 大小写敏感
- 使用缩进表示层级关系
- 缩进不允许使用tab,只允许使用空格
- 缩进的空格数不重要,只要相同层级的元素左对齐即可
#
表示注释- 字符串无需加引号,如果要加,’’与””表示字符串内容会被 转义/不转义
数据类型
- 字面量:单个的、不可再分的值,date、boolean、string、number、null
1 | k: v |
- 对象:键值对的集合,map、hash、object
1 | # 行内写法 |
- 数组:一组按照次序排列的值,array、list、queue
1 | # 行内写法 |
配置提示
自定义的类和配置文件绑定一般没有提示
不过可以加入一下依赖就会有提示
1 | <dependency> |
不过这个注解只是在开发的时候使用,打包的时候并没有必要打进去,可以通过一下配置进行排除
1 | <build> |
web开发
SpringMVC自动配置概览
Spring Boot provides auto-configuration for Spring MVC that works well with most applications.(大多场景我们都无需自定义配置)
The auto-configuration adds the following features on top of Spring’s defaults:
- Inclusion of
ContentNegotiatingViewResolver
andBeanNameViewResolver
beans.- 内容协商视图解析器和BeanName视图解析器
- Support for serving static resources, including support for WebJars (covered later in this document)).
- 静态资源(包括webjars)
- Automatic registration of
Converter
,GenericConverter
, andFormatter
beans.- 自动注册
Converter,GenericConverter,Formatter
- 自动注册
- Support for
HttpMessageConverters
(covered later in this document).- 支持
HttpMessageConverters
(后来我们配合内容协商理解原理)
- 支持
- Automatic registration of
MessageCodesResolver
(covered later in this document).- 自动注册
MessageCodesResolver
(国际化用)
- 自动注册
- Static
index.html
support.- 静态index.html 页支持
- Custom
Favicon
support (covered later in this document).- 自定义
Favicon
- 自定义
- Automatic use of a
ConfigurableWebBindingInitializer
bean (covered later in this document).- 自动使用
ConfigurableWebBindingInitializer
,(DataBinder负责将请求数据绑定到JavaBean上)
- 自动使用
If you want to keep those Spring Boot MVC customizations and make more MVC customizations (interceptors, formatters, view controllers, and other features), you can add your own
@Configuration
class of typeWebMvcConfigurer
but without@EnableWebMvc
.
不用@EnableWebMvc注解。使用@Configuration
+WebMvcConfigurer
自定义规则
If you want to provide custom instances of
RequestMappingHandlerMapping
,RequestMappingHandlerAdapter
, orExceptionHandlerExceptionResolver
, and still keep the Spring Boot MVC customizations, you can declare a bean of typeWebMvcRegistrations
and use it to provide custom instances of those components.
声明WebMvcRegistrations
改变默认底层组件
If you want to take complete control of Spring MVC, you can add your own
@Configuration
annotated with@EnableWebMvc
, or alternatively add your own@Configuration
-annotatedDelegatingWebMvcConfiguration
as described in the Javadoc of@EnableWebMvc
.
使用@EnableWebMvc
+@Configuration
+DelegatingWebMvcConfiguration
全面接管SpringMVC**
简单功能分析
静态资源访问
静态资源目录
只要静态资源放在类路径下:/static
(or /public
or /resources
or /META-INF/resources
)
访问地址:当前项目根路径/+静态资源名(http://127.0.0.1:8080/123.jpg)
原理:静态映射/**
接收到请求,先查找controller是否能够处理,如果不能处理,就把请求交给静态资源处理器;静态资源处理器也找不到则响应404页面(如果有rest路径和静态资源路径恰好相同,rest路径优先响应)
改变默认的静态资源路径
1 | spring: |
静态资源访问前缀
默认无前缀,下面配置需要访问(http://127.0.0.1:8080/res/123.jpg)
1 | spring: |
webjar
把静态资源以jar包的形式引入
自动映射 /webjar/**
文件夹
WebJars - Web Libraries in Jars
1 | <dependency> |
访问地址:http://127.0.0.1:8080/webjars/**jquery/3.5.1/jquery.js**
后面地址要按照依赖里面的包路径
欢迎页支持
- 静态资源路径下 index.html
- 可以配置静态资源路径
- 但不可以配置静态资源访问前缀,否则导致index.html不能被默认访问到
1
2
3spring:
mvc:
static-path-pattern: /res/** # 这个配置会导致 welcome page功能失效
自定义Favicon
favicon.ico放在静态资源目录下即可自动访问spring.mvc.static-path-pattern
配置同样会导致该功能失效
静态资源配置原理
springboot启动默认加载
xxxAutoConfiguration
类(自动配置类)SpringMVC功能的自动配置类
WebMvcAutoConfiguration
,生效1
2
3
4
5
6
7
8
9
10
//是一个servlet项目
//项目中包含这三个类
//项目中不存在这个bean
public class WebMvcAutoConfiguration {}给容器中配了什么
1
2
3
4
5
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware {}配置文件的相关属性和xxx进行了绑定
WebMvcProperties
==spring.mvc
WebProperties
==spring.web
配置类只有一个有参构造器
1 | //在只有一个构造器的情况下,创建这个bean会从容器中获取构造器中的所有参数 |
资源处理的默认规则
1 |
|
1 | public static class Resources { |
欢迎的处理规则
1 |
|
1 | WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders, |
1 | static WelcomePage resolve(TemplateAvailabilityProviders templateAvailabilityProviders, |
从上面的代码可以看到,欢迎页的最终是在底层写死了 /**
,所以当我们自定义前缀后,无法匹配也就会导致欢迎页失效。
favicon
favicon.ioc
是浏览器进行处理的,默认会去根目录下寻找,如果加入了前缀,就会导致图标不在根目录下,也就会找不到。
请求参数处理
请求映射
rest使用与原理
xxxMapping
- rest风格支持,使用HTTP请求方式动词来表示对资源的操作
- 以前:
/getUser
获取用户/deleteUser
删除用户/editUser
修改用户/saveUser
保存用户 - 现在:
/user
GET-获取用户 DELETE-删除用户 PUT-修改用户 POST-保存用户 - 核心Filter,
HiddenHttpMethodFilter
(仅限于表单请求,表单只能发送post和get请求,可以使用这个Filter实现delete和put请求,现在一般都是前后端分离,可以直接发送对应的请求)- 用法:表单的
meehod=post
,隐藏域_method=put
1
2
3
4<form method="post">
<input name="_method" value="delete" type="hidden">
<input type="button" value="发送delete请求">
</form> - SpringBoot中手动开启
1
2
3
4
5spring:
mvc:
hidden-method:
filter:
enabled: true
- 用法:表单的
- 扩展:如何把
_method
定义成自己喜欢的名字只需要定义一个Bean即可,spring默认的Filter上面标记了1
2
3
4
5
6
public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
methodFilter.setMethodParam("_m");
return methodFilter;
}@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
注解,当我们自定义了这个类型的Bean之后,springboot默认的就不会生效。
- 以前:
rest原理:
- 表单提交会带上_method=PUT
- 请求过来被
HiddenHttpMethodFilter
拦截- 请求是否正常,并且是POST请求
- 获取到
_method
的值 - 兼容一下请求:PUT, DELETE, PATCH
- 原生request(post),包装模式
requestWrapper
重写了getMethod
方法,返回的是传入的值 - 过滤器链放行的时候用
requestWrapper
,以后调用getMethod
方法调用的是requestWrapper
的
- 获取到
- 请求是否正常,并且是POST请求
rest使用客户端工具:
- 如PostMan直接发送PUT、DELETE等方式请求,无需Filter
请求映射原理
分析DispatcherServlet
直接看doDispatch
方法即可
1 | protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { |
springboot默认创建了6个HandlerMapping
,其中RequestMappingHandlerMapping
用来处理我们平时定义在Controller中的请求,保存了所有@RequestMapping
和Handler
的映射规则
所有的请求映射都在HandlerMapping
中
- SpringBoot自动配置欢迎页的
WelcomePageHandlerMapping
,访问/
能访问到index.html
- SpringBoot自动配置了默认的
RequestMappingHandlerMapping
- 请求进来,挨个尝试所有的
HandlerMapping
看是否能处理请求信息- 如果有就找到这个请求对应的handler
- 如果没有就下一个
HandlerMapping
- 我们如果需要自定义的映射处理,也可以自己给容器中放
HandlerMapping
1
2
3
4
5
6
7
8
9
10
11protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
普通参数与基本注解
注解
@PathVariable
- 从请求路径中变量的值
- 可以使用变量名称一个一个获取
- 也可以使用
Map<String,String>
方式获取所有的值,key和value只能是String1
2
3
4
public Map<String,Object> test( Integer id,
String name,
Map<String,String> pv){ ... }
@RequestHeader
- 获取请求头中的信息
- 可以通过名称获取请求头中单个参数的信息
- 可以使用
Map<String, String>
获取所有请求头中的信息 - 可以使用
MultiValueMap<String, String>
(多值map)获取所有请求头中的信息 - 可以使用
HttpHeaders
获取请求中的信息1
2
3
4
5
public Map<String, Object> test( String userAgent,
Map<String, String> pv,
MultiValueMap<String, String> mpv,
HttpHeaders httpHeaders){ ... }
@RequestParam
- /test?id=1&name=liming&hobby=篮球&hobby=足球
- 从请求中获取参数
- 可以通过名称获取单个参数
- 如果有多个名称相同的参数,可以使用集合接收参数
- 可以使用
Map<String, String>
获取所有参数,不过多个相同名称的参数只会保留第一个 - 可以使用
MultiValueMap<String, String>
获取参数,同名参数会存放到集合中1
2
3
4
5
6
7
public Map<String, Object> test( Integer id,
String name,
List<String> hobby,
Map<String,String> pv,
MultiValueMap<String,String> mpv
) { ... }
@CookieValue
- 获取cookie中的值
- 可以通过cookie中的key获取Cookie中某个key的值
- 可以通过key获取Cookie中的Cookie对象
1
2
3
public Map<String, Object> test( String cookie1,
Cookie cookie2 ){ ... }
@RequestBody
- 从请求中获取参数(因为只有post和put请求才有请求体,所以只有在这两种请求类型中使用)
- 把json串映射成
1
2
public Map<String, Object> test( Map<String,String> content ){ ... }
@RequestAttribute
- 从请求域中获取参数
forward
关键字转发,将一个请求转发到某个路径,两个请求公用的一个请求信息,所以可以从请求域中携带数据- 可以使用注解获取,也可以直接使用request方法获取
- 一般用户前后端不分离的项目传值使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class RequestController {
public String gotoPage(HttpServletRequest request){
request.setAttribute("name","小明");
return "forward:/success";
}
public Map<String,Object> index( String name){
Map<String,Object> map = new HashMap<>();
map.put("name",name);
return map;
}
}
@MatrixVariable
- 矩阵变量,需要绑定到路径变量中
- 语法:/cars/sell;low=34;brand=byd,audi,yd
1
2
3
4
5// /cars/sell;low=34;brand=byd,audi,yd
public Map<String, Object> cars( Integer low,
List<String> brand,
String path){...} - 矩阵变量是绑定在路径变量中的,路径变量可以设置多个,如果多个路径变量中有名称相同的矩阵变量,可以设置参数根据路径变量的名称获取矩阵变量,如果不设置,直接以矩阵变量名称获取,默认会获取到第一个
1
2
3
4// boss/1;age=12/2;age=34
public Map<String, Object> boss( Integer bossAge,
Integer empAge){ ... } - springboot默认禁用矩阵变量,需要手动配置开启
- 路径处理是通过
UrlPathHelper
进行处理的 - 其中有一个变量
removeSemicolonContent
(是否移除分号),该变量默认为true需要修改成false1
2
3
4
5
6
7
8
9
10
11
12
13//自定义WebMvcConfigurer,替换springboot的默认定义
//也可以继承WebMvcConfigurer接口,实现其中的方法进行处理
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer(){
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
urlPathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(urlPathHelper);
}
};
}
- 路径处理是通过
Servlet API
WebRequest
、ServletRequest
、MultipartRequest
、 HttpSession
、javax.servlet.http.PushBuilder
、Principal
、InputStream
、Reader
、HttpMethod
、Locale
、TimeZone
、ZoneId
ServletRequestMethodArgumentResolver
以上的部分参数
1 |
|
复杂参数
Map
、Model
(map、model里面的数据会被放在request的请求域 request.setAttribute
)、Errors/BindingResult
、RedirectAttributes
( 重定向携带数据)、ServletResponse(response)
、SessionStatus
、UriComponentsBuilder
、ServletUriComponentsBuilder
Map<String,Object> map、Model model HttpServletRequest request都可以给request域中放数据
示例代码如下,向Map、Model和request中分别放入参数,然后转发到另外一个接口,尝试能不能从request作用域中取出参数
1 |
|
- Map、Model类型的参数,会返回
mavContainer.getModel()
;两种类型的参数,返回的是同一个对象,对象类型是BindingAwareModelMap
,这个类既是Map又是Model(同时继承了Model和Map接口并做了相应实现)org.springframework.web.method.support.ModelAndViewContainer#getModel
第一个参数是Map参数,第二个是Model参数,他们获取到的是同一个对象
可以看到Map和Model中存放的参数最终都会到同一个对象中
自定义对象参数
可以自动类型转换与格式化,可以级联封装。
1 | /** |
POJO封装过程
ServletModelAttributeMethodProcessor
只是单体项目表单传参,前后端分离项目传的是json数据使用的是 RequestResponseBodyMethodProcessor
参数处理原理
HandlerMapping
中找到能处理请求的Handler(Controller.method()
那个类的那个方法)- 为当前Handler找到一个适配器
HandlerAdapter
;RequestMappingHandlerAdapter
- 适配器执行目标方法并确定方法参数的每一个值
HandlerAdapter
0- 支持方法上标注了@RequestMapping
注解的
1- 支持函数式编程
…..
执行目标方法
1 | //org.springframework.web.servlet.DispatcherServlet#doDispatch |
1 | //org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#handleInternal |
参数解析器-HandlerMethodArgumentResolver
- 确定要执行的木变更方法的每一个参数的值是什么;
- springboot目标方法能够写多少中参数类型,取决于参数解析器(目前springboot一共有27种解析器)
- 所有参数解析器的父接口
- 先调用
supportsParameter
方法判断当前解析器能不能解析该参数 - 如果能够解析,就调用
resolveArgument
方法进行解析
返回值处理器
- springboot能够返回多少中返回值类型,也取决于返回值处理器
如何确定目标方法每一个参数的值
1 | //org.springframework.web.method.support.InvocableHandlerMethod#getMethodArgumentValues |
挨个判断所有的参数解析器那个支持解析这个参数
1 | //org.springframework.web.method.support.HandlerMethodArgumentResolverComposite#getArgumentResolver |
解析这个参数的值
调用各自 HandlerMethodArgumentResolver 的 resolveArgument 方法即可
自定义类型参数 封装POJO
ServletModelAttributeMethodProcessor 这个参数处理器支持
是否为简单类型。
1 | public static boolean isSimpleValueType(Class<?> type) { |
1 |
|
**GenericConversionService
**:在设置每一个值的时候,找它里面的所有Converter那个可以将这个数据类型(request带来参数的字符串)转换到指定的类型(javaBean–Integer)
byte–file
1 |
|
未来我们可以给WebDataBinder
里面放自己的Converter
;private static final class StringToNumber<T extends Number> implements Converter<String, T>
自定义Converter
1 | //1、WebMvcConfigurer定制化SpringMVC的功能 |
目标方法执行完成
目标方法执行完毕后,将所有的数据都存放在ModelAndViewContainer
中,包含要去的页面地址View,还包含Model数据
处理派发结果
1 | //org.springframework.web.servlet.DispatcherServlet#doDispatch |
1 | //org.springframework.web.servlet.view.AbstractView#render |
1 | //org.springframework.web.servlet.view.InternalResourceView#renderMergedOutputModel |
1 | protected void exposeModelAsRequestAttributes(Map<String, Object> model, |
数据响应与内容协商
响应JSON
jackson.jar+@ResponseBody
1 | <dependency> |
1 | <!-- springboot的json处理主要是依靠jackson进行处理的,所以json场景中引入了jackson的依赖 --> |
我们只需要在controller方法或者类上面添加 @ResponseBody
注解,就会自动返回json格式数据
返回值解析器
springboot默认情况下有15个返回值解析器
1 | //org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#invokeAndHandle |
1 | //org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite |
1 | //org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor#handleReturnValue |
返回值解析器原理
- 1、返回值处理器判断是否支持这种返回值
supportsReturnType
- 2、返回值处理器调用
handleReturnValue
进行处理 - 3、
RequestResponseBodyMethodProcessor
可以处理返回值标了@ResponseBody
注解的- 1、利用
MessageConverters
进行处理将数据写为json- 1、内容协商(浏览器默认会以请求头的方式告诉服务器他能接受什么样的内容类型)
- 2、服务器最终根据自身能力,决定服务器能生成什么样的内容数据(浏览器可以接受
*/*
表示所有格式的数据) - 3、springMVC会挨个遍历容器底层的
HttpMessageConverter
,看谁能够处理?- 1、得到
MappingJackson2HttpMessageConverter
可以将对象写为JSON - 2、利用
MappingJackson2HttpMessageConverter
将对象转为JSON再写出去。
- 1、得到
- 1、利用
SpringMVC到底支持哪些返回值
1 | ModelAndView |
HttpMessageConverter原理
MessageConverter规范
HttpMessageConverter
:看是否支持将此 Class 类型的对象,转为MediaType
类型的数据
例如:将 Person 对象转为 JSON 或者将 JSON 转为 Person
默认的MessageConverter
(每个MessageConverter
中都会初始化自己能够支持的 MediaType
,Class类型匹配之后也需要支持的 MediaType
匹配才能处理)
0 - ByteArrayHttpMessageConverter
只支持返回类型为 byte[]
类型的
1 - StringHttpMessageConverter
只支持返回值类型为 String
类型的
2 - StringHttpMessageConverter
只支持返回值类型为 String
类型的
3 - ResourceHttpMessageConverter
只支持返回值类型为 Resource
接口实现类的
4 - ResourceRegionHttpMessageConverter
只支持返回值类型为 ResourceRegion
或其子类
5 - SourceHttpMessageConverter
支持类型为:DOMSource
、SAXSource
、StAXSource
、StreamSource
、Source
五种类型的
6 - FormHttpMessageConverter
只支持 MultiValueMap
类型
7 - MappingJackson2HttpMessageConverter
支持任何类型
8 - MappingJackson2HttpMessageConverter
支持任何类型
9 - Jaxb2RootElementHttpMessageConverter
标记了XmlRootElement
注解(支持注解方式xml处理)
最终MappingJackson2HttpMessageConverter
把对象转为JSON(利用底层Jackson的ObjectMapper
转换的)
内容协商
根据客户端接收能力不同,返回不同媒体类型的数据。
引入xml依赖
1 | <dependency> |
postman分别测试返回json和xml
只需要改变请求头中 Accept 字段,HTTP协议中规定的,告诉服务器本客户端可以接收的数据类型Accept: application/xml
接收xml格式数据Accept: application/json
接收json格式数据
开启浏览器参数方式内容协商功能
为了方便内容协商,开启基于请求参数的内容协商功能(浏览器在默认情况下是基于请求头的,但是浏览器的请求头没办法很方便的修改)
1 | spring: |
只需要在路径上添加format参数指定想要的请求类型即可
http://127.0.0.1:8080/test/person?format=xml
http://127.0.0.1:8080/test/person?format=json
新增了一个ParameterContentNegotiationStrategy
策略,策略中默认要解析的参数名称为format,并且支持xml和json两种格式数据
确定客户端接收什么样的内容类型:
1、ParameterContentNegotiationStrategy
策略优先确定是要返回json数据(获取请求头中的format的值)
2、最终进行内容协商返回给客户端json数据
内容协商原理
1、判断当前响应头中是否已经又确定的媒体类型。
MediaType
2、获取客户端(postMan、浏览器)支持接收的内容类型。(获取客户端Accept请求头字段)【
application/xml
】contentNegotiationManager
内容协商管理器 默认使用基于请求头的策略- HeaderContentNegotiationStrategy 确定客户端可以接收的内容类型
3、循环遍历当前系统中所有的
HttpMessageConverter
,看谁能支持操作这个对象(Person)4、找到支持操作
Person
的Converter
,把Converter
支持的媒体类型统计出来。5、客户端需要【
application/xml
】。服务端能力【10种、json、xml】6、进行内容协商匹配最佳媒体类型
7、再次循环所有的
HttpMessageConverter
找到支持处理返回值类型(Person
)并且支持最佳媒体类型的Converter
,调用它进行转化。
导入了jackson处理xml的依赖,xml的converter就会自动注入进来
1 | //WebMvcConfigurationSupport |
自定义MessageConverter
实现多协议数据兼容,json、xml、y-data(自定义类型)
0、@ResponseBody
响应数据出去,调用 RequestResponseBodyMethodProcessor
处理
1、Processor
处理方法返回值,通过HttpMessageConverter
处理
2、所有 HttpMessageConverter
合起来可以支持各种媒体类型数据的操作(读、写)
3、内容协商找到最终的 HttpMessageConverter
springMVC的功能,都有一个入口给容器中添加自定义,WebMvcConfigurer
1 |
|
请求的时候只需要设置请求头就可以实现数据格式切换
1 | GET http://127.0.0.1:8080/test/person |
自定义实现代码如下
1 | /** |
如何实现url参数形式切换数据格式?
自定义内容基于参数的内容协商策略
代码如下:
1 |
|
如上,自定义了基于参数的内容协商策略之后,由于是进行了覆盖,所以导致基于请求头的内容协商策略失效。
我们添加一些其他的功能也有可能导致同样的事情发生,所以有些扩展需要谨慎使用。
也可以使用如下配置来增加参数和媒体类型的对应:
1 | spring: |
视图解析与模板引擎
试图解析:springboot默认不支持JSP,需要引入第三方模板引擎技术实现页面渲染。
视图解析
视图解析原理流程
- 1、目标方法处理的过程中,所有数据都会被放在
ModelAndViewContainer
里面,包括数据和视图地址 - 2、方法的参数是一个自定义类型对象(从请求参数中确定的),也会放在
ModelAndViewContainer
里面 - 3、任何目标方法执行完成以后都会返回
ModelAndView
(数据和视图地址) - 4、
processDispatchResult
处理派发结果(页面该如何响应)- 1、
render(mv, request, response)
;进行页面渲染逻辑- 1、根据方法的String返回值得到 View 对象【定义了页面的渲染逻辑】
- 1、循环所有的视图解析器尝试是否能根据当前返回值得到view对象
- 2、得到了 login –>
ThymeleafView
(根据返回值得到对应的view) - 3、
ContentNegotiatingViewResolver
里面包含了下面所有的视图解析器,内部还是利用下面所有视图解析器得到视图对象 - 4、
view.render(mv.getModelInternal(), request, response)
; 视图对象调用自定义的 render 进行页面渲染工作RedirectView
如何渲染【重定向到一个页面】- 1、获取目标url地址
- 2、
response.sendRedirect(encodedURL)
; 重定向到啊对应页面
- 1、根据方法的String返回值得到 View 对象【定义了页面的渲染逻辑】
- 1、
视图解析:
- 返回值以
forward:
开始:new InternalResourceView(forwardUrl)
; –> 转发,request.getRequestDispatcher(path).forward(request, response)
; - 返回值以
redirect:
开始:new RedirectView()
–>render
就是重定向 - 返回值是普通字符串:
new ThymeleafView()
–> 使用视图解析器,进行文本解析,然后将解析后的视图返回到输出流
自定义视图解析器+自定义视图;
模板引擎-Thymeleaf
Thymeleaf简介
Thymeleaf is a modern server-side Java template engine for both web and standalone environments.
现代化、服务端Java模板引擎
基本语法
表达式
表达式名字 | 语法 | 用途 |
---|---|---|
变量取值 | ${…} | 获取请求域、session域、对象等值 |
选择变量 | *{…} | 获取上下文对象值 |
消息 | #{…} | 获取国际化等值 |
链接 | @{…} | 生成链接 |
片段表达式 | ~{…} | jsp:include作用,引入公共页面片段 |
字面量
文本值:’one text’ 单引号
数字:0,12,3.0 直接写数字即可
布尔值: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
设置单个值:
1 | <form action="subscribe.html" th:attr="action=@{/subscribe}"> |
设置多个值:
1 | <img src="../../images/gtvglogo.png" th:attr="src=@{/images/gtvglogo.png},title=#{logo},alt=#{logo}" /> |
以上两个的代替写法 th:xxx
1 | <input type="submit" value="Subscribe!" th:value="#{subscribe.submit}"/> |
所有h5兼容的标签写法:
https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#setting-value-to-specific-attributes
迭代
1 | <tr th:each="prod : ${prods}"> |
1 | <tr th:each="prod,iterStat : ${prods}" th:class="${iterStat.odd}? 'odd'"> |
条件运算
1 | <a href="comments.html" |
1 | <div th:switch="${user.role}"> |
属性优先级
thymeleaf使用
引入Starter
1 | <dependency> |
自动配置好了Thymeleaf
1 |
|
自动配好的策略:
- 1、所有thymeleaf的配置值都在
ThymeleafProperties
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration
- 2、配置好了
SpringTemplateEngine
(模板引擎)org.springframework.boot.autoconfigure.thymeleaf.TemplateEngineConfigurations
- 3、配好了
ThymeleafViewResolver
(视图解析器)org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration
- 4、我们只需要直接开发页面就可以了
1 | //ThymeleafProperties中默认的前缀和后缀 |
页面开发
1 |
|
拦截器
HandlerInterceptor接口
1 | /** |
配置拦截器
1 | /** |
拦截器原理
- 1、根据当前请求,找到 HandlerExecutionChain 【可以处理请求的handler以及handler的所有拦截器集合】
- 2、先顺序执行所有拦截器的
preHandle
方法- 1、如果当前拦截器的
preHandle
方法返回true,则执行下一个拦截器的preHandle
- 2、如果当前拦截器
preHandle
方法返回false,直接 倒序执行所有已经执行了的拦截器的afterCompletion
方法
- 1、如果当前拦截器的
- 3、如果任何一个拦截器返回false,则直接跳出不执行目标方法
- 4、所有拦截器都返回true,执行目标方法
- 5、倒序执行所有拦截器的
postHandle
方法, - 6、前面的步骤有任何异常都会直接倒序执行
afterCompletion
- 7、页面成功渲染完成以后,也会倒序触发
afterCompletion
方法
文件上传
页面表单
1 | <form th:action="@{/upload}" method="post" enctype="multipart/form-data"> |
文件上传代码
1 |
|
自动配置原理
文件上传自动配置类-MultipartAutoConfiguration-MultipartProperties
- 自动配置好了
StandardServletMultipartResolver
(文件上传解析器) - 原理步骤
- 1、请求进来使用文件上传解析器判断(
isMultipart
)并封装(resolveMultipart
,返回MultipartHttpServletRequest
)文件上传请求 - 2、参数解析器来解析请求中的文件内容,并封装成MultipartFile
- 3、将request中文件信息封装为Map;
LinkedMultiValueMap<String,List\<MultipartFile>>
FileCopyUtils
,工具类,方便文件复制
- 1、请求进来使用文件上传解析器判断(
异常处理
错误处理
默认规则
- 默认情况下,SpringBoot提供
/error
处理所有的错误映射 - 对于机器客户端,它将生成JSON响应,其中包含错误,HTTP状态和异常消息的详细信息,对于浏览器客户端,响应一个“whitelabel”错误视图,以HTML格式呈现相同的数据
- 要对其进行自定义,添加
View
解析为error
- 要完全替换默认行为,可以实现
ErrorController
并注册该类型的Bean定义,或添加ErrorAttributes
类型的组件,以使用现有机制替换其内容 erro/
下的4xx、5xx页面会被自动解析(当错误状态码为4开头的会自动找4xx页面,当错误状态码为5开头的会找5xx页面)
定制错误处理逻辑
- 自定义错误页
error/404.html
error/500.html
;有精确错误状态页面就匹配精确,没有就找非精确,比如4xx.html
,如果还没有,就展示默认错误页@ControllerAdvice
+@ExceptionHandler
处理全局异常;底层是ExceptionHandlerExceptionResolver
支持的- 启动时根据
@ExceptionHandler
注解判断哪个方法可以处理哪些异常,然后抓取到对应异常后就调用对应的方法,然后根据方法的返回值再进行处理
- 启动时根据
@ResponseStatus
+自定义异常,底层是ResponseStatusExceptionResolver
支持- 把ResponseStatus注解中标注的信息,底层调用
response.sendError(statusCode, resolvedReason);
tomcat发送的/error
- 把ResponseStatus注解中标注的信息,底层调用
- spring底层的异常,如参数类型转换异常;
DefaultHandlerExceptionResolver
处理;- 判断异常属于什么类型,然后设置对应的状态码,调用
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex.getMessage());
进行转发
- 判断异常属于什么类型,然后设置对应的状态码,调用
- 自定义实现
HandlerExceptionResolver
处理异常,可以作为默认的全局异常处理规则;(默认优先级最低,可以使用@Order
注解调整优先级) - 自定义
ErrorViewResolver
实现异常处理- 默认寻找可以处理对应错误的视图,比如
4xx.html
- 如果该异常没有任何情况能够处理,就会调用tomcat底层的
response.sendError()
方法,然后就会再次发起一次/error的请求,请求就会被对应controller接收处理 BasicErrorController
要去的页面地址是ErrorViewResolver
- 默认寻找可以处理对应错误的视图,比如
异常处理自动配置原理
- ErrorMvcAutoConfiguration自动配置异常处理规则
- 容器中的组件:类型:DefaultErrorAttributes –> id:errorAttributes
- public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver, Ordered
DefaultErrorAttributes
:定义错误页面中可以包含哪些数据。
- 容器中的组件:类型:BasicErrorController –> id:basicErrorController(json+白页 适配响应)
- 默认处理
/error
路径的请求,页面响应new ModelAndView("error", model)
- 容器中有组件 View –> id是error;(默认响应错误页)
- 容器中放组件
BeanNameViewResolver
(视图解析器);按照返回的视图名作为组件的id去容器中找view对象
- 默认处理
- 容器中的组件:类型:DefaultErrorViewResolver –> id:conventionErrorViewResolver
- 如果发生错误,会以HTTP的状态码作为视图地址(viewName),找到真正的页面
- error/4xx、5xx.html
- 容器中的组件:类型:DefaultErrorAttributes –> id:errorAttributes
BasicErrorController类上的@RequestMapping("${server.error.path:${error.path:/error}}")
表示如果设置了server.error.path
,就使用这个设置,如果没有设置就使用error.path
,如果这个也没有设置,就默认使用/error
;
类中有两个方法,第一个方法上标注了注解@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
,表示处理html类型的请求,返回值是ModelAndView
,视图名称是error
,最终会找到设置好的错误视图页面;
类中另一个方法,没有做特殊配置,返回json类型数据,也是json类型的请求.
异常处理步骤流程
- 1、执行目标方法,目标方法运行期间有任何异常都会被catch,而且标志当前请求结束,并且用
dispatchException
将异常信息进行封装 - 2、进入视图解析流程(页面渲染?)
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
- 会把视图和错误信息都传递进去,视图只有在方法正确执行的情况下才会有值
- 3、
mv = processHandlerException(request, response, handler, exception);
处理handler发生的异常,处理完成返回ModelAndView
;- 遍历所有的
handlerExceptionResolvers
,看谁能处理当前异常【HandlerExceptionResolver
处理器异常解析器】 - 2、系统默认的异常解析器:
DefaultErrorAttributes
先类处理异常,会把异常信息保存到request请求域中,并且返回null;- 默认没有任何处理器能处理异常,所以异常会被抛出
- 1、如果没有任何处理器能够处理异常,本次请求就会结束,然后底层会再次发起 /error 请求,该请求会被
BasicErrorController
处理 - 2、解析错误视图,遍历所有的
ErrorViewResolver
看谁能够解析(默认情况下只有一个) - 3、默认的
DefaultErrorViewResolver
,作用是把响应状态码作为错误页面的地址,error/500.html
- 4、模板引擎最终响应这个页面
error/500.html
- 1、如果没有任何处理器能够处理异常,本次请求就会结束,然后底层会再次发起 /error 请求,该请求会被
- 遍历所有的
Web原生组件注入(Servlet、Filter、Listenter)
使用Servlet API
- 在启动类上标记
@ServletComponentScan
注解,表示扫描哪些包下的servlet组件,默认是当前类所在包及其子包,也可以通过注解参数指定 @WebServlet(urlPatterns = "/my")
创建一个Servlet,会直接响应结果,没有经过Spring的拦截器- 在类上标记对应注解,并继承HttpServlet类重写对应的方法即可实现
@WebFilter(urlPatterns = "/*")
创建一个Servlet过滤器- 在类上标记对应注解,并继承Filter接口重写相应的方法
@WebListener
创建一个servlet监听器- 在类上标记对应注解,并继承对应的监听接口实现相应方法即可
扩展:DispatcherServlet
是如何注册进来的
- 在
DispatcherServletAutoConfiguration
中向容器注册了DispatcherServlet
的Bean - 容器中自动配置了
DispatcherServlet
,属性绑定到WebMvcProperties
;对应的配置文件配置项是spring.mvc
- 之后又将
DispatcherServlet
的bean作为入参做了二次封装,封装成DispatcherServletRegistrationBean
DispatcherServlet
默认映射的是/
路径
Tomcat-Servlet
多个Servlet都能处理同一层路径时,精确优先原则
使用RegistrationBean
ServletRegistrationBean
、FilterRegistrationBean
、ServletListenerRegistrationBean
使用spring提供的三个类进行封装,然后注册成Bean
1 |
|
嵌入式Servlet容器
切换嵌入式Servlet
- 默认支持的WebServer
Tomcat
、Jetty
、Undertow
ServletWebServerApplicationContext
容器启动自动寻找ServletWebServerFactory
并引导创建服务
- 切换服务器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15<!-- 想要切换springboot的web服务器,只需要将默认的tomcat依赖从web中排除然后引入新的web服务器的依赖即可 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
- 原理
- SpringBoot应用启动发现当前是Web应用。web场景包-默认导入tomcat
- web应用会创建一个web版的ioc容器,
ServletWebServerApplicationContext
ServletWebServerApplicationContext
启动的时候寻找ServletWebServerFactory
(Servlet的web服务器工厂—>Servlet的web服务器)- SpringBoot底层默认又很多的WebServer工厂;
TomcatServletWebServerFactory
、JettyServletWebServerFactory
、UndertowServletWebServerFactory
- 底层直接会又一个自动配置类。
ServletWebServerFactoryAutoConfiguration
ServletWebServerFactoryAutoConfiguration
导入了ServletWebServerFactoryConfiguration
(配置类)ServletWebServerFactoryConfiguration
配置类 根据动态判断系统中到底导入了哪个web服务器的包来创建不同的工厂(默认web-starter导入的是tomcat包),容器中就有了TomcatServletWebServerFactory
TomcatServletWebServerFactory
创建出Tomcat服务器并启动;TomcatWebServer
的构造器拥有初始化方法initialize()
,该方法中执行了this.tomcat.start();
启动tomcat- 内嵌服务器,就是手动把启动服务器的代码调用(tomcat核心jar包存在)
定制Servlet容器
- 实现
WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>
- 把配置文件的值和
ServletWebServerFactory
进行绑定
- 把配置文件的值和
- 修改配置文件
server.xxx
- 直接自定义
ConfigurableServletWebServerFactory
xxxxCustomizer: 定制化器,可以改变xxxx的默认规则
定制化原理
定制化的常见方式
- 修改配置文件
- xxxxCustomizer
- 编写自定义配置类,xxxConfiguration;@Bean替换、增加容器中默认组件;视图解析器
- web应用编写一个配置类实现
WebMvcConfigurer
接口即可定制化web功能;@Bean给容器中再扩展一些组件WebMvcConfigurer
接口中定义了大多数场景的扩展方法,只需要实现该接口的某些方法即可实现相应功能的定制
@EnableWebMvc
+WebMvcConfigurer
可以全面接管SpringMVC,所有规则全部自己重新配置;实现定制和扩展功能(高度自定义化)- 原理
- 1、
WebMvcAutoConfiguration
是默认的SpringMVC的自动配置功能类,(静态资源访问、欢迎页…) - 2、一旦使用
@EnableWebMvc
注解,会@Import(DelegatingWebMvcConfiguration.class)
- 3、
DelegatingWebMvcConfiguration
的作用,只包装SpringMVC最基本的使用- 注入系统所有
WebMvcConfigurer
类型的Bean,把所有功能定制的这些WebMvcConfigurer
合起来一起生效 - 只默认配置了一些非常底层的组件,比如
RequestMappingHandlerMapping
等,这些组件依赖的组件都是直接从容器中获取的 public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport{}
- 注入系统所有
- 4、
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
WebMvcAutoConfiguration
配置要想生效,需要容器中不存在WebMvcConfigurationSupport
类型的Bean才行,而@EnableWebMvc
注解引入的DelegatingWebMvcConfiguration
类就是WebMvcConfigurationSupport
的子类
@EnableWebMvc
导致WebMvcAutoConfiguration
没有生效
- 1、
原理分析套路
场景starter - xxxxAutoConfiguration - 导入xxx组件 - 绑定xxxProperties – 绑定配置文件项
数据访问
SQL
数据源的自动配置-HikariDataSource
导入JDBC场景
1 | <dependency> |
数据库驱动?
为什么导入jdbc场景,官方不导入驱动?
官方不知道我们接下来要操作什么数据库。
数据库版本和驱动版本对应。
1 | <!-- 默认版本: --> |
分析自动配置
自动配置的类
DataSourceAutoConfiguration
:数据源的自动配置- 修改数据源相关的配置:
spring.datasource
- 数据库连接池的配置,是容器中没有
DataSource
的时候才会自动配置 - 底层配置好好的连接池是:HikariDataSource
- springboot检查到我们没有配置数据源后会尝试引入其他的多个数据源,默认情况下,只有
HikariDataSource
的配置会符合条件,其余均不符合条件1
2
3
4
5
6
7
protected static class PooledDataSourceConfiguration {}
- springboot检查到我们没有配置数据源后会尝试引入其他的多个数据源,默认情况下,只有
- 修改数据源相关的配置:
DataSourceTransactionManagerAutoConfiguration
:事务管理器的自动配置JdbcTemplateAutoConfiguration
:JdbcTemplate
的自动配置,可以用来对数据进行crud- 通过修改
spring.jdbc
相关的配置来修改JdbcTemplate
- 容器中已经默认放了一个
JdbcTemplate
组件,可以直接注入使用
- 通过修改
JndiDataSourceAutoConfiguration
:jndi的自动配置XADataSourceAutoConfiguration
:分布式事务相关的自动配置
修改配置项
1 | spring: |
测试
1 |
|
使用Druid数据源
druid官方github地址
https://github.com/alibaba/druid
整合三方技术的两种方式
- 自定义
- 找starter
自定义方式
创建数据源
1 | <dependency> |
手动写相关配置实现对应的功能。
1 |
|
StatViewServlet
StatViewServlet
的用途包括:
1、提供监控信息展示的html页面
2、提供监控信息的JSON API
StatFilter
用于统计监控信息;如SQL监控,URL监控
系统中所有的filter:
别名 | Filter类名 |
---|---|
default | com.alibaba.druid.filter.stat.StatFilter |
stat | com.alibaba.druid.filter.stat.StatFilter |
mergeStat | com.alibaba.druid.filter.stat.MergeStatFilter |
encoding | com.alibaba.druid.filter.encoding.EncodingConvertFilter |
log4j | com.alibaba.druid.filter.logging.Log4jFilter |
log4j2 | com.alibaba.druid.filter.logging.Log4j2Filter |
slf4j | com.alibaba.druid.filter.logging.Slf4jLogFilter |
commonlogging | com.alibaba.druid.filter.logging.CommonsLogFilter |
慢SQL记录配置:
1 |
|
使用官方starter方式
引入druid-starter
1 | <dependency> |
分析自动配置
- 扩展配置项
spring.datasource.druid
DruidSpringAopConfiguration
: 监控SpringBean的,配置项:spring.datasource.druid.aop-patterns
DruidStatViewServletConfiguration
: 监控页面的配置,spring.datasource.druid.stat-view-servlet
- enabled缺失的时候会自动认定为true(默认开启),但是enabled为基本类型,有默认值false,所以默认情况下是不开启的
DruidWebStatFilterConfiguration
: web监控配置,spring.datasource.druid.web-stat-filter
DruidFilterConfiguration
: 所有Druid自己filter的配置1
2
3
4
5
6
7
8private static final String FILTER_STAT_PREFIX = "spring.datasource.druid.filter.stat";
private static final String FILTER_CONFIG_PREFIX = "spring.datasource.druid.filter.config";
private static final String FILTER_ENCODING_PREFIX = "spring.datasource.druid.filter.encoding";
private static final String FILTER_SLF4J_PREFIX = "spring.datasource.druid.filter.slf4j";
private static final String FILTER_LOG4J_PREFIX = "spring.datasource.druid.filter.log4j";
private static final String FILTER_LOG4J2_PREFIX = "spring.datasource.druid.filter.log4j2";
private static final String FILTER_COMMONS_LOG_PREFIX = "spring.datasource.druid.filter.commons-log";
private static final String FILTER_WALL_PREFIX = "spring.datasource.druid.filter.wall";
配置示例
1 | spring: |
整合Mybatis操作
https://github.com/mybatis
starter
1 | <dependency> |
配置模式
- 全局配置文件
SqlSessionFactory
: 自动配置好了SqlSession
:自动配置了SqlSessionTemplate
,组合了SqlSession
@Import(AutoConfiguredMapperScannerRegistrar.class)
- Mapper:只要我们写的操作mybatis的接口标记了
@Mapper
注解,就会自动被扫描进来
1 | .springframework.context.annotation.Configuration |
1 | mybatis: |
- 编写Mapper接口,标记
@Mapper
注解 - 编写sql映射文件并绑定mapper接口
- 在
application.yml
中指定mapper映射文件的位置,以及指定全局配置文件的信息(建议使用mybais.configuration
)
注解模式
1 |
|
混合模式
1 |
|
最佳实战:
- 引入mybatis-starter
- 配置
application.yml
中,指定mapper-locations
位置即可 - 编写Mapper接口,并标注
@Mapper
注解 - 简单方法直接注解方式
- 复杂方法编写mapper.xml进行绑定映射
@MapperScan("com.ys.mapper")
简化,mapper接口可以不再标注@Mapper
注解
整合Mybatis-plus 完成CRUD
什么是Mybatis-plus
MyBatis-Plus(简称 MP)是一个 MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
整合Mybatis-plus
1 | <dependency> |
自动配置:
MybatisPlusAutoConfiguration
:配置类,MybatisPlusProperties
配置绑定,mybatis-plus.xxx
就是对mybatis-plus的定制SqlSessionFactory
自动配置好了,默认从spring容器中获取数据源mapperLocations
自动配置好的,有默认值,classpath*:/mapper/**/*.xml
任意包的类路径下的所有mapper文件夹下的任意路径下的所有xml都是sql映射文件@Mapper
标注 的接口也会被自动扫描
优点:
- 只需要我们的Mapper继承
BaseMapper
就可以拥有crud的能力
NoSQL
Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。
Redis自动配置
1 | <dependency> |
自动配置:
RedisAutoConfiguration
自动配置类,RedisProperties
属性类,spring.redis.xxx
是对redis的配置- 连接工厂是准备好的,
LettuceConnectionConfiguration
、JedisConnectionConfiguration
,(默认使用Lettuce,可以进行切换) - 自动注入了
RedisTemplate<Object, Object>
- 自动注入了
StringRedisTemplate
k v都是string - 我们只要使用
StringRedisTemplate
和RedisTemplate
就可以操作redis了
RedisTemplate与Lettice
默认是使用的Lettice客户端
1 |
|
切换至jedis
增加jedis的依赖
1 | <dependency> |
如果是直接排除lettuce的依赖,然后引入jedis的依赖,那么不用写切换配置,spring会自动切换到jedis,如果没有排除lettuce,那么可以在yml中进行如下配置切换客户端工具。
1 | spring: |
单元测试
Junit5的变化
Spring Boot 2.2.0 版本开始引入 JUnit 5 作为单元测试默认库
作为最新版本的JUnit框架,JUnit5与之前版本的Junit框架有很大的不同。由三个不同子项目的几个不同模块组成。
JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
JUnit Platform: Junit Platform是在JVM上启动测试框架的基础,不仅支持Junit自制的测试引擎,其他测试引擎也都可以接入。
JUnit Jupiter: JUnit Jupiter提供了JUnit5的新的编程模型,是JUnit5新特性的核心。内部 包含了一个测试引擎,用于在Junit Platform上运行。
JUnit Vintage: 由于JUint已经发展多年,为了照顾老的项目,JUnit Vintage提供了兼容JUnit4.x,Junit3.x的测试引擎。
注意:
SpringBoot 2.4 以上版本移除了默认对 Vintage 的依赖。如果需要兼容junit4需要自行引入(不能使用junit4的功能 @Test)
JUnit 5’s Vintage Engine Removed from spring-boot-starter-test,如果需要继续兼容junit4需要自行引入vintage
1 | <dependency> |
springboot对Junit的支持:
1 | <dependency> |
SpringBoot整合Junit以后。
- 编写测试方法:@Test标注(注意需要使用junit5版本的注解)
- Junit类具有Spring的功能,@Autowired、比如 @Transactional 标注测试方法,测试完成后自动回滚
Junit5常用的注解
JUnit5的注解与JUnit4的注解有所变化
https://junit.org/junit5/docs/current/user-guide/#writing-tests-annotations
- @Test : 表示方法是测试方法。但是与JUnit4的@Test不同,他的职责非常单一不能声明任何属性,拓展的测试将会由Jupiter提供额外测试
- @ParameterizedTest : 表示方法是参数化测试,下方会有详细介绍
- @RepeatedTest : 表示方法可重复执行,下方会有详细介绍
- @DisplayName : 为测试类或者测试方法设置展示名称
- @BeforeEach : 表示在每个单元测试之前执行
- @AfterEach : 表示在每个单元测试之后执行
- @BeforeAll : 表示在所有单元测试之前执行
- @AfterAll : 表示在所有单元测试之后执行
- @Tag : 表示单元测试类别,类似于JUnit4中的@Categories
- @Disabled : 表示测试类或测试方法不执行,类似于JUnit4中的@Ignore
- @Timeout : 表示测试方法运行如果超过了指定时间将会返回错误
- @ExtendWith : 为测试类或测试方法提供扩展类引用
断言(assertions)
断言(assertions)是测试方法中的核心部分,用来对测试需要满足的条件进行验证。这些断言方法都是 org.junit.jupiter.api.Assertions 的静态方法。JUnit 5 内置的断言可以分成如下几个类别:
检查业务逻辑返回的数据是否合理。
所有的测试运行结束以后,会有一个详细的测试报告;
简单断言
用来对单个值进行简单的验证。如:
方法 | 说明 |
---|---|
assertEquals | 判断两个对象或两个原始类型是否相等 |
assertNotEquals | 判断两个对象或两个原始类型是否不相等 |
assertSame | 判断两个对象引用是否指向同一个对象 |
assertNotSame | 判断两个对象引用是否指向不同的对象 |
assertTrue | 判断给定的布尔值是否为 true |
assertFalse | 判断给定的布尔值是否为 false |
assertNull | 判断给定的对象引用是否为 null |
assertNotNull | 判断给定的对象引用是否不为 null |
1 |
|
数组断言
通过 assertArrayEquals 方法来判断两个对象或原始类型的数组是否相等
1 |
|
组合断言
assertAll 方法接受多个 org.junit.jupiter.api.Executable 函数式接口的实例作为要验证的断言,可以通过 lambda 表达式很容易的提供这些断言
1 |
|
异常断言
在JUnit4时期,想要测试方法的异常情况时,需要用 @Rule 注解的ExpectedException变量还是比较麻烦的。而JUnit5提供了一种新的断言方式 Assertions.assertThrows() ,配合函数式编程就可以进行使用。
1 |
|
超时断言
Junit5还提供了 Assertions.assertTimeout() 为测试方法设置了超时时间
1 |
|
快速失败
通过 fail 方法直接使得测试失败
1 |
|
前置条件(assumptions)
JUnit 5 中的前置条件(assumptions【假设】)类似于断言,不同之处在于 不满足的断言会使得测试方法失败 ,而不满足的 前置条件只会使得测试方法的执行终止 。前置条件可以看成是测试方法执行的前提,当该前提不满足时,就没有继续执行的必要。
1 |
|
assumeTrue 和 assumFalse 确保给定的条件为 true 或 false,不满足条件会使得测试执行终止。assumingThat 的参数是表示条件的布尔值和对应的 Executable 接口的实现对象。只有条件满足时,Executable 对象才会被执行;当条件不满足时,测试执行并不会终止。
嵌套测试
JUnit 5 可以通过 Java 中的内部类和@Nested 注解实现嵌套测试,从而可以更好的把相关的测试方法组织在一起。在内部类中可以使用@BeforeEach 和@AfterEach 注解,而且嵌套的层次没有限制。
1 |
|
参数化测试
参数化测试是JUnit5很重要的一个新特性,它使得用不同的参数多次运行测试成为了可能,也为我们的单元测试带来许多便利。
利用 @ValueSource 等注解,指定入参,我们将可以使用不同的参数进行多次单元测试,而不需要每新增一个参数就新增一个单元测试,省去了很多冗余代码。
@ValueSource : 为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型
@NullSource : 表示为参数化测试提供一个null的入参
@EnumSource : 表示为参数化测试提供一个枚举入参
@CsvFileSource:表示读取指定CSV文件内容作为参数化测试入参
@MethodSource:表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流)
当然如果参数化测试仅仅只能做到指定普通的入参还达不到让我觉得惊艳的地步。让我真正感到他的强大之处的地方在于他可以支持外部的各类入参。如:CSV,YML,JSON 文件甚至方法的返回值也可以作为入参。只需要去实现ArgumentsProvider 接口,任何外部文件都可以作为它的入参。
1 |
|
迁移指南
在进行迁移的时候需要注意如下的变化:
- 注解在
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
。
指标监控
SpringBoot Actuator
简介
未来每一个微服务在云上部署以后,我们都需要对其进行监控、追踪、审计、控制等。SpringBoot就抽取了Actuator场景,使得我们每个微服务快速引用即可获得生产级别的应用监控、审计等功能。
1 | <dependency> |
1.x和2.x的不同
如何使用
- 引入场景
- 访问 http://localhost:8080/actuator/*
- 暴露所有监控信息为HTTP
1 | management: |
- 测试
http://localhost:8080/actuator/beans
http://localhost:8080/actuator/configprops
http://localhost:8080/actuator/metrics
http://localhost:8080/actuator/metrics/jvm.gc.pause
http://localhost:8080/actuator/endpointName/detailPath
。。。。。。
可视化
https://github.com/codecentric/spring-boot-admin
Actuator Endpoint
最常使用的端点
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:日志记录
Health Endpoint
健康检查端点,我们一般用于在云平台,平台会定时的检查应用的健康状况,我们就需要Health Endpoint可以为平台返回当前应用的一系列组件健康状况的集合。
重要的几点:
- health endpoint返回的结果,应该是一系列健康检查后的一个汇总报告
- 很多的健康检查默认已经自动配置好了,比如:数据库、redis等
- 可以很容易的添加自定义的健康检查机制
Metrics Endpoint
提供详细的、层级的、空间指标信息,这些信息可以被pull(主动推送)或者push(被动获取)方式得到;
- 通过Metrics对接多种监控系统
- 简化核心Metrics开发
- 添加自定义Metrics或者扩展已有Metrics
管理Endpoints
开启与禁用Endpoints
- 默认所有的Endpoint除过shutdown都是开启的。
- 需要开启或者禁用某个Endpoint。配置模式为 *management.endpoint.<endpointName>.enabled = true*
1 | management: |
- 或者禁用所有的Endpoint然后手动开启指定的Endpoint
1
2
3
4
5
6
7
8management:
endpoints:
enabled-by-default: false
endpoint:
beans:
enabled: true
health:
enabled: true
暴露Endpoints
支持的暴露方式
- HTTP:默认只暴露health 和info Endpoint
- JMX:默认暴露所有Endpoint
- 除过health和info,剩下的Endpoint都应该进行保护访问。如果引入SpringSecurity,则会默认配置安全访问规则
ID | JMX | Web |
---|---|---|
auditevents |
Yes | No |
beans |
Yes | No |
caches |
Yes | No |
conditions |
Yes | No |
configprops |
Yes | No |
env |
Yes | No |
flyway |
Yes | No |
health |
Yes | Yes |
heapdump |
N/A | No |
httptrace |
Yes | No |
info |
Yes | Yes |
integrationgraph |
Yes | No |
jolokia |
N/A | No |
logfile |
N/A | No |
loggers |
Yes | No |
liquibase |
Yes | No |
metrics |
Yes | No |
mappings |
Yes | No |
prometheus |
N/A | No |
scheduledtasks |
Yes | No |
sessions |
Yes | No |
shutdown |
Yes | No |
startup |
Yes | No |
threaddump |
Yes | No |
定制Endpoint
定制Health信息
1 | import org.springframework.boot.actuate.health.Health; |
1 | management: |
1 |
|
定制info信息
常用两种方式
1、编写配置文件
1 | info: |
2、编写InfoContributor
1 | import java.util.Collections; |
http://localhost:8080/actuator/info 会输出以上方式返回的所有info信息
定制Metrics信息
SpringBoot支持自动适配的Metrics
- JVM metrics, report utilization of:
- Various memory and buffer pools
- Statistics related to garbage collection
- Threads utilization
- Number of classes loaded/unloaded
- CPU metrics
- File descriptor metrics
- Kafka consumer and producer metrics
- Log4j2 metrics: record the number of events logged to Log4j2 at each level
- Logback metrics: record the number of events logged to Logback at each level
- Uptime metrics: report a gauge for uptime and a fixed gauge representing the application’s absolute start time
- Tomcat metrics (
server.tomcat.mbeanregistry.enabled
must be set totrue
for all Tomcat metrics to be registered) - Spring Integration metrics
增加定制Metrics
1 | class MyService{ |
定制Endpoint
1 |
|
场景:开发ReadinessEndpoint来管理程序是否就绪,或者LivenessEndpoint来管理程序是否存活;
当然,这个也可以直接使用 https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-features.html#production-ready-kubernetes-probes
原理解析
Profile功能
为了方便多环境适配,springboot简化了prefile功能
application-profile功能
- 默认配置文件,
application.yml
,任何时候都会加载 - 指定环境配置文件,
application-{env}.yml
- 激活指定环境
- 配置文件激活
- 命令行激活:
java -jar xxx.jar --spring.profiles.active=prod
- 修改配置文件的任意值,命令行优先
- 默认配置与环境配置同时生效
- 同名项,profile配置优先(后面加载的会替换前面的)
@Profile条件装配功能
1 | //当前bean只有在环境为 production 的时候才会被加载 |
profile分组
1 | proddb = |
外部化配置
springboot可以使用以下14中方式添加配置项(按顺序)
如果存在同名项,后面的会覆盖前面的
- Default properties (specified by setting
SpringApplication.setDefaultProperties
). @PropertySource
annotations on your@Configuration
classes. Please note that such property sources are not added to theEnvironment
until the application context is being refreshed. This is too late to configure certain properties such aslogging.*
andspring.main.*
which are read before refresh begins.- Config data (such as
application.properties
files). - A
RandomValuePropertySource
that has properties only inrandom.*
. - OS environment variables.
- Java System properties (
System.getProperties()
). - JNDI attributes from
java:comp/env
. ServletContext
init parameters.ServletConfig
init parameters.- Properties from
SPRING_APPLICATION_JSON
(inline JSON embedded in an environment variable or system property). - Command line arguments.
properties
attribute on your tests. Available on@SpringBootTest
and the test annotations for testing a particular slice of your application.@DynamicPropertySource
annotations in your tests.@TestPropertySource
annotations on your tests.- Devtools global settings properties in the
$HOME/.config/spring-boot
directory when devtools is active.
外部配置源
常用:Java属性文件、YML文件、环境变量、命令行参数
配置文件查找位置
- classpath 根路径
- classpath 根路径下config目录
- java包当前目录
- java当前目录的config目录
- /config子目录的直接子目录(/config表示是linux环境根目录下)
注意:springboot启动会按照上面的顺序进行加载,如果有同名项,后面的会覆盖前面的
配置文件加载顺序
1、当前jar包内部的application.properties和application.yml
2、当前jar包内部的application-{profile}.properties和application-{profile}.yml
3、引用的外部jar包的application.properties和application.yml
4、引用的外部jar包的application-{profile}.properties和application-{profile}.yml
指定环境优先、外部优先、后面的可以覆盖前面的同名配置
自定义starter
starter启动原理
- starter-pom 引入autoconigurer
- autoconigurer包中配置使用
META-INF/spring.factories
中的EnableAutoConfiguration
的值,使得项目启动记载指定的配置类 - 编写自动配置类 xxxAutoConfiguration -> xxxProperties
- @Configuration
- @Conditional
- @EnableConfigurationProperties
- @Bean
- ……
引入starter — xxxAutoConfiguration — 容器中放入组件 — xxxProperties — 配置项
自定义starter
hello-spring-boot-starter(启动器)
hello-spring-boot-starter-autoconfigure(自动配置包)
SpringBoot原理
Spring原理【Spring注解】、SpringMVC原理、自动配置原理、SpringBoot原理
SpringBoot启动过程
- 创建 SpringApplication
- 保存一些信息。
- 判定当前应用的类型。ClassUtils。Servlet
- bootstrappers:初始启动引导器(List<Bootstrapper>):去spring.factories文件中找 org.springframework.boot.Bootstrapper
- 找 ApplicationContextInitializer;去spring.factories找 ApplicationContextInitializer
- List<ApplicationContextInitializer<?>> initializers
- 找 ApplicationListener ;应用监听器。去spring.factories找 ApplicationListener
- List<ApplicationListener<?>> listeners
- 运行 SpringApplication
- StopWatch
- 记录应用的启动时间
- 创建引导上下文(Context环境)createBootstrapContext()
- 获取到所有之前的 bootstrappers 挨个执行 intitialize() 来完成对引导启动器上下文环境设置
- 让当前应用进入headless模式。java.awt.headless
- 获取所有 RunListener(运行监听器)【为了方便所有Listener进行事件感知】
- getSpringFactoriesInstances 去spring.factories找 SpringApplicationRunListener.
- 遍历 SpringApplicationRunListener 调用 starting 方法;
- 相当于通知所有感兴趣系统正在启动过程的人,项目正在 starting。
- 保存命令行参数;ApplicationArguments
- 准备环境 prepareEnvironment();
- 返回或者创建基础环境信息对象。StandardServletEnvironment
- 配置环境信息对象。
- 读取所有的配置源的配置属性值。
- 绑定环境信息
- 监听器调用 listener.environmentPrepared();通知所有的监听器当前环境准备完成
- 创建IOC容器(createApplicationContext())
- 根据项目类型(Servlet)创建容器,
- 当前会创建 AnnotationConfigServletWebServerApplicationContext
- 准备ApplicationContext IOC容器的基本信息 prepareContext()
- 保存环境信息
- IOC容器的后置处理流程。
- 应用初始化器;applyInitializers;
- 遍历所有的 ApplicationContextInitializer 。调用 initialize.。来对ioc容器进行初始化扩展功能
- 遍历所有的 listener 调用 contextPrepared。EventPublishRunListenr;通知所有的监听器contextPrepared
- 所有的监听器 调用 contextLoaded。通知所有的监听器contextLoaded;
- 刷新IOC容器。 refreshContext
- 创建容器中的所有组件(Spring注解)
- 容器刷新完成后工作?afterRefresh
- 所有监听 器 调用 listeners.started(context); 通知所有的监听器 started
- 调用所有runners; callRunners()
- 获取容器中的 ApplicationRunner
- 获取容器中的 CommandLineRunner
- 合并所有runner并且按照@Order进行排序
- 遍历所有的runner。调用 run 方法
- 如果以上有异常,
- 调用Listener 的 failed
- 调用所有监听器的 running 方法 listeners.running(context); 通知所有的监听器 running
- running如果有问题。继续通知 failed 。调用所有 Listener 的 failed;通知所有的监听器 failed
Application Events and Listeners
https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-application-events-and-listeners
ApplicationContextInitializer
ApplicationListener
SpringApplicationRunListener