第25章 认识更多Spring MVC家族成员(一)

第25章 认识更多Spring MVC家族成员

本章内容

  • 文件上传与MultipartResolver

  • Handler与HandlerAdaptor

  • 框架内处理流程拦截与HandlerInterceptor

  • 框架内的异常处理与HandlerExceptionResolver

  • 国际化视图与LocalResolver

  • 主题(Theme)与ThemeResolver

在深入讲述Spring MVC框架之前,我们先暂时跳出对框架内主要角色的认知范围,再次“鸟瞰”SpringMVC框架总体上的逻辑结构。

到目前为止,我们主要认识了Spring MVC框架的五大主要角色,它们是HandlerMapping、Controller、ModelAndView、ViewResolver和View。在DispatcherServlet处理Web请求的过程中,它们顺序承担了相应的职责。我想,在之前的内容基础上,我们应该能够对整个Web请求的处理流程中各个角色所处的位置达成以下共识,如图25-1所示。

image-20220711152210186

它们就好像Spring MVC的“骨架”,有了它们,即使整个框架看起来就像是个“骷髅兵”,在某种程度上已经足俱战斗力。为了能够让整个Spring MVC框架看起来更加饱满,我们还有一段路程要走。在此之前,我们可以先提前看一下地图,以免迷失方向(见图25-2)。

image-20220711152251191

扫描二维码关注公众号,回复: 14376181 查看本文章

在这幅“地图”(图25-2)中,已经走过的“地点”只有图标,没有文字说明,而我们要经过的新的“地名”则都有标注。

  • MultipartResolver。我们将经过的第一站,它位于HandlerMapping之前,简单来说,如果有文件上传的请求,它将会大展身手。

  • HandlerInterceptor。HandlerInterceptor将对处理流程进行拦截。拦截的位置可以有三个地方可以选择,我想在“地图”中不难找到这些位置(斜线背景的竖向方框所标志的位置)。

  • HandlerAdaptor。实际上,SpringMVC并不只是支持Controller一种Handler类型。HandlerAdaptor可以帮助我们使用其他类型的Handler。

  • HandlerExceptionResolver。在处理具体Web请求的过程中,相应的Handler出现异常情况怎么办?HandlerExceptionResolver将为我们提供一种框架内的标准处理方式。

  • LocaleResolver。有了LocaleResolver,根据用户的Locale显示不同的视图变得很容易。

  • ThemeResolver。用户可以选择不同的主题(Theme)?ThemeResolver正是为这而生的!

下面让我按照顺序,带大家逐一领略“地图”中每一地点的“风土人情”。在按照“地图”的指示完成整个旅程的时候,我们将能够在开发过程中完全驾驭整个Spring MVC框架,不信?走着瞧啊!

25.1 文件上传与MultipartResolver

如果要在基于SpringMVC的Web应用程序中通过表单上传文件,那么MultipartResolver将是在服务器端处理文件上传的主要组件。

HTML页面中的表单最初所采用的application/x-www-form-urlencoded编码方式,并不足以满足文件上传的需要,所以,RFC1867(http://www.faqs.org/rfcs/fc1867.html)在此基础上增加了新的multipart/fomm-data编码方式以支持基于表单的文件上传。通常情况下,按照如下形式声明表单以及表单中的元素:

<FORM action=".." method="post" enctype="multipart/form-data">
	<INPUT NAME="fileElement" TYPE="file">
	<INPUT TYPE="submit" VALUE="Upload">
</FORM>

客户端浏览器将按照RFC1867所规定的格式,对提交表单内容进行编码,服务器端只需要根据RFC1867规定的格式对请求中的信息进行解码,就可获得客户端表单提交的数据,包括上传的文件。

既然RFC1867所规定的规则是一定的,所以,我们没有必要每次都根据这一规则分析每一请求中的信息。既然是通用的逻辑,当然也就有通用的类库,比如早期的jsp smart upload和Oreilly的COS类库,以及现在使用最多的Commons FileUpload(http://commons.apache.org/fileupload/)类库。实际开发中,我们只需要使用这些专门针对基于表单的文件上传处理类库即可。

在实现基于表单的文件上传功能的时候,Spring MVC框架底层实际上也是使用了以上几种类库。只不过,通过org.springframework.web.multipart.MultipartResolver接口的抽象,Spring MVC将具体选用哪一种类库的权利留给了我们。

25.1.1 使用MultipartResolver进行文件上传的简单分析

MultipartResolver的接口定义如下:

public interface MultipartResolver {
    
    
	boolean isMultipart(HttpServletRequest request);
	MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException;
	void cleanupMultipart(MultipartHttpServletRequest request);
}

当Web请求到达DispatcherServlet并等待处理的时候,DispatcherServlet首先会检查能否从自己的WebApplicationContext中找到一个名称为multipartResolver(由DispatcherServlet的常量MULTIPART_RESOLVER_BEAN_NAME所决定)的MultipartResolver实例。如果能够获得一个MultipartResolver的实例,DispatcherServlet将通过MultipartResolver的isMultipart(request)方法检查当前Web请求是否为multipart类型。如果是,DispatcherServlet将调用MultipartResolver的resolveMultipart(request)方法,并返回一个MultipartHttpServletRequest供后继处理流程使用,否则,直接返回最初的HttpServletRequest。

当Web请求类型为multipart的时候,MultipartResolver的resolveMultipart(request)所返回的MultipartHttpServletRequest将被作为后继处理流程所依赖的HttpServletRequest而使用。也就是说,对应最初请求的HttpServletRequest将被“偷梁换柱”为MultipartHttpServletRequest,此后处理流程各个环节中所使用的HttpServletRequest的具体类型为MultipartHttpServletRequest。当然,如果MultipartHttpServletRequest不能够提供比HttpServletRequest更多的能力,那么在这里“劳师动众”地使用Decerator模式进行“偷梁换柱”看起来也没太大意义了。下面让我们看该接口的定义:

public interface MultipartHttpServletRequest extends HttpServletRequest, MultipartRequest {
    
    
  
}

public interface MultipartRequest {
    
    
	Iterator getFileNames();
	MultipartFile getFile(String name);
  	Map getFileMap();
}

MultipartHttpServletRequest的附加能力来自于它的父接口MultipartRequest。简单地说,我们现在可以在某个Controller中,通过MultipartHttpServletRequest直接获取MultipartFile类所封装的上传后的文件,如下所示:

MultipartHttpServletRequest request = (MultipartHttpServletRequest)originalRequest;
MultipartFile file = request.getFile("fileParameter");

至于要将MultipartFile中保存的上传文件存入数据库,还是写入文件系统,那就看个人喜好或者应用场景的需要了。

当然,接口永远是接口,具体工作还得有人来做(这话是不是说过好几遍了)。Spring MVC框架内为MultipartResolver提供了两个可用的实现类,即org.springframework.web.multipart.commons.CommonsMultipartResolverorg.springframework.web.multipart.cos.CosMultipartResolver。前者使用Commons FileUpload类库实现,后者则使用OreillyCos类库实现。要启用Spring MVC框架内的文件上传支持,本质上讲,就是选择这两个实现类中的哪一个,然后将最终的选择添加到DispatcherServlet的WebApplicationContext。如果我们使用CommonsFileUpload进行文件上传,那么需要在DispatcherServlet的WebApplicationContext中添加如下bean定义:

<bean id="multipartResolver" class="org.springframework.Web.multipart.commons.CommonsMultipartResolver" p:maxUploadSize="1000000">
</bean>

现在,CommonsMultipartResolver(或者CosMultipartResolver)将负责分析当前multipart请求,然后将分析后的结果附着到要返回的MultipartHttpServletRequest实例(DefaultMultipartHttpServletRequest或者CosMultipartHttpServletRequest)上。当后继处理流程的Controller处理Web请求的时候,就可以使用特定的MultipartHttpServletRequest进行上传文件的获取和处理。当然,每个MultipartResolver都会有附加的属性定义以限定文件上传的行为。可以参阅Javadoc文档获得详细信息。

当MultipartResolver返回MultipartHttpServletRequest给后继处理流程,并且后继处理流程中的组件(通常是相应的Controller)也使用MultipartHttpServletRequest处理完相应的Web请求,DispatcherServlet将保证调用MultipartResolver的cleanupMultipart(..)方法,释放处理文件上传过程中所占用的系统资源。这样,整个文件上传的生命周期即告结束。

25.1.2 文件上传实践

要实现文件上传,首先按照刚才所阐述的内容,添加一个MultipartResolver的实例(或者CommonsMultipartResolver,或者CosMultipartResolver)到DispatcherServlet的WebApplicationContext中,然后再着手实际的工作。实际上,有了MultipartResolver帮我们返回的MultipartHttpServletRequest实例,表单中的<INPUT TYPE="file" ...>元素完全可以与普通的文本域那样享受同等对待。假设我们使用扩展AbstractController的方式来处理multipart/form-data类型的如下表单提交:

<form:form method="post" enctype="multipart/form-data" action="fileup1oad.do">
  选择上传文件<input name="fileToUpload" type="file"/><br/>
	文件说明<textarea name="comment"></textarea>
	<input name="submit" type="submit" value="提交"/>
</form:form>

我们的Controller实现看起来与使用Servlet处理通常的Web请求没有太大差别,如下方代码清单所示。

public class FileUploadController extends AbstractController {
    
    
	@Override
	protected ModelAndVie whandleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
    
    
		MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest)request;
		MultipartFile multipartFile = multipartRequest.getFile("fileToUpload");
		String fileComment = multipartRequest.getParameter("comment");
		byte[] fileContent = multipartFile.getBytes();
		//根据情况保存到数据库或者其他地方
		ModelAndView mav = new ModelAndView();
		//添加必要的模型数据以及视图信息
		return mav;
  	}
}

但是,在Spring MVC的Controller家族中,通常由SimpleFormController专门处理表单的提交,所以,扩展SimpleFormController才是比较合适的做法。不过在此之前,我们得为数据绑定提供一个Command对象,其定义如下方代码清单所示。

public class FileUploadingCommand {
    
    
	private MultipartFile fileToUpload;
	private String comment;
  
	//getter和setter方法定义.....
	
  	@Override
	public String toString() {
    
    
    	return new ToStringBuilder(this).append("fileToUpload", fileToUpload).append("corment", comment).toString();
    }
}

在此基础上,我们定义最终的FileUploadFormController也就有了,如下方代码清单所示。

public class FileUploadFormController extends SimpleFormController {
    
    
	public FileUploadFormController() {
    
    
		super.setCommandClass(FileUploadingCommand.class);
  	}
  
	@Override
  	protected void doSubmitAction(Object command) throws Exception {
    
    
		FileUploadingCommand commandBean = (FileUploadingCommand)command;
		MultipartFile multipartFile = commandBean.getFileToUpload();
		String comment = commandBean.getComment();
		FileUtils.writeByteArrayToFile(new File(".."), multipartFile.getBytes());
		// ...
  	}
}

如果将type="file"类型的input绑定到Spring MVC框架提供的MultipartFile类型,可能会让我们的Comnand对象过于依赖Spring框架的API。况且,获取MultipartFile的引用之后,我们依然需要将它按照byte[](或者数据流Stream或者字符串)的形式进行存储,所以,为了避免这些不便,我们也可以直接将Command对象中对应的字段类型声明为byte[]或者String类型。重构后的Command对象定义如下方代码清单所示。

public class FileUploadingCommand {
    
    
	private byte[] fileToUpload;
	private String conment;
  
	//getter和setter方法定义.....
  
	@Override
	public String toString() {
    
    
		return new ToStringBuilder(this).append("fi1eToUpload", fileToUpload).append("comment", comment).toString();
  	}
}

为了在数据绑定过程中数据能够成功转型,我们需要为DataBinder添加相应的自定义PropertyEditor实现。覆写SimpleFormController的initBinder(..)方法可以达到这个目的。工作完成后得到的最终用于文件上传的Controller实现类定义如下方代码清单所示。

public class FileUploadFormController extends SimpleFormController {
    
    
	public FileUploadFormController() {
    
    
		super.setCommandClass(FileUploadingCommand.class);
  	}
  
	@Override
	protected void doSubmitAction(Object command) throws Exception {
    
    
		Fi1eUploadingCommand commandBean = (FileUploadingCommand)command;
		byte[] fileContent = commandBean.getFileToUp1oad();
		String comment = commandBean.getComment();
		FileUtils.writeByteArrayToFile(new File(".."), fileContent);
		//...
  	}

  @Override
	protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) throws Exception {
    
    
		super.initBinder(request, binder);
		binder.registerCustomEditor(byte[].class, new ByteArrayMu1tipartFileEditor());
  	}
}

Spring MVC默认提供了两个自定义PropertyEditor实现类,一个就是我们刚刚使用的org.springframework.web.multipart.support.ByteArrayMultipartFileEditor,它将负责MultipartFile类型到byte[]类型的转换;另一个则是org.springframework.web.multipart.support.StringMultipartFileEditor,它负责MultipartFile类型到String类型的转换。

如果使用StringMultipartFileEditor的话,Command对象中对应文件的属性需要声明为String类型,这通常对应文本文件上传的情形。

有了相应的处理文件上传的Controller之后,只需要将它们添加到DispatcherServlet的WebApplicationContext启用即可。对此,你应该已经很熟悉了。总地来说,如果不去关心细节的话,在Spring MVC中实现文件上传还是比较惬意的事情。

猜你喜欢

转载自blog.csdn.net/qq_34626094/article/details/125789193
今日推荐