手把手教你如何玩转J2EE中的Listener监听器和Filter过滤器

  在Java Web中,有三大组件,分别是Servlet,Filter.、Filter。对于,Servlet这个内容,在这一篇文章中,进行了很详细的讲解,http://blog.csdn.net/cs_hnu_scw/article/details/74080739  所以,本篇文章主要是讲解关于监听器和过滤器中的内容。

一:Listener监听器

知识点1:什么是监听器?

       做过Swing或者AWT图像界面Java程序开发的话,应该对Listener与Event非常熟悉。Swing或者AWT中通过Listener与Event来处理事件,如鼠标事件、键盘事件等。先实现一个Listener接口,然后通过addListener()方法把Listener绑定到某个控件上,单机按钮时就会调用Listener的相应方法,并传回一个Event对象。
Java Web程序也一样,使用Listener与Event完成相应事件的处理。使用Listener不需要关注该类事件是怎样触发的或者怎么调用相应的Listener,只要记住该类事件触发时一定会调用相应的Listener。遵循Servlet规范的服务器完成了相应的工作。开发者只要在Listener里编写相关代码就OK了。

知识点2:八大监听器分别是哪些?

* 扩展知识点:
   * Servlet的三大域对象:
     * Request对象 - 一次请求
     * Session对象 - 一次会话
     * ServletContext对象 - Tomcat从启动到关闭
   * JSP的四大域对象:
     * page对象 - 当前页面
     * Request对象
     * Session对象
     * ServletContext对象

* 第一组:用于监听Servlet三个域对象的创建与销毁
     * ServletRequestListner
     * HttpSessionListener
     * ServletContextListener
* 第二组:用于监听Servlet三个域对象的属性变化(设置、删除、修改)
     * ServletRequestAttributeListener
     * HttpSessionAttributeListener
     * ServletContextAttributeListener
* 第三组:用于通知被绑定到Session对象的属性.
     * HttpSessionBindingListener
   * 第四组:用于监听Session的钝化与活化
     * HttpSessionActivationListener

它们各自的作用及其适用范围:

 * 第一组:
   * 作用:用于监听Servlet三个域对象的创建与销毁.
   * 三个域对象的创建与销毁:
     * Request对象:
       * 创建:发生请求时.
       * 销毁:请求完成时.
     * Session对象:
       * 创建:发生请求时,并且执行getSession()语句.
       * 销毁:执行Session的销毁方法invalidate().
     * ServletContext对象:
       * 创建:Tomcat启动时.
       * 销毁:Tomcat关闭时.

*第二组:

  *作用:用于监听Servlet三个域对象的修改,删除,保存

 如何自定义监听器:(针对第一组和第二组,因为它们两者非常类似)

   * 第一步:
     * 创建Java类,实现上述监听器接口之一.
     * 重写该接口提供的所有方法.
   * 第二步:
     * 在web.xml文件中配置Listener.
       <listener>
         <listener-class>自定义Listener的完整路径</listener-class>
       </listener>

 * 第三组:HttpSessionBindingListener
   * 注意:
     * 该监听器应该由JavaBean类实现.(可以将JavaBean绑定到Session上)
     * 谁被绑定到Session上,谁就实现该监听器.
     * 该监听器的作用:通知被绑定到Session的JavaBean对象.
     * 使用该监听器时,不需要在web.xml文件中进行配置的. 
 * 第四组:HttpSessionActivationListener

   * Session的序列化

     * 当Tomcat服务器正常关闭后,Session会自动被保存到本地硬盘中.
       * 在Tomcat服务器的安装目录:%Catalina_HOME%\work\Catalina\localhost\Web工程名目录中.
     * 当Tomcat服务器正常启动后,Session会自动从本地硬盘中读取到内存中.
       * 保存在本地硬盘中的序列化文件,自动销毁.
     * 问题:
       * Tomcat服务器为什么提供这么一个机制?

   * Session的钝化与活化

     * 实现步骤:
       * 编写Servlet设置Session.
       * 在Tomcat安装目录%Catalina_HOME%\conf\Catalina\localhost目录中,创建一个名为Web工程名的xml文件.
        
<?xml version="1.0" encoding="UTF-8"?>
<Context>
<!-- maxIdleSwap:指定多长时间后Session会被钝化.(单位为分钟) -->
<Manager className="org.apache.catalina.session.PersistentManager" maxIdleSwap="1" >
<!-- 
directory:指定钝化文件的保存目录.
钝化文件保存路径:${CATALINA_HOME}\work\Catalina\localhost\Web工程名\${sessionid}.session
-->
<Store className="org.apache.catalina.session.FileStore" directory="mysession" />
</Manager>
</Context>
       * 创建JavaBean,用于绑定到Session中.
         * 该JavaBean实现了HttpSessionActivationListener接口,并且重写该接口提供的所有方法.
* 该JavaBean实现了Serializable(序列化)接口(Session的钝化与活化是基于Session的序列化实现的)
     * Session钝化与活化和序列化的区别:
       * Session序列化:
         * Tomcat提供的自动机制.
* Session的序列化文件在重新被使用时,消失.
       * Session钝化与活化:
         * 手动实现.
* Session的钝化文件并不消失.

知识点3:实际案例之适用监听器----------完成在线人数的统计

第一步:编写web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
  <display-name>OnlineUserNumberComputer</display-name>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.html</welcome-file>
    <welcome-file>default.htm</welcome-file>
    <welcome-file>default.jsp</welcome-file>
  </welcome-file-list>
  <listener>
    <listener-class>com.hnu.scw.listener.MyContextServletListener</listener-class>
  </listener>
  <listener>
    <listener-class>com.hnu.scw.listener.MySessionListener</listener-class>
  </listener>
  <servlet>
    <description></description>
    <display-name>OnLineUserNumberDemo</display-name>
    <servlet-name>OnLineUserNumberDemo</servlet-name>
    <servlet-class>com.hnu.scw.demo.OnLineUserNumberDemo</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>OnLineUserNumberDemo</servlet-name>
    <url-pattern>/OnLineUserNumberDemo</url-pattern>
  </servlet-mapping>
</web-app>

第二步:编写监听器(这里用了servletContextListener和SessionListener两个监听器来完成)

扫描二维码关注公众号,回复: 145139 查看本文章

监听器一:

package com.hnu.scw.listener;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
/**
 * 用于监听当servlet容器进行初始化的时候,将在线人数置为0
 * @author scw
 *  2018-2-3
 */
public class MyContextServletListener implements ServletContextListener {

	@Override
	public void contextInitialized(ServletContextEvent servletContextEvent) {
		//1:拿到contextservlet对象
		ServletContext servletContext = servletContextEvent.getServletContext();
		//2:初始化在线人数
		servletContext.setAttribute("online", 0);
	}
	@Override
	public void contextDestroyed(ServletContextEvent arg0) {
		// TODO Auto-generated method stub
		
	}
}

监听器二:

package com.hnu.scw.listener;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
/**
 * 用于当用户登陆之后,在保存用户信息进入session的时候,将人数+1
 * 当用户退出之后,将人数-1
 * @author scw
 *
 */
public class MySessionListener implements HttpSessionListener {
	/**
	 * 当用户登陆之后,将用户信息保存进入session,那么就相当于在线人数+1
	 */
	@Override
	public void sessionCreated(HttpSessionEvent httpSessionEvent) {
		ServletContext servletContext = httpSessionEvent.getSession().getServletContext();
		int number = (int) servletContext.getAttribute("online");
		number = number + 1 ;
		servletContext.setAttribute("online", number);
	}

	/**
	 * 当用户退出的时候,即session会被销毁,所以在线人数要-1
	 */
	@Override
	public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
		ServletContext servletContext = httpSessionEvent.getSession().getServletContext();
		int number = (int) servletContext.getAttribute("online");
		number = number - 1 ;
		servletContext.setAttribute("online", number);	
	}
}

第三步:编写模拟用户成功登陆之后的Demo

package com.hnu.scw.demo;
import java.io.IOException;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
/**
 * 用于模拟用户登陆之后进行的操作(这里只是模拟,实际开发当然不只下面这样处理了)
 * scw
 * 2018-2-3
 */
public class OnLineUserNumberDemo extends HttpServlet {
   
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doPost(request, response);
	}	
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		//1:当进入的时候,就保存一个当前登陆用户的信息进入session(这是模拟登陆成功之后)
		HttpSession session = request.getSession();
		session.setAttribute("user", "我是用户");
		//2:取出当前的登陆人数(因为之前保存在servletcontext中,所以从这里面取)
		ServletContext servletContext = getServletContext();
		int number = (int) servletContext.getAttribute("online");
		//3:将当前的人数显示出在页面
		response.setContentType("text/html;charset=utf-8");
		response.getWriter().println("当前在线人数为:" +  number);
	}

}

二:Filter过滤器

知识点1:什么是过滤器?

      Filter译为过滤,是JavaEE的三大组件之一,用于在Servlet之外对Request或者Response进行修改。例如,污水净化设备可以看做现实中的过滤器,它负责将污水中的杂质过滤,从而使进入的污水变成净水。而对于Web应用程序来说,过滤器是一个驻留在服务器端的Web组件,它可以截取客户端和服务器端之间的请求与响应信息。

知识点2:过滤器使用的时机?


  Filter的作用:起到过滤的作用.
  Filter执行的时机:
     (1)在执行对应的Servlet之前.(过滤Request对象中的内容)
      (2)在执行对应的Servlet之后.(过滤Respons对象中的内容)
   过滤器的发展:
     * Filter最早在Servlet 2.3版本提供.
     * Filter在Servlet 2.5版本完善.

知识点3:如何使用过滤器

 Filter是JavaEE提供的一个接口.(自定义Filter需要实现该接口,并重写所有方法)
   * Filter提供的方法:
     * init()
     * doFilter()
     * destroy() 
  使用步骤:
     (1)创建Java类,实现Filter接口,并且重写所有方法.
     (2) 在web.xml文件中进行配置.

<filter>
		<filter-name>实现接口的类名</filter-name>
		<filter-class>实现接口的路径</filter-class>
	  </filter>
	  <filter-mapping>
		<filter-name>类名</filter-name>
		<url-pattern>/*</url-pattern>  //需要进行过滤的url
	  </filter-mapping>

知识点4:过滤器的生命周期

* 出生:
     * init()方法
   * 活着:
     * doFilter()方法
   * 死去:
     * destroy()方法
   * 扩展知识:
     * 面向对象:这个人就是对象,年龄、性别等是属性,出生、上学、结婚等方法.
       * 类与对象的区别:
         * 类:比作女生.
* 对象:就是范冰冰.
       * 实现、继承、多态、封装等概念.
     * 面向过程:这个人从出生,长大、上学、工作、...、去世.

知识点5:过滤器链

 * 问题:
     * 如何定义过滤器被执行的先后顺序?
     * Filter的doFilter()方法具有一个参数FilterChain,通过调用chain.doFilter()方法可以放行.
       * 在过滤器链中,执行chain.doFilter()方法,是否还是放行的作用?
       * 如果是,应该被放行到哪里去了?(单个Filter时,直接被放行到对应的Web资源[Servlet、JSP])
   * 解决以上问题:
     * chain.doFilter()方法依旧是放行方法.
     * 如果执行的不是过滤器链中最后一个过滤器的话,执行chain.doFilter()方法,会被放行到下一个过滤器里.
     * 如果执行的是过滤器链中最后一个过滤器的话,chain.doFilter()方法,才会被放行到对应Web资源中.
     * 过滤器链中的过滤器执行的先后顺序由web.xml文件中的<filter-mapping>标签定义的先后顺序决定.
   * 实际开发的意义:
     * 单个Filter完成单个任务.

知识点6:过滤器中init()方法中FilterConfig对象-----功能就类似servletConfig对象

 * 读取web.xml文件中的初始化参数.
   * 在web.xml文件中是如何配置的:

 <filter>
		<filter-name>实现接口的类名</filter-name>
		<filter-class>实现接口的路径</filter-class>
		<init-param>
			<param-name>自定义需要初始化的参数名字key</param-name>
			<param-value>自定义需要初始化的参数名字value</param-value>
		</init-param>
	  </filter>
   * FilterConfig的用法与ServletConfig一致.
   * 在web.xml文件中配置全局初始化参数<context-param>,通过ServletContext对象读取.

知识点7:过滤器中的web.xml文件中的映射配置

 * 完全匹配:/xxxx
   * 目录匹配:/aaaa/
   * 扩展名匹配:*.do
   * 优先级别:完全匹配 -> 目录匹配 -> 扩展名匹配

   * 如果当前Filter拦截对应Servlet的话:
     * 还可以使用<servlet-name>标签
   
* Filter拦截Servlet默认情况是拦截直接请求.也就是说默认对于请求转发的是不会进行过滤拦截的(通过下面就可以进行拦截)
     * 在web.xml文件中配置<filter-mapping>标签中具有<dispatcher>
     * <dispatcher>标签在同一个Filter的配置中,可以配置多个.
     * <dispatcher>标签的值:
       * REQUEST:是默认值,表示一次请求.
       * FORWARD:表示请求转发到.
       * INCLUDE:表示包含(例如JSP包含另一个JSP等)
       * ERROR:表示JSP的<%@ page errorPage=""%>

知识点8:实际案例之过滤器-----------完成servlet中的全文乱码处理

描述:当进行servlet开发的时候,接受前台传送过来的中文,很多情况都碰到乱码,那么处理乱码就是一个问题,虽然有处理的方式,但是都是只针对一个servlet进行,那么一个项目中,肯定有很多个,那么如何进行处理呢?这时候就可以通过过滤器来进行实现,并且,这个开发好了,以后可以直接使用,或者直接打个Jar包,既方便又实用。

扩展:Servlet处理Get和Post的乱码问题

针对Get:

String value = request.getParameter("");
	 value = new String(value.getBytes("ISO8859-1"),"utf-8");

	 response.setContentType("text/html;charset=utf-8");

针对Post:-------post方式的较容易处理

 request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");

开发步骤:


第一步:编写自定义的request对象

原因:因为对于Get方式的提交,那么处理中文会比较麻烦,就是因为要把所有的参数,都逐一进行编码处理,而常规的httprequest对象,则无法满足条件,所以需要自定义request对象。

package com.hnu.scw.filter.demo1;
import java.io.UnsupportedEncodingException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
/**
 * 自定义的Request对象
 * @author scw
 */
public class MyRequest extends HttpServletRequestWrapper {
	public MyRequest(HttpServletRequest request) {
		super(request);
	}

	@Override
	public String getParameter(String name) {
		String value = super.getParameter(name);
		if(super.getMethod().equalsIgnoreCase("GET")){
			try {
				value = new String(value.getBytes("ISO-8859-1"),"utf-8");
			} catch (UnsupportedEncodingException e) {
				e.printStackTrace();
			}
		}
		return value;
	}

}

第二步:编写过滤器

package com.hnu.scw.filter.demo1;
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;

/**
 * 当前Filter用于解决全站中文乱码问题.
 * @author scw
 */
public class EncodingFilter implements Filter {
	public void destroy() {
	}
	public void doFilter(ServletRequest req, ServletResponse resp,
			FilterChain chain) throws IOException, ServletException {
		
		//1 进行Request与Response对象的类型强转
		HttpServletRequest request = (HttpServletRequest)req;
		HttpServletResponse response = (HttpServletResponse)resp;	
		//2 解决中文乱码问题:1)请求的中文乱码(GET\POST);2)响应的中文乱码.
		request.setCharacterEncoding("utf-8");
		response.setContentType("text/html;charset=utf-8");	
		//3 创建自定义的Request对象
		MyRequest myRequest = new MyRequest(request);
		//3 放行
		chain.doFilter(myRequest, response);
	}
	public void init(FilterConfig arg0) throws ServletException {
	}

}

第三步:配置web.xml文件(就贴关键的过滤器配置,servlet的就不贴了)

<filter>
<filter-name>EncodingFilter</filter-name>
<filter-class>com.hnu.scw.filter.demo1.EncodingFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>EncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

第四步:进行测试

package com.hnu.scw.filter.demo1;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class EncodeServlet extends HttpServlet {
	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		String username = request.getParameter("username");//接受传送过来的中文参数
		System.out.println(username);//打印传送过来的get中文参数是否还乱码
	}

	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		String username = request.getParameter("username");	//模拟post传送的参数
		System.out.println(username);	 //打印接受过来的中文参数是否乱码
	}

}

知识点9:实例之过滤器的使用-------------自动登陆的功能

描述:有时候,我们会碰到,有些项目需要自动登陆功能,即当用户是之前登陆的,并且,用户选择了保存自动登陆,那么当下次再访问的时候,就不需要填写用户名和密码,而是直接能够正常访问,这个功能有时候还是很有用的。其实,这个通过过滤器就能够进行实现。

主要就写一下关于过滤器如何进行编写了,逻辑还是很清晰的。

过滤器类代码:

package com.hnu.scw.java.filter.demo2;
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.Cookie;
import javax.servlet.http.HttpServletRequest;

/**
 * 当前Filter用于完成用户自动登录功能.
 * @author scw
 */
public class AutoLoginFilter implements Filter {

	public void destroy() {
	}

	public void doFilter(ServletRequest req, ServletResponse resp,
			FilterChain chain) throws IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest)req;
		Cookie[] cookies = request.getCookies();
		if(cookies != null){
			for (Cookie cookie : cookies) {
				if(cookie != null){
					String name = cookie.getName();
					if("user".equals(name)){
						// 曾经登录过
						String value = cookie.getValue();	//下面进行分割,主要是因为cookie保存只能是字符串,那么用户的用户名和密码保存中间用了#字符来分割,所以这里就需要进行拆分 
						String username = value.split("#")[0];
						String password = value.split("#")[1];
				   //将用户信息继续保存到session中,方便后面业务需要
						request.getSession().setAttribute("user", username);
						chain.doFilter(request, resp);
					}else{
						// 没有登陆过,就放行到登陆页面去处理即可。(模拟)
						chain.doFilter(request, resp);
					}
				}
			}
		}else{
			// 没有登陆过,就放行到登陆页面去处理(模拟)
			chain.doFilter(request, resp);
		}
	}

	public void init(FilterConfig arg0) throws ServletException {
	}
}

注意:当进行登陆成功之后,再进行判断用户时候选择了自动登陆功能,然后将该用户的信息保存到cookie中,这样以后进行访问页面的时候,就从cookie中进行获取,判断是否有符合要求的cookie内容即可。这就是关键的登陆成功之后的操作。

     好了,这就是对于J2EE中的两个关键组件的详细分析,是不是挺简单的呢?但是作用却是非常强大的,因为SpringMvc框架中,很多用的都是这部分的知识点,这对于以后学习框架有很大的帮助,所以,还是对于基础要好好的掌握。

猜你喜欢

转载自blog.csdn.net/cs_hnu_scw/article/details/79247210