文件上传和下载 文件的上传和下载是日常开发中常见需求及功能,在Servlet3.0以前,官方并没有提供相关的API,如果要实现文件的上传需要借助apache的开源工具common-fileupload,实现文件的上传相对比较麻烦,但是Servlet3.0以后官方提供了文件上传的相关实现,在本章节内将详细了解文件的上传。
MultipartConfig注解 Servlet3.0 提供了MultipartConfig注解,用于配置文件上传的相关属性,例如上传路径,上传文件大小等,查看MultipartConfig源码。
1 2 3 4 5 6 7 8 9 10 11 12 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface MultipartConfig { String location () default "" ; long maxFileSize () default -1L ; long maxRequestSize () default -1L ; int fileSizeThreshold () default 0 ; }
下面对各个属性进行解释:
location:上传文件的临时目录
maxFileSize:上传文件大小的限制,如果超出该限制则抛出异常
maxRequestSize:最大请求限制,如果超出该限制则抛出异常
fileSizeThreshold:设置文件缓存的临界点,超过则先保存到临时目录
需要注意的是在控制文件上传的Servlet上必须要加该注解,否则程序将抛出异常
Part接口 了解了MultipartConfig以后,还需要了解Part类,Part接口的作用是获取文件名,向目录中写入上传文件等,下面先了解一下Part接口中常用的方法:
方法
描述
public void write(String fileName) throws IOException;
将文件写入指定的磁盘位置
public long getSize();
获取上传文件的大小
public String getName();
获取file空间的name属性
public InputStream getInputStream() throws IOException;
获取文件输入流
public String getSubmittedFileName();
获取上传文件的名称
文件上传 了解了MultipartConfig注解和Part接口后,就可以实现文件的上传功能。
单文件上传
新建JSP页面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%> <!DOCTYPE html > <html > <head > <meta charset ="utf-8" > <title > Insert title here</title > </head > <body > <form action ="${pageContext.request.contextPath}/upload" method ="post" enctype ="multipart/form-data" > 文件:<input type ="file" name ="file" > <input type ="submit" value ="提交" > </form > </body > </html >
新建Servlet
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package cn.bytecolleg.controller;import java.io.IOException;import javax.servlet.ServletException;import javax.servlet.annotation.MultipartConfig;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import javax.servlet.http.Part;@WebServlet("/upload") @MultipartConfig(maxFileSize = 1024*1024*50) public class UploadController extends HttpServlet { protected void doGet (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } protected void doPost (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("utf-8" ); response.setContentType("text/html;charset=utf-8" ); Part part = request.getPart("file" ); String path = "D:/upload/" ; String name = part.getSubmittedFileName(); System.out.println(name); part.write(path+name); response.getWriter().write("上传成功" ); } }
在上例中只配置了上传文件的最大限制为50M。如果超出此限制则抛出异常。另外在页面的表单上,一定要添加enctype=”multipart/form-data”属性,否则Servlet将无法获取上传的文件。
多文件上传 通常情况下还会遇到多文件上传,多文件上传和单文件上传的区别有如下两点:
在页面的input需要配置可以选择多文件
在Servlet中接收文件时需要调用request对象getParts()方法,该方法会获取所有上传的文件
JSP页面如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8" %> <!DOCTYPE html> <html> <head> <meta charset="utf-8" > <title>Insert title here</title> </head> <body> <form action="${pageContext.request.contextPath}/upload" method="post" enctype="multipart/form-data" > 文件:<input type="file" name="file" multiple="multiple" > <input type="submit" value="提交" > </form> </body> </html>
Servlet页面如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 package cn.bytecolleg.controller;import java.io.IOException;import java.util.ArrayList;import java.util.Collection;import java.util.List;import javax.servlet.ServletException;import javax.servlet.annotation.MultipartConfig;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import javax.servlet.http.Part;@WebServlet("/upload") @MultipartConfig(maxFileSize = 1024*1024*50) public class UploadController extends HttpServlet { protected void doGet (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } protected void doPost (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("utf-8" ); response.setContentType("text/html;charset=utf-8" ); List<Part> list = new ArrayList <>(); list = (List<Part>) request.getParts(); String path = "D:/upload/" ; for (int i = 0 ; i < list.size(); i++) { Part part = list.get(i); String name = part.getSubmittedFileName(); System.out.println(name); part.write(path+name); } response.getWriter().write("上传成功" ); } }
文件下载 文件下载核心是读取文件,如果文件是在项目的webapp目录下,直接访问对应的链接就可以下载,但是通常情况下都不会把文件放置在webapp目录下,这是因为随着文件的增加,项目部署和启动的时间也会增加,一般做法是将文件放置在硬盘中其他目录下,客户端通过链接访问控制下载的Servlet,有Servlet找到对应的文件,并将文件以流的形式发送给客户端。这么做的优点是减小了项目的体积,缩短启动和部署的时间,其次也可以更好的控制下载速度,防止同一时间下载请求过高导致服务器崩溃。控制下载的核心代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 <%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8" %> <!DOCTYPE html> <html> <head> <meta charset="utf-8" > <title>Insert title here</title> </head> <body> <a href="${pageContext.request.contextPath}/download?filename=a.zip" >a.zip</a> </body> </html>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 package cn.bytecolleg.controller;import java.io.FileInputStream;import java.io.IOException;import java.io.PrintWriter;import javax.servlet.ServletException;import javax.servlet.ServletOutputStream;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;@WebServlet("/download") public class DownloadController extends HttpServlet { private static final long serialVersionUID = 1L ; protected void doGet (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String name = request.getParameter("filename" ); String path = "D:/upload/" ; FileInputStream fis = new FileInputStream (path+name); byte [] b = new byte [1024 ]; response.setHeader("Content-Disposition" ,"attachment;filename=" + URLEncoder.encode(name,"UTF-8" )); ServletOutputStream sos = response.getOutputStream(); int i = 0 ; while ((i=fis.read(b))>-1 ) { sos.write(b); } sos.close(); fis.close(); } protected void doPost (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
在上例的DownServlet中,首先获取了要下载文件的名称,然后在对应的目录中寻找该文件,并使用流读取文件。读取到文件后获取ServletOutputStream对象,使用该对象向客户端输出文件流,需要注意的是输出前必须设置响应头,告诉客户端浏览器保存文件。
Ajax技术 Ajax是Asynchronous JavaScript and XML的缩写,意思是异步的JavaScript与XML。Ajax并不是一门新的语言或技术,它是JavaScript、XML、CSS、DOM等多种已有技术的组合,可以实现客户端的异步请求操作。从而可以实现在不需要刷新页面的情况下与服务器进行通信,减少了用户的等待时间,减轻了服务器和带宽的负担,提供更好的服务响应。
在传统的Web应用模式中,页面中用户的每一次操作都将触发一次返回Web服务器的HTTP 请求,服务器进行相应的处理(获得数据、运行与不同的系统会话)后,返回一个HTML页面给客户端。
而在Ajax应用中,页面中用户的操作将通过Ajax引擎与服务器端进行通信,然后将返回结果提交给客户端页面的Ajax引擎,再由Ajax引擎来决定将这些数据插入到页面的指定位置。
与传统的Web应用不同,Ajax在用户与服务器之间引入一个中间件XMLHttpRequest,该中间件可以在不提交整个页面的前提下进行局部提交,Ajax具有如下几个优点:
减轻服务器的负担。Ajax的原则是“按需求获取数据”,这可以最大程度地减少冗余请求和响应对服务器造成的负担。
可以把一部分以前由服务器负担的工作转移到客户端,利用客户端闲置的资源进行处理,减轻服务器和带宽的负担,节约空间和成本。
无刷新更新页面,Ajax使用XMLHttpRequest对象发送请求并得到服务器响应,在不需要重新载入整个页面的情况下,就可以通过DOM及时将更新的内容显示在页面上。
XMLHttpRequest对象 Ajax是XMLHttpRequest对象和JavaScript、XML、CSS、DOM等多种技术的组合。
Ajax使用的技术中,最核心的技术就是XMLHttpRequest,它是一个具有应用程序接口的JavaScript对象,能够使用超文本传输协议(HTTP)连接一个服务器,是微软公司为了满足开发者的需要,于1999年在IE 5.0浏览器中率先推出的。通过XMLHttpRequest对象,Ajax可以像桌面应用程序一样只同服务器进行数据层面的交换,而不用每次都刷新页面,也不用每次都将数据处理的工作交给服务器来完成,这样既减轻了服务器负担又加快了响应速度、缩短了用户等待的时间。
初始化XMLHttpRequest对象 在使用XMLHttpRequest对象发送请求和处理响应之前,首先需要初始化该对象,由于XMLHttpRequest不是一个W3C标准,所以对于不同的浏览器,初始化的方法也是不同的。通常情况下,初始化XMLHttpRequest对象只需要考虑两种情况,一种是IE浏览器,另一种是非IE浏览器
IE浏览器创建XMLHttpRequest对象代码如下:
1 var xhr = new ActiveXObject ("Mircrosoft.XMLHTTP" );
非IE浏览器创建XMLHttpRequest对象代码如下:
1 var xhr = new XMLHttpRequest ();
为了提高程序的兼容性,可以创建一个跨浏览器的XMLHttpRequest对象。创建一个跨浏览器的XMLHttpRequest对象其实很简单,只需要判断一下不同浏览器的实现方式,如果浏览器提供了XMLHttpRequest类,则直接创建一个实例,否则实例化一个ActiveX对象,具体代码如下:
1 2 3 4 5 6 var xhr = null ;if (window .XMLHttpRequest ){ xhr = new XMLHttpRequest (); }else if (window .ActiveXObject ){ xhr = new ActiveXObject ("Mircrosoft.XMLHTTP" ); }
在上面的代码中,调用window.ActiveXObject将返回一个对象或是null,在if语句中,会把返回值看做true或false(如果返回的是一个对象,则为true,如果返回null,则为false)。
XMLHttpRequest对象的常用方法 XMLHttpRequest对象提供了一些常用的方法,通过这些方法可以对请求进行操作。下面对XMLHttpRequest对象的常用方法进行介绍。
open()方法:用于设置进行异步请求目标的URL、请求方法以及其他参数信息。
open()方法的参数说明如表:
参数
说明
method
用于指定请求的类型,一般为GET或者POST
URL
用于指定请求地址,可以传递参数
asyncFlage
可选参数,用于指定是否异步请求,默认情况下位true
username
可选参数,用于指定请求用户名,可省略
password
可选参数,用于指定请求密码,可省略
send(content)方法:用于向服务器发送请求,如果请求声明为异步,该方法将立即返回,否则将等到接受响应为止,用于指定发送的数据,可以是DOM对象的实例、输入流或字符串。如果没有参数需要传递,可以设置为null。
setRequestHeader()方法:用于为请求的HTTP头设置值。setRequestHeader()方法的具体语法格式如下:
1 setRequestHeader("header","value")
header:用于指定HTTP头。value:用于为指定的HTTP头设置值。setRequestHeader()方法必须在调用open()方法之后才能调用。例如,在发送POST请求时,需要设置Content-Type请求头的值为“application/x-www-form-urlencoded”,这时就可以通过setRequestHeader()方法进行设置。
XMLHttpRequest对象的常用属性 XMLHttpRequest对象提供了一些常用属性,通过这些属性可以获取服务器的响应状态及响应内容,下面将对XMLHttpRequest对象的常用属性进行介绍。
onreadystatechange属性:onreadystatechange属性用于指定状态改变时所触发的事件处理器。在Ajax中,每个状态改变时都会触发这个事件处理器,通常会调用一个JavaScript函数。
readyState属性:属性用于获取请求的状态。该属性共包括5个属性值,如表所示。
值
含义
0
未初始化
1
正在加载
2
已加载
3
交互中
4
完成
responseText属性:responseText属性用于获取服务器的响应,表示为字符串
responseXML属性:responseXML属性用于获取服务器的响应,表示为XML。这个对象可以解析为一个DOM对象。
status属性:status属性用于返回服务器的HTTP状态码,常用的状态码如表所示。
值
含义
200
表示成功
202
表示请求被接受,但尚未成功
400
错误的请求
404
文件未找到
500
内部服务器错误
Ajax实战 Ajax可以通过XMLHttpRequest对象实现采用异步方式在后台发送请求。通常情况下,Ajax发送请求有两种,一种是发送GET请求,另一种是发送POST请求。但是无论发送哪种请求,都需要经过以下5个步骤。
初始化XMLHttpRequest对象。
创建一个与服务器的连接
为XMLHttpRequest 对象指定一个返回结果处理函数
向服务器发送请求。
解析响应结果。
例如注册的业务时需要检测用户名是否已经被注册,在Ajax出现之前,必须将表单提交以后将用户填写的用户名发送的服务端,服务端检测结束后再将结果返回给客户端,这个过程是一个很糟糕的体验,假如填写的信息比较多,信息提交后如果用户名已经被注册,服务器返回给客户端结果后,之前填写的数据都将重新填写。当Ajax技术出现后可以在不提交表单的前提下进行局部提交,并且不会出现提交表单时页面的闪白。下面就检查用户名是否重复进行示范:
客户端 register.html页面如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>注册页面</title> </head> <body> <form action="" method="post" > <input id="username" name="username" type="text" onblur="check()" placeholder="用户名" ><br> <input name="password" type="password" placeholder="密码" ><br> <input type="submit" value="注册" > </form> </body> </html> <script> function check () { var username = document.getElementById("username" ).value; var xhr = new XMLHttpRequest (); xhr.open("post" ,"${pageContext.request.contextPath}/check" ); xhr.setRequestHeader("Content-type" , "application/x-www-form-urlencoded" ); xhr.send("username=" +username); xhr.onreadystatechange = function (){ if (xhr.readyState==4 ){ if (xhr.status==200 ){ alert(xhr.responseText) } } } } </script>
在页面中定义了一个表单,用于填写用户注册的用户名和密码,并且在填写用户名的input上绑定了事件,当该input失去焦点是就会触发该事件,并调用check函数。
在check函数中首次创建了XMLHttpRequest对象,使用open函数设置了请求方式和请求地址,因为请求方式是post。因此还需要设置请求头(get请求则不需要),接着讲获取到的用户名使用send函数发送至服务器。
在代码①处,当XMLHttpRequest的readyState状态发生变化时都会调用后面的函数,在函数内判断XMLHttpRequest的状态以及服务器响应状态,当readyState值为4并且服务器成功响应后弹出服务端响应的内容。
服务端 服务端验证用户名的Servlet代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 package cn.bytecollege.controller;import javax.servlet.ServletException;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;import java.io.PrintWriter;@WebServlet("/check") public class CheckUsername extends HttpServlet { @Override protected void doGet (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request,response); } @Override protected void doPost (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("utf-8" ); response.setContentType("text/html;charset=utf-8" ); String username = request.getParameter("username" ); PrintWriter writer = response.getWriter(); if (username.equals("admin" )){ writer.write("post用户名重复" ); }else { writer.write("post用户名可用" ); } } }
服务端在接收到请求以后,先获取用户名,获取到用户名以后模拟从数据库查询数据,如果用户名是admin向客户度响应用户名重复。否则响应用户名可用。