SpringBoot2-BASE-3

SpringBoot2 基础篇 3

转载:https://blog.csdn.net/u011863024/article/details/113667634

1. 视图解析-Thymeleaf初体验

Thymeleaf is a modern server-side Java template engine for both web and standalone environments.

Thymeleaf’s main goal is to bring elegant natural templates to your development workflow — HTML that can be correctly displayed in browsers and also work as static prototypes, allowing for stronger collaboration in development teams.

With modules for Spring Framework, a host of integrations with your favourite tools, and the ability to plug in your own functionality, Thymeleaf is ideal for modern-day HTML5 JVM web development — although there is much more it can do.

Thymeleaf官方文档:https://www.thymeleaf.org/documentation.html

thymeleaf使用

引入Starter

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

自动配置好了thymeleaf

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(ThymeleafProperties.class)
@ConditionalOnClass({ TemplateMode.class, SpringTemplateEngine.class })
@AutoConfigureAfter({ WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class })
public class ThymeleafAutoConfiguration {
    ...
}

自动配好的策略

1. 所有thymeleaf的配置值都在 ThymeleafProperties
2. 配置好了 SpringTemplateEngine
3. 配好了 ThymeleafViewResolver
4. 我们只需要直接开发页面

public static final String DEFAULT_PREFIX = "classpath:/templates/";//模板放置处
public static final String DEFAULT_SUFFIX = ".html";//文件的后缀名

编写一个控制层:

@Controller
public class ViewTestController {
    @GetMapping("/hello")
    public String hello(Model model){
        //model中的数据会被放在请求域中 request.setAttribute("a",aa)
        model.addAttribute("msg","一定要大力发展工业文化");
        model.addAttribute("link","http://www.baidu.com");
        return "success";
    }
}

/templates/success.html:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1 th:text="${msg}">nice</h1>
<h2>
    <a href="www.baidu.com" th:href="${link}">去百度</a>  <br/>
    <a href="www.google.com" th:href="@{/link}">去百度</a>
</h2>
</body>
</html>

application.yml

server:
  servlet:
    context-path: /app #设置应用名

这个设置后,URL要插入/app, 如http://localhost:8080/app/hello.html。

基本语法

表达式

表达式名字 语法 用途
变量取值 ${…} 获取请求域、session域、对象等值
选择变量 *{…} 获取上下文对象值
消息 #{…} 获取国际化等值
链接 @{…} 生成链接
片段表达式 ~{…} jsp:include 作用,引入公共页面片段

字面量

文本值: ‘one text’ , ‘Another one!’ ,…
数字: 0 , 34 , 3.0 , 12.3 ,…
布尔值: 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

设置单个值

<form action="subscribe.html" th:attr="action=@{/subscribe}">
  <fieldset>
    <input type="text" name="email" />
    <input type="submit" value="Subscribe!" th:attr="value=#{subscribe.submit}"/>
  </fieldset>
</form>

设置多个值

<img src="../../images/gtvglogo.png"  
     th:attr="src=@{/images/gtvglogo.png},title=#{logo},alt=#{logo}" />

官方文档
Setting Attribute Values:https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#setting-attribute-values

迭代

<tr th:each="prod : ${prods}">
    <td th:text="${prod.name}">Onions</td>
    <td th:text="${prod.price}">2.41</td>
    <td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
</tr>

<tr th:each="prod,iterStat : ${prods}" th:class="${iterStat.odd}? 'odd'">
    <td th:text="${prod.name}">Onions</td>
    <td th:text="${prod.price}">2.41</td>
    <td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
</tr>

件运算

<a href="comments.html"
    th:href="@{/product/comments(prodId=${prod.id})}"
    th:if="${not #lists.isEmpty(prod.comments)}">view</a>

<div th:switch="${user.role}">
      <p th:case="'admin'">User is an administrator</p>
      <p th:case="#{roles.manager}">User is a manager</p>
      <p th:case="*">User is some other thing</p>
</div>

属性优先级

Order Feature Attributes
1 Fragment inclusion th:insert th:replace
2 Fragment iteration th:each
3 Conditional evaluation th:if th:unless th:switch th:case
4 Local variable definition th:object th:with
5 General attribute modification th:attr th:attrprepend th:attrappend
6 Specific attribute modification th:value th:href th:src …
7 Text (tag body modification) th:text th:utext
8 Fragment specification th:fragment
9 Fragment removal th:remove

官方文档

Attribute Precedence: https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#attribute-precedence

2. web实验-后台管理系统基本功能

项目创建

使用IDEA的Spring Initializr。

thymeleaf、
web-starter、
devtools、
lombok

登陆页面

/static 放置 css,js等静态资源

/templates/login.html 登录页

<html lang="en" xmlns:th="http://www.thymeleaf.org"><!-- 要加这玩意thymeleaf才能用 -->
<form class="form-signin" action="index.html" method="post" th:action="@{/login}">
    ...
    <!-- 消息提醒 -->
    <label style="color: red" th:text="${msg}"></label>
    
    <input type="text" name="userName" class="form-control" placeholder="User ID" autofocus>
    <input type="password" name="password" class="form-control" placeholder="Password">
    
    <button class="btn btn-lg btn-login btn-block" type="submit">
        <i class="fa fa-check"></i>
    </button>
    
    ...
    
</form>

/templates/main.html 主页

thymeleaf内联写法:

<p>Hello, [[${session.user.name}]]!</p>

登录控制层

@Controller
public class IndexController {
    /**
     * 来登录页
     * @return
     */
    @GetMapping(value = {"/","/login"})
    public String loginPage(){
        return "login";
    }
    @PostMapping("/login")
    public String main(User user, HttpSession session, Model model){ //RedirectAttributes
        if(StringUtils.hasLength(user.getUserName()) && "123456".equals(user.getPassword())){
            //把登陆成功的用户保存起来
            session.setAttribute("loginUser",user);
            //登录成功重定向到main.html;  重定向防止表单重复提交
            return "redirect:/main.html";
        }else {
            model.addAttribute("msg","账号密码错误");
            //回到登录页面
            return "login";
        }
    }
    
     /**
     * 去main页面
     * @return
     */
    @GetMapping("/main.html")
    public String mainPage(HttpSession session, Model model){
        
        //最好用拦截器,过滤器
        Object loginUser = session.getAttribute("loginUser");
        if(loginUser != null){
        	return "main";
        }else {
            //session过期,没有登陆过
        	//回到登录页面
            model.addAttribute("msg","请重新登录");
    	    return "login";
        }
    }  
}

模型
@AllArgsConstructor
@NoArgsConstructor
@Data
public class User {
    private String userName;
    private String password;
}

3. web实验-抽取公共页面

官方文档

Template Layout: https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#template-layout

公共页面/templates/common.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"><!--注意要添加xmlns:th才能添加thymeleaf的标签-->
<head th:fragment="commonheader">
    <!--common-->
    <link href="css/style.css" th:href="@{/css/style.css}" rel="stylesheet">
    <link href="css/style-responsive.css" th:href="@{/css/style-responsive.css}" rel="stylesheet">
    ...
</head>
<body>
<!-- left side start-->
<div id="leftmenu" class="left-side sticky-left-side">
    ...
    <div class="left-side-inner">
        ...
        <!--sidebar nav start-->
        <ul class="nav nav-pills nav-stacked custom-nav">
            <li><a th:href="@{/main.html}"><i class="fa fa-home"></i> <span>Dashboard</span></a></li>
            ...
            <li class="menu-list nav-active"><a href="#"><i class="fa fa-th-list"></i> <span>Data Tables</span></a>
                <ul class="sub-menu-list">
                    <li><a th:href="@{/basic_table}"> Basic Table</a></li>
                    <li><a th:href="@{/dynamic_table}"> Advanced Table</a></li>
                    <li><a th:href="@{/responsive_table}"> Responsive Table</a></li>
                    <li><a th:href="@{/editable_table}"> Edit Table</a></li>
                </ul>
            </li>
            ...
        </ul>
        <!--sidebar nav end-->
    </div>
</div>
<!-- left side end-->
<!-- header section start-->
<div th:fragment="headermenu" class="header-section">
    <!--toggle button start-->
    <a class="toggle-btn"><i class="fa fa-bars"></i></a>
    <!--toggle button end-->
    ...
</div>
<!-- header section end-->
<div id="commonscript">
    <!-- Placed js at the end of the document so the pages load faster -->
    <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>
    <!--common scripts for all pages-->
    <script th:src="@{/js/scripts.js}"></script>
</div>
</body>
</html>

/templates/table/basic_table.html

<!DOCTYPE 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 Table</title>
    <div th:include="common :: commonheader"> </div><!--将common.html的代码段 插进来-->
</head>
<body class="sticky-header">
<section>
<div th:replace="common :: #leftmenu"></div>
    
    <!-- main content start-->
    <div class="main-content" >
        <div th:replace="common :: headermenu"></div>
        ...
    </div>
    <!-- main content end-->
</section>
<!-- Placed js at the end of the document so the pages load faster -->
<div th:replace="common :: #commonscript"></div>
</body>
</html>

Difference between th:insert and th:replace (and th:include)
官方文档:https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#difference-between-thinsert-and-threplace-and-thinclude

4. web实验-遍历数据与页面bug修改

控制层代码:

@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}">Trident</td>
            <td th:text="${user.userName}">Internet</td>
            <td >[[${user.password}]]</td>
        </tr>
    </tbody>
</table>

5. 视图解析-【源码分析】-视图解析器与视图

视图解析原理流程:

1. 目标方法处理的过程中(阅读DispatcherServlet源码),所有数据都会被放在 ModelAndViewContainer 里面,其中包括数据和视图地址。
2. 方法的参数是一个自定义类型对象(从请求参数中确定的),把他重新放在 ModelAndViewContainer 。
3. 任何目标方法执行完成以后都会返回ModelAndView(数据和视图地址)。
4. processDispatchResult()处理派发结果(页面改如何响应)
    1. render(mv, request, response); 进行页面渲染逻辑
        1. 根据方法的String返回值得到 View 对象【定义了页面的渲染逻辑】
            1. 所有的视图解析器尝试是否能根据当前返回值得到View对象
            2. 得到了 redirect:/main.html --> Thymeleaf new RedirectView()。
            3. ContentNegotiationViewResolver 里面包含了下面所有的视图解析器,内部还是利用下面所有视图解析器得到视图对象。
            4. view.render(mv.getModelInternal(), request, response); 视图对象调用自定义的render进行页面渲染工作。
        2. RedirectView 如何渲染【重定向到一个页面】
        3. 获取目标url地址
        4. response.sendRedirect(encodedURL);

视图解析:

- 返回值以 forward: 开始: new InternalResourceView(forwardUrl); --> 转发request.getRequestDispatcher(path).forward(request, response);
- 返回值以 redirect: 开始: new RedirectView() --> render就是重定向
- 返回值是普通字符串:new ThymeleafView()—>

阅读源码:最好自己在IDE,打断点,且Debug模式运行实例,这样比较没那么沉闷。

6. 拦截器-登录检查与静态资源放行

1. 编写一个拦截器实现HandlerInterceptor接口
2. 拦截器注册到容器中(实现WebMvcConfigurer的addInterceptors())
3. 指定拦截规则(注意,如果是拦截所有,静态资源也会被拦截)

编写一个实现HandlerInterceptor接口的拦截器:

@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
    /**
     * 目标方法执行之前
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String requestURI = request.getRequestURI();
        log.info("preHandle拦截的请求路径是{}",requestURI);
        //登录检查逻辑
        HttpSession session = request.getSession();
        Object loginUser = session.getAttribute("loginUser");
        if(loginUser != null){
            //放行
            return true;
        }
        //拦截住。未登录。跳转到登录页
        request.setAttribute("msg","请先登录");
        // re.sendRedirect("/");
        request.getRequestDispatcher("/").forward(request,response);
        return false;
    }
    /**
     * 目标方法执行完成以后
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.info("postHandle执行{}",modelAndView);
    }
    /**
     * 页面渲染以后
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        log.info("afterCompletion执行异常{}",ex);
    }
}

拦截器注册到容器中 && 指定拦截规则:

@Configuration
public class AdminWebConfig implements WebMvcConfigurer{
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())//拦截器注册到容器中
                .addPathPatterns("/**")  //所有请求都被拦截包括静态资源
                .excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**",
                        "/js/**","/aa/**"); //放行的请求
}

7. 拦截器-【源码分析】-拦截器的执行时机和原理

1. 根据当前请求,找到HandlerExecutionChain(可以处理请求的handler以及handler的所有 拦截器)
2. 先来顺序执行 所有拦截器的 preHandle()方法。
    1. 如果当前拦截器preHandle()返回为true。则执行下一个拦截器的preHandle()
    2. 如果当前拦截器返回为false。直接倒序执行所有已经执行了的拦截器的 afterCompletion();。
3. 如果任何一个拦截器返回false,直接跳出不执行目标方法。
4. 所有拦截器都返回true,才执行目标方法。
5. 倒序执行所有拦截器的postHandle()方法。
6. 前面的步骤有任何异常都会直接倒序触发 afterCompletion()。
7. 页面成功渲染完成以后,也会倒序触发 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);
            }
        }
    }   
} 

8. 文件上传-单文件与多文件上传的使用

页面代码/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>

控制层代码

@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

9. 文件上传-【源码流程】文件上传参数解析器

文件上传相关的自动配置类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 extends InvocableHandlerMethod {	    
    ...
    public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
            Object... providedArgs) throws Exception {
        Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
        ...
    }
    
    @Nullable
    public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
            Object... providedArgs) throws Exception {
        Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
        ...
        return doInvoke(args);//反射调用
    }
    
    @Nullable
    protected Object doInvoke(Object... args) throws Exception {
        Method method = getBridgedMethod();
        ReflectionUtils.makeAccessible(method);
        return method.invoke(getBean(), args);
        ...
    }
    
    //处理得出multipart参数,准备稍后的反射调用(@PostMapping标记的上传方法)
    protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
            Object... providedArgs) throws Exception {
        MethodParameter[] parameters = getMethodParameters();
        ...
        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;
            }
            //关注点1
            if (!this.resolvers.supportsParameter(parameter)) {
                throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
            }
            try {
                //关注点2
                args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
            }
            catch (Exception ex) {
                ...
            }
        }
        return args;
    }    
}

public class RequestPartMethodArgumentResolver extends AbstractMessageConverterMethodArgumentResolver {
    //对应上面代码关注点1
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        //标注@RequestPart的参数
        if (parameter.hasParameterAnnotation(RequestPart.class)) {
            return true;
        }
        else {
            if (parameter.hasParameterAnnotation(RequestParam.class)) {
                return false;
            }
            return MultipartResolutionDelegate.isMultipartArgument(parameter.nestedIfOptional());
        }
    }
    //对应上面代码关注点2
    @Override
    @Nullable
    public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
            NativeWebRequest request, @Nullable WebDataBinderFactory binderFactory) throws Exception {
        HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
        Assert.state(servletRequest != null, "No HttpServletRequest");
        RequestPart requestPart = parameter.getParameterAnnotation(RequestPart.class);
        boolean isRequired = ((requestPart == null || requestPart.required()) && !parameter.isOptional());
        String name = getPartName(parameter, requestPart);
        parameter = parameter.nestedIfOptional();
        Object arg = null;
        //封装成MultipartFile类型的对象作参数
        Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
        if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {
            arg = mpArg;
        }
        
        ...
        return adaptArgumentIfNecessary(arg, parameter);
    }
}

public final class MultipartResolutionDelegate {
    ... 
    @Nullable
    public static Object resolveMultipartArgument(String name, MethodParameter parameter, HttpServletRequest request)
            throws Exception {
        MultipartHttpServletRequest multipartRequest =
                WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class);
        boolean isMultipart = (multipartRequest != null || isMultipartContent(request));
        if (MultipartFile.class == parameter.getNestedParameterType()) {
            if (!isMultipart) {
                return null;
            }
            if (multipartRequest == null) {
                multipartRequest = new StandardMultipartHttpServletRequest(request);
            }
            return multipartRequest.getFile(name);
        }
        else if (isMultipartFileCollection(parameter)) {
            if (!isMultipart) {
                return null;
            }
            if (multipartRequest == null) {
                multipartRequest = new StandardMultipartHttpServletRequest(request);
            }
            List<MultipartFile> files = multipartRequest.getFiles(name);
            return (!files.isEmpty() ? files : null);
        }
        else if (isMultipartFileArray(parameter)) {
            if (!isMultipart) {
                return null;
            }
            if (multipartRequest == null) {
                multipartRequest = new StandardMultipartHttpServletRequest(request);
            }
            List<MultipartFile> files = multipartRequest.getFiles(name);
            return (!files.isEmpty() ? files.toArray(new MultipartFile[0]) : null);
        }
        else if (Part.class == parameter.getNestedParameterType()) {
            if (!isMultipart) {
                return null;
            }
            return request.getPart(name);
        }
        else if (isPartCollection(parameter)) {
            if (!isMultipart) {
                return null;
            }
            List<Part> parts = resolvePartList(request, name);
            return (!parts.isEmpty() ? parts : null);
        }
        else if (isPartArray(parameter)) {
            if (!isMultipart) {
                return null;
            }
            List<Part> parts = resolvePartList(request, name);
            return (!parts.isEmpty() ? parts.toArray(new Part[0]) : null);
        }
        else {
            return UNRESOLVABLE;
        }
    }    
    ...
}
Contents
  1. 1. SpringBoot2 基础篇 3
    1. 1.1. 1. 视图解析-Thymeleaf初体验
      1. 1.1.1. thymeleaf使用
        1. 1.1.1.1. 引入Starter
        2. 1.1.1.2. 自动配置好了thymeleaf
      2. 1.1.2. 基本语法
        1. 1.1.2.1. 表达式
        2. 1.1.2.2. 字面量
        3. 1.1.2.3. 文本操作
        4. 1.1.2.4. 数学运算
        5. 1.1.2.5. 布尔运算
        6. 1.1.2.6. 比较运算
        7. 1.1.2.7. 条件运算
        8. 1.1.2.8. 特殊操作
        9. 1.1.2.9. 设置属性值-th:attr
        10. 1.1.2.10. 迭代
        11. 1.1.2.11. 件运算
        12. 1.1.2.12. 属性优先级
    2. 1.2. 2. web实验-后台管理系统基本功能
      1. 1.2.1. 项目创建
      2. 1.2.2. 登陆页面
      3. 1.2.3. 登录控制层
    3. 1.3. 3. web实验-抽取公共页面
    4. 1.4. 4. web实验-遍历数据与页面bug修改
    5. 1.5. 5. 视图解析-【源码分析】-视图解析器与视图
    6. 1.6. 6. 拦截器-登录检查与静态资源放行
    7. 1.7. 7. 拦截器-【源码分析】-拦截器的执行时机和原理
    8. 1.8. 8. 文件上传-单文件与多文件上传的使用
    9. 1.9. 9. 文件上传-【源码流程】文件上传参数解析器
|