Spring MVC【八】文件上传

目录

一、前言

1.文件上传的请求方法与内容类型和普通的请求方法对比

2.文件上传的HTTP请求对象与一般请求的对比

二、Java文件上传功能的实现方式

1.Servlet 3.0文件上传实现方式

2.SpringMVC文件上传功能的实现方式

2.1 独立使用MultipartResolver实现类处理文件上传

2.2通过中央控制器自动转换请求类型

2.3 文件类型参数自动匹配装箱


一、前言

Web前端文件上传的典型方式是使用enctype属性值multipart/form-data的表单(<form>)进行传送,在表单中添加type值为file的输入框,提交表单以二进制方式将文件传送到后端。在后端,Java Web使用HttpServlet处理HTTP请求,从request中获取文件流并进行处理。Java中使用较早并且使用较为广泛的处理文件上传的库是Apache提供的Commons FileUpload库,在Servlet3.0之后,Java官方提供了标准的文件上传处理的Servlet。在SpringMVC中,控制器类中使用请求映射方法可以用来处理前端对应的URL请求,这个方式对文件上传同样适用。Spring MVC对Apache Commons FileUpload和标准Servlet文件上传方式都提供支持,并且提供了统一的API处理文件上传。

1.文件上传的请求方法与内容类型和普通的请求方法对比

GET和POST是常用的HTTP请求方法,GET请求可以直接在URL中附加参数,也可以通过Form表单传递参数。

  • Form表单的method不设置或者设置成GET,浏览器使用application/x-www-form-urlencoded进行编码,将表单数据转换成一个字符串,附加到请求URL地址后面,GET请求一般用来传递ASCII字符集参数的传递,URL编码将一个非ASCII字符用3个字符表示,比如中文的“用户”,编码后的值是%E7%94%A8%E6%88%B7,这样就增加了请求的大小。GET方法参数效率不高、安全性低而且对请求参数的大小也有限制
  • Form表单的method设置成POST时,参数会放入请求体中。早期的Form表单只有application/x-www-form-urlencoded一种类型,用来传递大量的二进制数据是很低效的,于是出现了multipart/form-data的内容类型用来传递文件。使用类型时,对于GET请求URL后将不会在附加参数。

2.文件上传的HTTP请求对象与一般请求的对比

二、Java文件上传功能的实现方式

1.Servlet 3.0文件上传实现方式

在Servlet3.0之后,JavaServlet就支持文件上传的处理。Servlet3.0中定义了处理文件类型的标准接口Part,该接口具体由各应用服务器提供实现。比如Tomcat中的实现类是org.apache.catalina.core.ApplicationPart。Part接口中定义了包含获取请求头、文件输入流和文件相关属性等方法,主要如下:

  • getInputStream():获取上传文件输入流
  • getContentType():获取上传文件的内容类型。
  • getName()获取上传文件名。该方法获取的不是实际的文件名,而是file类型输入框的name属性值。实际文件名通过请求头的content-disposition属性解析获取。
  • getSize():获取上传文件的字节大小。
  • write(String fileName):将上传文件写入磁盘的目标文件中。
  • getHeader(String name):获取请求头的某个属性值。

前端表单提交的文件会被封装成Part类型的对象,通过HttpServletRequest的getParts()方法就可以直接获取。需要注意的是,用来处理文件上传的Servlet类要使用@MultipartConfig注解进行标注。示例如下:

<form action="uploadServlet" enctype="multipart/form-data" method="POST">
    文件域:<input type="file" name="myfile" /><br />
    普通域:<input type="text" name="username" />
    <button type="submit" value="提交"></button>
</form>
@MultipartConfig // 处理文件上传的Servlet注解
public class UploadServlet extends HttpServlet {
    private static final long serialVersionUID = -2815800236858114812L;

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        Collection<Part> parts = request.getParts();
	for (Part part : parts) {
	    // 此处得到的header类似于:form-data; name="myfile"; filename="QQ图片20210502113751.jpg"
	    String header = part.getHeader("content-disposition");
	    if (part.getContentType() != null) { // contentType为null时为text域
	        String[] fileInfoArray = header.split(";");
		int index = fileInfoArray[2].indexOf("\"");
		String fileName = fileInfoArray[2].substring(index + 1, fileInfoArray[2].length() - 1);
		part.write("D:\\upload" + fileName);
	    }
	}
        String userName = request.getParameter("username"); // 获取普通的表单域
	// ……
    }
}

上面Part不仅包含文件类型,也包含type值为text的文本类型。通过判断contentType属性是否有值等方式,可以区分是否是文件类型。

web.xml中配置Servlet,如下:

<servlet>
    <servlet-name>uploadServlet</servlet-name>
    <servlet-class>com.mec.servlet.UploadServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>uploadServlet</servlet-name>
    <url-pattern>/uploadServlet</url-pattern>
</servlet-mapping>

@MultipartConfig注解还可以通过设置以下属性来定义文件的上传行为。

  • fileSizeThreshold:保存在内存和磁盘中的临界值,默认是0,也就是都会写入临时文件中;
  • location:文件保存的路径;
  • maxFileSize:上传文件的最大限制,默认是没有限制;
  • maxRequestSize:请求的最大值限制(一个请求可以包含多个上传文件,也可以包含文本类型的数据),默认没有限制。

2.SpringMVC文件上传功能的实现方式

SpringMVC默认支持使用Apache Commons FileUpload和标准的Servlet处理文件上传,并提供了统一的文件处理接口,主要接口如下:

  • MultipartResolver:多部分解析器接口,该接口定义了如下两个方法:
    • isMultipart(HttpServletRequest request):判断请求是否包含文件。
    • resolveMultipart(HttpServletRequest request):将标准request对象进行解析,转换为多部分请求类型对象(MultipartHttpServletRequest)
  • MultipartHttpServletRequest:多部分HTTP请求接口。该接口继承自HttpServletRequest和MultipartRequest,并额外定义了getRequestMethod()和getRequestHeaders()方法,分别用于获取请求方法和请求头
  • MultipartRequest:多部分请求接口,主要用于文件处理。通过getFile(String name)和getFiles(String name)方法可以获取MultipartFile接口实例的文件对象。
  • MultipartFile:多部分文件接口,该接口定义了获取文件名getName()、文件内容类型(getContentType())、输入流(getInputStream())及写入目标文件的方法(transferTo(File dest))

在SpringMVC项目中,使用MultipartResolver实现类就可以在请求映射方法中解析请求对象处理文件上传。更常用的方式是在配置文件中配置multipartResolver的Bean,以作为DispatcherServlet的装备,这样请求在通过中央控制器转发时就会自动对请求类型进行转换,简化代码开发。

2.1 独立使用MultipartResolver实现类处理文件上传

@Controller
public class UploadController {

	@RequestMapping("/uploadMethod")
	@ResponseBody
	public void uploadMethod(HttpServletRequest request) throws IllegalStateException, IOException {
		request.setCharacterEncoding("UTF-8");

		MultipartResolver resolver = new CommonsMultipartResolver(request.getServletContext());
		MultipartHttpServletRequest multipartRequest = resolver.resolveMultipart(request);// 解析请求
		MultipartFile file = multipartRequest.getFile("myfile");
		file.transferTo(new File("D:\\upload" + file.getOriginalFilename()));

		String userName = multipartRequest.getParameter("username"); // 获取普通域
	}
}

标准Servlet多部分处理器(StandardServletMultipartResolver)的处理方式除了构造函数不一样,其他基本一致。StandardServletMultipartResolver类型的解析器初始化如下:

MultipartResolver resolver = new StandardServletMultipartResolver();

和单独使用Servlet类处理文件上传需要加上@MultipartConfig注解一样,在SpringMVC中需要在中央控制器的Servlet配置中(<servlet>标签内)加上<multipart-config />标签配置。但如果是基于代码配置,则方式如下:

//……
DispatcherServlet servlet = new DispatcherServlet(xmlWebContext);
ServletRegistration.Dynamic regsitration = servletContext.addServlet("dispatcher", servlet);
//……
// 进行此项设置。相当于<multipart-config />标签
regsitration.setMultipartConfig(new MultipartConfigElement(""));

<multipart-config />标签配置和MutipartConfigElement对象配置一样可以设置如下四项内容:

  • <location>或location:文件保存路径
  • <max-file-size>或maxFileSize:上传文件的最大限制
  • <max-request-size>或maxRequestSize:请求的最大值限制
  • <file-size-threshold>或fileSizeThreshold:保存在内存或者硬盘中的临界值

2.2通过中央控制器自动转换请求类型

DispatcherServlet在初始化策略时会使用initMultipartResolver()方法初始化多部分解析器,解析方式是看容器中是否存在名字是multipartResolver的Bean,若存在,在请求处理时会自动将HttpServletRequest转换成对应解析器的MultipartHttpServletRequest实现类型。这就需要在配置文件中增加Bean名字是multipartResolver的配置,Bean类型可根据需要选择CommonsMultipartResolver或StandardServletMultipartResolver。

<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"></bean>
@RequestMapping("/uploadMethod")
@ResponseBody
public void uploadMethod(MultipartHttpServletRequest multipartRequest) throws IllegalStateException, IOException {
    // 此处内部已经帮我们转换成MultipartHttpServletRequest实现类型DefaultMultipartHttpServletRequest,我们直接获取即可
    // 此时就不能通过new对象的方式创建解析器了,如果再通过原生方式则获取不到文件
    multipartRequest.setCharacterEncoding("UTF-8");
    MultipartFile file = multipartRequest.getFile("myfile");
    file.transferTo(new File("D:\\upload" + file.getOriginalFilename()));

    String userName = multipartRequest.getParameter("username"); // 获取普通域
}

配置multipartResolver的Bean交给容器之后,DispatcherServlet在转发请求时会调用该解析器的isMultipart(request)方法检查当前Web请求是否为文件上传类型。如果是则会调用resolveMultipart(request)方法对原始请求对象进行装饰,并返回MultipartHttpServletRequest接口实现类型的对象。

需要注意的是:如果配置的是标准Servlet的多部分解析(StandardServletMultipartResolver),同样需要在中央控制器Servlet的配置中加上<multipart-config />标签的配置,否则会报错。

2.3 文件类型参数自动匹配装箱

对于普通的文本类型参数,框架会根据属性名自动匹配装箱成请求映射方法的参数,文件类型也一样,通过@RequestParam进行匹配,对应的参数类型是MultipartFile。如果参数名和文件名相同,也就是表单中文件类型输入框的name值和参数名相同,则@RequestParam注解可以省略。我们再对上述代码进行简化,如下:

@RequestMapping("/uploadMethod")
@ResponseBody
//这里multipartFile如果改成myfile,那么@RequestParam注解是可以省略的
public void uploadMethod(@RequestParam(name = "myfile") MultipartFile multipartFile,
        @RequestParam(name = "username") String userName) throws IllegalStateException, IOException {
    String fileName = multipartFile.getOriginalFilename();// 获取目标文件名
    multipartFile.transferTo(new File("D:\\upload" + fileName));// 写入目标文件
    System.out.println(userName);    //操作username普通域提交项
}

上传多个文件时,可以在Form中指定每个file类型的输入框的名字都不一样,在映射方法中根据名字进行匹配。但更灵活的用法是让file类型输入框名字都一样,使用@RequestParam MultipartFile[] myfiles的方式进行匹配,此时@RequestParam注解不能省略,否则会报错。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值