spring学习: spring mvc上传文件方法分析

简介

    在用spring mvc做web开发的时候,有一个很典型的任务就是上传文件。 它的实现相对比较简单,可以说有固定的套路可以使用。当然,对于文件的上传以及管理在具体应用中还是相对比较复杂的,需要根据具体的情况来分析。目前来说,针对具体应用所使用的servlet容器版本,有两种常见的文件上传实现方法。一种是使用CommonsMultipartResolver,另外一种是StandardServletMultipartResolver。但是StandardServletMultipartResolver仅适用于servlet3.0及以上的容器中。我们针对两种的具体实现做一个分析讨论。

commons multipart resolver

   CommonsMultipartResolver相对来说可以适用于比较老一点的servlet容器版本。它的具体实现有对第三方的库的依赖。如果需要使用这种方式,需要在依赖文件里定义如下的内容:

<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.3.1</version>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-io</artifactId>
    <version>1.3.2</version>
</dependency>

   

    我们其他部分和前面文章中定义的项目类似。首先,我们定义的domain model对象如下:

public class Product {
	private Long id;
	private String productId;
	private String name;
	private BigDecimal unitPrice;
	private String description;
	private String manufacturer;
	private String category;
	private long unitsInStock;
	private long unitsInOrder;
	private boolean discontinued;
	private String condition;
	private MultipartFile productImage;

	public Product() {
		super();
	}

	public Product(String productId, String name, BigDecimal unitPrice) {
		this.productId = productId;
		this.name = name;
		this.unitPrice = unitPrice;
	}
}

    这里省略了部分内容。 另外,为了使得前面的multipartresolver生效,我们还需要修改dispatcher-servlet.xml文件,里面需要添加的内容如下:

<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
	<property name="maxUploadSize" value="10240000"/>
</bean>

    上述内容里定义了上传文件的大小限制,比如这里的大小是1M。我们可以根据具体的需要来调整。

    现在定义提交内容的表单页面:

扫描二维码关注公众号,回复: 471669 查看本文章
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<spring:message var="productIdLabel" code="addProduct.form.productId.label"/>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link rel="stylesheet"	href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css">
<title>Products</title>
</head>
<body>
	<section>
		<div class="jumbotron">
			<div class="container">
				<h1>Products</h1>
				<p>Add products</p>
			</div>
		</div>
	</section>
	<section class="container">
		<form:form  modelAttribute="newProduct" class="form-horizontal" enctype="multipart/form-data">
			<fieldset>
				<legend>Add new product</legend>

				<form:errors path="*" cssClass="alert alert-danger" element="div"/>
				<div class="form-group">
					<label class="control-label col-lg-2 col-lg-2" for="productId"><spring:message code="addProduct.form.productId.label"/></label>
					<div class="col-lg-10">
						<form:input id="productId" path="productId" type="text" class="form:input-large"/>
						<form:errors path="productId" cssClass="text-danger"/>
					</div>
				</div>

				<div class="form-group">
					<label class="control-label col-lg-2" for="productImage"><spring:message code="addProduct.form.productImage.label"/></label>
					<div class="col-lg-10">
						<form:input id="productImage" path="productImage" type="file"
							class="form:input-large" />
					</div>
				</div>

				<div class="form-group">
					<div class="col-lg-offset-2 col-lg-10">
						<input type="submit" id="btnAdd" class="btn btn-primary" value ="Add"/>
					</div>
				</div>
				
			</fieldset>
		</form:form>
	</section>
</body>
</html>

     出于篇幅的考虑,这里省略了部分内容。上述部分的要点在于对表单的定义部分,<form:form  modelAttribute="newProduct" class="form-horizontal" enctype="multipart/form-data"> 这里多了一个enctype="multipart/form-data"的定义,表示提交的内容包含有multipart内容,也就是说包含有文件相关的内容。然后,在productImage的部分也有如下的定义:<form:input id="productImage" path="productImage" type="file" class="form:input-large" />。 它表示输入的是文件类型。

    在定义好提交表单后,剩下的就是定义处理表单数据的controller了。它的实现如下:

@Controller
@RequestMapping("/products")
public class ProductController {

	@RequestMapping(value = "/add", method = RequestMethod.POST)
	public String processAddNewProductForm(@ModelAttribute("newProduct") @Valid Product productToBeAdded,
			BindingResult result) {
		if(result.hasErrors()) {
			return "addProduct";
		}

		MultipartFile productImage = productToBeAdded.getProductImage();
				
		if (productImage!=null && !productImage.isEmpty()) {
			try {
				productImage.transferTo(new File("/home/frank/"
						+ productToBeAdded.getProductId() + ".png"));
			} catch (Exception e) {
				throw new RuntimeException("Product Image saving failed", e);
			}
		}

		return "redirect:/products";
	}
}

    在processAddNewProductForm方法中,由于前面的表单绑定属性,这里就将得到MultipartFile的对象,productImage。由于MultipartFile有一个transferTo方法。于是只需要在transferTo方法里传入一个File对象就可以了。具体的实现可以参照附件里的uploadfile工程。

standard servlet multipart resolver

    上述的方法虽然足够通用,但是在新的servlet容器里,实现上传文件的方法可以更加简单。它不需要有对第三方库的依赖。这种新的方法需要做的修改如下。

    首先在web.xml里增加如下部分的定义内容:

<servlet>
		<servlet-name>dispatcher</servlet-name>
		<servlet-class>
			org.springframework.web.servlet.DispatcherServlet
		</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>classpath:/spring/dispatcher-servlet.xml</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
		<multipart-config>
                        <location>/home/frank/Documents</location>
			<max-file-size>2097152</max-file-size>
			<max-request-size>4194304</max-request-size>
		</multipart-config>
	</servlet>

    这里定义了上传文件的一些配置信息,比如上传的文件大小为2M,而发送请求的内容长度为4M。甚至这里也可以配置文件上传的路径。

    在这一步配置好之后,后面需要定义的是上传内容的表单,这里仅仅列出和上面示例不同的地方:

<div class="form-group">
	<label class="control-label col-lg-2" for="productImage"><spring:message code="addProduct.form.productImage.label"/></label>
	<div class="col-lg-10">
		<input id="productImage" name="productImage" type="file" class="form:input-large" />
	</div>
</div>

   这部分的内容和普通的html表单的定义一样,并没有使用spring taglib form。所以这里提交的字段productImage不会自动绑定到domain model中。在domain model定义中,我们可以去掉productImage的定义。这种方式有一个好处,就是有时候我们不需要将领域对象模型和需要上传的对象内容直接关联起来。但是在提交表单的时候又需要将它们放在一起处理。

    下面剩下的就是定义处理这个表单的controller:

@Controller
@RequestMapping("/products")
public class ProductController {
	
	@RequestMapping(value = "/add", method = RequestMethod.POST)
	public String processAddNewProductForm(@ModelAttribute("newProduct") Product productToBeAdded,
			@RequestPart("productImage") MultipartFile productImage,
			BindingResult result) {
		if(result.hasErrors()) {
			return "addProduct";
		}
						
		if (productImage!=null && !productImage.isEmpty()) {
			try {
				productImage.transferTo(new File(productToBeAdded.getProductId() + ".png"));
			} catch (Exception e) {
				throw new RuntimeException("Product Image saving failed", e);
			}
		}
		
		return "redirect:/products";
	}
}

     上述的代码有一个不一样的地方就是方法的定义里多了一个参数@RequestPart("productImage") MultipartFile productImage,在RequestPart定义的属性正好和我们前面表单里定义的productImage一致,这样在表单的这个地方它会自动绑定这个属性。然后剩下的内容就和前面的一样,将MultipartFile输出到某个文件。这部分详细内容的输出可以参考后面附件里的示例工程multiparthandler。

    当然,standard servlet multipart resolver并不止我们想象的这么简单。它默认会将表单里提交的所有内容都上传到指定的目录。如果我们尝试将controller里该方法的内容给清空再去看上传目录就会看到相关的上传。

总结

    在spring mvc里实现文件的上传其实比较简单,主要通过commons multipart resolver或者standard servlet multipart resolver来处理。它们的差别在于使用后面这种方法作为原生的支持更加简单一点。在实际文件上传中,一般我们需要考虑将文件上传到某个具体的路径,所以可以在方法里使用具体的参数。也可以在默认路径配置好的情况下单纯操作文件。

    其实最复杂的部分在于文件上传之后的管理。对于简单的文件处理,我们可以将文件设置到某个目录中。在实际工程中更加推荐采用一些云存储服务,将具体文件对象上传到云存储服务器。这样对于它们的管理更加简单方便,不会出现简单的文件目录管理问题。

参考材料

spring in action

猜你喜欢

转载自shmilyaw-hotmail-com.iteye.com/blog/2244896
今日推荐