@xxxMapping
:
Rest 风格支持(使用 Http 请求方式动词来表示对资源的操作)
以前:/getUser 获取用户 /deleteUser 删除用户 /editUser 删除用户 /saveUser 保存用户
现在:/user GET-获取用户 DELTE-删除用户 PUT-修改用户 POST-保存用户
核心Filter:HiddenHttpMethodFilter
如果想要使用 Rest 风格,那么在 SpringBoot 中还需要配置对应的属性:
spring:
mvc:
hiddenmethod:
filter:
enabled: true
在源码中可看,属性默认为 false,因此 Rest 风格需要自己进行配置
@Bean
@ConditionalOnMissingBean({HiddenHttpMethodFilter.class})
@ConditionalOnProperty(
prefix = "spring.mvc.hiddenmethod.filter",
name = {"enabled"}
// 属性默认 false,表示未开启
)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new OrderedHiddenHttpMethodFilter();
}
# 选择性开启,页面提交可以用,客户端直接发送 PUT 请求不需要配置 filter
spring:
mvc:
hiddenmethod:
filter:
enabled: true
正常情况下,SpringBoot 已经为这两个请求配置了 HiddenHttpMethod,但并不是修改底层
//################ HiddenHttpMethodFilter 类的内部 对请求的定义 ##########
private static final List<String> ALLOWED_METHODS;
public static final String DEFAULT_METHOD_PARAM = "_method";
private String methodParam = "_method";
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
HttpServletRequest requestToUse = request;
if ("POST".equals(request.getMethod()) && request.getAttribute("javax.servlet.error.exception") == null) {
String paramValue = request.getParameter(this.methodParam);
if (StringUtils.hasLength(paramValue)) {
// PUT 和 DELETE 经过 toUpperCase 方法转换,大小写都可以
String method = paramValue.toUpperCase(Locale.ENGLISH);
if (ALLOWED_METHODS.contains(method)) {
requestToUse = new HiddenHttpMethodFilter.HttpMethodRequestWrapper(request, method);
}
}
}
filterChain.doFilter((ServletRequest)requestToUse, response);
}
如果我们需要使用 PUT 或 DELTE 请求方式,那么我们需要将 method 参数改为 POST,在表单内部添加带有 name=“_method” 属性,在这个标签中的 value 处定义 PUT 或 DELETE 请求,做法如下:
// ############## 错误写法 ########### \\
<form action="/user" method="delete">
<input value="REST-DELETE 提交" type="submit">
</form>
// ############## 正确写法 ########### \\
<form action="/user" method="post">
<input type="hidden" name="_method" value="PUT">
<input value="REST-PUT 提交" type="submit">
</form>
// 处理 Get 请求
@GetMapping("/user")
public String getUser(){
return "GET-张三";
}
// 处理 Post 请求
@PostMapping("/user")
public String saveUser(){
return "POST-张三";
}
// 处理 Put 请求
@PutMapping("/user")
public String putUser(){
return "PUT-张三";
}
// 处理 Delete 请求
@DeleteMapping("/user")
public String deleteUser(){
return "DELETE-张三";
}
// 处理 Patch 请求
@PatchMapping("/user")
public String pacthUser(){
return "Pacth-张三";
}
在配置类中,重新声明 hiddenHttpMethodFilter 方法,并将该方法注入到容器中即可
/**
* 该类配置 Web 各种属性
* - proxyBeanMethods = false - 表示该类没有任何依赖
*/
@Configuration(proxyBeanMethods = false)
public class WebConfig {
/**
* 覆盖原有的 hiddenHttpMethodFilter 类
* @return
*/
@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
// 设置默认携带参数
methodFilter.setMethodParam("_m");
return methodFilter;
}
}
SpringMvc 功能分析,都从 org.springframework.web.servlet.DispatcherServlet -》 doService()
**在 DispatcherServlet 类中的 doDispatch() 里,如何找到处理请求的控制器的? **
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Object dispatchException = null;
try {
processedRequest = this.checkMultipart(request);
multipartRequestParsed = processedRequest != request;
// 下列一行代码获得到了该使用哪个 handler 处理这次请求的请求方式
mappedHandler = this.getHandler(processedRequest);
if (mappedHandler == null) {
this.noHandlerFound(processedRequest, response);
return;
}
getHandler():
在获取控制器方法时,传入了 requst 参数,其中该参数中就携带了以下 Mapping 规则
RequestMappingHandlerMapping:保存了所有 @Requestmapping 和 handler 的映射规则
所有的请求映射都保存在 HandlerMapping 中
/
能访问到 index.html注解 | 说明 |
---|---|
@PathVarivble | 路径变量; |
@RequestHeader | 获取请求头; |
@RequestAttribute | 获取 request 域属性; |
@Requestparam | 获取请求参数; |
@MatrixVariable | 矩阵变量; |
@CookieValue | 获取 cookie 值; |
@RequestBody | 获取请求体; |
获得路径中的变量参数,当变量如果是以两种变量组合的话,可以直接声明 Map 来获得 Map 对象,该对象的类型是<String,String>
@GetMapping("/car/{id}/owner/{name}")
public Map<String, Object> getCar(@PathVariable("id") Integer id,
@PathVariable("name") String name,
@PathVariable Map<String, String> pv){
Map<String, Object> map = new HashMap<>();
map.put("id", id);
map.put("name", name);
map.put("pv", pv);
return map;
// 返回结果 {"pv":{"name":"zhangsan","id":"2"},"name":"zhangsan","id":2}
}
获取请求头的中参数信息,当然如果以两种变量组合的话,一样可以用 Map 来接收参数
// 请求地址:http://localhost:8080/getHeader
@GetMapping("/getHeader")
public Map<String, Object> getRequsetHeader(@RequestHeader("User-Agent") String userAgent,
@RequestHeader Map<String, String> header){
Map<String, Object> map = new HashMap<>();
map.put("userAgent", userAgent);
map.put("header", header);
return map;
// 返回结果:一大串内容,包括了全部的请求头数据
}
获取请求体中的数据,可以用单个参数获取,也可以使用 List 列表来接收多个参数。
不同的参数也可以使用 Map 来接收,但是当出现相同的参数名,那么 Map 只会保留第一个存储的数据
// 发送请求:http://localhost:8080/getParam?age=18&inters=football&inters=basketball
@GetMapping("/getParam")
public Map<String, Object> getRequsetParam(@RequestParam("inters") List<String> inters,
@RequestParam Map<String, String> params,
@RequestParam("age") Integer age){
Map<String, Object> map = new HashMap<>();
map.put("inters", inters);
map.put("params", params);
map.put("age", age);
return map;
// 返回结果:{"inters":["football","basketball"],"params":{"age":"18","inters":"football"},"age":18}
}
获得 Cookie 的值,设置对应的 cookie 属性名,然后用字符串接收即可
如果想获得所有的 cookie 值,可以声明一个 Cookie 类型来接收数据
// 请求地址:http://localhost:8080/getCookie
@GetMapping("/getCookie")
public Map<String, Object> getCookieValue(@CookieValue("_ga") String _ga,
@CookieValue Cookie cookie){
Map<String, Object> map = new HashMap<>();
map.put("_ga", _ga);
map.put("cookie", cookie);
return map;
}
获取请求体中的内容
// 请求地址:http://localhost:8080/save
@PostMapping("/save")
public Map<String, Object> getRequestBody(@RequestBody String content){
Map<String, Object> map = new HashMap<>();
map.put("content", content);
return map;
}
获得 request 请求域中的内容信息
// 发送请求:http://localhost:8080/goto
@GetMapping("/goto")
public String goToPage(HttpServletRequest request){
request.setAttribute("msg","成功");
request.setAttribute("code",200);
return "forward:/success";// 转发至 /success 请求
}
@ResponseBody
@GetMapping("/success")
public Map<String, Object> success(@RequestAttribute("msg") String msg,
@RequestAttribute("code") Integer code,
// 因为传过来的是原生的 request,
// 所以也可以使用原生 request 来接收参数
HttpServletRequest request){
String msg1 = (String) request.getAttribute("msg");
Map<String, Object> map = new HashMap<>();
map.put("code",code);
map.put("msg",msg1);
map.put("data",msg);
return map;
// 请求结果:{"msg":"成功","code":200,"data":"成功"}
}
获取 矩阵变量 内容。
SpringBoot 默认禁用矩阵变量的功能!
根据 URI 规范 RFC 3986 中 URL 的定义,路径片段中可以包含键值对。规范中没有对应的术语…在 SpringMVC 它被称为矩阵变量.
矩阵变量可以出现在任何路径片段中,每一个矩阵变量都用分号(;)隔开
示例:/patch/{path;low=34;brand=byd,audi,yd}
SpringBoot 对于路径的处理,都要经过 UrlPathHelper 类来处理。
在该类中,将 removeSemicolonContent(移除分号内容)属性设置为 false 即可(默认 true)
在配置类中,编写以下代码即可
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
// 设置 removeSemicolonContent 属性为 false
urlPathHelper.setRemoveSemicolonContent(false);
// 重新设置 configurer 对象
configurer.setUrlPathHelper(urlPathHelper);
}
};
}
// 请求地址: http://localhost:8080/cars/sell;low=34;brand=byd,audi,yd
@GetMapping("/cars/{path}")
public Map<String, Object> carsSell(@MatrixVariable("low") Integer low,
@MatrixVariable("brand") List<String> brand){
HashMap<String, Object> map = new HashMap<>();
map.put("low", low);
map.put("brand", brand);
return map;
// 请求结果:{"low":34,"brand":["byd","audi","yd"]}
}
在访问请求时,/{path} 是随意的,这里指的是一个通配符。
当参数相同,在 @MatrixVariable 注解里设置对应参数即可
@MatrixVariable(value = "age",pathVar = "bossId")
:在 /{bossId} 路径中,获取参数名是 age 的值
// 请求地址: http://localhost:8080/boss/1;age=20/2;age=30
@GetMapping("/boss/{bossId}/{empId}")
public Map<String, Object> boss(@MatrixVariable(value = "age",pathVar = "bossId") Integer bossAge,
@MatrixVariable(value = "age",pathVar = "empId") Integer empAge){
HashMap<String, Object> map = new HashMap<>();
map.put("bossId", bossAge);
map.put("empId", empAge);
return map;
// 请求结果:{"empId":30,"bossId":20}
}
// DispatcherServler -- doDispatch
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 调用映射后的 Handler 的方法
protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
this.checkRequest(request);
ModelAndView mav;
if (this.synchronizeOnSession) {
HttpSession session = request.getSession(false);
if (session != null) {
Object mutex = WebUtils.getSessionMutex(session);
synchronized(mutex) {
mav = this.invokeHandlerMethod(request, response, handlerMethod);
}
} else {
mav = this.invokeHandlerMethod(request, response, handlerMethod);
}
} else {
mav = this.invokeHandlerMethod(request, response, handlerMethod);
}
if (!response.containsHeader("Cache-Control")) {
if (this.getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
this.applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
} else {
this.prepareResponse(response);
}
}
return mav;
}
确定将要执行的目标方法的每一个参数的值是什么;
SpringMVC目标方法能写多少种参数类型。取决于参数解析器。
==========================InvocableHandlerMethod==========================
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {
MethodParameter[] parameters = getMethodParameters();
if (ObjectUtils.isEmpty(parameters)) {
return EMPTY_ARGS;
}
Object[] args = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
MethodParameter parameter = parameters[i];
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
args[i] = findProvidedArgument(parameter, providedArgs);
if (args[i] != null) {
continue;
}
if (!this.resolvers.supportsParameter(parameter)) {
throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
}
try {
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
}
catch (Exception ex) {
// Leave stack trace for later, exception may actually be resolved and handled...
if (logger.isDebugEnabled()) {
String exMsg = ex.getMessage();
if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
logger.debug(formatArgumentError(parameter, exMsg));
}
}
throw ex;
}
}
return args;
}
@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
if (result == null) {
for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
if (resolver.supportsParameter(parameter)) {
result = resolver;
this.argumentResolverCache.put(parameter, result);
break;
}
}
}
return result;
}
调用各自 HandlerMethodArgumentResolver 的 resolveArgument 方法即可
ServletModelAttributeMethodProcessor 这个参数处理器支持
是否为简单类型。
public static boolean isSimpleValueType(Class<?> type) {
return (Void.class != type && void.class != type &&
(ClassUtils.isPrimitiveOrWrapper(type) ||
Enum.class.isAssignableFrom(type) ||
CharSequence.class.isAssignableFrom(type) ||
Number.class.isAssignableFrom(type) ||
Date.class.isAssignableFrom(type) ||
Temporal.class.isAssignableFrom(type) ||
URI.class == type ||
URL.class == type ||
Locale.class == type ||
Class.class == type));
}
@Override
@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
Assert.state(mavContainer != null, "ModelAttributeMethodProcessor requires ModelAndViewContainer");
Assert.state(binderFactory != null, "ModelAttributeMethodProcessor requires WebDataBinderFactory");
String name = ModelFactory.getNameForParameter(parameter);
ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
if (ann != null) {
mavContainer.setBinding(name, ann.binding());
}
Object attribute = null;
BindingResult bindingResult = null;
if (mavContainer.containsAttribute(name)) {
attribute = mavContainer.getModel().get(name);
}
else {
// Create attribute instance
try {
attribute = createAttribute(name, parameter, binderFactory, webRequest);
}
catch (BindException ex) {
if (isBindExceptionRequired(parameter)) {
// No BindingResult parameter -> fail with BindException
throw ex;
}
// Otherwise, expose null/empty value and associated BindingResult
if (parameter.getParameterType() == Optional.class) {
attribute = Optional.empty();
}
bindingResult = ex.getBindingResult();
}
}
if (bindingResult == null) {
// Bean property binding and validation;
// skipped in case of binding failure on construction.
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
if (binder.getTarget() != null) {
if (!mavContainer.isBindingDisabled(name)) {
bindRequestParameters(binder, webRequest);
}
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new BindException(binder.getBindingResult());
}
}
// Value type adaptation, also covering java.util.Optional
if (!parameter.getParameterType().isInstance(attribute)) {
attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
}
bindingResult = binder.getBindingResult();
}
// Add resolved attribute and BindingResult at the end of the model
Map<String, Object> bindingResultModel = bindingResult.getModel();
mavContainer.removeAttributes(bindingResultModel);
mavContainer.addAllAttributes(bindingResultModel);
return attribute;
}
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
WebDataBinder :web数据绑定器,将请求参数的值绑定到指定的JavaBean里面
WebDataBinder 利用它里面的 Converters 将请求数据转成指定的数据类型。再次封装到JavaBean中
GenericConversionService:在设置每一个值的时候,找它里面的所有converter那个可以将这个数据类型(request带来参数的字符串)转换到指定的类型(JavaBean – Integer)
byte – > file
@FunctionalInterfacepublic interface Converter<S, T>
将所有的数据都放在 ModelAndViewContainer;包含要去的页面地址View。还包含Model数据
InternalResourceView:
@Override
protected void renderMergedOutputModel(
Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Expose the model object as request attributes.
exposeModelAsRequestAttributes(model, request);
// Expose helpers as request attributes, if any.
exposeHelpers(request);
// Determine the path for the request dispatcher.
String dispatcherPath = prepareForRendering(request, response);
// Obtain a RequestDispatcher for the target resource (typically a JSP).
RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
if (rd == null) {
throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
"]: Check that the corresponding file exists within your web application archive!");
}
// If already included or response already committed, perform include, else forward.
if (useInclude(request, response)) {
response.setContentType(getContentType());
if (logger.isDebugEnabled()) {
logger.debug("Including [" + getUrl() + "]");
}
rd.include(request, response);
}
else {
// Note: The forwarded resource is supposed to determine the content type itself.
if (logger.isDebugEnabled()) {
logger.debug("Forwarding to [" + getUrl() + "]");
}
rd.forward(request, response);
}
}
请求发到控制器,由控制器进行对应的请求跳转,这样就可以称为响应
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- web 场景自动引入了json场景-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
<version>2.7.1</version>
<scope>compile</scope>
</dependency>
给前端自动返回json数据
ModelAndView;
Model;
View;
ResponseEntity;
ResponseBodyEmitter;
StreamingResponseBody;
HttpEntity;
HttpHeaders;
Collable;
DeferredResult;
ListenableFutre;
WebAsyncTask;
@ModelAttribute;
// 当标注 ResponseBody 注解,boot 底层就会使用 RequestResponseBodyMethodProcossor 来解析
@ResponseBody注解 --> RequestResponseBodyMethodProcossor
HttpMessageConverter:看是否支持将此 Class 类型的对象转为MediaType类型的数据
例子: Person 对象转为 Json。Json 转为 Person 对象
最终: MappingJackson2MessageConverter 把对象转为 JSON(利用底层的Jackson的objectmapper转换的)
根据客户端接收能力不同,返回不同狗媒体类型的数据
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
只需要该变请求头中accept字段。http协议中规定的,告诉服务器本客户端可以接收的数据类型
为了方便内容协商,开启基于请求参数的内容协商功能
# 开启参数模式-内容协商
spring:
contentnegotiation:
favor-parameter: true
发请求:
确定客户端接收什么样的内容类型;
场景定义:
1、浏览器发送请求直接返回xml [application/xml] jacksonXmlConverter
2、如果是ajax请求 返回json [application/json] jacksonJsonConverter
3、如果另外app发送请求,返回自定义协商数据 [application/x-XXX] xxxConverter
执行步骤:
执行操作:
创建自定义信息转换器。当继承HttpMessageConverter后,底层将自动检测到自定义的转换器
package com.renexdemo.converter;
import com.renexdemo.bean.Person;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import java.io.IOException;
import java.io.OutputStream;
import java.util.List;
public class RixMessageConverter implements HttpMessageConverter<Person> {
/**
* 能读
* @param clazz
* @param mediaType
* @return
*/
@Override
public boolean canRead(Class<?> clazz, MediaType mediaType) {
return false;
}
/**
* 能写
* @param clazz
* @param mediaType
* @return
*/
@Override
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
// 能写的类型设置为可通过类型,示例:Person.class
return clazz.isAssignableFrom(Person.class);
}
/**
* 获得所有媒体类型
* 服务器需要统计所有MessageConverter都能写出哪些内柔类型
*
* applkicatoin/x-rix
* @return
*/
@Override
public List<MediaType> getSupportedMediaTypes() {
// 设置转换的自定义类型
return MediaType.parseMediaTypes("application/x-rix");
}
/**
* 获得指定类型的媒体类型
* @param clazz
* @return
*/
@Override
public List<MediaType> getSupportedMediaTypes(Class<?> clazz) {
return null;
}
/**
* 读取 Person 类型的媒体类型
* @param clazz
* @param inputMessage
* @return
* @throws IOException
* @throws HttpMessageNotReadableException
*/
@Override
public Person read(Class<? extends Person> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
return null;
}
/**
* 写
* @param person
* @param contentType
* @param outputMessage
* @throws IOException
* @throws HttpMessageNotWritableException
*/
@Override
public void write(Person person, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
// 自定义协议数据的写出
String data = person.getName()+";"+person.getAge()+";"+person.getBirth();
// 写出
OutputStream body = outputMessage.getBody();
body.write(data.getBytes());
}
}
在自定义的 Web 配置类中,重写 configuraionContentNegotiation 方法,自定义内容协商策略
/**
* 自定义内容协商策略
* @param configurer
*/
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
// 接收所有的媒体类型
Map<String, MediaType> mediaTypes = new HashMap<>();
mediaTypes.put("json",MediaType.APPLICATION_JSON);
mediaTypes.put("xml",MediaType.APPLICATION_XML);
mediaTypes.put("rix",MediaType.parseMediaType("application/x-rix"));
// 指定支持解析哪些参数对应的哪些媒体类型
// 基于参数
ParameterContentNegotiationStrategy parameterStrategy = new ParameterContentNegotiationStrategy(mediaTypes);
parameterStrategy.setParameterName("ff");// 修改默认参数名(默认format)
// 基于请求头
HeaderContentNegotiationStrategy headerStrategy = new HeaderContentNegotiationStrategy();
configurer.strategies(Arrays.asList(parameterStrategy, headerStrategy));
}
再次重写 extendMessageConverters 方法,添加自定义信息转换器
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
// 添加自定义 信息转换器
converters.add(new RixMessageConverter());
}
视图解析:SpringBoot 默认不支持 JSP,需要引入第三方模板引擎技术实现页面渲染
视图解析:
概述:
Thymeleaf 是一个服务器端 Java 模板引擎,能够处理 HTML、XML、CSS、JAVASCRIPT 等模板文件。Thymeleaf 模板可以直接当作静态原型来使用,它主要目标是为开发者的开发工作流程带来优雅的自然模板,也是 Java 服务器端 HTML5 开发的理想选择。
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Index Page</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<p th:text="${message}">Welcome to BeiJing!</p>
</body>
</html>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
@AutoConfiguration(
after = {WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class}
)
@EnableConfigurationProperties({ThymeleafProperties.class})
@ConditionalOnClass({TemplateMode.class, SpringTemplateEngine.class})
@Import({ReactiveTemplateEngineConfiguration.class, DefaultTemplateEngineConfiguration.class})
自动配好的策略:
默认前缀后缀
public static final String DEFAULT_PREFIX = "classpath:/templates/";
public static final String DEFAULT_SUFFIX = ".html";
<!DOCTYPE html>
<!--xmlns:th="http://www.thymeleaf.org" 页面前言,可以出现 thymeleaf 提示-->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>thymeleaf_test</title>
</head>
<body>
<h1 th:text="${msg}">hhhhhh</h1>
<a th:href="${link}" href="www.baidu.com" >去百度</a>
<a th:href="@{link}" href="www.baidu.com" >去百度2</a>
</body>
</html>
创建拦截器,继承 HandlerInterceptor 接口
实现接口的方法
/**
* 目标方法执行完成前
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return false;
}
/**
* 目标方法执行完成后
* @param request
* @param response
* @param handler
* @param modelAndView
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
/**
* 页面渲染后
* @param request
* @param response
* @param handler
* @param ex
* @throws Exception
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
将拦截器添加进容器中(实现 WebMvcConfigurer 的 addInterceptors)
@Configuration
public class AdminConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
}
}
指定拦截器
registry.addInterceptor(new LoginInterceptor())
/* 所有请求都会被拦截,包括静态资源 */
.addPathPatterns("/**")
.excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**","/js/**");