java spring文件下载_使用 Spring MVC 实现文件下载

本文是为解答同道中人的问题而整理,同道中人可以任意传播,唯独期望在 CxDN 上全文照抄时,注明原文出处

文件下载是Web环境下常有的操作,如果借助于 Servlet 来实现,那就没有必要再使用 Spring MVC 这个框架了。

总是看到好多文章说是用 Spring MVC 实现文件下载,但总是在用 Servlet API 完成所有的下载操作,

典型的挂着 Spring MVC 的“羊头”卖着 Servlet 的“狗肉”。

本文基于这个现状,简单介绍一下纯粹以 Spring MVC 的方式实现文件下载操作( 关注 Servlet API 但不使用 Servlet API )。

1、创建一个maven-archetype-webapp工程

首先在 IDEA 中创建一个 maven-webapp 工程,并修改 pom.xml 文件

1.1、 修改 properties 部分的内容

参看如下代码进行修改:

UTF-8

1.8

1.8

5.0.8.RELEASE

1.2、 添加依赖

这里仅仅引入 Servlet 、JSP 、JSTL 、Spring Core 、Spring Cotnext 、Spring MVC 这几个部分的依赖。

javax.servlet

javax.servlet-api

3.1.0

provided

javax.servlet.jsp

javax.servlet.jsp-api

2.3.1

provided

javax.servlet.jsp.jstl

jstl

1.2

org.springframework

spring-core

${spring.version}

org.springframework

spring-context

${spring.version}

org.springframework

spring-webmvc

${spring.version}

1.3、修改 build 部分的内容

参看以下内容进行修改

${project.name}

org.apache.maven.plugins

maven-resources-plugin

2.7

UTF-8

org.apache.maven.plugins

maven-compiler-plugin

3.5.1

1.8

1.8

UTF-8

org.eclipse.jetty

jetty-maven-plugin

9.4.9.v20180320

8080

true

/

jetty.xml

org.apache.tomcat.maven

tomcat7-maven-plugin

2.2

8080

/

UTF-8

true

1.4、修改 /WEB-INF/web.xml

因为使用 maven-archetype-webapp 产生的 web.xml 中使用的 Servlet 版本奇低(居然还是 2.4 ),所以我们需要修改这个文件以便于支持新版本的 Servlet 。

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">

2、创建 Spring 配置文件

2.1、创建 Spring 根容器的配置文件

在 工程的 resources 目录下创建一个名称为 beans.xml 的 Spring 配置文件,其内容暂时如下所示:

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

2.2、创建 Spring MVC 配置文件

在 工程的 resources 目录下创建一个名称为 mvc.xml 的 配置文件 ,其内容暂定如下:

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:context="http://www.springframework.org/schema/context"

xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd

http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

3、在 web.xml 中添加对 Spring 的支持

在 WEB-INF/web.xml 中添加以下内容:

contextConfigLocation

classpath:beans.xml

org.springframework.web.context.ContextLoaderListener

CharacterEncodingFilter

org.springframework.web.filter.CharacterEncodingFilter

encoding

UTF-8

CharacterEncodingFilter

/*

REQUEST

FORWARD

INCLUDE

ERROR

ASYNC

mvc

org.springframework.web.servlet.DispatcherServlet

contextConfigLocation

classpath:mvc.xml

1

mvc

*.do

4、指定被下载文件的存放目录

找到 2.1 步骤中创建的 beans.xml 文件,并在其中添加以下内容:

这里需要注意,不要每次一提到上传文件,下载文件都要把文件放到当前的 Web 应用内部,其实可以将这些文件放到本地磁盘的任意位置,甚至是网络上某个位置。

所以,我们单独声明了一个名称为 location 的 bean ,并使用 构造注入 的方式 为 其注入 文件存储路径。

在这个 D:/files 就是我们要下载的文件所在的位置。

5、列出文件清单

实际应用中,应该根据实际需要提供下载连接,这里为了简化,直接将 D:/files 目录下所有的文件全部罗列出来。

5.1、开发控制器类

在 io.malajava.controller 包下创建一个 DownloadController 类,并在该类中注入 文件存放路径 ( location ) :

package io.malajava.controller;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Controller;

import org.springframework.web.bind.annotation.RequestMapping;

@Controller

@RequestMapping( "/download/**" )

public class DownloadController {

@Autowired

private String location ; // 注入存放文件的目录

public String getLocation() {

return location;

}

public void setLocation(String location) {

this.location = location;

}

}

5.2、添加返回文件清单的控制器方法

在 DownloadController 类中添加以下方法:

@GetMapping( "/list" )

public String list( Model model ){

// 创建一个表示 文件存放目录 的 File 实例

File storeDirectory = new File( location );

// 列出这个 File 实例对应的目录下所有的 文件 (使用FileFilter实现过滤)

File[] files = storeDirectory.listFiles(new FileFilter() {

@Override

public boolean accept(File fod) {

return fod.isFile(); // 仅当 fod 所表示的是个文件时才被保留

}

});

// 将 数组转换为 List 集合

List fileList = Arrays.asList( files );

// 将 List 集合添加到 Model 中

model.addAttribute( "fileList" , fileList );

return "list" ; // 返回视图名称

}

以上方法中使用了 FileFilter对 location 目录内部的 文件 和 子目录 进行了过滤,因此在最后返回的 File 数组中 仅保留 文件 ,不会包含子目录。

5.3、编写显示文件列表的页面

根据 2.2 步 中 mvc.xml 文件的配置,在 WEB-INF/views 目录下创建一个名称为 list.jsp 的页面 。

之所以这个页面叫 list.jsp 是因为 控制器方法 ( list(Model) ) 中返回的视图名称为 list ( return "list" ; )。

list.jsp 内容如下:

可下载文件列表

h3{ text-align: center ; }

.container { width: 80% ; margin: 15px auto ; }

.container div { line-height: 50px ; margin: 5px ; border:1px solid #dedede ; padding: 5px 5px ; }

没有文件可供下载

${file.name}

下载

在这里,我们使用 JSTL 和 EL 完成了对 fileList 的迭代。

因为 fileList 集合中存放的是 java.io.File 类型的对象,而 File 类中又有 getName 这样的方法返回 文件名称,因此可以使用 EL 表达式 ${ file.name } 来获取文件名称。

在每个文件名称之后,都添加了一个链接,用来实现下载操作:

下载

这个链接对应的 操作 稍后来完成。

5.4、显示文件列表

正常启动 Jetty 容器 或 Tomcat 容器后,在浏览器中输入:

http://localhost:8080/download/list.do

后即可列出文件清单:

dd4dd11d53605ce062d235fa274924f5.png

6、文件下载

6.1、确定文件的MIME类型

根据文件名称中的扩展名来确定其相应的MIME类型的确是一种粗暴的确定MIME类型的方法,但它真的很简单。

这里我们开发一个简单的工具类,并提供一个方法用来返回常见文件扩展名对应的MIME类型。

package io.malajava.helper;

import java.util.HashMap;

import java.util.Map;

public final class MimeHelper {

private static final String DEFAULT_TYPE = "application/octet-stream" ;

// 声明并创建一个用来存放 常见扩展名 和 MIME 类型 对应关系的 Map 集合

private static final Map MIME_TYPES = new HashMap<>();

static {

MIME_TYPES.put( "html" , "text/html" );

MIME_TYPES.put( "css" , "text/css" );

MIME_TYPES.put( "js" , "text/javascript" );

MIME_TYPES.put( "java" , "text/plain" );

MIME_TYPES.put( "jar" , "application/java-archive" );

MIME_TYPES.put( "xls" , "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" );

MIME_TYPES.put( "xlsx" , "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" );

// 如果需要更多格式,请自行添加

}

public static String mimeType( String filename ) {

if( filename == null || filename.trim().isEmpty() ){

throw new RuntimeException( "文件名称不能为空" );

}

// 在 filename 中搜寻最后一个 圆点 符号

int last = filename.lastIndexOf( "." ) ;

// 如果 filename 中没有 圆点 符号 或 圆点符号是最后一个

if( last == -1 || ( last == filename.length() - 1 ) ){

throw new RuntimeException( "文件名称中没有扩展名暂时不能确定文件类型" );

} else {

// 如果 filename 中存在 圆点 并且不是 最后一个符号

String extension = filename.substring( last + 1 );//截取从last+1到末尾的内容,当作文件扩展名

// 根据 文件扩展名 从 MIME_TYPES 中获取响应的 MIME 类型

String mimeType = MIME_TYPES.get( extension );

// 如果 未找到相应的 mime 类型,则返回默认的 mime 类型

return mimeType == null ? DEFAULT_TYPE : mimeType ;

}

}

}

实际上在 Spring 中提供了 org.springframework.http.MediaType 来表示 MIME 类型,我们这里为了简化,没有使用 MediaType 。

6.2、添加实现文件下载的控制器方法

6.2.1、添加方法声明

在 DownloadController 类中添加以下方法(这里仅提供方法声明,具体实现过程随后逐步展开):

@GetMapping( "/down" )

public ResponseEntity down( String filename , @RequestHeader( "user-agent" ) String userAgent ) {

}

注意这个方法的返回类型是 ResponseEntity ,它是 Spring 中用于向客户端返回数据时使用的类型。

6.2.2、ResponseEntity

通过 ResponseEntity 可以确定向客户端发送的数据内容、响应包头、响应状态等信息。(这与HttpServletResponse中的状态、报头、正文对应)

ResponseEntity 类的常用构造方法为:

public ResponseEntity( HttpStatus status )

public ResponseEntity( T body, HttpStatus status )

public ResponseEntity( MultiValueMap headers , HttpStatus status)

public ResponseEntity( T body, MultiValueMap headers, HttpStatus status )

在 ResponseEntity 类的构造中,通过 MultiValueMap 来设置 响应报头,而 MultiValueMap 接口常用的实现类是 HttpHeaders 。

通过 HttpHeaders 类可以设置响应报头( 能在 HttpServletResposne 中设置的这里都可以设置 ) 。

一个通用的设置响应报头的方法是:

public void set( String headerName, String headerValue ) {

比如设置 content-type 为 text/html 可以使用:

headers.set( "content-type" , "text/html;charset=UTF-8" );

6.2.3、content-disposition 报头

在 HttpServletResponse 的 响应报头 中,通过 content-disposition 报头可以"告知"浏览器对所接收到的内容的处理方式

response.setHeader( "content-disposition" , "inline" ) 表示浏览器需要在线显示接收到的内容

response.setHeader( "content-disposition" , "attachment;filename=被保存的文件名称" ) 表示浏览器需要将接收到的内容保存到客户端本地(filename指定了被保存的文件名称)

因此,如果要实现文件下载操作,就必须为响应对象设置 content-disposition 报头。

针对文件下载操作,在 Spring 5.x 中还新增了 ContentDisposition 类,其使用方式如下:

String filename = "被浏览器保存的文件名称" ;

ContentDisposition contentDisposition = ContentDisposition.parse("attachment;filename=" + filename);

headers.setContentDisposition( contentDisposition );

同样的操作在 Spring 4.x 中写作:

String filename = "被浏览器保存的文件名称" ;

headers.set( "content-disposition" , "attachment;filename=" + filename );

6.2.4、读取文件

java.nio.file.Path 接口的实例用来表示 操作系统中的 目录 或 文件 的路径。

通过 java.nio.file.Paths 的 get 方法可以返回 Path 实例:

public static Path get(String first, String... more)

在 get 方法中,第一个参数 first 是路径的第一个组成部分,第二个参数是个可变长参数,表示路径的其它组成部分。

get 方法的多个参数构成一个完整的 路径( Path ) ,比如 D:/files/hello.mp4 可以用以下形式来表示:

Path path = Paths.get( "D:/" , "files" , "hello.mp4" ) ;

或:

Path path = Paths.get( "D:/files/hello.mp4" ) ;

当获取到指定路径对应的 Path 实例后,借助于 java.nio.file.Files 类可以实现针对 Path 所表示的目录或文件的快捷操作:

判断 Path 所表示的路径是否存在

public static boolean exists(Path path, LinkOption... options)

判断 Path 所表示的是否是个 文件

public static boolean isRegularFile(Path path, LinkOption... options)

读取 Path 所表示的文件中的所有字节

public static byte[] readAllBytes(Path path) throws IOException

6.2.5、辅助方法

在 DownloadController 类中添加一个专门针对URL进行解码的方法:

/** 一个专门用来对 URL 进行解码的方法 */

private String decode( String url ) {

try {

return URLDecoder.decode( url , "UTF-8" );

} catch (UnsupportedEncodingException e) {

throw new RuntimeException( e );

}

}

在 DownloadController 类中添加一个专门针对URL进行编码的方法:

/** 一个专门用来对 URL 进行编码的方法 */

private String encode( String url ) {

try {

return URLEncoder.encode( url , "UTF-8" );

} catch (UnsupportedEncodingException e) {

throw new RuntimeException( e );

}

}

在 DownloadController 类中添加一个专门用来读取文件内容的方法:

/** 读取文件中的所有字节 */

public byte[] read( Path path ) {

byte[] bytes = null ;

try {

bytes = Files.readAllBytes( path ); // 读取文件中的所有字节,返回 byte 数组

} catch (IOException e) {

e.printStackTrace();

}

return bytes ;

}

6.2.6 完整地实现 down 方法

@GetMapping( "/down" )

public ResponseEntity down( String filename , @RequestHeader( "user-agent" ) String userAgent ) {

ResponseEntity responseEntity = null ;

filename = this.decode( filename ); // 解码得到文件名称

byte[] bytes = null ;

Path path = Paths.get( location , filename ) ; // 根据解码得到的文件名称获取其响应的 Path 对象

if( Files.isRegularFile( path ) ) { // 判断是否是个文件

bytes = this.read( path );

}

HttpHeaders headers = new HttpHeaders();

String mime = MimeHelper.mimeType( filename );

headers.set( "content-type" , mime );

filename = this.encode( filename );

headers.set( "content-disposition" , "attachment;filename=" + filename );

HttpStatus status = HttpStatus.OK ;

responseEntity = new ResponseEntity<>( bytes , headers , status );

return responseEntity ;

}

OK,至此,基于 Spring MVC 实现的 文件下载功能完成了。

已知存在的问题:

部分浏览器(比如FireFox)下载中文文件时,文件名称可能会存在乱码问题。

这个问题可以通过 参数中的 user-agent 来识别浏览器后再做出处理,随后奉上,今天暂且至此结束。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值