Top
  1. 过滤器
  2. 监听器

1. 过滤器

1.1. 过滤器

1.1.1. 什么是过滤器

过滤器是Servlet2.3规范之中一种特殊的Web组件,可以作为Servlet的辅助性插件存在。例如,对信息的筛选是很多Servlet里面的一个必须的前提,但是相同的功能在每个Servlet中都编写不仅仅不利于以后修改过滤逻辑,也不利于功能的重用,这时可以将这一部分非决定性的功能放在一个过滤器中,通过配置由容器控制所有请求在到达真正的处理逻辑Servlet之前先通过过滤器的检查。如果过滤逻辑需要修改,那么只需要修改这一个组件即可。所以这种可随时添加、修改,作为Servlet补充功能的Web组件是非常必要的。

1.1.2. 如何编写过滤器

编写过滤器遵循下列步骤:

  1. 编写一个实现了Filter接口的类
  2. 实现Filter接口的三个方法,过滤逻辑在doFilter方法中实现
  3. 在Web程序中注册过滤器
  4. 把过滤器和Web应用一起打包部署

步骤一、实现Filter接口

Filter是过滤器API中最核心的接口,定义一个Java类实现该接口以提供过滤逻辑。代码如下:

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;

public class CommentFilter implements Filter{
	//… …
}

步骤二、实现doFilter方法

Filter接口中共定义了三个方法,分别是init,doFilter,destroy方法。init方法在创建Filter时会被调用,且只调用一次,一般在该方法中做一些数据的准备工作,可以通过传入的FilterConfig参数获取在web.xml文件中配置提供给过滤器的初始参数。destroy方法只有在销毁过滤器对象时被调用一次,用于释放一些资源的操作。doFilter方法内编写过滤器的具体处理逻辑,会被多次执行。该方法共有三个参数,请求和响应用于获取数据以及追加数据,FilterChain是过滤器链,负责多过滤器的传递。

实现代码如下:

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.*;
import javax.servlet.http.*;

public class CommentFilter implements Filter{
		private FilterConfig config;
	
		public void destroy() {
			System.out.println("CommentFilter1's destroy...");
		}

		/*
		 * 容器会调用doFilter方法来处理请求(
	 	 * 相当于servlet的service方法)。
		 * 容器会将request对象(arg0)和response对象
		 * (arg1)作为参数传给doFilter方法。
		 * 如果调用了FilterChain(arg2)的doFilter(request,response)方法,
* 则容器会调用后续的过滤器或者servlet,否则请求处理完毕。
	 	 */
		public void doFilter(ServletRequest arg0, 
							  ServletResponse arg1, FilterChain arg2) 
							  throws IOException, ServletException {
				HttpServletRequest request = (HttpServletRequest)arg0;
				HttpServletResponse response =(HttpServletResponse)arg1;
				request.setCharacterEncoding("utf-8");
				response.setContentType("text/html;charset=utf-8");
				PrintWriter out = response.getWriter();
				String content = request.getParameter("content");
				String illegalStr = config.getInitParameter("illegalStr");
				if(content.indexOf(illegalStr) != -1){
					//有敏感字
					out.println("<h1>评论内容包含了敏感字</h1>");
				}else{
					//没有敏感字
					// 执行FilterChain的doFilter会调用后续的过滤器或者servlet。
					arg2.doFilter(arg0, arg1);
				}
				System.out.println("Filter1's doFilter end.");
		}
	
		/*
		 * FilterConfig对象可以用来访问过滤器的初始化参数。
		 * init方法只会执行一次。
		 */
		public void init(FilterConfig arg0) throws ServletException {
			System.out.println("CommentFilter1's init...");
			config = arg0;
		}
}

步骤三、注册过滤器

在web.xml文件中注册过滤器,代码如下:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" 
	xmlns="/java.sun.com/xml/ns/j2ee" 
	xmlns:xsi="/www.w3.org/2001/XMLSchema-instance" 
	xsi:schemaLocation="/java.sun.com/xml/ns/j2ee 
	/java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
  <!-- 过滤器 -->
  <filter>
  	<filter-name>filter1</filter-name>
  	<filter-class>web.CommentFilter</filter-class>
  	<!-- 初始化参数,由FilterConfig对象读取-->
  	<init-param>
  		<param-name>illegalStr</param-name>
  		<param-value>胡萝卜</param-value>
  	</init-param>
  </filter> 
  <filter-mapping>
  	<filter-name>filter1</filter-name>
  	<url-pattern>/comment</url-pattern>
  </filter-mapping>
</web-app>

步骤四、部署过滤器

将编译后的过滤器和其他Web组件类合在一起,连同web.xml文件放进应用程序结构中即可。

1.1.3. 过滤器的执行流程

图 – 1

如图-1所示为过滤器的执行流程。客户端发来请求后,不会直接将请求送达Servlet,而是先走过滤器1的doFilter方法中的code1,当遇到chain.doFilter()方法时,控制权交到service()方法,执行业务逻辑,但执行结束后并不会立即将响应返回给客户端,而是回到过滤器1的doFilter()方法中code2部分,如果该部分有代码就会执行,执行结束后才会将response对象返回给客户端。

从流程中可以看到,过滤器不仅仅对Servlet的执行前起到过滤作用,对于执行后同样有过滤效果。所以,过滤器是对request和response的检查。

1.1.4. 过滤器的优先级

在一个Web应用中,可以有多个过滤器,它们的优先级由位于web.xml文件中的声明顺序决定,具体是按照<filter-mapping>的顺序来决定的。如下代码所示,filter1和filter2都已经注册,执行顺序是filter2 ( filter1 。

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" 
	xmlns="/java.sun.com/xml/ns/j2ee" 
	xmlns:xsi="/www.w3.org/2001/XMLSchema-instance" 
	xsi:schemaLocation="/java.sun.com/xml/ns/j2ee 
	/java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
  <!-- 过滤器 -->
  <filter>
  	<filter-name>filter1</filter-name>
  	<filter-class></filter-class>
  </filter>
  <filter>
  	<filter-name>filter2</filter-name>
  	<filter-class></filter-class>
  </filter>
  <filter-mapping>
  	<filter-name>filter2</filter-name>
  	<url-pattern>/comment2</url-pattern>
  </filter-mapping> 
  <filter-mapping>
  	<filter-name>filter1</filter-name>
  	<url-pattern>/comment1</url-pattern>
  </filter-mapping> 
</web-app>

1.1.5. 多个过滤器的执行流程

图 – 2

如图-2所示,为多个过滤器的执行流程,过滤器1的doFilter的code1 ( 过滤器2的doFilter的code1 ( service()方法 ( 过滤器2的doFilter的code2 ( 过滤器1的doFilter的code2 ( 返回给客户端

在这个动作的传递过程中一定要写 chain.doFilter()

1.1.6. 过滤器的初始化参数

容器启动之后,会创建过滤器实例。通过init方法来完成过滤器的初始化。初始化时可以添加一些配置,提升动态性。而这些参数通过在web.xml文件中的<init-param>以name-value对的形式存在。

读取这些name-value对需要使用FilterConfig对象,从web.xml文件到FilterConfig对象的过程由容器完成,并通过参数传入到init方法之中,只需要设定一些成员对象保存数值就可以在doFilter方法中使用。

1.1.7. 初始化参数的配置

在web.xml文件中添加过滤器初始化参数的配置代码如下:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" 
	xmlns="/java.sun.com/xml/ns/j2ee" 
	xmlns:xsi="/www.w3.org/2001/XMLSchema-instance" 
	xsi:schemaLocation="/java.sun.com/xml/ns/j2ee 
	/java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
  <!-- 过滤器 -->
  <filter>
  	<filter-name>filter1</filter-name>
  	<filter-class>web.CommentFilter1</filter-class>
  	<!-- 初始化参数 -->
  	<init-param>
  		<param-name>illegalStr</param-name>
  		<param-value>胡萝卜</param-value>
  	</init-param>
  </filter>
  <filter-mapping>
  	<filter-name>filter1</filter-name>
  	<url-pattern>/comment</url-pattern>
  </filter-mapping>  
</web-app>

1.1.8. 读取初始化参数

读取初始化参数使用如下代码:

public class CommentFilter implements Filter{
		private FilterConfig config;	
		public void init(FilterConfig arg0) throws ServletException {
			config = arg0;
		}
		public void doFilter(ServletRequest arg0, 
							  ServletResponse arg1, FilterChain arg2) 
							  throws IOException, ServletException {
				String illegalStr = config.getInitParameter("illegalStr");
			    // … …
		}
public void destroy() {
			   // … …
		 }
}

1.1.9. 过滤器的特点

方便增加或减少某个功能模块,需要添加过滤就多部署一个class修改一下web.xml文件,需要减少某个功能只要删除web.xml中对应的声明即可。

方便修改处理逻辑。当把一些功能独立到某个模块时,如果逻辑变了,只修改这一个文件并更新就可以实现功能的改变,而不用修改每一个使用这个插件的组件。

2. 监听器

2.1. 监听器

2.1.1. 什么是监听器

servlet规范当中定义的一种特殊的组件,用来监听servlet容器产生的事件并进行响应的处理。

2.1.2. 生命周期相关的事件

容器创建或者销毁request,session,ServletContext(上下文/环境)时产生的事件(统计在线人数)。

2.1.3. 绑定数据相关的事件

调用了以上三个对象(request,response,ServletContext)的setAttribute,removeAttribute方法时产生的事件。

2.1.4. 如何编写监听器

step1,写一个java类,实现相应的监听器接口(共有8个接口)。要依据监听的事件类型来选择相应的监听器接口,比如要监听session对象的创建和销毁,要实现HttpSessionListener。

step2,在监听器接口方法中,实现相应的监听处理逻辑。比如,session对象被删除了,将人数减1。

step3,注册(在web.xml文件中配置即可)。

2.1.5. 编写Java类

public class CountListener implements HttpSessionListener{
 
private int count = 0;
 
public void sessionCreated(HttpSessionEvent arg0){
System.out.println("sessionCreated…");
count ++;
}
 
public sessionDestroyed(HttpSessionEvent arg0){
System.out.println("session destroyed…");
count--;
}
}

2.1.6. 实现处理逻辑

public void sessionCreated(HttpSessionEvent arg0){
System.out.print("sessionCreated…");
HttpSession session = args.getSession();
ServletContext ctx = session.getServletContext();
ctx.setAttribute("count",count);
}

2.1.7. 注册监听器

<listener>
	<listener-class>web.CountListener</listener-class>
</listener>

2.1.8. 监听器的应用场景

系统框架级别的代码经常需要检测容器中数据或对象的变化,以一个不受人为控制因素的触发为执行时机,所以对于需要根据数据变化来做出自动反馈的功能都可以使用到监听器.