您的位置:首页 > 编程语言 > Java开发

【笔记】Spring MVC学习指南(十一)上传文件

2015-10-06 22:49 597 查看
只有实现了Servlet3.0及其更高版本规范的Servlet容器,才支持文件上传。对于低版本的容器,则需要Apache Commons FileUpload元件及Apache commons IO,下载地址:http://commons.apache.org/proper/commons-fileupload/
   http://commons.apache.org/proper/commons-io/



为了使用Commons FileUpload,首先要在springmvc-config.xml中定义multipartResolver bean:

<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver"><!--使用Commons FileUpload-->
<property name="maxUploadSize" value="2000000"/>
</bean>


在HTML5中定义文件上传按钮的代码:

<!-- 文件多选框 -->
<input type="file" name="fieldName" multiple=""/>
<input type="file" name="fieldName" multiple="multiple"/>
<input type="file" name="fieldName" multiple/>
<!-- 文件单选框 -->
<input type="file" name="fieldName"/>


在SpringMVC中,上传的文件会被包在一个MultipartFile对象中,我们要做的,是新建一个带有该类型属性的类。MultipartFile本身是一个接口,包含一些获取及处理上传文件的api,其中最重要的是void transferTo(File destination),该方法将上传的文件保存到目标目录下。

首先需要编写包含MultipartFile属性的类:

package app11a.domain;

import org.springframework.web.multipart.MultipartFile;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.io.Serializable;
import java.util.List;

public class Product implements Serializable {
private static final long serialVersionUID = 74458L;

@NotNull
@Size(min = 1, max = 10)
private String name;

private String description;
private Float price;
private List<MultipartFile> images; // 上传的文件保存在此

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getDescription() {
return description;
}

public void setDescription(String description) {
this.description = description;
}

public Float getPrice() {
return price;
}

public void setPrice(Float price) {
this.price = price;
}

public List<MultipartFile> getImages() {
return images;
}

public void setImages(List<MultipartFile> images) {
this.images = images;
}
}


先来看ProductController.java:

package app11a.controller;

import app11a.domain.Product;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;

@Controller
public class ProductController {

private static final Log logger = LogFactory.getLog(ProductController.class);

@RequestMapping(value = "/product_input")
public String inputProduct(Model model) {
model.addAttribute("product", new Product());
return "ProductForm";
}

@RequestMapping(value = "/product_save")
public String saveProduct(HttpServletRequest servletRequest, @ModelAttribute Product product, BindingResult bindingResult, Model model) {

List<MultipartFile> files = product.getImages();

List<String> fileNames = new ArrayList<String>();
if (null != files && files.size() > 0) {
for (MultipartFile multipartFile : files) {
String fileName = null;
try {
fileName = new String(multipartFile.getOriginalFilename().getBytes("ISO8859-1"), "UTF-8"); // 这样才不会中文乱码,但是product.images[0].name依然无法正常显示,会是html编码
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
System.out.println(fileName);
fileNames.add(fileName);
File imageFile = new File(servletRequest.getServletContext().getRealPath("/image"), fileName); // 从这里可以知道,上传的文件会被保存到这个image文件夹下
try {
multipartFile.transferTo(imageFile); // 关键性代码,保存上传的文件
} catch (IOException e) {
e.printStackTrace();
}
}
}

// save product here
model.addAttribute("product", product);
return "ProductDetails";
}

}


对应的ProductForm.jsp:

<%@ page import="org.apache.commons.fileupload.servlet.ServletFileUpload" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page pageEncoding="UTF-8" %>
<!DOCTYPE HTML>
<html>
<head>
<title>Add Product Form</title>
<style type="text/css">@import url("<c:url value="/css/main.css"/>");</style>
</head>
<body>
<div id="global">
<form:form commandName="product" action="product_save" method="post" enctype="multipart/form-data"><%--enctype设置为multipart/form-data!!!--%>
<fieldset>
<legend>Add a product</legend>
<p>
<label for="name">Product Name: </label>
<form:input id="name" path="name" cssErrorClass="error"/>
<form:errors path="name" cssClass="error"/>
</p>
<p>
<label for="description">Description: </label>
<form:input id="description" path="description"/>
</p>
<p>
<label for="price">Price: </label>
<form:input id="price" path="price" cssErrorClass="error"/>
</p>
<p>
<label for="image">Product Image: </label>
<%-- input的type="file" --%>
<%-- input的name值,与product.images对应,如果多个input的name重复,以第一个为准。这里有个问题,如果图片名称为中文,会乱码 --%>
<input id="image" type="file" name="images[0]"/>
</p>
<p>
<label for="image2">Product Image2: </label>
<%--这里一定是images[1],如果为[0],会被之前的屏蔽,如果为[2]或者更大的数,会报空指针错误 --%>
<input id="image2" type="file" name="images[1]"/>
</p>
<p id="buttons">
<input id="reset" type="reset" tabindex="4">
<input id="submit" type="submit" tabindex="5" value="Add Product">
</p>
</fieldset>
</form:form>
</div>
</body>
</html>


ProductDetails.jsp:

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page pageEncoding="UTF-8" %>
<!DOCTYPE HTML>
<html>
<head>
<title>Save Product JSP</title>
<style type="text/css">@import url("<c:url value="/css/main.css"/>");</style>
</head>
<body>
<div id="global">
<h4>The product has been saved.</h4>
<p>
<h5>Details:</h5>
Product Name: ${product.name}<br/>
Description: ${product.description}<br/>
Price: $${product.price}
<p>Following files are uploaded successfully.</p>
<ol>
<c:forEach items="${product.images}" var="image">
<%--<c:set var="kx" value="${new String(image.getOriginalFilename().getBytes(\"ISO8859-1\"), \"UTF-8\")"} />--%>
<li>${image.originalFilename}
<img width="100" src="<c:url value="/image/"/>${image.originalFilename}"/>
</li>
</c:forEach>
</ol>
</p>
</div>
</body>
</html>


如果容器为Servlet3或者更高版本,则可以直接使用内置特性

<bean id="multipartResolver"
class="org.springframework.web.multipart.support.StandardServletMultipartResolver"> <!--使用Servlet3及以上版本自带的上传功能-->
</bean>


其余部分一样,不重复了。

11.10小节,可以说是本书到目前为止个人觉得最有难度的部分了,一来对javascript的这些内容不熟悉,二来还用到了XMLHttpRequest对象。

最重要的jsp页面(该有的注释都写了,如何实现上传进度的显示):

<!DOCTYPE HTML>
<html>
<head>
<script>
//        要上传的文件总长度, 已上传的字节数, 要上传的文件数, 已上传的文件数
var totalFileLength, totalUploaded, fileCount, filesUploaded;
function debug(s) {
var debug = document.getElementById('debug');
if (debug) {
debug.innerHTML = debug.innerHTML + '<br/>' + s;
}
}

function onUploadComplete(e) {
totalUploaded += document.getElementById('files').files[filesUploaded].size; /*files数组依旧是从零开始*/
filesUploaded++;
debug('complete ' + filesUploaded + " of " + fileCount);
debug('totalUploaded: ' + totalUploaded);
if (filesUploaded < fileCount) {
uploadNext();
} else {
var bar = document.getElementById('bar');
bar.style.width = '100%';
bar.innerHTML = '100% complete';
alert('Finished uploading file(s)');
}
}

function onFileSelect(e) {
// 对于e.target.files这段代码,表示第一次见,js还真是简单粗暴。。。
var files = e.target.files; // FileList object
var output = [];
fileCount = files.length;
totalFileLength = 0;
for (var i = 0; i < fileCount; i++) {
var file = files[i];
output.push(file.name, ' (', file.size, ' bytes, ', file.lastModifiedDate.toLocaleDateString(), ')'); // push()向数组的末尾添加元素,并返回新的数组长度
output.push('<br/>');
debug('add ' + file.size);
totalFileLength += file.size;
}
document.getElementById('selectedFiles').innerHTML = output.join(''); // join()将数组中的所有元素合并为一个字符串,不同元素之间通过指定的分隔符进行分隔
debug('totalFileLength:' + totalFileLength);
}

function onUploadProgress(e) {
if (e.lengthComputable) {
var percentComplete = parseInt((e.loaded + totalUploaded) * 100 / totalFileLength);
var bar = document.getElementById('bar');
bar.style.width = percentComplete + '%';
bar.innerHTML = percentComplete + ' % complete';
} else {
debug('unable to compute');
}
}

function onUploadFailed(e) {
alert("Error uploading file");
}

function uploadNext() {
var xhr = new XMLHttpRequest();
var fd = new FormData();
var file = document.getElementById('files').files[filesUploaded];
fd.append("multipartFile", file);
xhr.upload.addEventListener("progress", onUploadProgress, false); // 捕捉(true)or 冒泡(false),一般都为false。注意这里的progress事件,正是这个内置的事件类型才得以实现上传进度的获取,完全不需要自己去编写代码实现,之前一直觉得监控上传进度很厉害是以为这部分是需要自己编写代码来实现的!!!
xhr.addEventListener("load", onUploadComplete, false);
xhr.addEventListener("error", onUploadFailed, false);
xhr.open("POST", "file_upload"); // 请求方式, url
debug('uploading ' + file.name);
xhr.send(fd); // 提交,参数可以为null
}

function startUpload() {
totalUploaded = filesUploaded = 0;
uploadNext();
}
window.onload = function () {
document.getElementById('files').addEventListener('change', onFileSelect, false);
document.getElementById('uploadButton').addEventListener('click', startUpload, false);
}
</script>
</head>
<body>
<h1>Multiple file uploads with progress bar</h1>

<div id='progressBar' style='height:20px;border:2px solid green'>
<div id='bar' style='height:100%;background:#33dd33;width:0%'>
</div>
</div>
<form>
<input type="file" id="files" multiple/>
<br/>
<output id="selectedFiles"></output>
<input id="uploadButton" type="button" value="Upload"/>
</form>
<div id='debug' style='height:100px;border:2px solid green;overflow:auto'>
</div>
</body>
</html>


运行过程也还是不错的,下半部分的信息显示,感觉可以把样式改的更友好一些^_^









这篇的博客最早是上个月最后一天写的,保存了草稿之后今天才完成,嗯,就是因为那天不在状态,看js代码老走神 o(︶︿︶)o 唉
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: