目录
- 前言
- 案例分析
- 核心思路拆解
- 案例所涉及知识点
- 创建Springmvc项目
- 项目创建
- 目录介绍
- 单文件上传
- 前端设计
- 服务端设计
- 运行测试
- 多文件上传
- 前端设计
- 服务端设计
- 运行测试
- 文件下载
- 直接访问资源VS下载资源
- 前端设计
- 服务端设计
- 运行测试
- 总结与拓展
- 总结
- 案例拓展
前言
大家好,我是bigsai,今天我们学习Springmvc的文件上传下载。
文件上传和下载是互联网web应用非常重要的组成部分,它是信息交互传输的重要渠道之一。你可能经常在网页上传下载文件,你可能也曾沉浸于互联网技术的神秘,而本篇就为你解开它神秘的面纱。
公众号:
bigsai
中(微信搜索bigsai
)
案例分析
你肯定会问:通过本篇可能能够学到什么?
那我很负责任的告诉你,通过本篇文章,你能够掌握Springmvc文件上传(单文件、多文件)文件下载知识和内容的使用,并能够根据这些实现一些基本的案例。
核心思路拆解
你可能会问:,这么一个完整的项目是如何分工运行?
不急不急,我来告诉你,其实这么一个文件上传下载的项目,它是一个b-s结构的web项目,涉及到前端和服务端,从宏观来看它是这样的一个结构:
但是从文件上传、下载两个功能来看它们之间又是有所区别的,文件上传的主要核心是用户上传的文件服务端接受存储:
而文件下载更重要的部分是用户请求之后服务端给用户返回二进制文件:
所以文件上传和文件下载的项目大体结构相似,只是各个部分在具体实现上有差别,我们需要更多关注下文件上传和下载服务端的实现和区别。
案例所涉及知识点
在本案例中,用到了以下知识点:
html页面form表单:
在前端无论是html还是jsp等模板引擎编写上传的页面时候。<form>
标签就意为一个(文件)上传的表单。
- 表单能够包含若干 input 标签,而input标签又有不同类型比如文本字段、复选框、单选框、文件等等。
- 我们通常使用表单编写若干标签代表我们想要向服务端发送的数据,然后通过标签的按钮将数据请求提交至服务端。
- 表单的method表示请求的类型(一般为post),action表示需要请求的url地址,enctype表示传输数据类型。
Springmvc:
案例的文件上传和下载基于Springmvc,而我们在Springboot项目中整合Springmvc。
- 本案例使用Springmvc作为项目mvc架构的框架,将模型(Model),视图(View),控制器(Controller)分离降低项目的耦合性。
- 本案例使用Springmvc的MultipartFile接口和ResponseEntity接口实现文件上传和下载。
创建Springmvc项目
Springmvc为一个mvc架构的web框架,创建Springmvc项目的方式有很多,你可以选择直接通过IDEA创建Springmvc项目,也可以通过Maven方式创建web项目然后添加Springmvc的依赖,但这两种方式有太多的配置还需要配置tomcat,在效果一致的情况下咱们尽量简化一些开发配置类的工作,所以不采用以上两种方式创建项目。
而Springboot简化了Spring项目的开发,开箱即用,且内嵌tomcat,所以咱们选择创建基于Springboot且整合Springmvc的项目方便快捷,更能直奔主题进行操作。
项目创建
首先,打开IDEA,创建项目,选择Spring Initializr类型初始化点击next。
然后你会得到一个选择项目名和一些配置的页面,我们在Group中填写com,而Artifact咱们填写fileupload。点击next。
接着在选择对应模块依赖的时候,选择Spring web 模块,此模块就是包含Springmvc的web模块
接着选择需要创建项目的地址目录,点击next
这样你就可以得到一个完整的包含web模块(Springmvc)的Springboot项目,就可以在里面编写咱们项目的代码。
目录介绍
上面创建完的基于Springboot的Springmvc项目,默认有若干文件和文件夹,不同文件和文件夹有着不同的职责:
-
java
:用来编写java服务端相关代码,例如Controller,Dao,Service等。 -
application.properties
: 编写一些项目和框架的配置内容以及和第三方框架整合配置等 -
static
: 静态资源目录,用来存放html、JavaScript、图片等资源。 -
teamplates
:用来编写Thymeleaf等模板引擎,这里不使用 -
pom.xml
:编写maven项目jar包资源依赖。如果项目需要引入其他依赖或者修改打包方式可以进行修改。
对于web项目的文件上传,需要进行一定配置以满足我们的使用需求,我们在application.propertis进行以下配置:
# 允许项目中文件上传
spring.servlet.multipart.enabled=true
# 上传文件的临时目录 (一般情况下不用特意修改)
#spring.servlet.multipart.location=
# 上传文件最大为 1M (默认值 1M 根据自身业务自行控制即可)
spring.servlet.multipart.max-file-size=104857600
# 上传请求最大为 10M(默认值10M 根据自身业务自行控制即可)
spring.servlet.multipart.max-request-size=104857600
# 文件大小阈值,当大于这个阈值时将写入到磁盘,否则存在内存中,(默认值0 一般情况下不用特意修改)
spring.servlet.multipart.file-size-threshold=0
# 判断是否要延迟解析文件(相当于懒加载,一般情况下不用特意修改)
spring.servlet.multipart.resolve-lazily=false
当然,你对文件有大小等其他要求可以对配置进行自行更改。到这里带有Springmvc环境的项目已经创建完成啦,剩下的只需要编写前端、服务端代码运行测试即可。
单文件上传
下面请跟我实战 Springmvc单文件上传。一个完整的文件上传项目有两部分组成:前端界面和服务端程序。
前端设计
对于前端页面,我们使用你一定熟悉的html而不选用其他模板引擎。而form表单是html文件上传的核心组件,你在使用前需要了解它的一些属性。
表单的enctype属性
上面说了一个表单文件传输的大体流程,你也知道表单有个至关重要的属性:enctype
。而entype值通常有以下三种:
-
application/x-www-form-urlencoded
:默认编码方式,在发送前编码所有字符(默认)使用url编码方式,和get请求有些相似。但这种方式如果发送大量二进制数据效率会比较低。 -
multipart/form-data
:不对字符编码。在使用包含文件上传控件的表单时,必须使用该值。通常用来向服务端发送二进制数据,而我们的文件也主要以二进制的方式进行传输。 -
text/plain
:空格转换为 “+” 加号,但不对特殊字符编码。
所以本单文件上传案例中,需要注意以下事项:
- 表单的enctype要为multipart/form-data类型,表示二进制传输。
- 在一个form表单内定义一个input为file属性的标签,代表文件上传。
- form表单的method需要为post。
- enctype要为multipart/form-data类型,表示二进制传输。
前端页面的规则了解之后你在static下创建一个index1.html文件,里面具体的代码内容为:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>单文件上传</title>
</head>
<body>
<h2>单文件上传</h2>
<form action="onfile" method="post" enctype='multipart/form-data'>
<input type="file" name="file" ><br>
<input type="submit" value="提交">
</form>
</body>
</html>
其中action="onfile"
代表的为请求地址为onfile,这里都在项目内所以用相对地址即可,如果上传为其他接口也可填写对应的绝对地址。这样前端页面就编写完成,我们还需要编写文件上传对应服务端模块。
服务端设计
服务端主要负责文件接受,在前端看起来实现文件上传的页面很简单,但实际上在服务端的文件接收并没有那么容易,因为传过来的不光光是这一个(或多个)二进制文件,还附带一些头信息、文件名等等数据。打包过来的数据如果是文本数据解析可能还好,但是二进制文件数据一旦出现一点错误可能得到的整个文件都是损坏的。并且在咱们java web技术栈中文件上传也是有一定发展的历史的:
servlet文件上传(3.0以前)
在servlet3.0以前,文件上传在服务端接收需要使用request.getInputStream()
获取表单的二进制数据,但是在解析时候非常麻烦和复杂,对于文件上传这么一个很基本的模块在接收的时候可能要耗费很大的成本和精力去解决它,并且很多初级攻城狮很可能由于对io
模块陌生无法实现上传文件在服务端的接收。
所以这个时候一些具有责任感的公司、组织就把它们的解析方法贡献出来供大家使用,大家不需了解传输文件底层内容,这些开源的处理方式中,最流行的当属apache
旗下开源的commons-fileupload
和 commons-io
,把两个jar包加入到项目中你直接了解下这个api如何使用即可。有了这两个jar包,简单学习它的api,你就可以在普通的web项目中很容易的实现上传文件的功能!
servlet3.0以后
随着servlet版本更新,设计者可能看到javaweb开发中原生api对文件上传支持不太友好的问题,所以在api对文件上传的支持得到优化,简化了Java Web的开发。在servlet3.0中主要增加Part
这个类用来读取文件数据和信息,在Part中直接将传输文件的名称、头信息、二进制文件分割开,通过简单的api就可以实现文件上传的功能。不需要再添加外部jar包。
Springmvc文件上传
文件上传和下载是web开发常用模块,而Springmvc作为一款优秀的web框架,对很多模块和内容进行更高度的封装和集成,而这么常用的文件上传肯定是少不了的,所以Springmvc的文件上传基于apache
旗下开源的commons-fileupload
和 commons-io
包。将其进行二次集成和封装至Springmvc,将方法和内容封装至MultipartFile
接口让我们使用起来更加方便,能够容易实现单文件、多文件上传。
对于上述各种文件上传服务端实现方式,大致可以通过下图展示:
通过上图你就可明白Springmvc文件上传实现的原理,那么下面你就可以进行大显身手啦!Springmvc处理上传文件很简单,我们需要在java目录下创建一个uploadController.java
创建这么一个控制器,在上面加上@Controller注解。在Controller中编写以下代码:
@PostMapping("onfile")
@ResponseBody
public String onfile(MultipartFile file) throws IOException {
File file1 =new File("F:/fileupload/"+file.getOriginalFilename());//创建file对象
if(!file1.exists())
file1.createNewFile();//在磁盘创建该文件
file.transferTo(file1);//将接受的文件存储
return "sucucess";
}
其中:
- @PostMapping(“onfile”) 的意思为该请求方式为post,且请求的url在项目中的相对地址为onfile
- @ResponseBody指不返回web页面,而是返回字符串或json字符串,在这里我们直接用一个成功单词代表跳转后的界面。
- public String onfile(MultipartFile file) 函数名不重复就行,而MultipartFile file就是Springmvc封装的一个处理文件的接口,其中参数名(这里是file)要和前端界面文件名相同(input type=“file”,name="file"中的name),通过这个接口你可以更容易的对文件进行各种操作,而本案例就是将上传的文件保存到本地F盘。
对于函数中的几行核心代码各司其职,除了注释的解释外,大致的流程可以参考如下图:
运行测试
这样启动项目,在浏览器输入http://localhost:8080/index1.html
,选择文件上传,点击上传之后就可以在本地看到上传的文件啦。
至此,单文件上传就完成啦,单文件上传前端需要注意的就是form表单的method类型以及 enctype参数,而服务端也只需要用MultipartFile 接口就可以很容易的对文件进行接受。
多文件上传
上面讲的是单文件上传,很多时候你可能遇到的需求不光光是单文件上传。就比如你一定熟悉这个页面:
如上你可以看到,这么一次文件上传不止一个图片,并且数量也不确定,但都属于同一名称和集合的内容。这就是多文件上传。对于这种情况无论在前端还是服务端也是很容易处理的。
前端设计
我们这里实现一个多张图片的上传,首先在static目录下创建一个index2.html的页面。里面的具体内容为:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>多文件上传</title>
</head>
<body>
<h2>同一类别多个文件上传</h2>
<form name="onfile" action="onfiles2" method="post" enctype="multipart/form-data">
图片:
<input type="file" name="img"><br>
<input type="file" name="img"><br>
<input type="file" name="img"><br>
<input type="file" name="img"><br>
<input type="submit" value="提交">
</form>
</body>
</html>
这样前端页面就编写完成,其中action要改为onfiles2,也就是待会要在服务端编写的接口。还有注意的这些input 所有type为file代指类型为文件,而name均为img意思是上传一组名称为img图片的集合。
服务端设计
而在我们服务端,其实用MultipartFile[]数组就可以对这样的多文件进行接收,我们在controller中编写以下代码:
@PostMapping("onfiles2")
@ResponseBody
public String onfiles2(MultipartFile img[]) throws IOException {
for(int i=0;i<img.length;i++)
{
if(!img[i].isEmpty())//文件不空
{
File imgfile =new File("F:/fileupload/"+img[i].getOriginalFilename());
imgfile.createNewFile();
img[i].transferTo(imgfile);
logger.info(img[i].getOriginalFilename());
}
}
return "sucucess";
}
这个处理方式和前面的很相似,只不过是需要遍历MultipartFile[]
对每个文件进行接收处理,当然文件为空的时候不进行处理。
运行测试
这样打开浏览器输入:http://localhost:8080/index2.html
,上传文件测试效果:
这样一组类似相册上传的功能就完成啦,当然实际开发中的文件上传的要求肯定比这个要求严格很多,可能对文件的格式、大小都有一定的要求,这就要求你在前端和服务端都要对文件的后缀名、大小等信息进行校验,以达到自己场景化的需求。
文件下载
文件下载估计你在日常生活中会经常遇到,而你下载的其实就是服务端(服务器)的资源,对于文件类型有多种多样的,浏览器也能够识别很多种资源,事实上你现在访问的这个网页也是服务端的html文件、图片文件等资源,只不过这些资源浏览器能够显示而不会保存到本地。
直接访问资源VS下载资源
如果直接访问的资源是浏览器所不能识别解析的,例如doc、zip等类型文件,那访问的时候会默认下载到本地。而当你在Springmvc中使用下载功能时,无论是什么资源都以下载的形式返回给客户端。这种区别可以参考下图:
在文件下载方面的实现,servlet本身也是实现文件下载的,不过使用起来有点繁琐。其原理就是往HttpServletResponse response
的输出流写字节内容。而我们Springmvc对文件下载也做了封装,将下载功能封装至ResponseEntity
类中,我们在使用的时候也很方便。下面就来实战文件下载的功能。
首先,我们在F盘建立download文件夹,在里面添加对应文件,这个文件夹我们作为服务端的资源。
前端设计
我们在创建一个文件下载的前端页面,在static目录下创建index3.html
,页面的具体内容为:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Springmvc文件下载</title>
</head>
<body>
<h2>Springmvc文件下载</h2>
个人照片<a href="/download/个人照片.png">个人照片.png</a><br>
个人简历<a href="/download/个人简历.pdf">个人简历.pdf</a>
</body>
</html>
其中href是下载的超链接,download是下载的接口名,而链接最后面部分则是下载资源名称。
服务端设计
文件下载的原理就是服务端向客户端返回二进制流和信息,而Springmvc通过ResponseEntity完成。我们在controller中编写以下接口实现下载的功能:
@GetMapping("download/{filename}")
public ResponseEntity<byte[]>download(@PathVariable String filename) throws IOException {
//下载文件的路径(这里绝对路径)
String filepath= "F:/download/"+filename;
File file =new File(filepath);
//创建字节输入流,这里不实用Buffer类
InputStream in = new FileInputStream(file);
//available:获取输入流所读取的文件的最大字节数
byte[] body = new byte[in.available()];
//把字节读取到数组中
in.read(body);
//设置请求头
MultiValueMap<String, String> headers = new HttpHeaders();
headers.add("Content-Disposition", "attchement;filename=" + file.getName());
//设置响应状态
HttpStatus statusCode = HttpStatus.OK;
in.close();
ResponseEntity<byte[]> entity = new ResponseEntity<byte[]>(body, headers, statusCode);
return entity;//返回
}
这样就是实现了文件下载功能,如果用传统servlet的方式下载文件可能需要在HttpServletResponse response中设置各种信息,而使用Springmvc的ResponseEntity只需要将文件二进制主体、头信息以及状态码设置好即可进行文件下载,在易用性和简洁上更胜一筹。
运行测试
打开浏览器输入:http://localhost:8080/index3.html
;点击需要下载的文件,就实现了文件下载的功能,运行情况图如下:
此时你就遇到了一个文件下载非常常见的问题:中文文件名错误显示。这个解决方案也很容易解决,只需将Content-Disposition内容后面的文件名进行url编码即可,具体代码为(替换上面对于部分):
headers.add("Content-Disposition", "attchement;filename=" + URLEncoder.encode(file.getName(), "UTF-8"));
这样重启程序,刷新页面再次点击下载的链接,你就会发现文件被成功的下载了:
总结与拓展
至此,Springmvc的单文件上传、多文件上传以及文件下载你已经全部掌握了,是不是满满的成就感想去实现一个自己的小网站并把相关内容放进去?不过Springmvc文件上传下载虽然简单,但你依然需要掌握其原理,学好java中的io文件传输,这样在各种场景的文件传输任务中方能胜任。
总结
前面所讲文件上传,前端就是form表单用<input type="file">
表示客户端要上传文件,而服务端主要使用MultipartFile
或者MultipartFile[]
分别接收单个文件和多个文件。而在存储到本地也仅仅需要在本地磁盘创建对应文件然后MultipartFile
调用transferTo()
方法即可将上传的文件储存。
而文件下载的前端需要一个请求的url链接,服务端需要编写这个链接对应的接口。通过一些名称找到文件在本地真实的位置通过ResponseEntity
即可将二进制文件返回给客户达到文件下载的功能。而ResponseEntity
使用也很简单在创建时候只需要传入二进制主体、头和状态码即可成功返回,而这些Springmvc已进行了很好封装你可以直接使用。
而无论是文件上传、多文件上传还是文件下载,一个完整的案例大致都需要这样一个过程:
- 构思需求和页面大体样式
- 编写前端html页面
- 编写服务端响应的请求
- 启动程序运行测试
在其中过程如果有问题可以根据编译器的错误提示、运行时的错误日志找到根源进行修正,这样完整的案例就可以成功完成啦!
案例拓展
你是否觉得自己掌握的可以了?那好,咱们拓展提升一下,我给你来一个需求:单文件和多文件混合上传
假设小明需要实现一个文件上传功能,小明需要上传一份简历和若干份照片(小于3)。这个项目该如何设计呢?它的计划页面可能是这样的:
我觉得聪明的你一定不会被难住,对于前端界面的html有什么想法呢?
对于种类来说有简历和照片两种文件,对于它们各自来说,简历只有一份,而照片可能有多份。
那么咱们的html页面可以这样设计:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>个人信息上传</title>
</head>
<body>
<h2>个人信息上传</h2>
<form name="onfile" action="infoupload" method="post" enctype="multipart/form-data">
姓名:<input type="text" name="name" ><br>
年龄:<input type="text" name="age"> <br>
图片:<input type="file" name="img">
<input type="file" name="img">
简历:<input type="file" name="resume"><br>
<input type="submit" value="提交">
</form>
</body>
</html>
这里面和前面的单文件上传不同的是有多个input标签,此外action也要改成infoupload
意思是你需要写这么一个接口来处理这个文件上传的内容。在controller中编写以下代码:
private static Logger logger= LoggerFactory.getLogger(uploadController.class);
@PostMapping("infoupload")
@ResponseBody
public String onfile(String name,String age, MultipartFile img[],MultipartFile resume) throws IOException {
logger.info(name);//日志中打印传输的name
logger.info(age);
//接收img[]
for(int i=0;i<img.length;i++)
{
if(!img[i].isEmpty())//文件不空
{
File imgfile =new File("F:/fileupload/"+img[i].getOriginalFilename());
imgfile.createNewFile();
img[i].transferTo(imgfile);
}
}
//接收resume
File resumefile =new File("F:/fileupload/"+resume.getOriginalFilename());
//在磁盘中创建文件,此时文件存在但没有内容
resumefile.createNewFile();
//将接受的文件复制到创建的文件中
resume.transferTo(resumefile);
return "sucucess";
}
这个理解起来其实也很容易,这个和上面主要的区别就是函数中的多参数,其实每一个参数都是要和前端页面的form表单input标签的内容对应(名称一致)。form表单中的file类型在Springmvc的controller中就是对应MultipartFile类型,form表单中的text类型对应controller中的String类型。如果上传单个文件,在服务端就用MultipartFile类型参数接收,如果多文件就用MultipartFile[]进行接收。上传类型和个数根据你自己的需求设计定义。
我们启动程序打开浏览器输入http://localhost:8080/index4.html
选择文件进行上传,然后在本地你可以看到文件成功被保存。
至此,本篇的内容就结束了,本文主要简单讲解了Springmvc中文件上传、多文件上传、文件下载的实现,现在你已熟练掌握。青山不改,绿水长流,我们下期再见!下课!
博主微信公众号:bigsai
一个等你关注而朝思夜暮的程序猿。