官方文档 - Template Layout
中文文档
复习Template 语法,把链接放上来
/templates/common.html
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head th:fragment="commonheader">
...
head>
<body>
<div id="leftmenu" class="left-side sticky-left-side">
...
<div class="left-side-inner">
...
<ul class="nav nav-pills nav-stacked custom-nav">
<li><a th:href="@{/main.html}"><i class="fa fa-home">i> <span>Dashboardspan>a>li>
...
<li class="menu-list nav-active"><a href="#"><i class="fa fa-th-list">i> <span>Data Tablesspan>a>
<ul class="sub-menu-list">
<li><a th:href="@{/basic_table}"> Basic Tablea>li>
<li><a th:href="@{/dynamic_table}"> Advanced Tablea>li>
<li><a th:href="@{/responsive_table}"> Responsive Tablea>li>
<li><a th:href="@{/editable_table}"> Edit Tablea>li>
ul>
li>
...
ul>
div>
div>
<div th:fragment="headermenu" class="header-section">
<a class="toggle-btn"><i class="fa fa-bars">i>a>
...
div>
<div id="commonscript">
<script th:src="@{/js/jquery-1.10.2.min.js}">script>
<script th:src="@{/js/jquery-ui-1.9.2.custom.min.js}">script>
<script th:src="@{/js/jquery-migrate-1.2.1.min.js}">script>
<script th:src="@{/js/bootstrap.min.js}">script>
<script th:src="@{/js/modernizr.min.js}">script>
<script th:src="@{/js/jquery.nicescroll.js}">script>
<script th:src="@{/js/scripts.js}">script>
div>
body>
html>
/templates/table/basic_table.html
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
<meta name="description" content="">
<meta name="author" content="ThemeBucket">
<link rel="shortcut icon" href="#" type="image/png">
<title>Basic Tabletitle>
<div th:include="common :: commonheader"> div>
head>
<body class="sticky-header">
<section>
<div th:replace="common :: #leftmenu">div>
<div class="main-content" >
<div th:replace="common :: headermenu">div>
...
div>
section>
<div th:replace="common :: #commonscript">div>
body>
html>
Difference between th:insert
and th:replace
(and th:include
)
控制层代码:
@GetMapping("/dynamic_table")
public String dynamic_table(Model model){
//表格内容的遍历
List<User> users = Arrays.asList(new User("zhangsan", "123456"),
new User("lisi", "123444"),
new User("haha", "aaaaa"),
new User("hehe ", "aaddd"));
model.addAttribute("users",users);
return "table/dynamic_table";
}
页面代码:
<table class="display table table-bordered" id="hidden-table-info">
<thead>
<tr>
<th>#th>
<th>用户名th>
<th>密码th>
tr>
thead>
<tbody>
<tr class="gradeX" th:each="user,stats:${users}">
<td th:text="${stats.count}">Tridenttd>
<td th:text="${user.userName}">Internettd>
<td >[[${user.password}]]td>
tr>
tbody>
table>
ctrl+b进入代码
视图解析原理流程:
DispatcherServlet
源码),所有数据都会被放在 ModelAndViewContainer
里面,其中包括数据和视图地址。ModelAndViewContainer
。ModelAndView
(数据和视图地址)。processDispatchResult()
处理派发结果(页面改如何响应)
render(mv, request, response);
进行页面渲染逻辑
String
返回值得到 View
对象【定义了页面的渲染逻辑】View
对象redirect:/main.html --> Thymeleaf new RedirectView()
。ContentNegotiationViewResolver
里面包含了下面所有的视图解析器,内部还是利用下面所有视图解析器得到视图对象。view.render(mv.getModelInternal(), request, response);
视图对象调用自定义的render进行页面渲染工作。RedirectView
如何渲染【重定向到一个页面】response.sendRedirect(encodedURL);
视图解析:
- 返回值以 forward:
开始: new InternalResourceView(forwardUrl);
--> 转发request.getRequestDispatcher(path).forward(request, response);
- 返回值以 redirect:
开始: new RedirectView()
--> render就是重定向
- 返回值是普通字符串:new ThymeleafView()
—>
阅读源码:最好自己在IDE,打断点,且Debug模式运行实例,这样比较没那么沉闷。
需要复习一些request,response,session等知识点,把复习链接放在这里
编写一个拦截器实现HandlerInterceptor
接口
拦截器注册到容器中(实现WebMvcConfigurer
的addInterceptors()
)
指定拦截规则(注意,如果是拦截所有,静态资源也会被拦截】
编写一个实现HandlerInterceptor
接口的拦截器:
package edu.gdpu.config;
import edu.gdpu.interceptor.LoginInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class AdminWebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor()) //拦截器注册到容器中
.addPathPatterns("/**") //所有请求都被拦截包括静态资源
.excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**","/js/**");
// ?静态资源这里还可以这样子写的,在/static/**全部发现,需要在application.yml配置
// # 静态资源访问前缀
//#spring:
//# mvc:
//# static-path-pattern: /res/** 麻烦的是css,js等静态资源都要加上/static才会被访问到
}
}
拦截器注册到容器中 && 指定拦截规则:
@Configuration
public class AdminWebConfig implements WebMvcConfigurer{
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())//拦截器注册到容器中
.addPathPatterns("/**") //所有请求都被拦截包括静态资源
.excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**",
"/js/**"); //放行的请求
}
看了老师那么多源码解析,我都是懵懵懂懂的,就是没听,这次认真学习IDEA的debug调试,附上链接
HandlerExecutionChain
(可以处理请求的handler以及handler的所有 拦截器)preHandle()
方法。
preHandle()
返回为true
。则执行下一个拦截器的preHandle()
false
。直接倒序执行所有已经执行了的拦截器的 afterCompletion();
。false
,直接跳出不执行目标方法。true
,才执行目标方法。postHandle()
方法。afterCompletion()
。afterCompletion()
。DispatcherServlet
中涉及到HandlerInterceptor
的地方:
public class DispatcherServlet extends FrameworkServlet {
...
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;
Exception dispatchException = null;
...
//该方法内调用HandlerInterceptor的preHandle()
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
...
//该方法内调用HandlerInterceptor的postHandle()
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
//该方法内调用HandlerInterceptor接口的afterCompletion方法
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
//该方法内调用HandlerInterceptor接口的afterCompletion方法
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
...
}
}
private void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, Exception ex) throws Exception {
if (mappedHandler != null) {
//该方法内调用HandlerInterceptor接口的afterCompletion方法
mappedHandler.triggerAfterCompletion(request, response, ex);
}
throw ex;
}
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
...
if (mappedHandler != null) {
//该方法内调用HandlerInterceptor接口的afterCompletion方法
// Exception (if any) is already handled..
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
}
public class HandlerExecutionChain {
...
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
for (int i = 0; i < this.interceptorList.size(); i++) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
//HandlerInterceptor的preHandle方法
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
return false;
}
this.interceptorIndex = i;
}
return true;
}
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
throws Exception {
for (int i = this.interceptorList.size() - 1; i >= 0; i--) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
//HandlerInterceptor接口的postHandle方法
interceptor.postHandle(request, response, this.handler, mv);
}
}
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) {
for (int i = this.interceptorIndex; i >= 0; i--) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
try {
//HandlerInterceptor接口的afterCompletion方法
interceptor.afterCompletion(request, response, this.handler, ex);
}
catch (Throwable ex2) {
logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
}
}
}
}
/static/form/form_layouts.html
<form role="form" th:action="@{/upload}" method="post" enctype="multipart/form-data">
<div class="form-group">
<label for="exampleInputEmail1">邮箱label>
<input type="email" name="email" class="form-control" id="exampleInputEmail1" placeholder="Enter email">
div>
<div class="form-group">
<label for="exampleInputPassword1">名字label>
<input type="text" name="username" class="form-control" id="exampleInputPassword1" placeholder="Password">
div>
<div class="form-group">
<label for="exampleInputFile">头像label>
<input type="file" name="headerImg" id="exampleInputFile">
div>
<div class="form-group">
<label for="exampleInputFile">生活照label>
<input type="file" name="photos" multiple>
div>
<div class="checkbox">
<label>
<input type="checkbox"> Check me out
label>
div>
<button type="submit" class="btn btn-primary">提交button>
form>
Html中label标签
input标签的multiple,多文件上传
复习request、response
POST提交表单时EnType设置问题
@Slf4j
@Controller
public class FormTestController {
@GetMapping("/form_layouts")
public String form_layouts(){
return "form/form_layouts";
}
@PostMapping("/upload")
public String upload(@RequestParam("email") String email,
@RequestParam("username") String username,
@RequestPart("headerImg") MultipartFile headerImg,
@RequestPart("photos") MultipartFile[] photos) throws IOException {
log.info("上传的信息:email={},username={},headerImg={},photos={}",
email,username,headerImg.getSize(),photos.length);
if(!headerImg.isEmpty()){
//保存到文件服务器,OSS服务器
String originalFilename = headerImg.getOriginalFilename();
headerImg.transferTo(new File("H:\\cache\\"+originalFilename));
}
if(photos.length > 0){
for (MultipartFile photo : photos) {
if(!photo.isEmpty()){
String originalFilename = photo.getOriginalFilename();
photo.transferTo(new File("H:\\cache\\"+originalFilename));
}
}
}
return "main";
}
}
文件上传相关的配置类:
org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.MultipartProperties
文件大小相关配置项:
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=100MB
文件上传相关的自动配置类MultipartAutoConfiguration
有创建文件上传参数解析器StandardServletMultipartResolver
。
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({
Servlet.class, StandardServletMultipartResolver.class, MultipartConfigElement.class })
@ConditionalOnProperty(prefix = "spring.servlet.multipart", name = "enabled", matchIfMissing = true)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(MultipartProperties.class)
public class MultipartAutoConfiguration {
private final MultipartProperties multipartProperties;
public MultipartAutoConfiguration(MultipartProperties multipartProperties) {
this.multipartProperties = multipartProperties;
}
@Bean
@ConditionalOnMissingBean({
MultipartConfigElement.class, CommonsMultipartResolver.class })
public MultipartConfigElement multipartConfigElement() {
return this.multipartProperties.createMultipartConfig();
}
@Bean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
@ConditionalOnMissingBean(MultipartResolver.class)
public StandardServletMultipartResolver multipartResolver() {
//配置好文件上传解析器
StandardServletMultipartResolver multipartResolver = new StandardServletMultipartResolver();
multipartResolver.setResolveLazily(this.multipartProperties.isResolveLazily());
return multipartResolver;
}
}
//文件上传解析器
public class StandardServletMultipartResolver implements MultipartResolver {
private boolean resolveLazily = false;
public void setResolveLazily(boolean resolveLazily) {
this.resolveLazily = resolveLazily;
}
@Override
public boolean isMultipart(HttpServletRequest request) {
return StringUtils.startsWithIgnoreCase(request.getContentType(), "multipart/");
}
@Override
public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException {
return new StandardMultipartHttpServletRequest(request, this.resolveLazily);
}
@Override
public void cleanupMultipart(MultipartHttpServletRequest request) {
if (!(request instanceof AbstractMultipartHttpServletRequest) ||
((AbstractMultipartHttpServletRequest) request).isResolved()) {
// To be on the safe side: explicitly delete the parts,
// but only actual file parts (for Resin compatibility)
try {
for (Part part : request.getParts()) {
if (request.getFile(part.getName()) != null) {
part.delete();
}
}
}
catch (Throwable ex) {
LogFactory.getLog(getClass()).warn("Failed to perform cleanup of multipart items", ex);
}
}
}
}
public class DispatcherServlet extends FrameworkServlet {
@Nullable
private MultipartResolver multipartResolver;
private void initMultipartResolver(ApplicationContext context) {
...
//这个就是配置类配置的StandardServletMultipartResolver文件上传解析器
this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
...
}
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;//最后finally的回收flag
...
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
//做预处理,如果有上传文件 就new StandardMultipartHttpServletRequest包装类
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
...
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
...
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
}
....
finally {
...
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
...
return this.multipartResolver.resolveMultipart(request);
...
}
}
protected void cleanupMultipart(HttpServletRequest request) {
if (this.multipartResolver != null) {
MultipartHttpServletRequest multipartRequest =
WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class);
if (multipartRequest != null) {
this.multipartResolver.cleanupMultipart(multipartRequest);
}
}
}
}
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
跳到以下的类
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
implements BeanFactoryAware, InitializingBean {
@Override
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ModelAndView mav;
...
mav = invokeHandlerMethod(request, response, handlerMethod);
...
return mav;
}
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
if (this.argumentResolvers != null) {
//关注点
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
...
invocableMethod.invokeAndHandle(webRequest, mavContainer);
...
return getModelAndView(mavContainer, modelFactory, webRequest);
}
finally {
webRequest.requestCompleted();
}
}
}
this.argumentResolvers
其中主角类RequestPartMethodArgumentResolver
用来生成
public class ServletInvocableHandlerMethod e