上传和下载

上传

SpringMVC 中处理上传有两种方式:

  1. 使用Apache Commons FileUpload组件
  2. 利用Servlet3.0及其更高的版本的内置支持。Servlet3.0开始默认支持文件上传功能,Tomcat7.0版本就开始支持Servlet3.0.如果将应用程序部署到支持Servlet3.0及其更高版本的容器中,推荐使用这种方式

客户端准备

为了文件上传,必须将HTML表格的enctype属性值设置为multipart/form-data,例如:

表格中中必须包含类型为file的一个input元素,它将显示成一个按钮,点击时,它会打开一个对话框,用来选择文件(spring表单库中没有type=file,所以还是用HTML的input)

在HTML5之前,如果想要上传多个文件,必须使用多个文件input元素。但是在HTML5中,通过input元素引用多个multiple属性,使得多个文件上传变得更加简单。在HTML5中编写以下任意一行代码,便可生成一个按钮来选择多个文件:

<input type="file" name="fieldName" multiple/>

<input type="tfile" name="fieldName" multple="multiple"/>

<input type="file" name="fieldName" multiple=""/>i

MultipartFile 接口

在SpringMVC中处理已经上传的文件是很简单。上传到SpringMVC引用程序中的文件会被包在一个MultipartFile对象中。你唯一的任务是:用类型为MultipartFile的属性编写一个domain类。

org.springfarmwork.web.multipart.MultipartFile 接口具有以下方法:

接口 作用
byte[] getBytes() 以字节数组的形式返回文件的内容
String getContextType() 返回文件的内容类型
InputStram getInputStram()

返回一个inputStream,从中读取文件的内容

String getName() 一多部分的形式返回参数的名称
String getOriginalFilename() 返回客户端本地驱动器中的初始文件名
long getSize() 以字节为单位,返回文件的大小
boolean isEmpty() 表示被上传文件是否为空
void transferTo(File destination) 将文件保存在目标目录下

用Commons FileUpload上传文件

对于版本低于Servlet3.0的容器,需要使用Apache Commons FileUpload组件,为了让Commons FileUpload 正常工作,还需要另一个Apache Commons组件:Apache Commons IO。

导入所需jar包之后,还需要在SpringMVC配置文件中定义multipartResolver bean,这里使用的CommonsMultipartResolver对应使用Commons组件

<!-- 配置使用commons-fileupload进行文件上传 -->
 <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
   <property name="maxUploadSize" value="2000000"/>
</bean>

当然我们可以设置一个上传的最大值(maxUploadSize 字节为单位),如果没有这个属性,则没有最大文件容量限制。没有最大文件容量限制不意味着可以上传任意大小的文件,因为上传过大的文件要花费很长的时间,这样会导致服务器超时。为了处理超大文件的问题,可以利用HTML5 File API将文件切片,然后再分别上传这些文件。

Domain类

其实就是将对象的属性类型设置为MultipartFile类型,为了适配多个文件上传,我们可以用一个List:

控制器

我们使用Multipart提供的API操作文件即可:获得Multipart类型的属性对象->获得文件名->设置保存路径->使用transferTo方法保存

    @RequestMapping("/save-product")
    public String saveProduct(HttpServletRequest request,
            @ModelAttribute Product product,BindingResult result,Model model) {
        List<MultipartFile> files = product.getImages();
        if(files!=null&&files.size()>0) {
            for(MultipartFile file:files) {
                String fileName=file.getOriginalFilename();
                fileNames.add(fileName);
                File imageFile=new File(request.getServletContext().getRealPath("/image"),fileName);
                try {
                    file.transferTo(imageFile);
                } catch (Exception e) {
                    e.printStackTrace();
                } 
            }
        }
        //保存product进model
        model.addAttribute("product", product);
        return "productDetails";
    }

这是一段将上传文件保存在部署路径下的image中的Action

页面

上传页面最重要的就是type=file的input:

我们在domain中使用的是名为images的List<MultipartFile>,所以在页面中可以使用数组的方式给input的name赋名,有多个上传input,下标就累加。

显示页面中,我们可以使用JSTL(foreach标签)+EL遍历出来即可:

    <c:forEach items="${product.images }" var="image">
      <li>${image.originalFilename }</li>
      <img width="100" src='<c:url value="/image/${image.originalFilename }"></c:url>'/>
    </c:forEach>

文件名使用originalFilename获得即可(可以看到MultipartFile的API有一个getOriginalFileName方法)。将img标签的src指向对应的图片路径即可。

用Servlet3.0及其更高版本上传文件

有了Servlet3.0,我们就不再需要Commons FileUpload组件了,不用再添加这个jar了。Servlet3.0以及其更高版本的容器中进行服务器文件上传编程,是围绕着注解类型MultipartConfig和javax.servlet.http.Part接口进行的。处理上传文件的Servlet必须以@MultipartConfig进行注解。

@MultipartConfig注解类型中出现的属性,如下:

  • maxFileSize:上传问文件的最大容量,默认没有限制,超过指定大小的文件会被拒绝。
  • maxRequestSize:表示多部分HTTP请求允许的最大容量,默认没有限制
  • location:文件保存的位置
  • fileSizeThreshold:上传文件超过这个容量界限,就会被写入磁盘

我们说了处理上传的Servlet需要用@MultipartConfig修饰,但是SpringMVC的DispatchServlet如果不修改源码就无法进行注解。但值得庆幸的是,Servlet3中有一种比较容易的方法,能使一个Servlet变成MultipartConfig Servlet,即给部署描述符(web.xml)中的Servlet声明赋值:

    <servlet>
        <servlet-name>springDispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
        <!--  配置servlet3.0文件上传-->
        <multipart-config>
          <max-file-size>20848820</max-file-size>
          <max-request-size>418018841</max-request-size>
          <file-size-threshold>1048576</file-size-threshold>
        </multipart-config>
    </servlet>
    <servlet-mapping>
        <servlet-name>springDispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

此外,会需要在SpringMVC配置文件中使用一个不同于Commons方式的解析器:

<!-- 配置使用servlet3.0文件上传 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.support.StandardServletMultipartResolver"></bean>

其他的domain,Controller,页面我们都不需要修改,即可完成基于Servlet3.0及其以上版本的SpringMVC上传

总结一下这种方式:只是web.xml中包含一个multipart-config元素,springMVC配置文件使用的是StandardServletMultipartResolver这个解析器,其他的没什么与基于Commons FileUpload组件的方式不同,还是使用MultipartFile的API操作。

使用Part形式接受上传的文件

如果是将应用部署到Servlet3.0的容器中,那么MultiPartFile还有一个替代方案:SpringMVC接受javax.servlet.http.Part作为控制器方法的参数,可以使用Part替换MultipartFile。

    @ResponseBody
    @RequestMapping(value="/upload_lunwen",method=RequestMethod.POST)
    public boolean saveProduct(@RequestParam(value="lunwen",required=true)Part lunwen,
            @RequestParam("userId")String userId,HttpServletRequest request) {

Part接口与MultipartFile接口没有太大的不同,只是方法名有一些差异:比如getSubmittedFileName()对应于getOriginalFilenmae(); write()对应于transferTo()。

值得一提的是,如果在编写控制器方法的时候,通过Part参数的形式接受文件上传,那么就不需要MutilpartResolver了,只有使用MutilpartFile的时候,我们才需要MutilpartResolver。

下载

像图片和HTML文件这样的静态资源,在浏览器中打开正确的URL即可下载。只要是该资源放在应用程序的目录下,或者放在应用程序目录的子目录下,而不是放在WEB-INF下。Servlet/JSP容器就会将该资源发送到浏览器。

然而,有时静态资源是放在WEB-INF或应用程序之外,或者是保存在一个数据库中,或者有时需要控制它的访问权限,防止其他网站交叉引用它。如果出现以上任意一种情况,都必须通过编程来发送资源。

为了将文件这样的静态资源发送到浏览器,需要在控制器中完成以下工作:

  1. 在方法中添加HttpServletResponse参数
  2. 为相应的内容设置文件内容类型(Content-Type)。如果不清楚内容类型,并且希望浏览器始终显示Save As(另存为)对话框(Chrome浏览器并不会显示,它默认都下载),则将它设置为application/octet-stream。这个值是不区分大小写的。
  3. 添加一个名为Content-Disposition的HTTP响应标题,并赋值为attachment;filename=fileName,这里的fileName是默认的文件名,应该出现在File Download(文件下载)对话框中,它通常与文件名同名,但并非一定如此。当然我们也可以自定义文件名。

 之后我们就可以通过操作流来下载文件了:

FileInputStream fis=new FileInputStream(file);
BufferedInputStram bis=new BufferedInputStream(fis);
byte[] bytes=new byte[bis.available()];
response.setContentType(contentType);
OutputStream os=response.getOutputStream();
bis.read(bytes);
os.write(bytes);

首先要读取文件作为FileInputStream,并将内容加载到一个字节数组。随后,获取HttpServletResponse的OutputStream,并调用其write方法传入字节数组。

但是将文件发送到HTTP客户端的更好方法是使用Java NIO的Files.copy()方法:

Path path=Paths.get(...);
Files.copy(path,response.getOutputStream);

代码更少,运行速度更快。

例子

我们将文本文件1.txt放在/WEB-INF/file下,如果没有登录进到登录页进行登录,只有登录用户在可以下载这个文件,为了不让程序太复杂,我们使用写死用户名lz和密码123。开始使用SpringMVC下载这个静态资源吧。

Controller

登录请求方法:

    @RequestMapping("/login")
    public String login(@ModelAttribute Login login,HttpSession session,Model model) {
        if("lz".equals(login.getUserName())&&"123".equals(login.getPassword())) {
                session.setAttribute("loggedId", true);
                return "main";
            }else {
            return "loginForm";
        }
    }

登录成功就到/WEB-INF/main.jsp页面下载,没有登录就去/WEB-INF/loginForm.jsp登录.loginForm.jsp就是个登录页;main.jsp有一个超链接,指向 downloadResource 这个请求。

重头戏:下载的请求方法:

    @RequestMapping("/downloadResource")
    public String downloadResource(HttpSession session,HttpServletResponse response,
            HttpServletRequest request,Model model) {
        if(session==null||session.getAttribute("loggedId")==null) {
            model.addAttribute("login", new Login());
            return "loginForm";
        }
        String dataDir=request.getServletContext().getRealPath("/WEB-INF/file");
        Path path=Paths.get(dataDir, "1.txt");
        if(Files.exists(path)) {
            response.setContentType("application/octet-stream");
            response.addHeader("Content-Disposition", "attachment;filename=1.text");
            try {
                Files.copy(path, response.getOutputStream());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

先通过session判断是否登录成功,如果没有就去登录,登录用户就通过NIO下载。设置ContentType->设置Content-Desposition这个响应头->调用Files.copy()方法下载

防止交叉引用

有人可以你网站中静态资源的链接放在他的网站上,好像这些资源原本就属于他的一样。如果通过编程控制,使得只有referer的值与你的域名相同时才放出资源,就可以防止这种行为发生。当然,有些聪明的窃贼仍有办法下载你的东西,但是绝不会像从前那样不费吹灰之力就能办到。

只要是页面中请求服务器就会带上Referer这个请求头,我们直接拿到就可以:

 

posted @ 2018-01-29 17:52  OverZeal  阅读(306)  评论(0编辑  收藏  举报