SpringBoot3-Web开发
SpringBoot的Web开发能力,由SpringMVC提供。
Web开发的三种方式
方式 | 处理过程 | 注意事项 | 实现效果 |
---|---|---|---|
全自动 | 直接编写控制逻辑 | 全部使用自动给配置默认效果 | |
手自一体 | @Configuration、 配置WebMvcConfigurer、 配置WebMvcRegistrations | 不要标注 @EnableWebMvc | 手动配置效果 手动设置部分功能 定义MVC底层组件 |
全手动 | @Configuration 配置WebMvcConfigurer | 标注@EnableWebMvc | 禁用自动配置效果 全手动设置 |
给容器中放一个配置类,使用
@Configuration
标注,让配置类继承WebMvcConfigurer
但是不要标注@EnableWebMvc
注解,可以实现手自一体的效果。
1、Web场景
自动配置
①整合web场景
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
②starter
的spring-boot-starter
引入了autoconfigure
功能
③@EnableAutoConfiguration
注解使用@Import(AutoConfigurationImportSelector.class)
批量导入组件,加载"META-INF/spring/%s.imports"
文件中配置的所有组件。
④在自动导入的配置类中绑定了配置文件的一堆配置项,比如:
-
SpringMVC的所有配置以
spring.mvc
开头 -
Web场景通用的配置在
spring.web
-
文件上传配置在
spring.servlet.multipart
-
服务器的配置
server
比如:编码方式
默认效果
默认配置
-
包含了
ContentNegotiatingViewResolver
和BeanNameViewResolver
组件,方便视图解析。 -
默认的静态资源处理机制:静态资源放在
static
文件夹下即可直接访问。 -
自动注册了
Converter
,GenericConverter
,Formatter
组件,适配常见的数据类型转换和格式化需求。 -
支持
HttpMessageConverters
,可以方便返回json等数据类型。 -
注册
MessageCodesResolver
,方便国际化及错误消息处理。 -
支持静态
index.html
。 -
自动使用
ConfigurableWebBindingInitializer
,实现消息处理、数据绑定、类型转换、数据校验等功能。
特别提示:
①如果想保持boot mvc的默认配置,并且自定义更多的mvc配置,如:interceptors,formatters, view controllers等,可以使用
@Configuration
注解添加一个WebMvcConfigurer
类型的配置类,但不要标注@EnableWebMvc
。②如果想保持boot mvc的默认配置,但要自定义核心组件实例,比如
RequestMappingHandlerMapping
,RequestMappingHandlerAdapter
或ExceptionHandlerExceptioonResolver
,给容器中放一个WebMvcRegistrations
组件即可。③如果想全面接管Spring MVC,
@Configuration
标注一个配置类,并加上@EnableWebMvc
注解,实现WebMvcConfigurer
接口。
2、静态资源
WebMvcAutoConfiguration
生效条件
效果
放入了两个filter
HiddenHttpMethodFilter
:页面表单提交Rest请求(GET、POST、PUT、DELETE)
FormContentFilter
:表单内容Filter,GET(数据放URL后面)、POST(数据放请求体)请求携带数据,但是PUT和DELETE的请求数据会被忽略,FormContentFilter
是为了解决这个问题的。
@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled")
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new OrderedHiddenHttpMethodFilter();
}
@Bean
@ConditionalOnMissingBean(FormContentFilter.class)
@ConditionalOnProperty(prefix = "spring.mvc.formcontent.filter", name = "enabled", matchIfMissing = true)
public OrderedFormContentFilter formContentFilter() {
return new OrderedFormContentFilter();
}
给容器中放入了
WebMvcConfigurer
组件,给SpringMVC添加各种定制功能,所有的功能最终会和配置文件进行绑定
WebMvcProperties
:以spring.mvc
开头的配置文件
WebProperties
:以spring.web
开头的配置文件
@Configuration(proxyBeanMethods = false)
@Import(EnableWebMvcConfiguration.class)//额外导入了其他配置
@EnableConfigurationProperties({ WebMvcProperties.class, WebProperties.class })
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware {
}
WebMvcConfigurer接口
它提供配置SpringMVC底层的所有组件入口。
静态资源规则源码
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
return;
}
addResourceHandler(registry, this.mvcProperties.getWebjarsPathPattern(),
"classpath:/META-INF/resources/webjars/");
addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
registration.addResourceLocations(this.resourceProperties.getStaticLocations());
if (this.servletContext != null) {
ServletContextResource resource = new ServletContextResource(this.servletContext, SERVLET_LOCATION);
registration.addResourceLocations(resource);
}
});
}
规则一
访问
/webjars/**
路径就去classpath:/META-INF/resources/webjars/
下找资源(查看依赖中的内容)
规则二
规则二:
访问
/**
路径就去静态资源的四个默认位置下找资源classpath:/META-INF/resources/
classpath:/resources/
classpath:/static/
classpath:/public/
registration.addResourceLocations(this.resourceProperties.getStaticLocations()
规则三
静态资源默认都有缓存规则的设置 i、cachePeriod:缓存周期(以s为单位),多久不再需要给服务器重新请求 ii、cacheControl:HTTP缓存控制 iii、useLastModified:是否使用最后一次修改。配合HTTP cache规则
浏览器访问了一个静态资源,如果这个服务没有发生变换,下次访问的时候可以直接让浏览器用自己缓存中的内容,而不用给服务器发请求。
registration.setCachePeriod(getSeconds(this.resourceProperties.getCache().getPeriod()));
registration.setCacheControl(this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl());
registration.setUseLastModified(this.resourceProperties.getCache().isUseLastModified());
EnableWebMvcConfiguration源码
属于WebMvcAutoConfiguration的一个内部类。
//SpringBoot 给容器中WebMvcConfigurationSupport组件
//如果我们自己放了WebMvcConfigurationSupport组件,Boot的WebMvcAutoConfiguration都会失效
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(WebProperties.class)
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {
}
HandlerMapping
:根据请求路径,找哪个handler处理请求。
WelcomePageHandlerMapping
:
-
问
/**
路径下的所有请求都在以前四个静态资源路径下找,欢迎页也是。 -
找
index.html
只要静态资源的位置有一个index.html页面,项目启动就默认访问。
在容器中加入WebMvcConfigurer就能配置底层行为的原因
-
WebMvcConfiguration是一个自动配置类,它里面有一个
EnableWebMvcConfiguration
-
EnableWebMvcConfiguration
继承于DelegatingWebMvcConfiguration
,这两个都生效 -
DelegatingWebMvcConfiguration
利用DI把容器中所有WebMvcConfiguration
注入进来 -
别人调用
DelegatingWebMvcConfiguration
的方法配置底层规则,而它调用所有WebMvcConfiguration
的配置底层方法。
默认规则
①静态资源映射
静态资源映射规则在
WebMvcAutoConfiguration
中进行了定义
-
/webjars/**
的所有路径资源都在classpath:/META-INF/resources/webjars
-
/**
的所有路径资源都在classpath:/META/resources/
、classpath:/resources/
、calsspath:/static/
、calsspath:/public/
-
所有静态资源都定义了
缓存规则
。浏览器访问过一次,就会缓存一段时间,但此功能参数无默认值-
period
:缓存间隔。默认0s; -
cacheControl
:缓存控制。默认无 -
useLastModified
:是否使用lastModified
头。默认是false
-
②静态资源缓存
如①所述所有静态资源都定义了缓存规则
。浏览器访问过一次,就会缓存一段时间,但此功能参数无默认值
测试缓存机制
在calsspath/static下放置一个静态资源(图片)
配置application.properties
#1、spring.web:
# 配置国际化的区域信息
#配置静态资源策略(开启、处理链、缓存)
#开启静态资源映射规则
spring.web.resources.add-mappings=true
#设置缓存
spring.web.resources.cache.period=3600
#缓存详细合并项控制,覆盖period配置,
# 浏览器第一次请求服务器,服务器告诉浏览器此资源缓存7200秒,7200秒以内的所有此资源访问不用发给服务器请求
spring.web.resources.cache.cachecontrol.max-age=7200
#使用资源last-modified时间,来对此服务器和浏览器的资源是否相同,相同返回304
spring.web.resources.cache.use-last-modified=true
注意:
spring.web.resources.cache.period
是用来设置静态资源在浏览器中的缓存过期时间。这个配置项告诉浏览器在多长时间内可以使用缓存的资源,而不需要向服务器发送请求。在这个例子中,缓存过期时间为1小时。
spring.web.resources.cache.cachecontrol.max-age
是用来设置服务器告诉浏览器此资源的缓存时间。这个配置项告诉浏览器在多长时间内可以使用缓存的资源,而不需要向服务器发送请求。在这个例子中,服务器会告诉浏览器缓存时间为2小时。
自定义静态资源规则
自定义静态资源路径,自定义缓存规则
①配置application.properties方式
#2、spring.mvc
##2.1.自定义webjars路径前缀
spring.mvc.webjars-path-pattern=/webjars/**
##2.2.静态资源访问路径前缀
spring.mvc.static-path-pattern=/**
#静态资源的文件夹
spring.web.resources.static-locations=classpath:/a/, classpath:/b/
spring.mvc
:静态资源访问前缀路径
spring.web
:静态资源目录、静态资源缓存策略
②代码方式
容器中只要有一个WebMvcConfigurer组件。配置的底层行为都会生效。
@Configuration//这是一个配置类
public class MyConfig implements WebMvcConfigurer {
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
//保留之前的配置
WebMvcConfigurer.super.addResourceHandlers(registry);
//自己添加一些
registry.addResourceHandler("static/**").addResourceLocations("classpath:/static/", "classpath:/templates/")
.setCacheControl(CacheControl.maxAge(7200, TimeUnit.SECONDS));
}
};
}
}
③欢迎页
欢迎页规则在WebMvcAutoConfiguration
中进行了定义 a、在静态资源目录下找index.html
b、没有就在templates
下找index
模板页
④Favicon
favicon.ico
通常显示在浏览器标签页。在这段代码中,在静态资源目录下寻找名为 favicon.ico
的文件。
3、路径匹配
Spring5.3之后加入了更多的
请求路径匹配
的实现策略。以前只支持AntPathMatcher
策略,现提供了PathPatternParser
策略。并且可以让我们指定到底使用哪种策略。
①Ant风格路径语法
ant风格的路径模式语法具有以下规则:
规则 | 含义 |
---|---|
* | 表示任意的0个或多个字符 |
? | 表示任意一个字符 |
** | 表示任意数量的目录 |
{} | 表示一个命名的模式占位符 |
[] | 表示字符集合,例如[a-z]表示小写字母 |
示例:
*.html
:匹配任意名称,扩展名为.html的文件 /folder1/*/*.java
:匹配在folder1目录下的任意两级目录下的.java文件 /folder2/**/*.jsp
:匹配在folder2目录下任意目录深度的.jsp文件 /{type}/{id}.html
:匹配任意文件名为{id}.html,在任意命名的{type}目录下的文件
注意: Ant风格的路径模式语法中的特殊特殊字符需要转义,如: 要匹配文件路径中的星号,则需要转义为\* 要匹配文件路径中的问好,则需要转义为\?
②模式切换
AntPathMatcher与PathPatternParser是Spring框架中用于处理路径模式匹配的工具类。 PathPatternParser在jmh基准测试下,有6~8被吞吐量提升,降低30%~40%的空间分配率 PathPatternParser兼容AntPathMatcher语法,并支持更多类型的路径模式 PathPatternParser"**"多端匹配的支持仅允许在模式末尾使用
@Slf4j
@RestController
public class HelloController {
/**
* springBoot默认使用新版的路径匹配(PathPatternParser)
不能匹配双星在中间的情况,剩下的和AntPathMatcher使用方法相同
可以在配置文件中使用:spring.mvc.pathmatch.matching-strategy=ant_path_matcher改为老版策略
* @param request
* @param path
* @return
*/
@GetMapping("/a*/b?/{p1:[a-f]+}/**")
public String hello(HttpServletRequest request, @PathVariable("p1") String path){
log.info("路径变量p1:{}", path);
String url = request.getRequestURI();
return url;
}
}
总结: 使用默认的路径匹配规则,是由
PathPatternParser
提供的 如果路径中间出现问题需要有**,替换成Ant风格路径。
4、内容协商
一套系统适配多端数据返回。
①多端内容适配
-
默认规则
-
基于请求头的内容协商(默认开启)
客户端向服务端发送请求,携带HTTP标准的Accept请求头
Accept:application/json
、text/xml
、text/yml
服务端根据客户端请求头期望的数据类型进行动态返回。 -
基于请求参数内容协商(需要手动开启)
a、发送请求
GET/projects/spring-boot?format=json
b、匹配到@GetMapping("/projects/spring-boot")
c、根据参数协商,优先返回json类型数据(需要开启参数匹配设置) d、发送请求GET/projects/spring-boot?format=xml
,优先返回xml类型数据。
-
效果演示
场景:
请求同一个接口,可以返回json和xml不同格式的数据,使用官方Postman进行测试。
实体类(Person)
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Person {
private Long id;
private String name;
private String email;
private Integer age;
}
Controller
返回JSON格式
@Slf4j
@RestController
public class HelloController {
/**
* json适配,默认支持把对象转化为json,web场景默默人导入了jackson处理json的包;jackson-core
* jackson也支持把数据转化为xml。但需要导入xml相关依赖
* @return
*/
@GetMapping("/person")
public Person person(){
Person person = new Person();
person.setId(1L);
person.setName("Louie");
person.setEmail("xxx@qq.com");
person.setAge(23);
return person;
}
}
返回XML格式
①引入支持xml内容的依赖
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
②实体类中标注注解
@JacksonXmlRootElement//可以写出为xml文档
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Person {
private Long id;
private String name;
private String email;
private Integer age;
}
注意:
在浏览器中不能正常显示,可以将浏览器的Accept复制在Postman中新加一个Accept,关闭原来的Accept
-
配置协商规则与支持类型
-
修改内容协商方式
#开启基于请求参数的内容协商功能。默认参数名是format spring.mvc.contentnegotiation.favor-parameter=true #自定义参数名,默认为format spring.mvc.contentnegotiation.parameter-name=myParam
-
效果
-
②内容协商原理-HttpMessageConverter
熟悉
HttpMessageConverter
怎么工作,何时工作,就可以定制HttpMessageConverter
来实现多端内容协商。
实现
编写
WebMvcConfigurer
提供的configureMessageConverters
底层,修改底层的MessageConverter
@ResponseBody
由HttpMessageConverter
处理,标注了@ResponseBody
的返回值将会由支持它的HttpMessageConverter
写给浏览器。
原理
a、controller方法的返回值标注了@ResponseBody
注解
b、请求进来先来到DispartureServlet
的doDispatch()
进行处理
c、找到HandlerAdapter
适配器
d、利用适配器执行目标方法RequestMappingHandlerAdapter
来执行
e、调用involeHandlerMethod
执行目标方法。目标方法执行之前,HandlerMethodArgumentResolver
(参数解析器)确定目标方法每一个参数值。HandlerMethodReturnValueHandler
(返回值处理器)确定目标方法的返回值该增么处理。
f、incokeAndHandle()
真正执行目标方法。目标方法执行完成,会导出返回值对象。
g、找到一个合适的返回值处理器HandlerMethodReturnValueHandler
h、最终找到RequestResponseBodyMethodProcessor
能处理标注了@RsponseBody
注解的方法
i、RequestResponseBodyMethodProcessor
调用writerWithMessageConverters
,利用MessageConverter
把返回值写出去。
总结:
@ResponseBody
由HttpMessageConverter
处理,HttpMessageConverter
会先进行内容协商。WebMvcAutoConfiguration
通过addDefaultHttpMessageConverters
添加了默认的MessageConverter
如下:ByteArrayHttpMessageConverter
:支持字节数据读取StringHttpMessageConverter
:支持字符串读写ResourceHttpMessageConverter
:支持资源读写ResourceRegionHttpMessageConverter
:支持分区资源写出AllEncompassingFormHttpMessageConverter
:支持表单xml/json读写MappingJackson2HttpMessageConverter
:支持请求响应Json读写遍历所有的
MessageConverter
看谁支持这种内容类型的数据,将支持的类型写出。 系统默认的MessageConverter功能有限,仅用于json或普通返回数据。额外增加新的内容协商功能,必须增加新的HttpMessageConverter
。
③WebMvcConfigurationSupport
该类提供了很多的默认设置。判断系统中是否有相应的类,如果有,就加入相应的HttpMessageConverter
jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) &&
ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);
jackson2SmilePresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.smile.SmileFactory", classLoader);
jackson2CborPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.cbor.CBORFactory", classLoader);
④自定义内容返回
场景:增加yaml返回支持
步骤
-
导入依赖
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
</dependency>
-
把对象写出成一个yaml
@Slf4j
@RestController
public class HelloController {
public static void main(String[] args) throws JsonProcessingException {
Person person = new Person();
person.setId(1L);
person.setName("Louie");
person.setEmail("xxx@qq.com");
person.setAge(23);
// new YAMLFactory().disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER);可以去掉三个横线
ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
String s = mapper.writeValueAsString(person);
System.out.println(s);
}
}
-
编写配置
#增加一种新的内容类型
spring.mvc.contentnegotiation.media-types.yaml=text/yaml
-
MyYamlHttpMessageConverter
增加HttpMessageConverter
组件,专门负责把对象写出为yaml格式
public class MyYamlHttpMessageConverter extends AbstractHttpMessageConverter<Object> {
private ObjectMapper objectMapper = null;
//实例化
public MyYamlHttpMessageConverter(){
//告诉SpringBoot这个MessageConverter支持哪种媒体类型
super(new MediaType("text", "yaml", Charset.forName("UTF-8")));
//把对象转化为yaml
this.objectMapper = new ObjectMapper(new YAMLFactory());
}
@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);
}
}
}
-
编写配置类
@Configuration//这是一个配置类
public class MyConfig implements WebMvcConfigurer {
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
@Override//配置一个能把对象转为yaml的messageConverter
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
WebMvcConfigurer.super.configureMessageConverters(converters);
converters.add(new MyYamlHttpMessageConverter());
}
};
}
}
总结:
1、配置媒体类型支持
spring.mvc.contentnegotiation.media-types.yaml=text/yaml2、编写对应的HttpMessageConverter,告诉Boot这个支持的媒体类型 3、把MessageConverter组件加入到底层。(容器中放一个
WebMvcConfigurer
组件,并配置底层的MessageConverter
)
5、模板引擎
由于SpringBoot使用了嵌入式Servlet容器,所以JSP默认不能使用,如果需要服务端页面渲染,优先考虑使用模板引擎。
SpringBoot模板引擎的自动配置包括:FreeMarker、Groovy、Thymeleaf、Mustache
Thymeleaf整合
Thymeleaf官网
基础语法
①核心用法
th:xxx
动态渲染指定的html标签属性值、或者th指令(遍历、判断等) th:text
:标签体内文本值渲染
<h3 th:text="${msg}">哈哈</h3>
<!--th:text:替换标签体的内容-->
<h3 th:utext="${msg}">呵呵</h3>
<!--th:utext:不会转义html标签,显示html该有的样式-->
th:属性
:标签指定属性渲染
<img th:src="${url}"/>
<!--访问动态路径-->
th:attr
:标签任意属性渲染
<img src="picture.jpg" style="width: 300px" th:attr="src=${url}, style=${modelStyle}"/>
<!--访问动态路径, 和动态的样式-->
th:if
、th:each
...:其他th指令
<img src="${url}" th:if="${show}"/>
<!--访问动态路径, 并且由控制器传入的show来判断它是否显示-->
表达式:用来动态取值
${}:变量取值,使用model共享给页面的值都直接用${} @{}:url路径 #{}:国际化消息 ~{}:片段引用 *{}:变量选择(需要配合th:object绑定对象)
系统工具和内置对象可以查看详细文档。
param:请求参数对象
<p>参数是否存在: ${param.containsKey('parameterName')}</p>
session:session对象
<p>Session属性: ${session.getAttribute('attributeName')}</p>
application:application对象
<p>Application属性: ${application.getAttribute('attributeName')}</p>
#execInfo:模板执行信息
<p>模板名称: ${#execInfo.templateName}</p>
<p>模板行号: ${#execInfo.templateLine}</p>
#messages:国际化消息
<p>国际化消息: #{messageKey}</p>
#uris:uri/url工具
<a href="${#uris.encodeUrl('/path')}">链接</a>
#conversions:类型转换工具
<p>转换为数字: ${#conversions.stringToNumber('10')}</p>
#dates:日期工具,是java.util.Date对象的工具类
<p>当前日期: ${#dates.format(#dates.createNow(), 'yyyy-MM-dd')}</p>
#calendars:类似dates,只不过是java.util.calendar对象的工具类
#temporals:JDK8+java.time API工具类
#numbers:数字操作工具
#strings:字符串操作
#objects:对象操作
#bools:bool操作
#arrays:array工具
#lists:list工具
#sets:set工具
#maps:map工具
#aggrregates:集合聚合工具(sum、avg)
#ids:id生成工具
②语法示例
-
表达式
-
变量取值:${...}
-
url取值:@{...}
-
国际化消息:#{}
-
变量选择:*{...}
-
片段引用:~{...}
-
-
文本操作
-
拼串:+
-
文本替换:|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:href="@{/product/list}" ②th:attr="calss=${active}" ③th:attr="src=@{/images/gtvglogo.peng},title=${logo}, alt=#{logo}" ④th:checked="${user:active}"
遍历
语法:th:each="元素名, 迭代状态:${集合}"
迭代状态(iterStat)包含下面的属性: index:当前遍历元素的索引,从0开始 count:当前遍历元素的索引,从1开始 size:需要遍历元素的总数量 current:当前正在遍历的元素对象 event/odd:是否偶数/奇数行 first:是否第一个元素 last:是否最后一个元素
使用方式
导入场景
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
实体类Person
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Person {
private Long id;
private String name;
private String email;
private Integer age;
private String pwd;
}
编写测试页面
list.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" xmlns:th="http://www.thymeleaf.org">
<title>person列表页</title>
</head>
<body>
<table border="1px" cellpadding="0" >
<tr>
<th>#</th>
<th>名字</th>
<th>邮箱</th>
<th>年龄</th>
<th>密码</th>
<th>状态信息</th>
</tr>
<tr th:each="person, status:${persons}">
<td></td>
<td th:text="${person.name}"></td>
<td>[[${person.email}]]</td>
<td th:text="${person.age}"></td>
<td th:text="${person.pwd}"></td>
<td>
index: [[${status.index}]]<br/>
count:[[${status.count}]]<br/>
size: [[${status.size}]]<br/>
current(当前对象): [[${status.current}]]<br/>
even: [[${status.even}]]<br/>
odd: [[${status.odd}]]<br/>
first: [[${status.first}]]<br/>
last: [[${status.last}]]<br/>
</td>
</tr>
</table>
</body>
</html>
controller
@GetMapping("/list")
public String list(Model model){
List<Person> person = Arrays.asList(
new Person(1L, "张三01", "aaa1@qq.com", 12, "asd"),
new Person(2L, "张三02", "aaa2@qq.com", 18, "akd"),
new Person(3L, "张三03", "aaa3@qq.com", 19, "ajd"),
new Person(4L, "张三04", "aaa4@qq.com", 22, "aad"),
new Person(5L, "张三05", "aaa5@qq.com", 23, "afd"),
new Person(6L, "张三06", "aaa6@qq.com", 23, "abd")
);
model.addAttribute("persons", person);
return "list";
}
判断th:if
<td th:text="|${person.age} / ${person.age>=18?'成年':'未成年'}|"></td>
<td th:if="${#strings.isEmpty(person.email)}" th:text="'已失联'"></td>
<!--删除两-->
<td th:if="${not #strings.isEmpty(person.email)}" th:text="${person.email}"></td>
th:switch
属性优先级
优先级 | 特点 | 属性 |
---|---|---|
1 | 片段包含 | th:insert th:replace |
2 | 遍历 | th:each |
3 | 判断 | th:if th:unless th:switch th:case |
4 | 定义本地变量 | th:object th:with |
5 | 通用方式属性修改 | th:attr th:attrprepend th:attrappend |
6 | 指定属性修改 | th:value th:href th:src |
7 | 文本值 | th:text th:utext |
8 | 片段指定 | th:fragment |
9 | 片段移除 | th:remove |
注意:
在行内使用的时候,可以有如下的两种写法:
[[...]]
或[(...)]
#如 size: [[${status.size}]]<br/>
变量选择
模板布局
将通用的内容抽取出来。
属性 | 含义 |
---|---|
th:fragment | 定义模板 |
~{templatename::selector}(~{模板名 :: 片段名}) | 引用模板 |
th:insert、th:replace | 插入模板 |
footer表示公共页面的名称。
自动配置原理
①开启了org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration
自动配置 ②属性绑定在ThymeleafProperties
中,对应配置文件前缀spring.thymeleaf
内容 ③所有的模板页面默认在classpath:/templates
文件夹下 ④默认效果 a、所有的模板页面在classpath:/templates
下面找 b、后缀名为.html
devtools
SpringBoot提供的工具,在修改页面后。在IDEA中ctrl+F9重新编译。
使用方式
方式一
<!--热启动功能-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
java代码的修改,如果使用devtools
热启动了,可能会引起一些BUG,建议使用重启。
方式二
也可以在setting的Compiler中勾选Build project automatically,然后点击ctrl + alt + shift + /打开Register,勾选其中的compiler.automake.allow.when.app.running。(当idea失去焦点5秒时自动构建。不用一直点)
热部署范围配置
默认不触发重启的目录列表
/META-INF/maven /META-INF/resource /resources /static /public /templates
可以通过修改配置文件的方式修改这个范围。
spring:
devtools:
restart:
exclude: static/**, public/**, config/application.yaml
关闭热启动的功能
可以在application.yaml中设置enabled=false,但可能存在的情况是其他配置文件相同内容的覆盖,所以可在主程序中设置
public static void main(String[] args) {
System.setProperty("spring.devtools.restart.enabled", "false");
SpringApplication.run(SSMPApplication.class, args);
}
6、国际化
国际化的自动配置参照
MessageSourceAutoConfiguration
实现步骤
-
SpringBoot在类路径下查找message资源绑定文件。文件名为:
messages.properties
-
多语言可以定义多个消息文件,命名为message_区域代码.properties。如:
-
messages.properties
:默认 -
messages_en_US.properties
:英文环境 -
messages_zh_CN.properties
:中文环境
-
在程序中可以自动注入Message组件,获取国际化的配置项值
-
在页面中可以使用表达式#{}获取国际化的配置选项
实现方式
配置文件方式
配置文件
messages.properties
login=Login
sign=Sign-Up
messages_en_US.properties
login=Login
sign=Sign-Up
messages_zh_CN.properties
login=登录
sign=注册
展示页面earth.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" xmlns:th="http://www.thymeleaf.org">
<title>Title</title>
</head>
<body>
<!--国际化:配置页面的值去哪个文件取到key-->
<div>
<button th:text="#{login}"></button>
<button th:text="#{sign}"></button>
</div>
</body>
</html>
controller
@GetMapping("/internationalize")
public String test(){
return "earth";
}
代码方式
需要使用@RestController
@Autowired//国际化取消息用的组件
MessageSource messageSource;
@GetMapping("/haha")
public String haha(HttpServletRequest req){
Locale locale = req.getLocale();
//利用代码的方式获取国际化配置文件中指定的值
String login = messageSource.getMessage("login", null, locale);
return login;
}
7、错误处理
错误处理的自动配置都在
ErrorMvcAutoConfiguration
中,有两大核心机制 1、SpringBoot会自动适应处理错误(你设置的format是什么它就可以给你响应什么数据),响应页面或JSON数据 2、SpringMvc的错误处理机制依然保留,MVC处理不了,才会交给boot进行处理。
测试
@ExceptionHandler
@Controller
public class ExceptionController {
@GetMapping("/exception")
public String testException(){
int i = 10 / 0;
return "index";
}
/**
* 1、@ExceptionHandler标识一个方法处理错误,默认只能处理这个类发生的的指定错误
* 2、@ControllerAdvice统一处理所有错误
* @param e
* @return
*/
@ResponseBody
@ExceptionHandler(Exception.class)
public String handlerException(Exception e){
return "OHH~, 错误原因:" + e.getMessage();
}
}
@ControllerAdvice
控制器
@Controller
public class ExceptionController {
@GetMapping("/exception")
public String testException(){
int i = 10 / 0;
return "index";
}
}
handler
@ControllerAdvice
public class GlobalExceptionHandler {
/**
* 1、@ExceptionHandler标识一个方法处理错误,默认只能处理这个类发生的的指定错误
* 2、@ControllerAdvice统一处理所有错误
* @param e
* @return
*/
@ResponseBody
@ExceptionHandler(Exception.class)
public String handlerException(Exception e){
return "OHH~同一处理, 错误原因:" + e.getMessage();
}
}
错误处理类(BasicErrorController)
表示它从server.error.path读取配置文件,如果没有配置就会读取/error
-
发生错误以后,转发给/error路径,SpringBoot在底层写好一个BasicErrorController的组件,专门处理这个请求。
-
错误页面解析方式
//1、解析错误的视图地址
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
//2、如果解析不到错误页面的地址,默认的错误页就是error页
return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
容器中专门有一个错误视图解析器(在ErrorMvcAutoConfiguration
)
@Bean
@ConditionalOnBean(DispatcherServlet.class)
@ConditionalOnMissingBean(ErrorViewResolver.class)
DefaultErrorViewResolver conventionErrorViewResolver() {
return new DefaultErrorViewResolver(this.applicationContext, this.resources);
}
DefaultErrorViewResolver
@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
ModelAndView modelAndView = resolve(String.valueOf(status.value()), model);
if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
}
return modelAndView;
}
private ModelAndView resolve(String viewName, Map<String, Object> model) {
String errorViewName = "error/" + viewName;
TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName,
this.applicationContext);
if (provider != null) {
return new ModelAndView(errorViewName, model);
}
return resolveResource(errorViewName, model);
}
private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
for (String location : this.resources.getStaticLocations()) {
try {
Resource resource = this.applicationContext.getResource(location);
resource = resource.createRelative(viewName + ".html");
if (resource.exists()) {
return new ModelAndView(new HtmlResourceView(resource), model);
}
}
catch (Exception ex) {
}
}
return null;
}
容器中有一个默认的名为error的view;提供了默认白页功能。
@Bean(name = "error")
@ConditionalOnMissingBean(name = "error")
public View defaultErrorView() {
return this.defaultErrorView;
}
规则:
-
解析一个错误页
-
如果发生了5xx,4xx ①、如果有模板引擎,默认会在
classpath:/templates/error/精确码.html
②、如果没有模板引擎,在静态资源文件夹下找精确码.html
(模糊匹配) -
如果匹配不到
精确码.html
,有模板引擎就去找classpath:/templates/error/5xx/4xx.html
,没有模板引擎,在静态资源文件夹下找5xx.html
,4xx.html
。
-
如果模板引擎路径下面有
error.html
页面,就直接渲染。
测试
templates下error
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" xmlns:th="http://www.thymeleaf.org">
<title>error</title>
</head>
<body>
模板引擎 ERROR
</body>
</html>
-
最佳处理错误方式
-
前后端分离
后台发生的所有错误,使用
@ControllerAdvice
+@ExceptionHandler
进行异常处理
-
服务端页面渲染 - HTTP状态码规定错误 给
classpath:/templates/error/
放入常用精确的错误码页面,500.html
/404.html
给classpath:/templates/error/
放通用模糊匹配的错误页面。4xx.html
/5xx.html
- 业务错误 核心业务,每种错误都因该代码控制跳转到定制错误页面。 通用业务,在classpath:/templates/error.html
页面显示错误信息。(出现的错误信息通过SpringBoot都将它们放在了ModelAndView,只需要通过Thymeleaf方式取值即可)
8、嵌入式容器
Servlet容器
:管理,运行Servlet组件(Servlet,Filter, Listener)的环境,一般值服务器。
自动配置原理
SpringBoot默认嵌入了Tomcat作为Servlet容器。自动配置类是
ServletWebServerFactoryAutoConfiguration
,EnbeddedWenServerFactoryCustomizerAutoConfiguration
@AutoConfiguration(after = SslAutoConfiguration.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {
}
a、ServletWebServerFactoryAutoConfiguration
配置类,绑定了所有和服务器有关的配置server
b、绑定了自动配置类嵌入式容器场景。 c、ServletWebServerFactoryAutoConfiguration
导入了嵌入式的三大服务器Tomcat
、Jetty
、Undertow
i、导入Tomcat
、Jetty
、Undertow
都有条件注解,系统中有这个类才行(相关依赖) ii、默认Tomcat
配置生效 iii、它们都给容器中ServletWebServerFactory
放了一个web服务器工厂(造web服务器) iV、web服务器工厂都有一个功能,getWebServer
获取web服务器 d、ServletWebServerApplicationContext
ioc容器,启动的时候会调用(createWebServer)创建web服务器,而createWebServer
会在onRefresh
启动。(Spring容器刷新的时候,会预留一个时机,刷新子容器。onRefresh)
总结:
Web场景的Spring容器启动,在onRefresh的时候会调用Web服务器的方法。 Web服务器的创建是通过WebServerFactory实现,容器中又会根据导入的依赖的条件注解启动相关服务器。
用法
修改server下的相关配置即可修改服务器参数,通过给容其中放入一个
ServletWebServerFactory
来禁用SpringBoot默认放的工厂,实现自定义嵌入任意服务器。
9、全面接管SpringMVC
SpringBoot默认配置好了SpringMVC的所有常用特性,如果我么需要全面接管SpringMVC的所有配置并禁用默认配置,仅需要编写一个WebMvcConfigurer配置类,并标注
@EnableWebMvc
即可。 全手动模式:@EnableWebMvc
:禁用默认设置WebMvcConfigurer
组件:定义MVC的底层行为
@EnableWebMvc原理
①WebMvcAutoConfiguration
:web场景的自动配置类。
SpringMVC自动场景配置了如下所有的默认行为 a、支持RESTful的filter:HiddenHttpMethodFilter b、支持非POST请求,请求体携带数据:FormContentFilter c、导入了@Import(EnableWebMvcConfiguration): i、WelcomePageHandlerMapping
:欢迎页功能支持(模板引擎目录,金泰资源目录放index.html),项目访问/就默认展示这个页面 ii、LocaleResolver
:国际化解析器 iii、ThemeResolver
:主题解析器 iv、FlashMapManager
:临时数据共享 v、FormattingConversionService
:数据格式化、类型转化 vi、Validator
:数据校验JSR303
提供的数据校验功能 vii、RequestMappingHandlerMapping
找每个请求由谁处理的映射关系 viii、WebBindingInitializer
:请求参数的封装与绑定 ix、ExceptionHandlerExceptionResolver
:默认的异常解析器 x、ContentNegotiationManager
:内容协商管理器
d、WebMvcAutoConfigurationAdapter
配置生效,它是一个WebMvcConfigurer
,定义mvc底层组件 i、定义好WebMvcConfigurer
底层组件的默认功能 ii、视图解析器:InternalResourceViewResolver
iii、视图解析器:BeanNameViewResolver
,视图名(controller方法返回值字符串)就是组件名 iV、内容协商解析器:ContentNegotiatingViewResolver
v、请求上下文的过滤器:RequestContextFilter
:任意位置直接获取当前请求(RequestContextHolder) vi、静态资源链规则 vii、ProblemDetailsExceptionHandler
:错误详情 e、定义了默认的底层行为:WebMvcConfigurer
②@EnableWebMvc禁用默认行为
a、给容器中导入DelegatingWebMvcConfiguration
组件,它是WebMvcConfigurationSupport
b、WebMvcAutoConfiguration
有一个核心的注解@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
,容器中没有WebMvcConfigurationSupport
,WebMvcAutoConfiguration
才生效 c、@EnableWebMvc
导入WebMvcConfigurationSupport
导致WebMvcAutoConfiguration
失效。导致禁用了。
10、Web新特性
Problemdetails
它是
RFC 7807
定义的规范,用于定义错误信息返回格式。
文档:RFC 7807: Problem Details for HTTP APIs
原理
@Configuration(proxyBeanMethods = false)
//配置过一个属性spring.mvc.problemdetails.enabled=true
@ConditionalOnProperty(prefix = "spring.mvc.problemdetails", name = "enabled", havingValue = "true")
static class ProblemDetailsErrorHandlingConfiguration {
@Bean
@ConditionalOnMissingBean(ResponseEntityExceptionHandler.class)
ProblemDetailsExceptionHandler problemDetailsExceptionHandler() {
return new ProblemDetailsExceptionHandler();
}
}
a、
ProblemDetailsExceptionHandler
是一个@ControllerAdvice
集中处理系统异常 b、处理以下指定异常,如果系统出现以下异常,会被SpringBoot支持RFC 7807
的方式返回异常
@ExceptionHandler({
HttpRequestMethodNotSupportedException.class,
HttpMediaTypeNotSupportedException.class,
HttpMediaTypeNotAcceptableException.class,
MissingPathVariableException.class,
MissingServletRequestParameterException.class,
MissingServletRequestPartException.class,
ServletRequestBindingException.class,
MethodArgumentNotValidException.class,
NoHandlerFoundException.class,
AsyncRequestTimeoutException.class,
ErrorResponseException.class,
ConversionNotSupportedException.class,
TypeMismatchException.class,
HttpMessageNotReadableException.class,
HttpMessageNotWritableException.class,
BindException.class
})
函数式web
SpringMVC5.2之后允许我们使用函数式的方式,定义web的请求处理流程。
Web请求处理的方式: a、@Controller
+ @RequestMapping
:耦合式(路由、业务耦合) b、函数式Web:分离式(路由、业务分离)
使用场景实例:
场景 User RESTful-CRUD GET/user/1 获取1号用户 GET/user 获取所有用户 POST/user 请求体携带JSON,新增一个用户 PUT/user/1 请求体携带JSON,修改1号用户 DELETE/user/1删除1号用户
实现步骤
核心类 RouterFunction RequestPredicate ServerRequest ServerResponse
①实体类User
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Long id;
private String name;
private String email;
private Integer age;
private String pwd;
}
②编写路由请求WebFunctionConfig
package com.louis.config;
import com.louis.business.UserBusinessHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.servlet.function.*;
/**
* @author XRY
* @date 2023年07月13日15:15
*/
/**
* GET/user/1 获取1号用户
* GET/user 获取所有用户
* POST/user 请求体携带JSON,新增一个用户
* PUT/user/1 请求体携带JSON,修改1号用户
* DELETE/user/1 删除1号用户
*/
@Configuration
public class WebFunctionConfig {
/**
* 函数式web
* 1、给容器中放一个Bean RouterFunction<ServerResponse>
* 2、每个业务准备一个自己的handler,但它的声明需要使用HandlerFunction类型
*
* 函数式web核心四大对象
* 1、RouterFunction:定义路由信息。发什么请求、谁来处理
* 2、RequestPredicate:请求谓语。请求方式(Get、POST)、请求参数
* 3、ServerRequest:封装请求完整数据
* 4、ServerResponse:封装响应完整数据
*/
/* @Bean
public RouterFunction<ServerResponse> userRoute(){
//+s表示某个类的工具类
RouterFunctions.route() //开始定义路由信息
.GET("/user/{id}", RequestPredicates.accept(MediaType.ALL), request -> {
//业务处理
//构造响应
return ServerResponse.ok().build();
}) //RequestPredicates.accept(MediaType.ALL)表示接收任意类型的参数
return null;
}*/
//@Bean给容器中放组件的时候,如果参数是一个对象,它会默认从容器中取,相当于自动注入
@Bean
public RouterFunction<ServerResponse> userRoute(UserBusinessHandler userBusinessHandler){
return RouterFunctions.route()
.GET("/user/{id}", RequestPredicates.accept(MediaType.ALL), userBusinessHandler::getUser/*相当于调用userBusinessHandler的方法*/)
.GET("/users", userBusinessHandler::getUsers)
.POST("/user", RequestPredicates.accept(MediaType.APPLICATION_JSON), userBusinessHandler::saveUser)
.PUT("/user/{id}", RequestPredicates.accept(MediaType.APPLICATION_JSON), userBusinessHandler::updateUSer)
.DELETE("/user/{id}", userBusinessHandler::deleteUser)
.build();
}
}
③业务处理类UserBusinessHandler
/**
* 专门处理User有关的业务
*/
@Slf4j
@Component
public class UserBusinessHandler {
/**
* 查询指定id的user
* 他要代替原来的handlerFunction
* public interface HandlerFunction<T extends ServerResponse> {
* T handle(ServerRequest request) throws Exception;
*
* }
* @param request
* @return
*/
public ServerResponse getUser(ServerRequest request) throws Exception{
String id = request.pathVariable("id");
log.info("查询{}用户信息", id);
//业务处理
User user = new User(1L, "louis", "aa@qq.com",23, "1234");
//构造响应
return ServerResponse.ok().body(user);
}
/**
* 获取所有用户
* @param request
* @return
* @throws Exception
*/
public ServerResponse getUsers(ServerRequest request) throws Exception{
log.info("查询所有用户信息完成");
//业务处理
List<User> users = Arrays.asList(
new User(1L, "张三01", "aaa1@qq.com", 12, "asd"),
new User(2L, "张三02", "", 18, "akd"),
new User(3L, "张三03", "", 19, "ajd"),
new User(4L, "张三04", "aaa4@qq.com", 22, "aad"),
new User(5L, "张三05", "aaa5@qq.com", 23, "afd"),
new User(6L, "张三06", "aaa6@qq.com", 23, "abd")
);
return ServerResponse.ok().body(users);//凡是body中的数据,就是以前的@ResponseBody原理,利用HttpMessageConverter写出为json
}
/**
* 保存用户
* @param request
* @return
*/
public ServerResponse saveUser(ServerRequest request) throws ServletException, IOException {
//提取请求体
User body = request.body(User.class);
log.info("保存信息:{}", body);
return ServerResponse.ok().build();
}
/**
* 更新用户
* @param request
* @return
*/
public ServerResponse updateUSer(ServerRequest request) throws ServletException, IOException {
User body = request.body(User.class);
log.info("用户信息更新:{}", body);
return ServerResponse.ok().build();
}
/**
* 删除用户
* @param request
* @return
*/
public ServerResponse deleteUser(ServerRequest request) {
String id = request.pathVariable("id");
log.info("删除用户-{}-信息", id);
return ServerResponse.ok().build();
}
}
④测试
getUser
getUsers
saveUser
updateUSer
deleteUser