Filter过滤器和Listener监听器


一、Filter过滤器

1.1 概念

1.1.1 引入

当访问服务器的资源时,过滤器可以将请求拦截下来,完成一些特殊的功能,如:在过滤器中解决乱码。

  • JavaWeb的三大组件

    1、Servlet
    2、Filter
    3、Listener

1.1.2 特点

1、过滤器本身不是目标资源(不能直接被访问)
2、过滤器是再访问目标对象前后执行的,过滤器是双向的
3、过滤器可以配置多个
4、过滤器拦截的都是多个目标资源

1.1.3 作用

一般用于完成通用的操作。如:登录验证、统一编码处理、敏感字符过滤…

1.2 快速入门

  • 步骤

    1、定义一个类,实现接口Filter
    2、实现方法
    3、配置拦截路径

    • web.xml
    • 注解
  • 配置版的代码演示

MyFilter.java

//创建java类 实现 javax.servlet.Filter接口 实现doFiLter方法
public class MyFilter implements Filter {

	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		//request就是当前请求对象  所有的请求数据都包含在其中(注意 这时还是ServletRequest对象 不能使用HttpServletRequest对象方法)
		//response就是对于本次请求的响应对象 (因为响应结束后还是通过过滤器返回 所以会创建)
		//chain 过滤器链对象 代表是否继续执行 执行doFilter方法可以继续向下执行 否则到此中断
		
		System.out.println("过滤器执行前");
		// 放行 只有调用这个方法 对应的servlet才能继续执行
		chain.doFilter(request, response);
		System.out.println("过滤器执行后");
	}
}

web.xml配置

		<!-- 配置过滤器 -->
        <!-- 配置过滤器的基本的信息 -->
        <filter>
            <!-- 过滤器的名称,可以是任意的,一般都和类名称是相同 -->
            <filter-name>Demo1Filter</filter-name>
            <!-- 配置类的全路径 -->
            <filter-class>com.yh.Demo1Filter</filter-class>
        </filter>
        <!-- 配置过滤器的映射信息 -->
        <filter-mapping>
            <!-- 配置过滤器的名称,通过该名称找到过滤器,该名称和上面名称必须相同 -->
            <filter-name>Demo1Filter</filter-name>
   
            <!-- 配置过滤器过滤的url -->
			<!-- 1.设置过滤的具体url -->
			<!-- <url-pattern>/b</url-pattern> -->
			<!-- 2.设置过滤指定后缀 -->
			<!-- 过滤以指定后缀结尾的url -->
			<!-- <url-pattern>*.do</url-pattern> -->
			<!-- 3.全部过滤 -->
			<!-- 所有请求url都进行过滤 -->
		  	<url-pattern>/*</url-pattern> 
			<!-- 也可以过滤指定的二级目录 -->
			<!-- <url-pattern>/ai/*</url-pattern> -->
            <url-pattern>/*</url-pattern>
        </filter-mapping>
  • 注解版的代码演示
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

@WebFilter("/*")//访问所有资源之前,都会执行该过滤器
public class FilterDemo1 implements Filter {
    public void destroy() {
    }

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
        System.out.println("FilterDemo1被执行了...");
        //放行
        chain.doFilter(request, response);
    }

    public void init(FilterConfig config) throws ServletException {

    }
}

1.3 Filter的执行流程

1、执行过滤器
2、执行放行后的资源
3、回来执行过滤器放行代码下边的代码

1.4 Filter的生命周期

与srvelt相同,都是通过三个方法进行操作,不同的是Filter接口中init方法与destory方法为默认方法(可以不实现)。

  • 过滤器实例被创建(服务器启动;与servlt一样通过设置,不同版本默认可能不同)
    • init():在服务器启动后,会创建Filter对象,过滤器被创建后,立即调用init方法进行初始化。只执行一次。用于加载资源。
  • 过滤器的实例提供服务(每拦截一次就执行一次
    • doFilter():每一次请求被拦截资源时,会执行。执行多次。
  • 过滤器实例对象被销毁(服务器关闭,销毁)
    • destroy():在服务器关闭后,Filter对象被销毁。如果服务器是正常关闭,过滤器实例被销毁之前,调用执行destroy()方法。只执行一次。用于释放资源。

如果存在多个过滤器过滤相同的URL,根据xml配置顺序决定过滤器执行顺序。

执行的顺序与栈类似先进后出(先执行的过滤器 最后执行结束代码)。

1.5 FilterChain接口

FilterChain:代表的是过滤器链(多个过滤器组成在一起)。

1.5.1 作用

用来放行,如果有下一个过滤器,执行下一个过滤器,若没有,则执行目标资源。

1.5.2 特点

1、过滤器链是由Tomcat服务器提供的。
2、执行顺序是由filter-mapper顺序决定的。

@WebFilter("/*")
public class MyFilter1 implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
        System.out.println("myFilter1过滤器执行");
        //FilterChain过滤器链
        //该对象代码当前服务器所有的过滤器
        //当当前过滤器执行后之后有其他过滤器时doFilter方法代表执行下一个过滤器的doFilter方法
        //当当前过滤器执行之后没有其他过滤器时doFilter方法代表放行 执行请求的服务
        //需要注意的,即使请求的服务器不存在 对应的过滤器也会执行(WEB-INF除外)
        //如果是配置Filter 那么过滤器执行的顺序由配置mapping顺序决定
        //如果使用注解配置,配置书写在类中,所以执行的顺序与文件顺序有关(相同url的过滤器 )
        //注解形式就是在加载对应class之后根据calss中注解生成配置
        //先加载的class会先生成配置,所以符合原本的mapping书写顺序
        chain.doFilter(request, response);

        System.out.println("myFilter1过滤器执行");
    }
}

1.6 FilterConfig接口

FilterConfig:过滤器配置对象,代表的是过滤器的配置信息,通过该接口中的一些方法来获取到当前的过滤器的配置文件信息。

方法名功能
getFilterName获取过滤器名字
getInitParameter(String name)获取过滤器中的初始化参数信息
getInitParamsterNames()获取过滤器中的所有初始化参数信息
getServletContext()获取ServletContext域对象
  • 代码演示
	/**
	 * 每次拦截该方法都会执行了
	 */
	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {
		System.out.println("demo2Filter执行了...");
		// 放行
		chain.doFilter(request, response);
	}
	
	/**
	 * 过滤器实例对象一被创建,立即调用Init方法做初始化
	 */
	public void init(FilterConfig config) throws ServletException {
		
		// 先获取到过滤器配置文件中的<filter-name>的值
		String filterName = config.getFilterName();
		System.out.println("过滤器的名称:"+filterName);
		
		// 可以获取到初始化参数
		String username = config.getInitParameter("username");
		String password = config.getInitParameter("password");
		System.out.println(username+" : "+password);
		
		// 获取初始化参数的名称
		Enumeration<String> e = config.getInitParameterNames();
		while(e.hasMoreElements()){
			String key = e.nextElement();

			String value = config.getInitParameter(key);
			System.out.println(key+" : "+value);
		}

		// 获取到ServletContext对象(非常重要)
		ServletContext context = config.getServletContext();
		
		System.out.println("Demo2Filter已经被创建了...");
	}

过滤器配置

<filter>
    <filter-name>Demo2Filter</filter-name>
    <filter-class>com.yh.Demo2Filter</filter-class>
    <init-param>
      <param-name>username</param-name>
      <param-value>root</param-value>
    </init-param>
    <init-param>
      <param-name>password</param-name>
      <param-value>123456</param-value>
    </init-param>
 </filter>

1.7 过滤器配置详解

1.7.1 拦截路径配置

  1. 具体资源路径/index.jsp 只有访问index.jsp资源时,过滤器才会被执行。
  2. 拦截目录/user/* 访问/user下的所有资源时,过滤器都会被执行。
  3. 后缀名拦截*.jsp 访问所有后缀名为jsp资源时,过滤器都会被执行。
  4. 拦截所有资源/* 访问所有资源时,过滤器都会被执行。

1.7.2 拦截方式配置

拦截方式配置:资源被访问的方式。

  • 注解配置

    设置dispatcherTypes属性:

    • REQUEST:默认值。浏览器直接请求资源
    • FORWARD:转发访问资源
    • INCLUDE:包含访问资源
    • ERROR:错误跳转资源
    • ASYNC:异步访问资源
  • web.xml配置

设置<dispatcher></dispatcher>标签即可。

<!-- 过滤器的基本信息 -->
    <filter>
        <!-- 过滤器的名称,名称可以任意,默认情况下和类名称是相同的 -->
        <filter-name>Demo4Filter</filter-name>
        <!-- 配置类的全路径,该过滤器的包名+类名 -->
        <filter-class>com.yh.Demo4Filter</filter-class>
    </filter>

    <!-- 过滤器的映射的信息(过滤器的拦截哪些目标资源) -->
    <filter-mapping>
        <!-- 配置过滤器的名称,通过该名称找到过滤器,该名称和上面的名称必须相同的 -->
        <filter-name>Demo4Filter</filter-name>
        <!-- 拦截的路径,拦截哪些资源 采用的是目录匹配比较多-->
        <url-pattern>/*</url-pattern>
    </filter-mapping>
  • <dispatcher>标签可以决定过滤器拦截哪些方式?
    • REQUEST
    • FORWARD
    • INCLUDE
    • ERROR

1.8 案例

1.8.1 解决参数中文乱码问题

在使用jsp后响应数据的乱码基本不会出现,因为在jsp已经设置了各种的编码(由jsp请求发送的参数也很少乱码,但是依旧存在),解决中文乱码的方式是在servlet获取数据之前设置编码集合,但是每个servlet都设置编码集造成了代码的冗余,可以使用过滤器来解决这一问题,在所有servlet执行之前,通过过滤器设置请求与响应的编码集,这样通过过滤器的原理就可以只在过滤器书写一次,即可解决中文乱码问题。

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;

@WebFilter("/*")
public class EncodingFilter implements Filter{
private String code = "UTF-8";
	@Override
	public void destroy() {
		// TODO Auto-generated method stub
		
	}

	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {
		request.setCharacterEncoding(code);
        response.setCharacterEncoding(code);
        response.setContentType("text/html;charset=" + code);
        chain.doFilter(request, response);
	}

	@Override
	public void init(FilterConfig arg0) throws ServletException {
		code = filterConfig.getInitParameter("code");
	}
}

1.8.2 实现自动登录功能

可以设置过滤器校验请求,必须在登录后再能继续请求对应的服务,如果没有登录跳转至登录页面,如果在每个服务doGet请求进行书写会造成代码的冗余,可以将登录验证操作书写在过滤器中,这样当请求时通过过滤器验证是否登录过,如果登录过继续请求对应的服务,如果没有登录过则跳转登录页面进行登录。

AutologinFilter.java

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

/**
 * 自动登录的功能
 * @author Administrator
 */
public class AutologinFilter implements Filter{

	@Override
	public void destroy() {
		// TODO Auto-generated method stub
		
	}

	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {
		/**
		 * * 先从session中获取existUser的用户?返回两种结果
			* 如果获取到了,说明session中已经存在该用户了,已经登录了。放行!!
			* 如果没有获取到呢?目的:完成自动登录。获取指定名称的Cookie(目的:通过Cookie来获取到用户名和密码)
			* 如果没有获取到Cookie。说明Cookie根本就没有!放行!!
			* 如果获取到了Cookie,说明Cookie存在的,说明用户已经勾选了自动登录的功能!!
			* 从Cookie中获取到用户名和密码,去数据库中查询
			* 如果查询到的用户为null,说明用户名或者密码是错误的,(有可能被别人修改的)。不能让他自动登录的。放行!!
			* 如果查询到了用户,说明Cookie中的用户名和密码是正确的,完成自动登录。
			就把刚才查询出来的user保存到session中。放行!!
		 */
		// 从session获取用户
		HttpServletRequest req = (HttpServletRequest) request;
		HttpSession session = req.getSession();
		User user = (User) session.getAttribute("existUser");
		// 如果user不为空,说明session中存在该用户的,放行
		if(user != null){
			chain.doFilter(req, response);
		}else{
			// 说明获取到的user是null的
			Cookie[] cookies = req.getCookies();
			// 获取指定名称的cookie
			Cookie c = CookieUtil.getCookieByName(cookies, "autologin");
			//如果c为null,说明用户没有选择过自动登录,不用做自动登录
			if(c == null){
				chain.doFilter(req, response);
			}else{
				// 说明c不为null,找到了cookie,从cookie获取用户名和密码
				String value = c.getValue();

				String username = value.split(",")[0];
				String password = value.split(",")[1];
				// 已经获取到了用户名和密码,去数据库查询一次
				UserDao dao = new UserDao();
				// 通过用户名和密码查询该用户
				User existUser = dao.login(username, password);
				// 如果返回的用户为null,说明用户名或者错误了
				if(existUser == null){
					// 放行
					chain.doFilter(req, response);
				}else{
					// 用户已经存在了
					session.setAttribute("existUser", existUser);
					chain.doFilter(req, response);
				}
			}
		}
	}

	@Override
	public void init(FilterConfig arg0) throws ServletException {
		// TODO Auto-generated method stub
		
	}
}

LoginServlet.java

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


public class LoginServlet extends HttpServlet {

	private static final long serialVersionUID = -38438478374394783L;

	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		
		// 获取请求参数
		String username = request.getParameter("username");
		String password = request.getParameter("password");
		
		UserDao dao = new UserDao();
		User existUser = dao.login(username, password);
		// 如果用户范围null
		if(existUser == null){
			request.setAttribute("msg", "亲!用户名或者密码错误了!!");
			request.getRequestDispatcher("/demo2/login.jsp").forward(request, response);
		}else{
			// 接收用户是否已经勾选了自动登录的复选框
			String autoLogin = request.getParameter("auto_login");
			// 如果和固定的值相同了,说明选中了
			if("autoOK".equals(autoLogin)){
				// 把用户名和密码拼接成字符串	aaa,aaa
				String msg = existUser.getUsername()+","+existUser.getPassword();
				// 证明勾选了,先把用户名和密码偷偷的保存起来  
				Cookie c = new Cookie("autologin", msg);
				// 设置有效时间
				c.setMaxAge(60*60);
				
				// 设置有效路径
				c.setPath("/");
		
				// 会写cookie
				response.addCookie(c);
			}
			
			// 成功了,把对象存入到session中
			request.getSession().setAttribute("existUser", existUser);
			response.sendRedirect(request.getContextPath()+"/demo2/home.jsp");
		}
		
	}

	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		doGet(request, response);
	}
}

1.8.3 敏感词汇过滤

  • 需求

    1、对day17_case案例录入的数据进行敏感词汇过滤
    2、敏感词汇参考《敏感词汇.txt》
    3、如果是敏感词汇,替换为 ***

  • 分析

    1、对request对象进行增强。增强获取参数相关方法
    2、放行。传递代理对象

  • 增强对象的功能

    设计模式:一些通用的解决固定问题的方式
    1、装饰模式
    2、代理模式

    • 概念:
      1、真实对象:被代理的对象
      2、代理对象:
      3、代理模式:代理对象代理真实对象,达到增强真实对象功能的目的
    • 实现方式:
      1、静态代理:有一个类文件描述代理模式
      2、动态代理:在内存中形成代理类
      • 实现步骤:
        1、代理对象和真实对象实现相同的接口
        2、代理对象 = Proxy.newProxyInstance();
        3、使用代理对象调用方法。
        4、增强方法
        • 增强方式:
      1. 增强参数列表
      2. 增强返回值类型
      3. 增强方法体执行逻辑
  • 代码实现

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;

/**
 * 敏感词汇过滤器
 */
@WebFilter("/*")
public class SensitiveWordsFilter implements Filter {


    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
        //1.创建代理对象,增强getParameter方法
        ServletRequest proxy_req = (ServletRequest) Proxy.newProxyInstance(req.getClass().getClassLoader(), req.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //增强getParameter方法
                //判断是否是getParameter方法
                if(method.getName().equals("getParameter")){
                    //增强返回值
                    //获取返回值
                    String value = (String) method.invoke(req,args);
                    if(value != null){
                        for (String str : list) {
                            if(value.contains(str)){
                                value = value.replaceAll(str,"***");
                            }
                        }
                    }
                    
                    return  value;
                }

                //判断方法名是否是 getParameterMap

                //判断方法名是否是 getParameterValue

                return method.invoke(req,args);
            }
        });

        //2.放行
        chain.doFilter(proxy_req, resp);
    }
    private List<String> list = new ArrayList<String>();//敏感词汇集合
    public void init(FilterConfig config) throws ServletException {

        try{
            //1.获取文件真实路径
            ServletContext servletContext = config.getServletContext();
            String realPath = servletContext.getRealPath("/WEB-INF/classes/敏感词汇.txt");
            //2.读取文件
            BufferedReader br = new BufferedReader(new FileReader(realPath));
            //3.将文件的每一行数据添加到list中
            String line = null;
            while((line = br.readLine())!=null){
                list.add(line);
            }

            br.close();

            System.out.println(list);

        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public void destroy() {
    }

}

1.8.4 网站访问量的统计

  • 需求

    • 请求该网站任意URL都算是对网站的请求,将请求进行保存到application域中,当每次服务器请求,经过过滤器时,都讲对应域中的数据取出+1并存入,在控制台打印。
    • 创建过滤器类,配置过滤路径为/*,在doFilter()方法中书写代码,获取application指定key对应数据,如果为null则赋值1,否则加1后再次放入,书写统计servlet,请求servlet在页面打印当前服务器自启动访问的URL及其访问的次数。
  • 代码实现

VisitCountFilter.java

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;

@WebFilter("/*")
public class VisitCountFilter implements Filter {
    public void destroy() {
    }

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {

        ServletContext application = request.getServletContext();
        Map<String, Integer> map = (Map<String, Integer>) application.getAttribute("map");

        HttpServletRequest httpServletRequest = (HttpServletRequest)request;
        String url = httpServletRequest.getRequestURL().toString();
        if (map.containsKey(url)) {
            int num = map.get(url);
            map.put(url, num + 1);
        } else {
            map.put(url, 1);
        }
        application.setAttribute("map", map);

        chain.doFilter(request, response);
    }

    public void init(FilterConfig config) throws ServletException {
        ServletContext application = config.getServletContext();
        Map<String, Integer> map = new LinkedHashMap<String, Integer>();
        application.setAttribute("map", map);
    }
}

LoginServlet.java

import javax.servlet.ServletContext;
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 java.io.IOException;
import java.util.Map;
import java.util.Set;

@WebServlet("/loginServlet")
public class LoginServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html;charset=UTF-8");
        ServletContext application = request.getServletContext();
        Map<String, Integer> map = (Map<String, Integer>) application.getAttribute("map");
        Set<String> keySet = map.keySet();
        for (String string:keySet) {
            response.getWriter().write(string +"==>"+ map.get(string) +"次</br>");
        }
    }
}

二、Listener监听器

2.1 概念

监听器:是一个类,监听另一个java类的状态的改变。(商店报警装置,房间失火喷水装置)

  • 由于是web端代码,所以只能监听web端应用对象(request/session/application)。

2.2 组成部分

  • 事件源:被监听的对象(request/session/application)。
  • 监听器:监听的对象(创建实现对应监听接口的类)。
  • 监听方法 : 实现监听接口重写的对应方法。
  • 事件对象的作用:可以获取到的事件源对象(在监听器中事件源对象)。

2.3 Web监听器

由于域对象添加方法与修改方法都是使用setAttribute(“key”,“value”)形式添加,所以在进行操作代码书写可能出现混淆,但是监听器不会混淆,监听器只会根据对应的事件执行对应的方法,只有当前对应的域对象中没有数据时调用setAttribute监听器才会执行对应的监听方法,如果已存在监听器之后执行的都是监听修改的方法(相同的key 监听器添加数据方法只执行一次),同理在删除对应数据时也一样,当对应域中不存在对应的key时,即使执行的对应的removeAttribute方法,监听器对应方法也不会执行。

2.3.1 监听请求域对象监听器

2.3.1.1 监听请求域对象的创建和销毁
  • ServletRequestListener:监听request请求域对象的创建和销毁。

MyRequestListener.java

//监听请求域对象的创建与销毁
//先实现接口,提供配置文件
public class MyRequestListener implements ServletRequestListener {

	// 创建请求对象的监听方法
	public void requestInitialized(ServletRequestEvent sre) {
		// ServletRequestEvent代表请求事件对象
		// 可以获取请求对象相应的数据
		// 获取当前发生事件的请求对象
		ServletRequest servletRequest = sre.getServletRequest();
		// 之后可以通过请求对象获取请求域中相应的数据
		System.out.println("请求对象创建");
	}

	// 销毁请求对象的监听方法
	public void requestDestroyed(ServletRequestEvent sre) {
		System.out.println("请求对象销毁");
	}
}

web.xml配置监听器

	<!-- 配置监听器-->
	<listener>
		<listener-class>com.yh.listener.MyRequestListener</listener-class>
	</listener>	
2.3.1.2 监听request请求域对象中的数据
  • ServletRequestAttributeListener:监听request请求域对象中的数据。

MyRequestAttrListener.java

//监听请求域对象中数据的操作
public class MyRequestAttrListener implements ServletRequestAttributeListener {
	// 当调用setAttribute时添加新的数据时执行的方法
	public void attributeAdded(ServletRequestAttributeEvent srae) {
		// ServletRequestAttributeEvent 代表域对象值改变事件对象
		// 可以从该对象中获取改变时域对象的值
		// 获取对应的请求对象
		ServletRequest servletRequest = srae.getServletRequest();
		// 获取发生事件的key
		String name = srae.getName();
		// 获取发生事件的value
		Object value = srae.getValue();
		System.out.println("域对象添加数据:" + name + "=>" + value);
	}

	// 当调用removeAttribute删除数据时执行的方法
	public void attributeRemoved(ServletRequestAttributeEvent srae) {
		// 获取发生事件的key
		String name = srae.getName();
		// 获取发生事件的value
		Object value = srae.getValue();
		System.out.println("域对象删除数据:" + name + "=>" + value);
	}

	// 当调用setAttribute时修改已有数据时执行的方法
	public void attributeReplaced(ServletRequestAttributeEvent srae) {
		// 获取发生事件的key
		String name = srae.getName();
		// 获取发生事件的value
		Object value = srae.getValue();
		//修改获取的是修改前的数据
		System.out.println("域对象修改数据:" + name + "=>" + value);
		//修改后的数据可以通过获取请求对象之后获取对应的key
		ServletRequest servletRequest = srae.getServletRequest();
		System.out.println("修改后");
		System.out.println("域对象修改数据:" + name + "=>" + servletRequest.getAttribute(name));
	}
}

web.xml配置监听器

	<listener>
		<listener-class>com.yunhe.listener.MyRequestAttrListener</listener-class>
	</listener>

2.3.2 监听application应用域对象监听器

  • ServletContextListener:监听应用于对象的创建与销毁
  • ServletContextAttributeListener:监听域对象中数据的创建、修改、删除。

MyApplicationListener.java

//ServletContextListener 监听应用于对象的创建与销毁
//ServletContextAttributeListener  监听域对象中数据的创建 修改 删除
public class MyApplicationListener implements ServletContextListener, ServletContextAttributeListener {

	// 当服务器创建时执行
	public void contextInitialized(ServletContextEvent sce) {
		// ServletContextEvent 代表发生事件的对象
		// 可以由该获取域对象 继续操作获取域对象中数据
		ServletContext servletContext = sce.getServletContext();
		System.out.println("应用域对象创建");
	}

	// 当服务器正常关闭时时执行
	public void contextDestroyed(ServletContextEvent sce) {
		// 注意:应用域对象随服务器的启动与关闭创建与销毁
		System.out.println("应用域对象销毁");
	}

	// 监听域对象数据的添加
	public void attributeAdded(ServletContextAttributeEvent scae) {
		// ServletContextAttributeEvent 代表发生事件的对象
		// 获取key
		String name = scae.getName();
		// 获取value
		Object value = scae.getValue();
		System.out.println("应用域对象存值:" + name + "=>" + value);
	}

	// 监听域对象数据的修改
	public void attributeReplaced(ServletContextAttributeEvent scae) {
		// 获取key
		String name = scae.getName();
		// 获取value
		Object value = scae.getValue();
		Object newValue = scae.getServletContext().getAttribute(name);
		System.out.println("应用域对象修改数据:将"+name+"  "+value+"=>"+newValue);
	}

	// 监听域对象数据的删除
	public void attributeRemoved(ServletContextAttributeEvent scae) {
		// 获取key
		String name = scae.getName();
		// 获取value
		Object value = scae.getValue();
		System.out.println("应用域对象删除数据:"+name+"=>"+value);
	}
}

web.xml配置监听器

  <listener>
    <listener-class>com.yunhe.listener.MyApplicationListener</listener-class>
  </listener>

2.3.3 监听session会话域对象监听器

2.3.3.1 监听session会话域的对象和数据
  • HttpSessionListener:监听session会话域对象的创建与销毁
  • HttpSessionAttributeListener:监听session会话域对象中数据的操作。

MySessionListener.java

//@WebListener注解形式创建listener对象 在加载类时会自定进行监听器的配置
//HttpSessionListener  session对象创建销毁监听器接口
//HttpSessionAttributeListener  session对象值操作监听器接口
@WebListener
public class MySessionListener implements  HttpSessionListener, HttpSessionAttributeListener {

    //HttpSessionListener 提供的监听session创建的方法
    @Override
    public void sessionCreated(HttpSessionEvent se) {
        //HttpSessionEvent 代表发生事件时的对象 可以获取相应的信息
        HttpSession session = se.getSession();
        System.out.println("session创建:"+session.getId());
    }

    //HttpSessionListener 提供的监听session销毁的方法
    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        HttpSession session = se.getSession();
        System.out.println("session销毁:"+session.getId());
    }

    //HttpSessionAttributeListener 提供监听session对象添加新的数据时执行
    @Override
    public void attributeAdded(HttpSessionBindingEvent sbe) {
        //HttpSessionBindingEvent 代表监听对象
        //获取添加数据的key
        String name = sbe.getName();
        //获取添加数据的value
        Object value = sbe.getValue();
        System.out.println("session添加数据:"+name+"=>"+value);
    }
    //HttpSessionAttributeListener 提供监听session对象修改数据时执行
    @Override
    public void attributeReplaced(HttpSessionBindingEvent sbe) {
        //获取session对象
        HttpSession session = sbe.getSession();

        //获取添加数据的key
        String name = sbe.getName();
        //获取添加数据的value
        Object value = sbe.getValue();
        Object newValue = session.getAttribute(name);
        System.out.println("session修改数据:"+name+" "+value+"=>"+newValue);
    }
    HttpSessionAttributeListener 提供监听在session对象删除数据时执行
    @Override
    public void attributeRemoved(HttpSessionBindingEvent sbe) {
        //获取添加数据的key
        String name = sbe.getName();
        //获取添加数据的value
        Object value = sbe.getValue();
        System.out.println("session删除数据:"+name+"=>"+value);
    }
}
2.3.3.2 监听HttpSession中JavaBean状态的改变
  • 概述

    监听器:是监听HttpSession域对象的,操作的都是JavaBean,在web.xml中不需要进行配置(编写JavaBean类实现监听器接口)。

    • 因为session域对象存储数据为Object,所以是可以存储自定义对象的,有时我们需要知道session在什么时候绑定了对象,就可以使用指定的对象实现HttpSessionBindingListener接口完成需求。
  • JavaBean类实现HttpSessionBindingListener接口

    //创建session域对象要保存对象 并实现HttpSessionBindingListener接口
  public class User implements HttpSessionBindingListener {
      private String username;
      private String password;
  
      public User() {
      }
  
      public User(String username, String password) {
          this.username = username;
          this.password = password;
      }
  
      public String getUsername() {
          return username;
      }
  
      public void setUsername(String username) {
          this.username = username;
      }
  
      public String getPassword() {
          return password;
      }
  
      public void setPassword(String password) {
          this.password = password;
    }
  
      @Override
      public String toString() {
          return "User{" +
                  "username='" + username + '\'' +
                  ", password='" + password + '\'' +
                  '}';
      }
  
      //当当前类的对象被session绑定时 执行的方法
      @Override
      public void valueBound(HttpSessionBindingEvent event) {
          // HttpSessionBindingEvent 发生事件的事件对象
          //获取绑定时的key
          String name = event.getName();
          //获取绑定时的value
          Object value = event.getValue();
          System.out.println("session绑定了User对象:"+name+"=>"+value);
      }
  
      //当当前类的对象被session解绑时  执行的方法
      @Override
      public void valueUnbound(HttpSessionBindingEvent event) {
  
          String name = event.getName();
          Object value = event.getValue();
          System.out.println("session解绑了User对象:"+name+"=>"+value);
      }
  }
2.3.3.3 监听session对象的钝化与活化
  • 概述

    session对象存储在服务器中,但是当服务关闭时可能还存在很多没有到达过期时间的session对象,导致session没有到达过期时间就会被删除,tomcat容器提供了钝化与活化方法解决这一问题。

    • session钝化:通过序列化的方式将session对象以及存储的数据转存到硬盘中。
    • session活化:当服务器启动时,通过反序列化将session对象读取到内存中。
  • JavaBean类实现HttpSessionActivationListener接口

    同样需要将session存储的对象实现对应的监听方法,为了完成钝化与活化要求对象实现序列化接口。

    /**
     * 目的:完成钝化和活化
     * 钝化是序列化到磁盘上(实现Serializable接口)
     * 
     * @author Administrator
     */
    public class User2 implements Serializable,HttpSessionActivationListener{
    	
    	private String username;
    	
    	public String getUsername() {
    		return username;
    	}
    
    	public void setUsername(String username) {
    		this.username = username;
    	}
    
    	/**
    	 * 活化
    	 */
    	public void sessionDidActivate(HttpSessionEvent arg0) {
    		System.out.println("User2被活化了...");
    	}
    	
    	/**
    	 * 钝化
    	 */
    	public void sessionWillPassivate(HttpSessionEvent arg0) {
    		System.out.println("User2被钝化了...");
    	}
    	
    
    }
2.3.4.3 好处

对session机制的优化

  • 可以在META-INF下创建context.xml设置钝化保存位置
	<Context>
		<Manager className="org.apache.catalina.session.PersistentManager" maxIdleSwap="1">
		<Store className="org.apache.catalina.session.FileStore" directory="D://yunhe"/>
		</Manager>
	</Context>

钝化后会使用sessionid作为文件名进行保存,只有在服务器再次启动,客户端再次请求时服务器获取sessionid之后进行活化。

2.4 案例

  • 需求

    使用监听器完成 在线人数的展示。

    • 创建session监听器以及jsp页面
    • 当请求jsp页面时 jsp页面展示当前session对象个数
    • 在jsp页面书写按钮 点击按钮删除当前会话
  • 代码演示

count.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>count</title>
</head>
<body>
	<!-- 在当前页面显示在线人数 -->
	当前在线人数【${count}】人

	<!-- 书写安全退出按钮 -->
	<button>
		<a href="quit">安全退出</a>
	</button>
</body>
</html>

QuitServlet.java

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;

@WebServlet("/quitServlet")
public class QuitServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
       
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		this.doPost(request, response);
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		response.setCharacterEncoding("UTF-8");
		response.setContentType("text/html;charset=UTF-8");
		request.getSession().invalidate();
		response.getWriter().write("退出成功");
	}
}

CountListener.java

import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

@WebListener
public class CountListener implements HttpSessionListener {
	private int count = 0;

	public void sessionCreated(HttpSessionEvent se) {
		// 该方法主要执行就说明有session会话创建
		count++;
		//获取会话对象存入
		HttpSession session = se.getSession();
		session.setAttribute("count", count);
	}

	public void sessionDestroyed(HttpSessionEvent se) {
		// 该方法主要执行就说明有session会话创建
		count--;
		//获取会话对象存入
		HttpSession session = se.getSession();
		session.setAttribute("count", count);
	}
}

每日一点点进步
不进则退

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

璃尔 °

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值