1.Listener
1.1 監聽三大作用域創建和銷燬的監聽
package com.hxuner.listener;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
/*
1.Servlet事件監聽器步驟
實現web開發中的事件監聽功能需要兩個步驟:
(1)寫一個類實現對應接口。 implements *****Listener
(2)在將寫好的監聽器類註冊到web.xml中。
<listener>
<listener-class>com.hxuner.listener.MyServletContextListener</listener-class>
</listener>
2.監聽器的生命週期
當web應用啓動後,會加載web.xml中配置的所有監聽器的類,並實例化
加載順序是 listener>filter>servlet(load-on-startup)
3.JavaEE提供監聽器的目的
允許開發者參與到這些對象的生命週期中,添加自己的邏輯
Servlet/Filter 開發者在項目中實際使用的是自己寫的類,最後實例化的是自己寫的類的對象
這種情況下,我們可以通過重寫父類方法init/destory來參與到其生命週期中
但是,ServletContext/HttpSession/ServletRequest 使用的並不是開發者自己寫的類,這種情況下,開發者沒有機會將邏輯添加到init/destory方法中
因此JavaEE設計了對應的監聽器,保證在這些組件的生命週期過程中,調用對應的監聽器的方法,開發者可以提供對應監聽器的實現類,在其中添加需要執行的邏輯,藉此參與到對應的生命週期中
*/
/*
ServletContextListener 監聽器用於監聽 ServletContext 對象的創建和銷燬。
生命週期:在web應用啓動時創建出來 之後一直駐留在內存中唯一的代表當前web應用 直到web應用移除出容易或服務器關閉時 隨着web應用的銷燬ServletContext對象跟着被銷燬
作用範圍:整個web應用範圍
主要功能:在整個web應用範圍內 整個web應用存活期間 共享數據
*/
public class MyServletContextListener implements ServletContextListener {
//當ServletContext對象被創建時,該方法調用
@Override
public void contextInitialized(ServletContextEvent sce) {
//sce.getServletContext()獲取事件中封裝的當前ServletContext對象
ServletContext sc=sce.getServletContext();
System.out.println("監聽聽ServletContext被創建"+sce.getServletContext());//sce.getServletContext()獲取事件中封裝的當前ServletContext對象
//當前創建,馬上向其中添加一個鍵值對
sc.setAttribute("app", sc.getContextPath());// key=app,value=當前web應用的映射路徑
}
//當ServletContext對象被銷燬時,該方法調用
@Override
public void contextDestroyed(ServletContextEvent sce) {
// TODO Auto-generated method stub
System.out.println("監聽聽ServletContext被銷燬"+sce.getServletContext());
}
}
package com.hxuner.listener;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
/*
HttpSessionListener監聽器用於監聽HttpSession對象的創建和銷燬。
生命週期:在第一次調用request.getSession()時創建 自殺 - session.invalidate 超時 - 30分鐘 意外身亡 - 服務器非正常關閉
作用範圍:整個會話
主要功能:在整個會話範圍內 整個會話的生命週期期間 共享數據
鈍化:在服務器正常關閉時 仍然在存活期間的session 會被序列化後保存在tomcat的work目錄下 這個過程稱之爲session的鈍化
活化:在服務器正常啓動時 會將鈍化的session再恢復到內存中 繼續使用 這個過程稱之爲session的活化
*/
public class MyHttpSessionListener implements HttpSessionListener{
//當session對象被創建時調用
@Override
public void sessionCreated(HttpSessionEvent se) {
// TODO Auto-generated method stub
System.out.println("監聽到session對象被創建"+se.getSession());
}
//當session對象被銷燬時調用
@Override
public void sessionDestroyed(HttpSessionEvent se) {
// TODO Auto-generated method stub
System.out.println("監聽到session對象被銷燬"+se.getSession());
}
}
package com.hxuner.listener;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
/*
* ServletRequestListener監聽器用於監聽ServletRequest對象的創建和銷燬。
ServletRequestListener
生命週期:請求開始時創建 請求結束時銷燬
作用範圍:整個請求
主要功能:在請求過程中 在請求範圍內 共享數據
*/
public class MyServletRequestListener implements ServletRequestListener{
@Override
public void requestDestroyed(ServletRequestEvent sre) {
// TODO Auto-generated method stub
System.out.println("監聽到request被創建"+sre.getServletRequest());
}
@Override
public void requestInitialized(ServletRequestEvent sre) {
// TODO Auto-generated method stub
System.out.println("監聽到request被銷燬"+sre.getServletRequest());
}
}
1.2 監聽三大作用域中存入值,修改值和刪除值的監聽
package com.hxuner.listener;
import javax.servlet.ServletRequestAttributeEvent;
import javax.servlet.ServletRequestAttributeListener;
public class zMyServletContextAttributeListener implements ServletRequestAttributeListener {
@Override
public void attributeAdded(ServletRequestAttributeEvent srae) {
// TODO Auto-generated method stub
System.out.println("監聽到request作用域添加了屬性");
System.out.println("Addedname"+srae.getName()+",value="+srae.getValue());
}
@Override
public void attributeRemoved(ServletRequestAttributeEvent srae) {
// TODO Auto-generated method stub
System.out.println("監聽到request作用域刪除了屬性");
System.out.println("Removedname"+srae.getName()+",value="+srae.getValue());
}
@Override
public void attributeReplaced(ServletRequestAttributeEvent srae) {
System.out.println("監聽到request作用域修改了屬性");
System.out.println("Replacedname"+srae.getName()+",value="+srae.getValue());
}
}
package com.hxuner.listener;
import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionBindingEvent;
public class zMyHttpSessionAttributeListener implements HttpSessionAttributeListener{
@Override
public void attributeAdded(HttpSessionBindingEvent se) {
// TODO Auto-generated method stub
System.out.println("監聽到session作用域添加了屬性");
System.out.println("Addedname"+se.getName()+",value="+se.getValue());
}
@Override
public void attributeRemoved(HttpSessionBindingEvent se) {
// TODO Auto-generated method stub
System.out.println("監聽到session作用域移除了屬性");
System.out.println("Removedname"+se.getName()+",value="+se.getValue());
}
@Override
public void attributeReplaced(HttpSessionBindingEvent se) {
// TODO Auto-generated method stub
System.out.println("監聽到session作用域修改了屬性");
System.out.println("Replacedname"+se.getName()+",value="+se.getValue());
}
}
package com.hxuner.listener;
import javax.servlet.ServletRequestAttributeEvent;
import javax.servlet.ServletRequestAttributeListener;
/*
* 監聽域三個作用域屬性變化的監聽器
ServletContextAttributeListener ServletContext作用域的屬性
HttpSessionAttributeListener Session作用域的屬性
ServletRequestAttributeListener Request作用域的屬性
這三個接口中都定義了三個方法來處理被監聽對象中的屬性的增加,刪除和替換的事件,
同一個事件在這三個接口中對應的方法名稱完全相同,只是接受的參數類型不同。
*/
public class zMyServletRequestAttributeListener implements ServletRequestAttributeListener{
//attributeAdded 方法
//當向被監聽器對象中增加一個屬性時,web容器就調用事件監聽器的 attributeAdded 方法進行相應,這個方法接受一個事件類型的參數,監聽器可以通過這個參數來獲得正在增加屬性的域對象和被保存到域中的屬性對象。
@Override
public void attributeAdded(ServletRequestAttributeEvent srae) {
// TODO Auto-generated method stub
System.out.println("監聽到request作用域添加了屬性");
System.out.println("Addedname"+srae.getName()+",value="+srae.getValue());
}
//(attributeRemoved 方法
//當刪除被監聽對象中的一個屬性時,web 容器調用事件監聽器的這個方法進行相應。
@Override
public void attributeRemoved(ServletRequestAttributeEvent srae) {
// TODO Auto-generated method stub
System.out.println("監聽到request作用域刪除了屬性");
System.out.println("Removedname"+srae.getName()+",value="+srae.getValue());
}
//attributeReplaced 方法
//當監聽器的域對象中的某個屬性被替換時,web容器調用事件監聽器的這個方法進行相應。
@Override
public void attributeReplaced(ServletRequestAttributeEvent srae) {
// TODO Auto-generated method stub
System.out.println("監聽到request作用域修改了屬性");
System.out.println("Replacedname"+srae.getName()+",value="+srae.getValue());
}
}
1.3 監聽JavaBean和Session關係的監聽
package com.hxuner.JavaBean;
import java.io.Serializable;
import javax.servlet.http.HttpSessionActivationListener;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;
import javax.servlet.http.HttpSessionEvent;
/*
監聽JAVABEAN在Session域中狀態變化的監聽器
1.Javabean在Session中的狀態
保存在 Session 域中的對象可以有多種狀態:
(1)綁定到 Session 中。
(2)從Session 域中解除綁定。
(3)隨Session 被鈍化
(4)隨Session被活化
2.
HttpSessionBindingListener - 使javabean自己感知自己在session域中被加入或移除的狀態變化的監聽器
HttpSessionActivationListener - 使javabean自己感知自己在session域中隨着session被鈍化 活化 狀態變化的監聽器,必須實現Serializable序列化接口
這兩個監聽器比較特殊 不需要單獨寫類來實現 也不需要在web.xml中進行配置 只需要讓javabean自己來實現即可
*/
public class Person implements HttpSessionBindingListener,HttpSessionActivationListener,Serializable{
private static final long serialVersionUID = 1L;
//----------------------HttpSessionBindingListener接口---- 綁定/解綁---------------------------------------------------
//實現了HttpSessionBindingListener接口的 JavaBean 對象可以感知自己被綁定到 Session 中和從 Session 中刪除的事件。
//當對象被綁定到 HttpSession 對象中時觸發
@Override
public void valueBound(HttpSessionBindingEvent event) {
System.out.println("監聽到JavaBean被綁定到HttpSession對象中");
}
//當對象從 HttpSession 對象中解除綁定時觸發
@Override
public void valueUnbound(HttpSessionBindingEvent event) {
System.out.println("監聽到JavaBean從HttpSession對象中解除綁定");
}
//------------------------HttpSessionActivationListener接口 -----鈍化/活化-,必須實現Serializable序列化------------------------
//實現了HttpSessionActivationListener接口的 JavaBean 對象可以感知自己被活化和鈍化的事件。
//當綁定到 HttpSession 對象中的對象將要隨 HttpSession 對象被鈍化時觸發
@Override
public void sessionWillPassivate(HttpSessionEvent se) {
System.out.println("監聽到JavaBean隨HttpSession對象被鈍化");
}
//當綁定到 HttpSession 對象中的對象將要隨 HttpSession 對象被活化時觸發
@Override
public void sessionDidActivate(HttpSessionEvent se) {
System.out.println("監聽到JavaBean隨 HttpSession對象被活化");
}
//-------------------------------------------------------------------------------
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
1.4web.xml配置Listener
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<display-name></display-name>
<!-- 配置一個監聽器 -->
<!-- 監聽器的生效不是基於URL,基於事件,web應用啓動後,會按順序加載所有的監聽器並實例化 -->
<!-- web.xml中的加載順序 listener>filter>servlet(load-on-startup>) -->
<!-- 如果Servlet配置了該標籤<load-on-startup>,且值>=0,則該Servlet在web應用啓動的時候就加載 -->
<listener>
<listener-class>com.hxuner.listener.MyServletContextListener</listener-class>
</listener>
<listener>
<listener-class>com.hxuner.listener.MyHttpSessionListener</listener-class>
</listener>
<listener>
<listener-class>com.hxuner.listener.MyServletRequestListener</listener-class>
</listener>
<listener>
<listener-class>com.hxuner.listener.zMyServletRequestAttributeListener</listener-class>
</listener>
<listener>
<listener-class>com.hxuner.listener.zMyHttpSessionAttributeListener</listener-class>
</listener>
<listener>
<listener-class>com.hxuner.listener.zMyServletContextAttributeListener</listener-class>
</listener>
<servlet>
<servlet-name>UploadServlet</servlet-name>
<servlet-class>com.hxuner.servlet.UploadServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>UploadServlet</servlet-name>
<url-pattern>/UploadServlet</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<base href="<%=basePath%>">
<title>My JSP 'index.jsp' starting page</title>
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="This is my page">
<!--
<link rel="stylesheet" type="text/css" href="styles.css">
-->
</head>
<body>
This is my JSP page. <br>
<!-- 輸出當前web應用映射路徑 -->
page=${pageContext.request.contextPath }<br> <%--默認表示當前web應用映射路徑 --%>
page=${app} <%-- EL表達式,簡潔,通過ServletContext監聽器在ServletContext創建的時候就傳過來key=app,value=ServletContext.getPath()--%>
</body>
</html>
2.文件上傳
package com.hxuner.servlet;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.UUID;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
public class UploadServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//1.如果是一個文件上傳表單,不能使用傳統方法獲取請求
//String username=request.getParameter("username");
//System.out.println("username="+username); //輸出null
//String addr=request.getParameter("addr");
//System.out.println("addr="+addr); //輸出null
//2.獲取請求實體中所有內容
/*
ServletInputStream sis=request.getInputStream();
byte[] array=new byte[10];
//使用輸入流讀取一次內容,讀取的內容存入array中,len的值是讀取字節的長度
int len=sis.read(array);
while(len!=-1){
String str=new String(array,0,len);
System.out.println(str);
len=sis.read(array);
}
sis.close();
輸出-----------------------------265001916915724
Content-Disposition: form-data; name="username"
å¼ ä¸
-----------------------------265001916915724
Content-Disposition: form-data; name="addr"
èå¸
-----------------------------265001916915724
Content-Disposition: form-data; name="file"; filename="176.20.190.64.txt"
Content-Type: text/plain
ÎÒÊÇÒ»¸öºÜ´ÏÃ÷µÄÈË
-----------------------------265001916915724--
*/
//獲取ServletContext對象,解決路徑難問題
//sc.getRealPath("虛擬路徑")->返回該資源的絕對路徑
//該方法默認從WebRoot文件夾下開始找
ServletContext sc=this.getServletContext();
//使用傳統的方式不能解決文件上傳表單的亂碼問題
//request.setCharacterEncoding("utf-8");
//------------正確使用commons.fileupload.jar 這個包依靠commons-io進行操作 來接受上傳文件--------------------------
//1.構造工廠DiskFileItemFactory(FileItem的工廠)時,指定內存緩衝區大小和臨時文件存放位置。
//int sizeThreshold 內存緩衝區的大小
//File repository 臨時文件存放位置
//文件上傳時需要將請求的實體內容全部讀取後才能做處理,此時需要將實體內容緩衝起來 內存緩衝快但是耗費內存 文件緩衝慢,但是可以存放大量數據
//所以此處提供了兩個選項 if(數據大小小於內存緩衝區的大小sizeThreshold){使用內存做緩衝 速度快}
// else if(文件大小超過了內存緩衝區的大小){在repository指定的位置下創建臨時文件來緩衝數據}
DiskFileItemFactory factory=new DiskFileItemFactory(1024, new File(sc.getRealPath("/temp")));
//2.使用DiskFileItemFactory 對象創建ServletFileUpload對象,進行通用配置。
ServletFileUpload fileUpload=new ServletFileUpload(factory);
//2.1判斷當前表單是否是一個文件上傳表單 enctype爲multipart/form-data類型
//boolean isMultipartContent(HttpServletRequest request)
if(!fileUpload.isMultipartContent(request)){
throw new RuntimeException("請使用正確的文件上傳表單");
}
//2.2設置單個文件的最大大小 setFileSizeMax(long fileSizeMax)
fileUpload.setFileSizeMax(1024*1024); //1MB
//2.3設置單次上傳的所有文件的總大小設置 setSizeMax(long sizeMax)
fileUpload.setSizeMax(1024*1024*5); //5MB
//2.4---注意3:設置字符集,解決文件名的亂碼問題---
fileUpload.setHeaderEncoding("utf-8");
//3.實際讀取輸入流中的內容,封裝成FileItem(對錶單中每一個input進行封裝)
//List<FileItem> parseRequest(HttpServletRequest request)
try {
List<FileItem> list=fileUpload.parseRequest(request);
//對FileItem集合進行操作,獲取表單中的數據
if(list!=null){
for(FileItem fileItem:list){
//判斷當前FileItem是不是一個普通字段項 如果返回true表示這是一個普通字段項 返回false表示是一個文件上傳項
//boolean isFormField()
if(fileItem.isFormField()){ //是普通的input輸入框的內容
//如果是普通字段項
//String getFieldName() //獲取字段項的名稱
//String getString() //獲取字段項的值
//String getString(String encode) //獲取字段項的值
//獲取input的name
String name=fileItem.getFieldName();
//獲取input的value
//String value=fileItem.getString();
String value=fileItem.getString("utf-8"); //---注意1:通知工具類使用utf-8進行解碼,解決提交參數亂碼問題---
System.out.println("name="+name+",value="+value);
}else{ //是文件上傳項
//String getName() //獲取文件名
//InputStream getInputStream() //獲取文件內容的流
//delete() //刪除臨時文件
//獲取上傳的文件的文件名
String fileName=fileItem.getName();
//-----------特別注意1 ie瀏覽器bug-------------
//ie瀏覽器的部分版本,在上傳文件時,會使用文件的完整路徑作爲文件名
//a.txt c:users\administra\desktop\a.txt
if(fileName.contains("\\")){ //文件名不允許存在\
fileName=fileName.substring(fileName.lastIndexOf("\\")+1);//文件名從最後一個\+1截取到最後
}
//--------特別注意3 文件名重複-------
//多個上傳名稱相同時 文件會發生覆蓋
//解決方案:應該想辦法讓文件名 儘量不要重複 - 在文件名的前面拼接UUID來保證文件名絕對不會重複
fileName=UUID.randomUUID()+"_"+fileName; //數據庫裏保存了該filename與用戶的對應關係
//-------特別注意4:上傳文件目錄下文件過多----------
//一個文件夾下文件過多會造 訪問緩慢,甚至有可能無法訪問
//解決方案:所以應該想辦法將這些文件分目錄存儲,利用文件名的hascode的16進製表示生成對應的目錄。
String hsStr=Integer.toHexString(fileName.hashCode());
//補足8位
while(hsStr.length()<8){
hsStr="0"+hsStr;
}
//生成中間路徑
String midPath="/";
for(int i=0;i<hsStr.length();i++){
midPath=midPath+"/"+hsStr.charAt(i)+"/";
}
// 用本變量保存實際存儲的路徑
// sc.getRealPath方法返回的路徑會去掉最後的 /
String savePath=sc.getRealPath("/WEB-INF/upload"+midPath);
// 在服務器上創建對應的文件夾
new File(savePath).mkdirs();
//獲取上傳的文件的輸入流in->read
InputStream is=fileItem.getInputStream();
FileOutputStream fos=null;
try {
//輸入流 out ->Write
fos=new FileOutputStream(savePath+"/"+fileName);
/*
* ----------- 特別注意2:文件上傳保存位置問題---------------------
上傳一個這個index.jsp到upload裏,然後訪問瀏覽器/upload/index.jsp就可以通過上傳的jsp寫的代碼顯示所有文件內容
所以不讓用戶通過瀏覽器直接訪問其上傳的文件。
文件上傳保存的位置一定不能被外界直接訪問 防止用戶瀏覽器訪問 下載資源 或執行jsp惡意代碼
要麼保存在WEB-INF下保護起來
要麼放在本地磁盤其他位置,保證通過瀏覽器無法直接訪問
*/
byte[] array=new byte[100];
int len=is.read(array);
while(len!=-1){
fos.write(array,0,len);
len=is.read(array);
}
} catch (Exception e) {
// TODO: handle exception
}finally{
if(is!=null){
is.close();
}
if(fos!=null){
fos.close();
}
//---注意2:在關流之後,要注意刪除臨時文件,需要在關流之後調用---
fileItem.delete();
}
}
}
}
} catch (FileUploadException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<%--
提供一個帶有文件上傳項的表單
文件上傳的輸入框必須有name屬性才能被上傳
文件上傳的表單必須是post提交
文件上傳的表單必須設置enctype=multipart/form-data
--%>
<form action="${app}/UploadServlet" method="post" enctype="multipart/form-data"> <%--文件上傳的表單必須是post提交 文件上傳的表單必須設置enctype=multipart/form-data --%>
用戶名<input type="text" name="username"><br>
地址<input type="text" name="addr"><br>
文件<input type="file" name="file"><br> <%-- 文件上傳的輸入框必須有name屬性才能被上傳--%>
<input type="submit" value="提交"><br>
</form>
特別注意2:<br>
上傳一個這個index.jsp到upload裏,然後訪問瀏覽器/upload/index.jsp就可以通過上傳的jsp寫的代碼顯示所有文件內容<br>
所以不讓用戶通過瀏覽器直接訪問其上傳的文件。<br>
文件上傳保存的位置一定不能被外界直接訪問 防止用戶瀏覽器訪問 下載資源 或執行jsp惡意代碼<br>
要麼保存在WEB-INF下保護起來 <br>
要麼放在本地磁盤其他位置,保證通過瀏覽器無法直接訪問<br>
</body>
</html>