[理论知识]
我们在软件开发工作中,会经常遇到需要用户上传文件的情况,比如上传头像、上传商品图片等等。首先我们要了解文件上传的技术原理,上传文件并非是将文件直接从客户端传送到服务器端,网络中传递的数据都是基于字符的,客户端首先将用户选择的文件中的字符读取出来,将字符传递给服务器,服务器再将收到的字符写入到服务器中的指定文件,以这样的方式实现了文件的上传功能。
因此我们知道,上传文件一般使用POST方式,因为GET方式传输数据有大小限制,另外上传数据的编码格式需要使用multipart/form-data,不能使用form表单默认的application/x-www-form-urlencoded。
[步骤解读一]上传文件的POST报文
本文中,小博老师将为大家演示如何实现文件上传的功能。首先我们创建一个jsp文件,提供上传文件的表单,核心代码如下:
<form action="BWFUpload" method="POST" enctype="multipart/form-data">
用户名称:<input type="text" name="nickName"/><br/><br/>
上传图片:<input type="file" name="upload"/><br/><br/>
<input type="submit" value="开始上传"/>
</form>
我们看到form表单的enctype属性,这个属性时encoding type的缩写,表示表单中数据提交时的编码格式,这个属性的值我们必须要修改成multipart/form-data才能实现文件上传的功能,但是此属性一旦修改,就会改变我们获取表单提交数据的方式,比如我们创建Servlet先来获取文本框中的提交数据:
@WebServlet("/BWFUpload")
public class BWFUploadServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
response.setHeader("Content-Type", "text/html; charset=UTF-8");
PrintWriter out = response.getWriter();
String nickName = request.getParameter("nickName");
out.println( nickName );
}
}
通过浏览器我们提交文本框数据,会发现我们再使用request.getParameter(String key)的方式,已经无法获取表单提交的数据了:
form表单的enctype属性值修改为multipart/form-data后,form表单就不会对控件中数据进行编码了,而是将整个form表单中的数据,生成post报文提交给服务器。我们修改Servlet中的代码,输出post报文给大家看一下,核心代码如下:
@WebServlet("/BWFUpload")
public class BWFUploadServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
response.setHeader("Content-Type", "text/html; charset=UTF-8");
PrintWriter out = response.getWriter();
// 通过request对象创建输入字节流
InputStream is = request.getInputStream();
// 准备StringBuilder拼接post报文
StringBuilder builder = new StringBuilder();
int i; // 准备int变量,获取每次读取到的字节
// 循环从字节输入流中读取数据
while( ( i = is.read() ) != -1 ){
builder.append( (char)i );
}
is.close(); // 关闭流
// 输出post报文
out.println(builder.toString());
}
}
我们通过浏览器访问jsp页面,在表单中填写文本信息和上传文件:
提交表单给Servlet后,post报文如下:
仔细观察post报文,我们会发现其中包含了我们上传的文本信息,以及上传文件的基础信息(name="upload"; filename="bwf_logo.png" Content-Type: image/png ‰PNG),以及上传的文件的内容(内容过大,这里不复制了),在这样的情况下,如果我们需要自己从post报文中截取需要的信息,开发效率会非常低下,小博老师强烈建议使用现成的第三方插件来完成后续任务。
[步骤解读二]JspSmartUpload插件使用
我们可以在网上下载一个第三方插件-JspSmartUpload,将jar文件放入项目lib文件夹中即可。我们修改Servlet,使用JspSmartUpload插件先获取文本框中信息,核心代码如下:
@WebServlet("/BWFUpload")
public class BWFUploadServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
response.setHeader("Content-Type", "text/html; charset=UTF-8");
PrintWriter out = response.getWriter();
// 实例化 SmartUpload 对象
SmartUpload upload = new SmartUpload();
// 初始化参数
upload.initialize(getServletConfig(), request, response);
try{
// 解读post报文,将上传文件生成临时文件,其他普通参数封装成map
upload.upload();
// 将解读post报文后拆分的数据,重新封装成SmartUpload包中的Request对象
// 注意此处的Request对象是com.jspsmart.upload.Request而非HttpServletRequest
// 但是两者的使用方法基本一致
Request req = upload.getRequest();
// 从 新封装的 Request对象中获取文本信息
String nickName = req.getParameter("nickName");
out.println(nickName);
}catch(Exception e){
e.printStackTrace();
}
}
}
用浏览器访问jsp页面,在表单中填写文本信息提交:
提交给Servlet后,文本信息获取成功:
[步骤解读三]JspSmartUpload插件实现文件上传
接下来,我们修改Servlet中的代码,实现文件上传功能,核心代码如下:
@WebServlet("/BWFUpload")
public class BWFUploadServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
response.setHeader("Content-Type", "text/html; charset=UTF-8");
PrintWriter out = response.getWriter();
// 实例化 SmartUpload 对象
SmartUpload upload = new SmartUpload();
// 初始化参数
upload.initialize(getServletConfig(), request, response);
try{
// 解读post报文,将上传文件生成临时文件,其他普通参数封装成map
upload.upload();
// 获取所有上传文件在服务器存放的临时文件
Files files = upload.getFiles();
// 循环每一个临时文件
for( int i = 0 ; i <= files.getCount() - 1; i++ ){
// 获取当前循环到的临时文件
File file = files.getFile(i);
// 为文件生成新的文件名
String filename = UUID.randomUUID().toString()+file.getFileName().substring( file.getFileName().lastIndexOf(".") );
// 将当前内循环到的临时文件 另存到指定目标位置
file.saveAs( getServletContext().getRealPath( "uploads/"+filename ) );
}
}catch(Exception e){
e.printStackTrace();
}
}
}
上传文件功能实现: