JavaEE-07 過濾器和監聽器

學習要點

  • 過濾器
  • 監聽器

 

過濾器Filter

過濾器的概念

  • 過濾器位於客戶端和web應用程序之間,用於檢查和修改二者之間流過的請求和響應。
  • 在請求到達Servlet/JSP以前,過濾器截獲請求。
  • 在響應送給客戶端以前,過濾器截獲響應。
  • 多個過濾器造成一個過濾器鏈,過濾器鏈中不一樣過濾器的前後順序由部署文件web.xml中過濾器映射<filter-mapping>的順序決定。
  • 最早截獲客戶端請求的過濾器將最後截獲Servlet/JSP的響應信息。

過濾器的鏈式結構

能夠爲一個Web應用組件部署多個過濾器,這些過濾器組成一個過濾器鏈,每一個過濾器只執行某個特定的操做或者檢查。這樣請求在到達被訪問的目標以前,須要通過這個過濾器鏈。html

 

實現過濾器

在Web應用中使用過濾器須要實現javax.servlet.Filter接口,實現Filter接口中所定義的方法,並在web.xml中部署過濾器。java

public class MyFilter implements Filter {

    public void init(FilterConfig fc) {
        //過濾器初始化代碼
    }

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
        //在這裏能夠對客戶端請求進行檢查
        //沿過濾器鏈將請求傳遞到下一個過濾器。
        chain.doFilter(request, response);
        //在這裏能夠對響應進行處理

    }

    public void destroy( ) {
        //過濾器被銷燬時執行的代碼
    }

}

  

Filter接口經常使用方法

方法名稱web

功能描述安全

public void init(FilterConfig config)session

容器在實例化過濾器調用。FilterConfig對象包含Filter相關的配置信息。app

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)框架

每當請求和響應通過過濾器鏈時,容器都調用一次該方法。過濾器的一個實例能夠同時服務於多個請求,須要注意線程同步問題,儘可能不用或少用實例變量。 在過濾器的doFilter()方法實現中,任何出如今FilterChain的doFilter方法以前地方,request是可用的;在doFilter()方法以後response是可用的。webapp

public void destroy()ide

容器調用destroy()方法指出將從服務中刪除該過濾器。若是過濾器使用了其餘資源,須要在這個方法中釋放這些資源。post

 

 

部署過濾器

在Web應用的WEB-INF目錄下,找到web.xml文件,在其中添加以下代碼來聲明Filter。

<filter>
    <filter-name>MyFilter</filter-name>
    <filter-class>
        com.etc.web.MyFilter
    </filter-class>
    <init-param>
        <param-name>codeFilter</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
</filter>
//針對一個Servlet作過濾
<filter-mapping>
    <filter-name>MyFilter</filter-name>
    <servlet-name>MyServlet</servlet-name>
</filter-mapping>
//針對URL Pattern作過濾
<filter-mapping>
    <filter-name>MyFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

  

<filter-mapping>標記是有前後順序的,它的聲明順序說明容器是如何造成過濾器鏈的。過濾器應當設計爲在部署時很容易配置的形式。經過使用初始化參數,能夠獲得複用性很高的過濾器。

過濾器邏輯與Servlet邏輯不一樣,它不依賴於任何用戶狀態信息,由於一個過濾器實例可能同時處理多個徹底不一樣的請求。

 

新聞發佈系統中,添加Post亂碼處理過濾器

在web.xml中配置過濾器信息

<web-app>
  <filter>
      <filter-name>EncodeFilter</filter-name>
	  <filter-class>com.etc.news.web.EncodeFilter</filter-class>
	  <init-param>
	  	<param-name>encode</param-name>
	  	<param-value>UTF-8</param-value>
	  </init-param>	  
  </filter>
  <filter-mapping>
  	  <filter-name>EncodeFilter</filter-name> 
  	  <url-pattern>/*</url-pattern>  	   	  
  </filter-mapping>
 </web-app>

  

註解方式部署過濾器 

<!-- @WebFilter(urlPatterns = {"/*"},filterName="EncodeFilter" ,initParams = {@WebInitParam(name = "encode", value = "utf-8")}) -->

  

編寫自定義類EncodeFilter

import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public class EncodeFilter implements Filter {
	private String encode = null;

	public void destroy() {
		encode = null;
	}
	
	public void init(FilterConfig filterConfig) throws ServletException {
		String encode = filterConfig.getInitParameter("encode");
		if (this.encode == null) {
			this.encode = encode;
		}
	}

	// 對全部頁面設置字符集
	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {
		if (null == request.getCharacterEncoding()) {
			request.setCharacterEncoding(encode);
		}
		chain.doFilter(request, response);
response.setContentType("text/html;charset="+encode);
response.setCharacterEncoding(encode);
	}
}

  

支持post和get方式的編碼過濾器

HttpServletRequestWrapper和HttpServletResponseWrapper類

  • Servlet2.1規範中的filter引入了一個功能強大的攔截模式。Filter能在request到達servlet的服務方法以前攔截HttpServletRequest對象,而在服務方法轉移控制後又能攔截HttpServletResponse對象。
  • 可是HttpServletRequest中的參數是沒法改變的,如果手動執行修改request中的參數,則會拋出異常。且沒法獲取到HttpServletResponse中的輸出流中的數據,由於HttpServletResponse中輸出流的數據會寫入到默認的輸出端,你手動沒法獲取到數據。
  • 咱們能夠利用HttpServletRequestWrapper包裝HttpServletRequest,用HttpServletResponseWrapper包裝HttpServletResponse,在Wrapper中實現參數的修改或者是response輸出流的讀取,而後用HttpServletRequestWrapper替換HttpServletRequest,HttpServletResponseWrapper替換HttpServletResponse。這樣就實現了參數的修改設置和輸出流的讀取。
  • HttpServletRequestWrapper是HttpServletRequest的一個實現類,因此能夠用HttpServletRequestWrapper替換HttpServletRequest。ServletRequestWrapper採起了裝飾器器模式,實際上內部操做的就是構造方法中傳遞的ServletRequest。
  • HttpServletResponseWrapper是HttpServletResponse的實現類,因此HttpServletResponseWrapper能夠替換HttpServletResponse。同ServletResponseWrapper同樣,ServletResponseWrapper也是採去了裝飾器模式,內部操做的也是構造方法中傳遞的ServletResponse。
  • 裝飾器模式:在java輸入輸出流中常見,處理流常常須要節點流做爲參數來構建。例如BufferedInputStream,BufferedOutputSteam、BuffereReader、BuferedWriter等等。

請求裝飾類示例代碼

import java.io.UnsupportedEncodingException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

/**
 * 對Get方式傳遞的請求參數進行編碼
 */
public class CharacterEncodingRequest extends HttpServletRequestWrapper {
	private HttpServletRequest request = null;
	public CharacterEncodingRequest(HttpServletRequest request) {
		super(request);
		this.request = request;
	}
	/**
	 * 對參數從新編碼
	 */
	@Override
	public String getParameter(String name) {
		String value = super.getParameter(name);
		if (value == null)
			return null;
		String method = request.getMethod();
		if ("get".equalsIgnoreCase(method)) {
			try {
				value = new String(value.getBytes("ISO8859-1"), 
request.getCharacterEncoding());
			} catch (UnsupportedEncodingException e) {
				e.printStackTrace();
			}
		}
		return value;
	}
}

  

過濾器代碼

import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class CharacterEncodingFilter implements Filter {
	private String encode = "UTF-8";// 默認UTF-8編碼
	public void init(FilterConfig filterConfig) throws ServletException {
		String encoding = filterConfig.getInitParameter("encode");
		if (encoding != null) {
			this.encode = encoding;
		}
	}

	public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) resp;
		// 設置request編碼
		request.setCharacterEncoding(encode);
		chain.doFilter(new CharacterEncodingRequest(request), response);
		// 設置響應信息編碼
		response.setContentType("text/html;charset=" + encode);
		response.setCharacterEncoding(encode);
	}
	public void destroy() {
	}
}

  

 

HttpServletRequestWrapper用於實現敏感字過濾

項目結構

 

 

定義HTTP包裝類

package com.etc.filter;

import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

/** http 請求包裝器 */
public class HttpRequestWrapper extends HttpServletRequestWrapper {

	private Map<String, String> map = null;// 須要替換內容的集合key敏感字 value替換內容

	public HttpRequestWrapper(HttpServletRequest request) {
		super(request);
	}

	/**
	 * 敏感字替換
	 * 
	 * @param rs請求字符串
	 * @return 替換敏感字後的字符串
	 */
	public String replace(String rs) {
		StringBuffer rssb = new StringBuffer(rs);// StringBuffer修改字符串不產生副本,非線程安全,速度快
		Set<String> keys = this.getMap().keySet();// map的key爲敏感字集合
		Iterator<String> it = keys.iterator();// 敏感字迭代器
		//String ss = null;// 存儲key變量
		while (it.hasNext()) {
			String key = it.next();
			int index = rssb.indexOf(key);// 查找字符串中是否存在須要替換的內容
			if (index != -1 && key != null) {// 找到敏感字而且敏感字集合該敏感字不爲空,執行替換
				//ss = key;
				rssb.replace(index, index + key.length(), this.getMap().get(key));// 替換敏感字
			}
		}
		// if (ss != null) {
		// if (rssb.toString().indexOf(ss) == -1) {// 確保已經替換完畢
		// return rssb.toString();
		// } else {// 再次進行替換
		// return replace(rssb.toString());
		// }
		// }
		return rssb.toString();
	}

	//Servlet中實際調用的getParameter方法: 重寫的getParameter()方法
	public String getParameter(String str) {
		String content = super.getParameter(str);
		return replace(content);// 返回被替換文本
		// if (str.equals("pager.offset")) {// JSP視圖使用pager-taglib分頁框架的分頁信息。忽略處理
		// return super.getParameter(str);
		// } else {// 敏感字替換
		// String content = super.getParameter(str);
		// return replace(content);// 返回被替換文本
		// }
	}

	public Map<String, String> getMap() {
		return map;
	}

	public void setMap(Map<String, String> map) {
		this.map = map;
	}

}

  

定義內容過濾器

package com.etc.filter;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.http.HttpServletRequest;

/** 內容過濾器 */
@WebFilter(filterName = "ContentFilter", urlPatterns = "*.action", initParams = {
		@WebInitParam(name = "filePath", value = "/WEB-INF/words") })
public class ContentFilter implements Filter {

	private Map<String, String> map = new HashMap<String, String>();

	// 過濾器的初始化:讀取敏感字文件
	public void init(FilterConfig config) throws ServletException {
		String filePath = config.getInitParameter("filePath");// 從配置文件中取得文件的相對路徑/WEB-INF/words
		ServletContext context = config.getServletContext();// 讀取上下文環境
		String realPath = context.getRealPath(filePath);// 絕對路徑D:\Tomcat7\webapps\guestbook\WEB-INF\words
		FileReader fr = null;//節點流
		BufferedReader br = null;//處理流
		// 根據相對路徑取得絕對路徑
		try {
			fr = new FileReader(realPath);// 根據絕對路徑,經過文件流來讀取文件
			br = new BufferedReader(fr);
			String line = null;
			while ((line = br.readLine()) != null) {
				String[] str = line.split("==");// 按照==拆分字符串
				map.put(str[0], str[1]);
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				if (br != null) {
					br.close();
				}
				if (fr != null) {
					fr.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

	}

	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		// 處理請求中的數據:使用HttpRequestWrapper包裝類替換HttpRequest
		HttpRequestWrapper hrw = new HttpRequestWrapper((HttpServletRequest) request);
		hrw.setMap(map);
		chain.doFilter(hrw, response);// 過濾鏈
	}

	@Override
	public void destroy() {
		this.map = null;
	}
}

  

 

監聽器

Servlet事件

Web容器管理Servlet/JSP相關的生命週期。

Servlet事件是指HttpServletRequest對象、HttpSession對象、ServletContext對象生成、銷燬或相關屬性進行了設置等等事件。

Servlet監聽器

監聽器由web容器管理,它的做用是監聽Servlet有效事件,並根據需求作出適當響應。下表爲Servlet和JSP中的8個Listener和6個Event類。

Listener接口

監聽Event

ServletContextListener

ServletContextEvent

ServletContextAttributeListener

ServletContextAttributevent

HttpSessionListener

HttpSessionEvent

HttpSessionActivationListener

HttpSessionAttributeListener

HttpSessionBindingEvent

HttpSessionBindingListener

ServletRequestListener

ServletRequestEvent

ServletRequestAttributeListener

ServletRequestAttributeEvent

 

Servlet監聽器的功能和Java的GUI的監聽器相似,能夠監聽Servlet容器因爲Web應用程序中狀態改變而產生的相應事件,而後接受和處理這些事件。

 

監聽Servlet上下文

用於監聽ServletContext對象的建立、刪除和添加屬性,以及刪除和修改操做,主要用到如下接口:

1.ServletContextListener接口

該接口主要用來監聽SerrvletContext的建立和刪除,他提供瞭如下兩個方法,也稱爲「web應用程序的生命週期方法」:

    • contextInitialized(ServletContextEvent event)方法:通知正在收聽的對象應用程序已經被加載及初始化。
    • contextInitialized(ServletContextEvent event)方法:通知正在收聽的對象應用程序已經被載出、即將關閉。

2.ServletContextAttributeListener接口

用來監聽ServletContext屬性的增長、刪除及修改,它提供了一下三個方法:

    • attributeAdded(ServletContextAttributeEvent event)方法:如有對象加入application範圍,通知正在收聽的對象。
    • attributeReplaced(ServletContextAttributeEvent event)方法:若在application範圍內的對象取代另外一個對象,通知正在收聽的對象。
    • attributeRemoved(ServletContextAttributeEvent event)方法:如有對象從application範圍被移除,則通知正在收聽的對象。

 

監聽HTTP會話

提供了4個接口監聽HTTP會話(HTTPSEession)信息。

1.HttpSessionListener接口

該接口監聽HTTP會話的建立及撤銷,它提供了兩個方法:

    • sessionCreated(HttpSessionEvent  event)方法:通知正在收聽的對象,session已經被加載及初始化。
    • sessionDestroyed(HttpSessionEvent  event)方法:通知正在收聽的對象,session已經被載出(HttpSessionEvent類的主要方法是getSession,可使用該方法回傳一個session對象)。

2.HttpSessionActivationListener接口

該接口實現監聽HTTP會話active和passivate狀況,它提供了以下2個方法。

  • sessionDidActivate(HttpSessionEvent  event)方法:通知正在收聽的對象,其session已經變爲有效狀態。
  • sessionWillPassivate(HttpSessionEvent  event)方法:通知正在收聽的對象,其session已經變爲無效的狀態。

3.HttpSessionAttributeListener接口

該接口實現監聽HTTP會話中屬性的設置請求,它提供了3個方法:

  • attributeAdded(HttpSessionBindingEvent event)方法:如有對象加入session的範圍,通知正在收聽的對象。
  • attributeRemoved(HttpSessionBindingEvent event)方法:如有對象從session的範圍移除,通知正在收聽的對象(HttpSessionBindingEvent類主要有三個方法:getName()、getSession()、getValues())。
  • attributeReplaced(HttpSessionBindingEvent event)方法:若在session範圍內一個對象取代另外一個對象,則通知在收聽的對象。

4.HttpSessionBindingListener接口

該接口實現監聽HTTP會話中對象的綁定信息,它是惟一不須要在web.xml中設置Listener的,它提供了兩個方法:

  • valueBound(HttpSessionBindingEvent event)方法:當有對象加入session範圍時,自動調用。
  • valueUnbound(HttpSessionBindingEvent  event)方法:當有對象從session範圍內移除時,會被自動調用。

 

監聽Servlet請求

用來監聽客戶端的請求,一旦在監聽程序中獲取了客戶端的請求,就能夠統一處理請求,它提供了兩個接口。

1.ServletRequestListener接口

    • requestInitialized(ServletRequestEvent  event)方法:通知正在收聽的對象,ServletRequest已經被加載及初始化。
    • requestDestroyed(ServletRequestEvent  event)方法:通知正在收聽的對象,ServletRequest已經被載出,即將關閉。

2.ServletRequestAttributeListener接口

    • attributeAdded(ServletRequestAttributeEvent event)方法:如有對象加入request的範圍,通知正在收聽的對象。
    • attributeRemoved(ServletRequestAttributeEvent event)方法:如有對象從request範圍移除,通知正在收聽的對象。
    • attributeReplaced(ServletRequestAttributeEvent event)方法:如有對象在request範圍被取代,通知正在收聽的對象。

 

 

監聽器實例:使用監聽器查看在線用戶

1.UserContainer類

package com.etc.listener;

import java.util.LinkedList;
import java.util.List;

/** 在線用戶操做類 ————單例模式 */
public class UserContainer {
	private static UserContainer userContainer = new UserContainer();
	
	private List<String> list = null;//保存用戶帳號

	private UserContainer() {
		this.list = new LinkedList<String>();
	}

	/** 在線用戶操做類單例模式 :全部的用戶都保存在一個UserInfoList對象中 */
	public static UserContainer getInstance() {
		return userContainer;
	}

	/** 添加用戶 */
	public boolean addUser(String user) {
		if (user != null) {
			this.list.add(user);
			return true;
		} else {
			return false;
		}
	}

	/** 刪除用戶 */
	public void removeUser(String user) {
		if (user != null) {
			for (int i = 0; i < list.size(); i++) {
				if (user.equals(list.get(i))) {
					list.remove(i);
				}
			}
		}
	}

	/** 獲取用戶列表 */
	public List<String> getList() {
		return this.list;
	}

}

  

2.監聽在線用戶類

 

package com.etc.listener;

import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;

/** 監聽在線用戶類 */
@WebListener
public class UserListener implements HttpSessionBindingListener {

	private String user;// 用戶字符串
	private UserContainer userContainer = UserContainer.getInstance();// 用戶信息處理實例

	public UserListener() {
		this.user = null;
	}

	/** 設置在線監聽人員 */
	public void setUser(String user) {
		this.user = user;
	}

	/** 獲取在線人員 */
	public String getUser() {
		return this.user;
	}

	/** 當有對象加入session範圍時,自動調用 */
	@Override
	public void valueBound(HttpSessionBindingEvent arg0) {
		System.out.println(this.user + "上線 ");
		this.userContainer.addUser(this.user);
	}

	/** 當有對象從session範圍內移除時,會被自動調用 */
	@Override
	public void valueUnbound(HttpSessionBindingEvent arg0) {
		System.out.println(this.user + "下線 ");
		this.userContainer.removeUser(this.user);
	}
}

  

3.Servlet處理類

package com.etc.action;

import java.io.IOException;
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 javax.servlet.http.HttpSession;

import com.etc.listener.UserContainer;
import com.etc.listener.UserListener;

@WebServlet("/ServletLogin.action")
public class ServletLogin extends HttpServlet {
	private static final long serialVersionUID = 1L;

	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		HttpSession session = request.getSession();
		UserContainer userContainer = UserContainer.getInstance();
		UserListener userListener = new UserListener();
		String user = request.getParameter("name").trim();// 獲取用戶名
		if (user.length() == 0) {
			user = "默認用戶";
		}
		userListener.setUser(user);// 設定監聽用戶
		session.setAttribute("userListener", userListener);
		userContainer.addUser(userListener.getUser());// 用戶信息添加到用戶信息處理類
		session.setMaxInactiveInterval(30);
		System.out.println(userContainer.getList().size());
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		doGet(request, response);
	}

}

  

 

4.登錄處理代碼

<%
		UserInfoList list = UserInfoList.getInstance();
		UserInfoTrace trace = new UserInfoTrace();
		request.setCharacterEncoding("utf-8");
		String user = request.getParameter("name").trim();//獲取用戶名
		if (user.length() == 0) {
			user = "默認用戶";
		}
		trace.setUser(user);//設定監聽用戶
		session.setAttribute("trace", trace);	
		list.addUser(trace.getUser());//用戶信息添加到用戶信息處理類
		session.setMaxInactiveInterval(30);
	%>
	<h3>當前登陸用戶</h3>
	<ul>
		<%
			Vector<String> vector = list.getList();
			for (int i = 0; i < vector.size(); i++) {
				out.print("<li>" + vector.elementAt(i) + "</li>");
			}
		%>
	</ul>

  

5.登錄視圖代碼

  

<%@ 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>登錄視圖</title>
</head>
<body>
	<form action="ServletLogin.action" method="post">
		<p>
			用戶名:<input type="text" name="name">
		</p>
		<p>
			<input type="submit" value="登錄">
		</p>
	</form>
</body>
</html>
相關文章
相關標籤/搜索