【0】README
1)本文部分文字描述转自:“Spring In Action(中/英文版)”,旨在review  “spring(7)spring mvc 的高级技术” 的相关知识;
2)本文将会看到如何编写控制器来处理文件上传,如何处理控制器所抛出的异常,以及如何在模型中传递数据,使其能够在重定向之后仍然存活;
【1】spring mvc 配置的替代方案
【1.1】 自定义 DispatcherServlet配置
【1.2】添加其他的Servlet 和 Filter
【1.3】在web.xml 中声明 DispatcherServlet
【2】处理multipart 形式的数据
1)应用需求:Spittr 在新用户注册的时候需要上传头像,在发布Spittle的时候需要插入图片(同微博一样);
2)intro:图片是二进制数据,multipart格式的数据 会将一个表单拆分为多个部分(part),每个部分对应一个输入域;
3)在一般表单输入域中,它所对应的部分会防止文本型数据,但如果上传文件的话,它所对应的部分可以是二进制,下面展现了 multipart的请求体;

【2.1】配置multipart解析器
1)intro:DispatcherServlet并没有实现任何解析 multipart 请求数据的功能,它将该任务委托给了 spring 中 MultipartResolver 接口的实现,通过这个实现类来解析multipart 请求中的内容;
2)spring3.1 开始,内置了两个 MultipartResolver 的实现;
2.1)CommonsMultipartResolver:使用 Jakarta Commons  FileUpload 解析 multipart请求;
2.2)StandardServletMultipartResolver:依赖于Servlet3.0 对 multipart 请求 的支持;(干货——优选方案,因为它不依赖于第三方库)
【2.1.1】使用 Servlet3.0解析 multipart请求
1)在spring 应用上下文中,将StandardServletMultipartResolver 声明为bean :
@Beanpublic MultipartResolver multipartResolver() throws IOException {return new StandardServletMultipartResolver();}
2)如何限制 StandardServletMultipartResolver 的工作方式呢?(如限制用户上传文件的大小和文件类型)(干货——我们就不会直接创建 DispatcherServlet实例并将其注册到 Servlet上下文中)
2.1)看个荔枝:最基本的 DispatcherServlet multipart配置,它将临时路径设置为 "/tmp/spittr/uploads"
DispatcherServlet ds = new DispatcherServlet();
Dynamic registration = context.addServlet("appServlet", ds);
registration.addMapping("/");
registration.setMultipartConfig(new MultipartConfigElement("/tmp/spittr/uploads"));

2.2)如果配置DispatcherServlet 的 Servlet初始化类继承了 AbstractAnnotationConfigDispatcherServletInitializer 或 AbstractDispatcherServletInitializer,那么我们就不会直接创建 DispatcherServlet实例并将其注册到 Servlet上下文中;这样的话,将不会有对 Dynamic Servlet registration 的引用供我们使用了。但我们可以通过重载 customizeRegistration() 方法 来配置 multipart 的具体细节;
@Override
protected void customizeRegistration(Dynamic registration) {registration.setMultipartConfig(new MultipartConfigElement("/tmp/spittr/uploads"));
}

对以上代码的分析(Analysis):上述代码所使用的 只有一个参数的 MultipartConfigElement 构造器,指定的是文件系统中的一个绝对目录,上传文件将会临时写入到该目录中;
3)处理设置临时路径,还可以设置其他参数(parameters):
parameter1)上传文件的最大容量(以字节为单位),默认是没有限制的;
parameter2)整个mulitpart 请求的最大容量(以字节为单位),不会关心有多少个part以及每个part的大小,默认是没有限制的;
parameter3)在上传的过程中,如果文件大小得到了一个指定最大容量(以字节为单位),将会写入到临时文件路径中。默认值为0.也就是所上传的文件都会写入到磁盘上;
3.1)看个荔枝:限制文件大小不超过2M,整个请求不超过4M,而且所有的文件都写到磁盘上,则设置为:
@Override
protected void customizeRegistration(Dynamic registration) {registration.setMultipartConfig(new MultipartConfigElement("/tmp/spittr/uploads",2097152, 4194304, 0));
}

Supplement)使用xml 配置来设置的话,如下:
<servlet><servlet-name>appServlet</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><load-on-startup>1</load-on-startup><multipart-config><location>/tmp/spittr/uploads</location><max-file-size>2097152</max-file-size>    <max-request-size>4194304</max-request-size></multipart-config>
</servlet>

【2.1.2】 配置 JAKARTA COMMONS FILEUPLOAD MULTIPART RESOLVER
1)intro:如果我们需要将应用部署到非 Servlet3.0 容器中,就使用该配置;spring 内置了CommonsMultipartResolver,可以作为 StandardServletMultipartResolver 配置的替代方案;
2)如何配置CommonsMultipartResolver
2.1)将CommonsMultipartResolver 声明为 spring bean的 简单方式如下:
@Bean
public MultipartResolver multipartResolver() {return new CommonsMultipartResolver();
}

2.2)CommonsMultipartResolver:不会强制要求设置临时文件路径,默认case下,这个路径就是 Servlet容器的临时目录;不过通过 updateTempDir属性类设置不同 位置;

@Bean
public MultipartResolver multipartResolver() throws IOException {CommonsMultipartResolver multipartResolver =new CommonsMultipartResolver();multipartResolver.setUploadTempDir(new FileSystemResource("/tmp/spittr/uploads"));return multipartResolver;
}

2.3)看个荔枝: 设置最大的文件容量为2M,最大的内存大小为0字节(所有的文件都会写到磁盘中),与MultipartConfigElement 不同的是,我们无法设置 multipart 请求整体的最大容量;
@Bean
public MultipartResolver multipartResolver() throws IOException {CommonsMultipartResolver multipartResolver =new CommonsMultipartResolver();multipartResolver.setUploadTempDir(new FileSystemResource("/tmp/spittr/uploads"));multipartResolver.setMaxUploadSize(2097152);multipartResolver.setMaxInMemorySize(0);return multipartResolver;
}

【2.2】处理multipart请求
1)intro:保存图片到文件系统有两种方式(method)
method1)multipartFile;
method2)part形式
2)编写spring 控制器来接收上传的文件: 最常见的方式就是在某个控制器方法上添加 @RequestPart注解;
2.1)修改前台模板添加上传图片插件;(省略)
2.2)修改控制器方法
@RequestMapping(value="/register", method=POST)public String processRegistration(@RequestPart("profilePicture") byte[] profilePicture, // highlight line.@Valid Spitter spitter,Errors errors) {
//...
}

对以上代码的分析(Analysis):
A1)profilePicture属性:将会给定一个 byte数组,这个数组中包含了请求中对应的part数据(通过@RequestPart类指定);如果用户提交表单的时候没有选择文件,那么这个数组是空的(而不是null);
A2)获取到图片数据后:processRegistration方法接下来就是将文件保存到某个位置了;(下面讲如何处理文件的存储)
【2.2.1】接收 MultipartFile
1)intro:spring提供了MultipartFile接口,它为处理multipart数据提供了内容丰富的对象;

对以上代码的分析(Analysis):
A1)Multipart提供了获取上传文件byte的方式,但是它所提供的功能并不仅限于此,还能获得原始的文件名,大小以及内容类型;
A2)它还提供了一个 InputStream,用来将文件数据以流的方式进行读取;
A3)MultipartFile 还提供了一个便利的 transferTo()方法,能够帮助我们将上传的文件写入到文件系统中;
profilePicture.transferTo(new File("/data/spittr/" + profilePicture.getOriginalFilename()));

【2.2.2】将文件保存到 Amazon S3中(省略)

【2.2.3】以Part的形式接收上传的文件
1)intro:如果你需要将应用程序部署到 Servlet3.0的容器中,那么会有Multipart的一个替代方案——spring mvc 接收 javax.servlet.http.Part 作为控制器方法的参数;
2)若使用 Part来替换 MultipartFile的话,那么 processRegistration() 方法签名会变成如下形式:
@RequestMapping(value="/register", method=POST)
public String processRegistration(@RequestPart("profilePicture") Part profilePicture,@Valid Spitter spitter,Errors errors) {//...
}
3)Part接口方法一览:
package javax.servlet.http;
import java.io.*;
import java.util.*;
public interface Part {
public InputStream getInputStream() throws IOException;public String getContentType();public String getName();public String getSubmittedFileName();public long getSize();public void write(String fileName) throws IOException;public void delete() throws IOException;public String getHeader(String name);public Collection<String> getHeaders(String name);public Collection<String> getHeaderNames();
}
对以上代码的分析(Analysis):
A1)Part方法与 MultipartFile 方法有些类似:如getSubmittedFileName() 方法 同 getOriginalFilename()方法类似,write()方法 与 transferTo()方法类似;
A2)借助于该方法(write方法),可以将上传的文件写入文件系统中:
profilePicture.write("/data/spittr/" +profilePicture.getOriginalFilename());

Attention)只有使用 MultipartFile 的时候才需要 MultipartResolver;
【3】处理异常
1)intro:spring提供了多种方式将异常转换为响应:
way1)特定的spring 异常将会自动映射为指定 的 HTTP 状态码;
way2)异常上可以添加 @ResponseStatus注解,从而将其映射为某一个HTTP 状态码;
way3)在方法上添加 @ExceptionHandler 注解,使其用来处理异常;

2)处理异常的最简单的方法:就是将其映射到 HTTP 状态码上,进而放到响应中;

【3.1】将异常映射为 HTTP 状态码
1)intro:spring 的一些异常会默认映射为 HTTP状态码;
对上表的分析(Analysis): 以上异常一般会由 spring 自身抛出,作为 DispatcherServlet处理过程中或执行校验时出现问题的结果;
2)@ResponseStatus注解:spring 提供了一种机制,通过@ResponseStatus注解 将异常也会为 HTTP 状态码;
2.1)不加 @ResponseStatus注解的case:SpittleNotFoundException 将会产生500状态码的响应,实际上,如果出现任何没有映射的异常,响应都会带有500状态码;
2.2)加上 @ResponseStatus注解的case:使用该注解将 SpittleNotFoundException 映射为 HTTP 状态码 404;
@ResponseStatus(value=HttpStatus.NOT_FOUND,reason="spittle not found")
public class SpittleNotFoundException extends RuntimeException{
}

【3.2】编写异常处理的方法
1)problem+solution:
1.1)problem:如果我们想在响应中不仅要包括状态码,还要包含所产生的错误,怎么来处理?
1.2)solution:我们不能将异常视为 HTTP 错误了,而是要按照处理请求的方式来处理异常;
2)看个荔枝:

对以上代码的分析(Analysis):若视图创建的Spittle 已存在数据库中,则抛出DuplicateSpittleException,这样一来,该方法就有两条路径,每个路径有不同的输出;
3)如何让saveSpittle方法只关注正确的路径,而让其他方法处理异常?
3.1)step1:首先将saveSpittle方法中的异常处理剥离掉;
3.2)step2:为SpittleController 添加新方法,处理抛出的异常;

对@ExceptionHandler注解的分析:该注解能处理同一个控制器中所有处理器方法所抛出的异常;所以我们不用在每个可能抛出 DuplicateSpittleException 方法中添加异常处理代码,这一个方法就涵盖了所有功能;
【4】为控制器添加通知
1)intro:有没有一种方法能够处理所有控制器中处理器方法所抛出的异常呢?
2)spring3.2 提供了solution:控制器通知,它是任意带有 @ControllerAdvice注解 的类,这个类会包含一个或多个如下类型的方法;
func1)@ExceptionHandler 注解标注的方法;
func2)@InitBinder注解标注的方法;
func3)@ModelAttribute注解标注的方法;
Attention)在带有@ControllerAdvice注解的类中,以上这些方法会运用到整个应用程序所有控制器中带有 @RequestMapping注解的方法上;
3)@ControllerAdvice注解最为实用的一个case是:将所有 @ExceptionHandler 方法收集到一个类中,这样所有控制器的异常就能在一个地方进行一致的处理了;(干货——@ControllerAdvice注解最为实用的一个case)
4)看个荔枝:如我们想将DuplicateSpittleException 的处理方法用到整个应用程序的所有控制器上;
【5】跨重定向请求传递数据
1)传递数据;
1.1)传递简单数据(如String,int类型):使用 URL 模板进行重定向;
1.2)传递复杂数据(如对象):使用 Flash 属性;
2)problem+solution:
2.1)problem:正在发起重定向功能的方法该如何发送数据给重定向的目标方法呢?一般来讲,当一个处理器方法完成后,该方法所指定的模型数据将会copy 到 请求中,并作为请求中的属性,请求会转发(forward)到视图上进行渲染。因为控制器方法和视图所处理的是同一个请求,所以在转发过程中,请求属性能够得以保存;但是重定向(redirect) 的case就不同了;

2.2)solution:如下图所示,当控制器的结果是重新向的话,原始的请求就结束了,并且会发出一个新的 GET  请求;原始请求中所带有的模型数据也就消亡了;

Attention)显然,对于重定向来说,模型并不能用来传递数据;(干货——对于重定向来说,模型并不能用来传递数据)
3)其他数据传递方案:能够从发起重定向的方法传递数据给处理重定向方法中:
way1)使用 URL 模板以路径变量 或/和 查询参数的形式传递数据;
way2)通过flash 属性发送数据;
【5.1】 通过URL 模板进行重定向
1)通过路径变量和查询参数传递数据;(以下使用String连接的写法很危险)
return "redirect:/spitter/{username}";

2)spring还提供了使用模板的方式来定义重定向URL:
@RequestMapping(value="/register", method=RequestMethod.POST)public String processRegistration(Spitter spitter, Model model) {spitterRepository.save(spitter);model.addAttribute("username", spitter.getUsername());return "redirect:/spitter/{username}";}

对以上代码的分析:username 作为占位符填充到了URL 模板中,所以username中所有不安全字符都会进行转义;
3)除此之外,模型中所有其他的原始类型值都可以添加到 URL中作为查询参数;
@RequestMapping(value = "/register", method = RequestMethod.POST)public String processRegistration2(Spitter spitter, Model model) {spitterRepository.save(spitter);model.addAttribute("username", spitter.getUsername()); // highlight line.model.addAttribute("spitterId", spitter.getId()); // highlight line.return "redirect:/spitter/{username}";}

对以上代码的分析: 因为模型中的spitterId 属性没有匹配重定向URL 中的任何占位符,所以它会自动以查询参数的形式附加到重定向URL上;
Attention)通过路径变量和查询参数传递数据 有一个限制: 它只能用来发送简单的数据(如String类型 和 数字的值);
【5.2】使用flash属性(发送复杂数据)
1)problem+solution:
1.1)problem:发送实际的 Spitter对象,而不是简单的int类型数据;
1.2)solution:
1.2.1)schema1:将Spitter对象 放入到 会话中,然后重定向后再将其从会话中取出;
1.2.2)schema2:spring 提供了提供了将数据发送为 flash 属性的功能。flash 属性会一直携带这些数据直到下一次请求才会消失;(干货——flash属性的作用)
2)RedirectAttributes 提供了一组 addFlashAttribute() 方法来添加flash属性,如下:

3)通过flash属性传递数据的原理:在重定向前,所有的flash属性都会复制到会话中,重定向后,存在会话中的flash 属性会被取出,并从会话转移到模型中,如下图所示:

4)看个荔枝:

spring(7)spring mvc 的高级技术相关推荐

  1. spring api 中文_Spring高级技术梳理

    序言 本系列除了SpringData部分, 其余部分全部是基于SpringBoot 2.0以上版本, 更新则更强, 尽量不与主流脱节. 我们不是时代的弄潮儿, 我们只是先进技术的追随者~~~ Spri ...

  2. Spring MVC Boot Cloud 技术教程汇总

    转载自 Spring MVC & Boot & Cloud 技术教程汇总 昨天我们发布了Java成神之路上的知识汇总,今天继续. Java成神之路技术整理(长期更新) 以下是Java技 ...

  3. Spring MVC Boot Cloud 技术教程汇总(长期更新)

    昨天我们发布了Java成神之路上的知识汇总,今天继续. Java成神之路技术整理(长期更新) 以下是Java技术栈微信公众号发布的关于 Spring/ Spring MVC/ Spring Boot/ ...

  4. Spring高级技术梳理

    Spring高级技术梳理 序言 正文 SpringData部分 Spring全家桶之SpringData--预科阶段 Spring全家桶之SpringData--Spring 整合Hibernate与 ...

  5. Spring、Spring MVC、Spring Boot三者的关系还傻傻分不清楚?

    点击蓝色"程序猿DD"关注我 回复"资源"获取独家整理的学习资料! 在本文中,你将获取到有关于Spring,Spring MVC和Spring Boot的概述, ...

  6. Spring、Spring MVC、Spring boot、Spring Cloud面试题(史上最全面试题,精心整理100家互联网企业,面试必过)

    最全面试题,精心整理100家互联网企业面经,祝你面试成功.面试必过(2023优化版)已发布在个人微信公众号[面向Offer学编程],优化版首先修正了读者反馈的部分答案存在的错误,同时根据最新面试总结, ...

  7. Redis+Nginx+设计模式+Spring全家桶+Dubbo阿里P8技术精选文档

    最近花了很长的时间去搜罗Java核心技术好文,我把每个Java核心技术的优选文章都整理成了一个又一个的文档.昨天也是终于全部整理好了,今天就把这些东西分享给老铁们,也能为老铁们省去不少麻烦,想学什么技 ...

  8. Redis+分布式+设计模式+Spring全家桶+Dubbo阿里P8技术精选文档

    最近花了很长的时间去搜罗Java核心技术好文,我把每个Java核心技术的优选文章都整理成了一个又一个的文档.昨天也是终于全部整理好了,今天就把这些东西分享给老铁们,也能为老铁们省去不少麻烦,想学什么技 ...

  9. 福利继续:赠书《Spring Cloud微服务-全栈技术与案例解析》

    <Spring Cloud微服务-全栈技术与案例解析> 在互联网时代,互联网产品的最大特点就是需要快速发布新功能,支持高并发和大数据.传统的架构已经慢慢不能支撑互联网业务的发展,这时候微服 ...

最新文章

  1. arp/ip地址/路由--总之很乱
  2. CUBRID学习笔记 1 简介 cubrid教程
  3. P1005 采药(Tyvj)
  4. Mysql的date_format与date_sub
  5. #0 scrapy爬虫学习中遇到的坑记录
  6. Win10网络共享设置的方法步骤
  7. java 中文 转义_java下载url路径包含中文需要转义的操作
  8. 【java】深入了解JAVA可变长度的参数
  9. 网络媒体教程:人物素描
  10. 关于Word样式自动更新的详解
  11. CVPR2021 MotionRNN: A Flexible Model for Video Prediction with Spacetime-Varying Motions
  12. Python - 深度学习系列2-人脸比对 Siamese
  13. android7.1的SnapdragonCamera之CameraActivity逻辑流程分析整体
  14. 原始套接(ARP协议的使用)
  15. godot mysql_Go 每日一库之 godotenv
  16. 2016年linux认证考试,2016年Linux认证考试复习要点辅导
  17. C#中BackgroundImage与Image的区别
  18. Chrome 浏览器 原生工具进行网页长截图
  19. 全基因组尺度的增强子--靶基因映射图谱解码非编码突变
  20. android应用更新升级

热门文章

  1. Cities(2020昆明C)
  2. Acwing1086. 恨7不成妻(未解决)
  3. 洛谷P3357:最长k可重线段集问题(网络流)
  4. CF1556F-Sports Betting【状压dp,数学期望】
  5. P7726-天体探测仪(Astral Detector)【构造】
  6. CF848E-Days of Floral Colours【dp,分治NTT】
  7. nssl1446-小智的旅行【dp】
  8. nssl1320,jzoj(初中)2108-买装备【dfs,水题】
  9. POJ3348-Cows【凸包,计算几何】
  10. jzoj3793,P2090-数字对【更相减损术,欧几里得算法,数论】