JavaEE-07 过滤器和监听器

学习要点

  • 过滤器
  • 监听器

 

过滤器Filter

过滤器的概念

  • 过滤器位于客户端和web应用程序之间,用于检查和修改两者之间流过的请求和响应。
  • 在请求到达Servlet/JSP之前,过滤器截获请求。
  • 在响应送给客户端之前,过滤器截获响应。
  • 多个过滤器形成一个过滤器链,过滤器链中不同过滤器的先后顺序由部署文件web.xml中过滤器映射<filter-mapping>的顺序决定。
  • 最先截获客户端请求的过滤器将最后截获Servlet/JSP的响应信息。

过滤器的链式结构

可以为一个Web应用组件部署多个过滤器,这些过滤器组成一个过滤器链,每个过滤器只执行某个特定的操作或者检查。这样请求在到达被访问的目标之前,需要经过这个过滤器链。

 

实现过滤器

在Web应用中使用过滤器需要实现javax.servlet.Filter接口,实现Filter接口中所定义的方法,并在web.xml中部署过滤器。

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接口常用方法

方法名称

功能描述

public void init(FilterConfig config)

容器在实例化过滤器调用。FilterConfig对象包含Filter相关的配置信息。

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

每当请求和响应经过过滤器链时,容器都调用一次该方法。过滤器的一个实例可以同时服务于多个请求,需要注意线程同步问题,尽量不用或少用实例变量。 在过滤器的doFilter()方法实现中,任何出现在FilterChain的doFilter方法之前地方,request是可用的;在doFilter()方法之后response是可用的。

public void destroy()

容器调用destroy()方法指出将从服务中删除该过滤器。如果过滤器使用了其他资源,需要在这个方法中释放这些资源。

 

 

部署过滤器

在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>

  

posted @ 2018-03-01 11:21  rask  阅读(365)  评论(0编辑  收藏  举报