第2章 SpringMVC实现文件上传
2.1 文件上传的回顾
2.1.1 文件上传的必要前提
1、form表单的enctype取值必须是:multipart/form-data(指定传输数据为二进制数据,例如图片、mp3、文件。http请求中的multipart/form-data,会将表单的数据处理为一条消息,以标签为单元,用分隔符分开。 既可以上传键值对,也可以上传文件)
默认值:application/x-www-form-urlencoded(会将表单内的数据转换为键值对)
2、method属性取值必须是post:如果是get会把你请求的东西放在地址栏上,地址栏的大小是有限制的,装不了多少数据;大数据的提交必须选post。
3、提供一个文件选择域<input type=”file” />
2.1.2 文件上传的原理分析
当 form 表单的 enctype 取值不是默认值后,request.getParameter()将失效。
enctype=”application/x-www-form-urlencoded”时,form 表单的正文内容是:key=value&key=value&key=value
当form表单的enctype 取值为Mutilpart/form-data时,请求正文内容就变成:
每一部分都是 MIME 类型描述的正文,如下:
-----------------------------7de1a433602ac 分界符
Content-Disposition: form-data; name="userName" 协议头
aaa 协议的正文
-----------------------------7de1a433602ac
Content-Disposition: form-data; name="file";
filename="C:\Users\zhy\Desktop\fileupload_demofile\b.txt"
Content-Type: text/plain 协议的类型(MIME 类型)
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
-----------------------------7de1a433602ac--
2.1.3 借助第三方组件实现文件上传
这些组件给我们提供了api,可以很方面的解析上传文件的请求体,不需要我们过于细节的去解析那个请求体(不需要我们知道请求体的格式也能正确解析)。这个组件是Apache提供的。
<!-- 导入文件上传的jar包 -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
2.1.4 传统文件上传方式:
借助了2.1.3的第三方组件;需要解析整个请求体。
前端提交表单,其实所有的内容最终全部会被封装到request这个对象中。所以得解析request从而拿到你上传文件的这个选项。
前端:
<form action="user/fileUpload1/" method="post" enctype="multipart/form-data">
选择文件:<input type="file" name="upload"> <br>
<input type="submit" value="上传">
</form>
后端:
@RequestMapping("/fileUpload1")
public String fileUpload1(HttpServletRequest request) throws Exception {
System.out.println("fileUpload1...");
// 使用fileupload组件完成文件上传
// 上传位置
String path = request.getSession().getServletContext().getRealPath("/uploads/");
// 判断,该路径是否存在
File file = new File(path);
if (!file.exists()) {
// 创建该文件夹
file.mkdirs();
}
// 解析request对象,获取上传文件项
DiskFileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
// 解析request
List<FileItem> items = upload.parseRequest(request);
// 遍历
for (FileItem item : items) {
// 进行判断,当前item对象是否是上传文件项
if (item.isFormField()) {
// 说明是普通表单项
} else {
// 说明是上传文件项
// 获取上传文件的名称
String filename = item.getName();
// 把文件的名称设置唯一值,uuid(好处:以后上传相同的文件不会覆盖)
String uuid = UUID.randomUUID().toString().replace("-", "");
filename = uuid + "_" + filename;
// 完成文件上传(往path路径传名为filename的这个文件)
item.write(new File(path, filename));
// 删除临时文件
// (这个上传会帮你产生临时文件,一旦你上传文件的大小大于10KB,就有临时文件,当把文件传完你可以将其删掉;
// 如果小于10KB,它就在内存当中帮你生成缓存的一个文件,这个不用处理)
item.delete();
}
}
return "success";
}
特别注意:
配置Tomcat服务器的时候一定要选择这个:
不要选上面那个,不然文件不会上传到工程目录下target目录里去,而是会跑到Tomcat安装路径的webapps,具体是什么路径可以自己断点调试看看就知道了。
2.2 SpringMVC传统方式的文件上传
2.2.1 原理分析:
说明:配置好文件解析器后,会通过参数绑定的方式将文件上传项赋值给controller方法里的MultipartFile对象。与传统方式相比的优势,不需要自己作request对象的解析工作了。
注意:
1、前端页面的input框得name为upload,后端controller方法也必须为upload,否则参数绑定不上。
2、配置文件解析器的时候bead的id值必须为multipartResolver,不能随意改动,因为底层框架默认是用这个id名的。
2的补充说明:文件上传的解析器 id是固定的,不能起别的名称,否则无法实现请求参数的绑定。(不光是文件,其他 字段也将无法绑定)
2.2.2 具体操作
在springmvc.xml配置文件中配置,
<!--配置文件解析器对象 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!--value单位大小为字节,配置10MB=10*1024*1024-->
<property name="maxUploadSize" value="10485760"/>
</bean>
编写controller方法,
@RequestMapping("/fileUpload2")
public String fileUpload2(HttpServletRequest request, MultipartFile upload) throws Exception {
System.out.println("SpringMVC传统方式的文件上传...");
// 使用fileupload组件完成文件上传
// 上传位置
String path = request.getSession().getServletContext().getRealPath("/uploads/");
// 判断,该路径是否存在
File file = new File(path);
if (!file.exists()) {
// 创建该文件夹
file.mkdirs();
}
// 说明是上传文件项
// 获取上传文件的名称
String filename = upload.getOriginalFilename();//拿到传统的名称,就是你文件的名字
System.out.println(upload.getName());
// 把文件的名称设置唯一值,uuid(好处:以后上传相同的文件不会覆盖)
String uuid = UUID.randomUUID().toString().replace("-", "");
filename = uuid + "_" + filename;
// 完成文件上传(往path路径传名为filename的这个文件)
upload.transferTo(new File(path, filename));//不需要删除临时文件了,框架帮你处理过
return "success";
}
2.2.3 特别注意:
SpringMVC传统方式上传文件,也需要上面的第三方提供的组件(
<!-- 导入文件上传的jar包 --> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.1</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.4</version> </dependency>
),因为文件解析器(multipartResolver)需要用到里面的jar包,只要配置了这个文件解析器就不需要导入那个jar包,下面跨服务器上传同理也需要。
2.3 SpringMVC跨服务器方式的文件上传
示意图:
2.3.1 搭建图片服务器
配置两个Tomcat服务器
第一个Tomat服务器不变作为应用服务器,
第二个记得改端口等参数,作为图片服务器(文件服务器)
配置好之后,再新建一个Module:
这个Module不需要做太多的事,只需要增加一个文件夹一会上传的文件就到这里,然后就是修改一下index.jsp的提示语即可。
最后再把这个项目部署到上面配置的那个Tomcat图片服务器里。
导入jar包,是由sun公司提供的
<!--跨服务器文件上传-->
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-core</artifactId>
<version>1.18.1</version>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-client</artifactId>
<version>1.18.1</version>
</dependency>
2.3.2 编写后端代码
@RequestMapping("/fileUpload3")
public String fileUpload3(MultipartFile upload) throws Exception {
System.out.println("SpringMVC跨服务器方式的文件上传...");
// 定义上传文件服务器路径
String path = "http://localhost:9090/fileuploadserver_war_exploded/uploads/";
// 说明是上传文件项
// 获取上传文件的名称
String filename = upload.getOriginalFilename();
// 把文件的名称设置唯一值,uuid
String uuid = UUID.randomUUID().toString().replace("-", "");
filename = uuid + "_" + filename;
// 创建客户端的对象,注意选对jar包
Client client = Client.create();
// 和图片服务器进行连接
WebResource webResource = client.resource(path + filename);
// 上传文件,需要个是一个byte[]数组(字节数组)
webResource.put(upload.getBytes());
return "success";
}
2.3.3 启动两个Tomcat服务器进行测试
坑
1、报错UniformInterfaceException 405
Message
Request processing failed; nested exception is com.sun.jersey.api.client.UniformInterfaceException: PUT http://localhost:9090/uploads/92f40f26e58e47ffacb304f7243fad55_girl8.jpg returned a response status of 405 Method Not Allowed
Root Cause
com.sun.jersey.api.client.UniformInterfaceException: PUT http://localhost:9090/uploads/92f40f26e58e47ffacb304f7243fad55_girl8.jpg returned a response status of 405 Method Not Allowed
原因:Tomcat权限问题。
打开tomcat/conf/web.xml找到相关配置信息可以看到
<!-- readonly Is this context "read only", so HTTP -->
<!-- commands like PUT and DELETE are -->
<!-- rejected? [true] -->
通过注释可以知道,默认情况下我们进行put或者delete操作时候,服务器是拒绝访问的,所以想要向服务器上传文件必须将readonly属性设置为false,自己手动添加这个属性并设置为false,允许读写操作:
代码:
<init-param>
<param-name>readonly</param-name>
<param-value>false</param-value>
</init-param>
2、报错 409
Request processing failed; nested exception is com.sun.jersey.api.client.UniformInterfaceException: PUT http://localhost:9090/fileuploadserver_war_exploded/uploads/55244f1d4d214cc78ea38edcae046de4_girl8.jpg returned a response status of 409 Conflict
原因:上面的新工程在webapp目录下新建的一个空的uploads文件夹,如果文件夹下没有内容是空的话,部署的时候是不会在target目录的响应位置创建uploads文件夹的。
解决方式:
1、在uploads文件夹下随便创个文件,一会部署的时候就会在target下生成uploads文件夹了;
2、手动去target目录下的相应位置创建一个;
3、后端自己写代码判断不存在这个文件夹就创建一个。