第三章 深入Servlet技术
3.1 配置Servlet
- <servlet-name>,<servlet-class>是必须配置的,以便于web容器知道浏览器具体访问的是哪个servlet。
- <init-param>用于初始化参数,在servlet中可使用getServletContext().getInitParam(String paramName)来获取初始化参数值。
- <load-on-startup>配置该servlet加载方式,置1时Tomcat将在启动时便加载该servlet,否在会在第一次请求时加载。
3.2 配置<servlet-mapping>
3.3 response生成图片验证码
//创建BufferedImage对象,设置图片的长度宽度和色彩。 BufferedImage image = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB); OutputStream os = response.getOutputStream(); //取得Graphics对象,用来绘制图片 Graphics g = image.getGraphics(); …… …… …… //释放此图形的上下文以及它使用的所有系统资源,类似于关闭流 g.dispose(); //通过ImageIO对象的write静态方法将图片输出。 ImageIO.write(image, "JPEG", os); os.close();
3.4 读取web.xml参数
上一节中创建一个servlet用来输出图片,当需要输出其它格式时,需要修改源代码。因此这种常量可以写在配置文件中,servlet读取参数即可。
例如:
3.5 上下文参数(context-param)
上一节中在web.xml中配置的初始化参数都是在特定的servlet中配置的,因此只能被此servlet读取。使用<context-param>可配置全局参数。例如:
3.6 资源注射(@Resource)
3.7 注射数据源
3.8 上传客户端
代码如下:
1 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> 2 <html> 3 <head> 4 <title>上传文件</title> 5 <meta http-equiv="keywords" content="keyword1,keyword2,keyword3"> 6 <meta http-equiv="description" content="this is my page"> 7 <meta http-equiv="content-type" content="text/html; charset=UTF-8"> 8 <link rel="stylesheet" type="text/css" href="css/style.css"> 9 </head> 10 11 <body> 12 <form action="servlet/UploadServlet" method="post" enctype="multipart/form-data"> 13 <div align="center"><br/> 14 <fieldset style="width:90%"> 15 <legend>上传文件</legend><br/> 16 <div class='line'> 17 <div align='left' class="leftDiv">上传文件一</div> 18 <div align='left' class="rightDiv"> 19 <input type="file" name="file1" class="text"> 20 </div> 21 </div> 22 <div class='line'> 23 <div align='left' class="leftDiv">上传文件二</div> 24 <div align='left' class="rightDiv"> 25 <input type="file" name="file2" class="text"> 26 </div> 27 </div> 28 <div class='line'> 29 <div align='left' class="leftDiv">上传文件说明一</div> 30 <div align='left' class="rightDiv"><input type="text" name="description1" class="text"></div> 31 </div> 32 <div class='line'> 33 <div align='left' class="leftDiv">上传文件说明二</div> 34 <div align='left' class="rightDiv"><input type="text" name="description2" class="text"></div> 35 </div> 36 <div class='line'> 37 <div align='left' class="leftDiv"></div> 38 <div align='left' class="rightDiv"><br/> 39 <input type="submit" value=" 上传文件 " class="button"> 40 </div> 41 </div> 42 </fieldset> 43 </div> 44 </form> 45 </body> 46 </html> 47 48 49 注意:上传文件必须设置method="post" enctype="multipart/form-data"
3.9 上传服务器
上一节中,通过html界面向服务器传送两个文件,和两个文件说明。这一节,将会对收到的请求进行解析,获取文件和文件说明。这里需要使用org.apache.commons.fileupload包。
代码如下:
1 File file1 = null, file2 = null;//声明文件流 2 String description1 = null, description2 = null; 3 4 // 使用 DiskFileUpload 对象解析 request 5 DiskFileUpload diskFileUpload = new DiskFileUpload(); 6 List<FileItem> list = diskFileUpload.parseRequest(request); 7 8 for(FileItem fileItem : list){ //对list中的所有FileItem进行遍历,并判断其类型 9 if(fileItem.isFormField()){ //判断一个参数域是普通的表单输入域,还是文件上传域,如果该方法返回真的话,则是前者,如果为假,则是后者。 10 // 如果是 文本域 11 if("description1".equals(fileItem.getFieldName())){ 12 // 如果该 FileItem 名称为 description1 13 out.println("遍历到 description1 ... <br/>"); 14 description1 = new String(fileItem.getString().getBytes(), "UTF-8"); 15 } 16 if("description2".equals(fileItem.getFieldName())){ 17 // 如果该 FileItem 名称为 description2 18 out.println("遍历到 description2 ... <br/>"); 19 description2 = new String(fileItem.getString().getBytes(), "UTF-8"); 20 } 21 } 22 else{ 23 // 否则,为文件域 24 if("file1".equals(fileItem.getFieldName())){ 25 // 服务器端文件,放在 attachment文件夹下 26 file1 = new File(this.getServletContext().getRealPath("attachment"), remoteFile.getName());//第二个参数为子目录名 27 file1.getParentFile().mkdirs(); 28 file1.createNewFile(); 29 30 // 写文件,将 FileItem 的文件内容写到文件中 31 InputStream ins = fileItem.getInputStream(); 32 OutputStream ous = new FileOutputStream(file1); 33 34 try{ 35 byte[] buffer = new byte[1024]; 36 int len = 0; 37 while((len=ins.read(buffer)) > -1) 38 ous.write(buffer, 0, len); 39 out.println("已保存文件" + file1.getAbsolutePath() + "<br/>"); 40 }finally{ 41 ous.close(); 42 ins.close(); 43 } 44 }
思路:
- DiskFileUpload 对象通过parseRequest解析request并存入list集合,存放类型为FileItem
- 遍历list,通过对list中每个元素调用fileItem.isFormField(),返回false则说明该元素是文件
- 通过fileItem.getFieldName()获取该元素的文件名,通过InputStream ins = fileItem.getInputStream();创建输入流,
OutputStream ous = new FileOutputStream(File file1);创建输出流。
将输入流写入到输入流。
3.10 上传进度条
前端:Ajax查询上传进度
后端:写一个监听器(继承ProgressListener,是org.apache.commons.fileupload的一个接口),文件上传过程中,会不断调用这个监听器。
写一个状态类,用于保存上传状态相关数据(比如文件大小,上传时间,上传速度,百分百)
后端代码:
1 public class ProgressUploadServlet extends HttpServlet { 2 3 private static final long serialVersionUID = -4935921396709035718L; 4 5 public void doPost(HttpServletRequest request, HttpServletResponse response) 6 throws ServletException, IOException { 7 8 // 上传状态 9 UploadStatus status = new UploadStatus(); 10 11 // 监听器 12 UploadListener listener = new UploadListener(status); 13 14 // 把 UploadStatus 放到 session 里 15 request.getSession(true).setAttribute("uploadStatus", status); 16 17 // Apache 上传工具 18 ServletFileUpload upload = new ServletFileUpload( 19 new DiskFileItemFactory()); 20 21 // 设置 listener 22 upload.setProgressListener(listener); 23 24 try { 25 List itemList = upload.parseRequest(request); 26 27 for (Iterator it = itemList.iterator(); it.hasNext();) { 28 FileItem item = (FileItem) it.next(); 29 if (item.isFormField()) { 30 System.out.println("FormField: " + item.getFieldName() 31 + " = " + item.getString()); 32 } else { 33 System.out.println("File: " + item.getName()); 34 35 // 统一 Linux 与 windows 的路径分隔符 36 String fileName = item.getName().replace("/", "\\"); 37 fileName = fileName.substring(fileName.lastIndexOf("\\")); 38 39 File saved = new File("C:\\upload_test", fileName); 40 saved.getParentFile().mkdirs(); 41 42 InputStream ins = item.getInputStream(); 43 OutputStream ous = new FileOutputStream(saved); 44 45 byte[] tmp = new byte[1024]; 46 int len = -1; 47 48 while ((len = ins.read(tmp)) != -1) { 49 ous.write(tmp, 0, len); 50 } 51 52 ous.close(); 53 ins.close(); 54 55 response.getWriter().println("已保存文件:" + saved); 56 } 57 } 58 } catch (Exception e) { 59 e.printStackTrace(); 60 response.getWriter().println("上传发生错误:" + e.getMessage()); 61 } 62 } 63 64 public void doGet(HttpServletRequest request, HttpServletResponse response) 65 throws ServletException, IOException {//Ajax访问该servlet时会调用此函数 66 67 response.setHeader("Cache-Control", "no-store"); 68 response.setHeader("Pragrma", "no-cache"); 69 response.setDateHeader("Expires", 0); 70 71 UploadStatus status = (UploadStatus) request.getSession(true) 72 .getAttribute("uploadStatus"); 73 74 if (status == null) { 75 response.getWriter().println("没有上传信息"); 76 return; 77 } 78 79 long startTime = status.getStartTime(); 80 long currentTime = System.currentTimeMillis(); 81 82 // 已传输的时间 单位:s 83 long time = (currentTime - startTime) / 1000 + 1; 84 85 // 传输速度 单位:byte/s 86 double velocity = ((double) status.getBytesRead()) / (double) time; 87 88 // 估计总时间 单位:s 89 double totalTime = status.getContentLength() / velocity; 90 91 // 估计剩余时间 单位:s 92 double timeLeft = totalTime - time; 93 94 // 已完成的百分比 95 int percent = (int) (100 * (double) status.getBytesRead() / (double) status 96 .getContentLength()); 97 98 // 已完成数 单位:M 99 double length = ((double) status.getBytesRead()) / 1024 / 1024; 100 101 // 总长度 单位:M 102 double totalLength = ((double) status.getContentLength()) / 1024 / 1024; 103 104 // 格式:百分比||已完成数(M)||文件总长度(M)||传输速率(K)||已用时间(s)||估计总时间(s)||估计剩余时间(s)||正在上传第几个文件 105 String value = percent + "||" + length + "||" + totalLength + "||" 106 + velocity + "||" + time + "||" + totalTime + "||" + timeLeft 107 + "||" + status.getItems(); 108 109 response.getWriter().println(value); 110 } 111 112 }
前端代码:
1 <%@ page language="java" contentType="text/html; charset=UTF-8"%> 2 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> 3 <html> 4 <head> 5 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> 6 <title>Insert title here</title> 7 <style type="text/css"> 8 body, td, div {font-size: 12px; font-familly: 宋体; } 9 #progressBar {width: 400px; height: 12px; background: #FFFFFF; border: 1px solid #000000; padding: 1px; } 10 #progressBarItem {width: 30%; height: 100%; background: #FF0000; } 11 </style> 12 </head> 13 14 <body> 15 16 <iframe name=upload_iframe width=0 height=0></iframe> 17 18 <form action="servlet/ProgressUploadServlet" method="post" enctype="multipart/form-data" target="upload_iframe" onsubmit="showStatus(); "> 19 20 <input type="file" name="file1" style="width: 350px; "> <br /> 21 <input type="file" name="file2" style="width: 350px; "> <br /> 22 <input type="file" name="file3" style="width: 350px; "> <br /> 23 <input type="file" name="file4" style="width: 350px; "> <input type="submit" 24 value=" 开始上传 " id="btnSubmit"></form> 25 26 <div id="status" style="display: none; "> 27 上传进度条: 28 <div id="progressBar"><div id="progressBarItem"></div></div> 29 <div id="statusInfo"></div> 30 </div> 31 32 <br/> 33 <br/> 34 <br/> 35 <br/> 36 <br/> 37 38 <script type="text/javascript"> 39 40 var _finished = true; 41 42 function $(obj){ 43 return document.getElementById(obj); 44 } 45 46 function showStatus(){ //主函数 47 _finished = false; 48 $('status').style.display = 'block'; 49 $('progressBarItem').style.width = '1%'; 50 $('btnSubmit').disabled = true; 51 52 setTimeout("requestStatus()", 1000); //每隔1000ms执行一次requestStatus()函数 53 } 54 55 function requestStatus(){ 56 57 if(_finished) return; 58 59 var req = createRequest(); //建立请求 60 61 req.open("GET", "servlet/ProgressUploadServlet"); 62 req.onreadystatechange=function(){callback(req);}// Ajax得到响应进入callback(req)函数 63 req.send(null); 64 65 setTimeout("requestStatus()", 1000); 66 } 67 68 function createRequest() 69 { 70 if(window.XMLHttpRequest)//ns 71 { 72 return new XMLHttpRequest(); 73 }else//IE 74 { 75 try{ 76 return new ActiveXObject("Msxml2.XMLHTTP"); 77 }catch(e){ 78 return new ActiveXObject("Microsoft.XMLHTTP"); 79 } 80 } 81 return null; 82 } 83 function callback(req){ 84 85 if(req.readyState == 4) { 86 if(req.status != 200){ 87 _debug("发生错误。 req.status: " + req.status + ""); 88 return; 89 } 90 91 _debug("status.jsp 返回值:" + req.responseText); 92 93 var ss = req.responseText.split("||"); 94 95 // 格式:百分比||已完成数(M)||文件总长度(M)||传输速率(K)||已用时间(s)||估计总时间(s)||估计剩余时间(s)||正在上传第几个文件 96 $('progressBarItem').style.width = '' + ss[0] + '%'; 97 $('statusInfo').innerHTML = '已完成百分比: ' + ss[0] + '% <br />已完成数(M): ' + ss[1] + '<br/>文件总长度(M): ' + ss[2] + '<br/>传输速率(K): ' + ss[3] + '<br/>已用时间(s): ' + ss[4] + '<br/>估计总时间(s): ' + ss[5] + '<br/>估计剩余时间(s): ' + ss[6] + '<br/>正在上传第几个文件: ' + ss[7]; 98 99 if(ss[1] == ss[2]){ 100 _finished = true; 101 $('statusInfo').innerHTML += "<br/><br/><br/>上传已完成。"; 102 $('btnSubmit').disabled = false; 103 } 104 } 105 } 106 function _debug(obj){ 107 var div = document.createElement("DIV"); 108 div.innerHTML = "[debug]: " + obj; 109 document.body.appendChild(div); 110 } 111 112 </script> 113 114 </body> 115 </html
监听器代码:
1 import org.apache.commons.fileupload.ProgressListener; 2 3 public class UploadListener implements ProgressListener { 4 5 private UploadStatus status; 6 7 public UploadListener(UploadStatus status) { 8 this.status = status; 9 } 10 11 public void update(long bytesRead, long contentLength, int items) { 12 status.setBytesRead(bytesRead); 13 status.setContentLength(contentLength); 14 status.setItems(items); 15 } 16 }
状态类代码:
1 public class UploadStatus { 2 3 private long bytesRead; 4 5 private long contentLength; 6 7 private int items; 8 9 private long startTime = System.currentTimeMillis(); 10 11 public long getBytesRead() { 12 return bytesRead; 13 } 14 15 public void setBytesRead(long bytesRead) { 16 this.bytesRead = bytesRead; 17 } 18 19 public long getContentLength() { 20 return contentLength; 21 } 22 23 public void setContentLength(long contentLength) { 24 this.contentLength = contentLength; 25 } 26 27 public int getItems() { 28 return items; 29 } 30 31 public void setItems(int items) { 32 this.items = items; 33 } 34 35 public long getStartTime() { 36 return startTime; 37 } 38 39 public void setStartTime(long startTime) { 40 this.startTime = startTime; 41 } 42 43 }
3.11 Servlet生命周期
之前在web.xml中配置的参数需要每次都在doGet()或doPost()中获取,效率较低。因此可以在servlet中写入init()函数,在函数中获取web.xml参数。这样,当用户第一次访问该servlet时,会获取初始化参数,而下一次访问servlet时便不再初始化参数,因此提高效率。
3.12 Servlet直接的跳转(Forward)
3.13 Servlet之间的跳转(重定向Redirect)
3.14 Servlet之间的跳转(自动刷新Refresh)
3.15 线程安全
servlet不是线程安全的,因此谨慎使用类的变量,应尽量将变量定义在函数doGet()内部。