本文实现的功能: springMvc实现多文件上传,以及使用拦截器对文件类型,文件大小进行拦截. 如果上传的有一个是文件为空,就拦截.
本文运用的知识点: 在文章第4章总结.代码中也有详细的注释
一: 环境准备
1. 导入maven依赖:
<dependencies>
<!--springmvc-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!--servlet-api-->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.1</version>
<scope>provided</scope>
</dependency>
<!--jsp-api-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!--文件上传-->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
</dependencies>
2.项目结构
3. web.xml 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!--配置SpringMVC的前端控制器(核心控制器)-->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--配置初始化参数,用于读取SpringMVC配置文件(指定配置文件的位置),使得dispatcherServlet被创建时,就加载配置文件,初始化Spring容器-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<!--设置DispatcherServlet控制器,在服务器启动(应用加载)的时候创建对象,取值只能是非0正整数,表示启动顺序,数字越小优先级越高-->
<load-on-startup>1</load-on-startup>
</servlet>
<!--配置dispatcherServlet的映射路径-->
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<!--把dispatcherServlet设置成 默认的缺省处理器 (覆盖Tomcat的默认处理器)-->
<url-pattern>/</url-pattern>
</servlet-mapping>
<!--配置一个过滤器,解决乱码-->
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<!--设置过滤器的属性值-->
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<!--启动过滤器-->
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<!--过滤所有请求-->
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
4. springmvc.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mcv="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--开启mvc的注册驱动,实际上SpringMVC会自动加载组件,这里手动加载是程序健壮性-->
<mvc:annotation-driven />
<!--开启注解包扫描-->
<context:component-scan base-package="com.zuoyueer"/>
<!--静态资源的配置:因为DispatcherServlet不能处理静态资源,所以我们设置成让Tomcat处理静态资源-->
<mcv:default-servlet-handler/>
<!--配置视图解析器,用于将 逻辑视图 解析为 物理视图 -->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--指定视图文件的所在路径,前缀和后缀-->
<!--前缀:指定文件夹的路径-->
<property name="prefix" value="/"/>
<!--后缀:指定文件名的后缀-->
<property name="suffix" value=".jsp"/>
</bean>
<!--配置文件上传解析器:id必须是: multipartResolver
文件上传的解析器 id是固定的,不能起别的名称,否则无法实现请求参数的绑定。
(不光是文件,其他 字段也将无法绑定-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!--设置上传文件的最大容量,单位是字节, -1 表示不限制,我把它注释了是为了使用自定义的拦截器-->
<!-- <property name="maxUploadSize" value="-1"/>-->
<!--设置编码,解决文件名乱码问题-->
<property name="defaultEncoding" value="utf-8"/>
</bean>
<!--自定义拦截器(实际上是拦截链,类似于过滤器链)-->
<mvc:interceptors>
<!--每个拦截器的执行顺序,就是定义的顺序,在前面的先执行,被拦截了就不停止-->
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.zuoyueer.interceptor.FileSizeInterceptor">
<property name="maxSize" value="30000"/>
</bean>
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.zuoyueer.interceptor.FileTypeInterceptor">
<property name="suffixList" value="jpg,gif,png"/>
</bean>
</mvc:interceptor>
</mvc:interceptors>
<!--自定义异常处理器-->
<bean class="com.zuoyueer.exception.MyExceptionHandler"/>
</beans>
5. 前端页面(一个请求页面,2个结果页面,结果页面太简单,就是打印一句话,我就不贴出来了)
<%--
Created by IntelliJ IDEA.
User: Zuoyueer
Date: 2019/11/28
Time: 15:32
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>文件上传(普通方式)</title>
</head>
<h1>最基本的文件上传</h1>
<form action="${pageContext.request.contextPath}/file/upload" enctype="multipart/form-data" method="post">
<input type="text" name="username"><br>
<input type="file" name="files"> <br>
<input type="submit" value="上传文件">
</form>
<h1>对上传文件的路径和名称进行处理</h1>
<form action="${pageContext.request.contextPath}/file/upload2" enctype="multipart/form-data" method="post">
<input type="file" name="files"> <br>
<input type="submit" value="上传文件">
</form>
<h1>多文件上传,name属性值必须一样</h1>
<form action="${pageContext.request.contextPath}/file/upload3" enctype="multipart/form-data" method="post">
<input type="file" name="files"> <br>
<input type="file" name="files"> <br>
<input type="file" name="files"> <br>
<input type="submit" value="上传文件">
</form>
</body>
</html>
二:控制器 FileController.java
package com.zuoyueer.conntroller;
import org.apache.commons.io.FileUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.UUID;
/**
* @author Zuoyueer
* Date: 2019/11/28
* Time: 15:37
* @projectName Framework
* @description: 文件控制器
*/
@Controller
@RequestMapping("file")
public class FileController {
/**
* 同时上传多个文件
*/
@RequestMapping("upload3")
public String upload3(@RequestParam MultipartFile[] files, HttpServletRequest request) throws IOException {
//直接遍历,不需要做非空判断,因为files不可能为空,即使不选择文件也会有其他内容
for (MultipartFile file : files) {
String fileName="";
String originalFilename = file.getOriginalFilename();
//getName获取的是表单的name属性值,别用错了
String name = file.getName();
System.out.println(originalFilename+" : "+name);
String uuid = UUID.randomUUID().toString().replace("_", "").toUpperCase();
fileName= uuid+"_"+originalFilename;
String basePath = request.getServletContext().getRealPath("file3");
String datePath = new SimpleDateFormat("yyy-MM-dd").format(new Date());
File destDir = new File(basePath + "/" + datePath);
if(!destDir.exists()){
destDir.mkdirs();
}
File destFile = new File(destDir, fileName);
file.transferTo(destFile);
}
return "success";
}
/**
* 对上传文件的路径和名称进行处理
*/
@RequestMapping("upload2")
public String upload2(MultipartFile files, HttpServletRequest request) throws IOException {
//定义文件名
String fileName = "";
//获取原始的文件名
String originalFilename = files.getOriginalFilename();
//防止文件名重复,设置随机文件名,toUpperCase将字母大写
String uuid = UUID.randomUUID().toString().replace("-", "").toUpperCase();
//得到最终的我文件名
fileName = uuid + "_" + originalFilename;
//设置存储路径
String basePath = request.getServletContext().getRealPath("files2");
//解决同一个文件夹中文件过多问题,每天一个新的文件夹
String datePath = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
//创建文件夹
File destDir = new File(basePath + "/" + datePath);
if (!destDir.exists()) {
destDir.mkdirs();
}
//上传文件
File destFile = new File(destDir, fileName);
files.transferTo(destFile);
//跳转
return "success";
}
/**
* 最基本的文件上传
*/
@RequestMapping("upload")
public String upload(MultipartFile files, HttpServletRequest request) throws IOException {
//设置保存路径
String path = request.getServletContext().getRealPath("files");
//根据路径创建保存文件夹
File destDir = new File(path);
if (!destDir.exists()) {
destDir.mkdirs();
}
//保存文件
File destFile = new File(destDir, files.getOriginalFilename());
files.transferTo(destFile);
//跳转
return "success";
}
}
实际上,到这里,就以及能实现,文件的上传了,只不过有一些问题需要解决
- 文件类型进行限制
- 文件的大小进行限制
- 特别是多文件上传的时候,要实现即使有一个文件为空,就阻止提交.
- 以及不满足以上情况的异常处理
三 .拦截器的实现
文件类型拦截器
package com.zuoyueer.interceptor;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* @author Zuoyueer
* Date: 2019/11/28
* Time: 17:54
* @projectName Framework
* @description: 上传文件类型拦截器
*/
public class FileTypeInterceptor extends HandlerInterceptorAdapter {
//允许上传的文件类型
private String suffixList;
//set方法的目的是为了,能在配置文件中指定文件类型,实现解耦
public void setSuffixList(String suffixList) {
this.suffixList = suffixList;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
boolean flag = true;
//判断是否是文件上传请求
if (request instanceof MultipartHttpServletRequest) {
// 转换request,解析出request中的文件
MultipartHttpServletRequest multipartHttpServletRequest = (MultipartHttpServletRequest) request;
// 获取文件集合
List<MultipartFile> files = multipartHttpServletRequest.getFiles("files");
//遍历集合中的文件
for (MultipartFile file : files) {
//如果有一个文件为空,就不允许上传
if (file.isEmpty()){
request.setAttribute("errorMessage", "文件为空");
request.getRequestDispatcher("/error.jsp").forward(request, response);
//直接拦截,不继续执行下面的其他判断了
return false;
}
String originalFilename = file.getOriginalFilename();
//对文件类型进行检查,不符合就拦截
if (!checkFile(originalFilename)) {
request.setAttribute("errorMessage", "不支持的文件类型");
request.getRequestDispatcher("/error.jsp").forward(request, response);
flag = false;
}
}
}
return flag;
}
//检查后缀名是否符合要求
private boolean checkFile(String fileName) {
//获取文件后缀(文件类型)
String suffix = fileName.substring(fileName.lastIndexOf(".") + 1);
if (suffixList.contains(suffix.trim().toLowerCase())) {
return true;
}
return false;
}
}
文件大小拦截器
package com.zuoyueer.interceptor;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.fileupload.servlet.ServletRequestContext;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author Zuoyueer
* Date: 2019/11/28
* Time: 19:47
* @projectName Framework
* @description: 上传文件大小拦截器
*/
public class FileSizeInterceptor extends HandlerInterceptorAdapter {
private long maxSize;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (request!=null && ServletFileUpload.isMultipartContent(request)){
//Servlet请求的上下文信息,都封装在ServletRequestContext对象中
ServletRequestContext servletRequestContext = new ServletRequestContext(request);
System.out.println(servletRequestContext.contentLength());
//获取请求内容的大小,包括请求中的全部数据
long contentLength = servletRequestContext.contentLength();
if (contentLength>maxSize){
throw new MaxUploadSizeExceededException(maxSize);
}
}
return true;
}
public void setMaxSize(long maxSize) {
this.maxSize = maxSize;
}
}
异常处理器
package com.zuoyueer.exception;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author Zuoyueer
* Date: 2019/11/28
* Time: 18:31
* @projectName Framework
* @description: 自定义异常处理器
*/
public class MyExceptionHandler implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
ModelAndView modelAndView = new ModelAndView();
//上传文件大小超过限制抛出的异常处理
if (e instanceof MaxUploadSizeExceededException){
modelAndView.addObject("errorMessage", "文件过大");
modelAndView.setViewName("error");
}
//其他异常
return modelAndView ;
}
}
四: 总结
MultipartFile
接口
使用之前必须导入文件上传依赖和配置文件上传解析器
MultipartResolver 用于处理文件上传,当收到请求时 DispatcherServlet 的 checkMultipart() 方法会调用 MultipartResolver 的 isMultipart() 方法判断请求中是否包含文件。如果请求数据中包含文件,则调用 MultipartResolver 的 resolveMultipart() 方法对请求的数据进行解析,然后将文件数据解析成 MultipartFile 并封装在 MultipartHttpServletRequest (继承了 HttpServletRequest) 对象中,最后传递给 Controller,在 MultipartResolver 接口中有如下方法:
- boolean isMultipart(HttpServletRequest request); // 是否是 multipart
- MultipartHttpServletRequest resolveMultipart(HttpServletRequest request); // 解析请求
- void cleanupMultipart(MultipartHttpServletRequest request);
MultipartFile 封装了请求数据中的文件,此时这个文件存储在内存中或临时的磁盘文件中,需要将其转存到一个合适的位置,因为请求结束后临时存储将被清空。在 MultipartFile 接口中有如下方法:
- String getName(); // 获取参数的名称
- String getOriginalFilename(); // 获取文件的原名称
- String getContentType(); // 文件内容的类型
- boolean isEmpty(); // 文件是否为空
- long getSize(); // 文件大小
- byte[] getBytes(); // 将文件内容以字节数组的形式返回
- InputStream getInputStream(); // 将文件内容以输入流的形式返回
- void transferTo(File dest); // 将文件内容传输到指定文件中
在文件上传解析器中,还能配置很多属性,其中有一个resolveLazily属性,是懒加载,能够实现文件没有上传(文件过大,网速慢)完也能执行接下来的操作.可惜,没有文件类型的属性,使用,我们必须自定义拦截器来限制文件类型
MultipartHttpServletRequest
这个接口实际上是对request接口的增强,是文件解析器帮我们完成的封装, 我们直接使用它里面的方法,就能获取到请求中的各种内容.
它的方法有很多,与文件上传相关常用的,就是上面拦截器中使用的那几个~
ServletRequestContext
这个实现类提供对HTTP Servlet的请求所需的请求信息的访问,我之所用这个纯粹是为了用以下而已
实际上是multipartHttpServletRequest.getContentLength()
也能获取请求体的大小.
以下博客对我帮助很大,感谢大佬的分享!
https://www.iteye.com/blog/exceptioneye-1314958
https://www.cnblogs.com/tengyunhao/p/7670293.html