spring(7)spring mvc 的高级技术

【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 :

@Bean
 public 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)看个荔枝:

时间: 2024-10-16 08:43:54

spring(7)spring mvc 的高级技术的相关文章

笔记34 Spring MVC的高级技术——处理multipart形式的数据

一.需求介绍: Spittr应用在两个地方需要文件上传.当新用户注册应用的时候,我 们希望他们能够上传一张图片,从而与他们的个人信息相关联.当用 户提交新的Spittle时,除了文本消息以外,他们可能还会上传一 张照片. 二.multipart介绍 一般表单提交所形成的请求结果是很简单的,就是以"&"符分割的多 个name-value对.但是当上传二进制数据时,如上传图片,就出现问题.与之不同的是,multipart格式的数据会将一个表单拆分为多个 部分(part),每个部分对

【Spring】Spring MVC高级技术

前言 前面学习了简单的Spring Web知识,接着学习更高阶的Web技术. 高级技术 Spring MVC配置的替换方案 自定义DispatcherServlet配置 在第五章我们曾编写过如下代码. public class SpitterWebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class<?>[] getRootConfigClasse

Spring框架和MVC原理

Spring框架和MVC原理 目录 Spring框架 SpringMVC工作原理 参考资料 回到顶部 Spring框架 Spring当前框架有20个jar包,大致可以分为6大模块: Core Container AOP and Instrumentation Messaging Data Access/Integration Web Test Spring框架提供了非常丰富的功能,因此整个架构也很庞大. 在我们实际的应用开发中,并不一定要使用所有的功能,而是可以根据需要选择合适的Spring模块

spring、spring MVC、spring Boot

Spring 是一个"引擎" Spring MVC 是基于 Spring 的一个 MVC 框架 Spring Boot 是基于 Spring4 的条件注册的一套快速开发整合包 Spring 最初利用"工厂模式"( DI )和"代理模式"( AOP )解耦应用组件 Spring 框架是一个分层架构,由 7 个定义良好的模块组成. Spring 模块构建在核心容器之上,核心容器定义了创建.配置和管理 bean 的方式:组成 Spring 框架的每个模

ssm(spring,spring mvc,mybatis)框架

ssm框架各个技术的职责 spring :spring是一个IOC DI AOP的 容器类框架 spring mvc:spring mvc 是一个mvc框架 mybatis:是一个orm的持久层框架 一般web项目的架构: jsp界面 ===> 控制层(Controller)== >业务逻辑层(Service)==>持久化层(dao)== >数据库层(Database) spring 框架是一个容器,作用在所有层. spring mvc 主要作用在控制层 mybatis主要作用在持

Spring Boot 与 MVC 的区别,这些终于搞明白了!

作者:潜龙勿用 Spring 框架就像一个家族,有众多衍生产品例如 boot.security.jpa等等.但他们的基础都是Spring 的 ioc和 aop ioc 提供了依赖注入的容器 aop ,解决了面向横切面的编程,然后在此两者的基础上实现了其他延伸产品的高级功能. Spring MVC是基于 Servlet 的一个 MVC 框架 主要解决 WEB 开发的问题,因为 Spring 的配置非常复杂,各种XML. JavaConfig.hin处理起来比较繁琐.于是为了简化开发者的使用,从而创

Extjs5.0从入门到实战开发信息管理系统(Extjs基础、Extjs5新特性、Spring、Spring mvc、Mybatis)视频教程

Extjs5.0从入门到实战开发信息管理系统(Extjs基础.Extjs5新特性.Spring.Spring mvc.Mybatis)视频教程下载   联系QQ:1026270010 Extjs作为一款优秀的JS前端开发框架以其良好的架构.丰富的UI组件库.完善的文档和社区支持等诸多优点拥有广泛的市场应用空间,开发人员无需过多的关注HTML.CSS甚至各种常用JS算法,只需把精力放在业务逻辑上,利用各种组件的相互组合调用便可轻松而高效的开发出系统的前端页面. Extjs5在之前版本的基础上又推出

Spring与Struts2整合VS Spring与Spring MVC整合

Spring与Struts2整合,struts.xml在src目录下 1.在web.xml配置监听器 web.xml <!-- 配置Spring的用于初始化ApplicationContext的监听器 --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <contex

java框架整合例子(spring、spring mvc、spring data jpa、hibernate)

这是自己参考springside开源项目整合的框架,主要整合了spring.spring mvc.spring data jpa.hibernate这几个框架,对于这几个框架其中感觉比较舒服的还是spring data jpa这个框架,这个框架在写dao类的时候,只需要写一个接口声明,spring data jpa会自动的实现其实现类,使用起来比较方便,至于详细的使用方法还请自己百度吧,因为我也不清楚.个人感觉还有一个比较不错的地方就是能够打印sql语句,都知道hibernate打印的sql语句