Filter过滤器 和 Listener

Filter过滤器

什么是过滤器

简而言之,就是一个门,要想过去必须通过这道门的考验。

过滤器会对浏览器所有的请求进行过滤,让满足条件的请求通行,起到一个拦截的作用

过滤器的作用

  1. 对一些敏感词汇进行过滤
  2. 统一设置编码,提高了代码的复用性
  3. 自动登录

过滤器的使用

  1. 实现 Filter类,(javax.servlet.* 包下面的)
public class MyLoginFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        //项目启动的时候就会调用该方法,与ServletContext类似
    }
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        //具体过滤的内容和操作,每次过滤都会调用这个方法

        //过滤完之后,要放行,使用该方法,如果不使用,就会一直拦截
        filterChain.doFilter(servletRequest,servletResponse);
    }
    @Override
    public void destroy() {
        //当销毁时候调用,服务器关闭的时候销毁
    }
}
  1. 注册过滤器

在web.xml中注册,与Servlet类似

<filter>
        <filter-name>AutoLoginFilter</filter-name>
        <filter-class>com.filter.AutoLoginFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>AutoLoginFilter</filter-name>
        <url-pattern>/*</url-pattern>
        <!--表示拦截所有的请求-->
</filter-mapping>

Filter的生命周期

  • Filter的创建

当项目启动的时候就会创建,并且调用init(FilterConfig filterConfig) 方法

  • Filter的销毁

当服务器关闭的时候销毁

Filter的执行顺序

在这里插入图片描述

当有多个过滤器时候,执行顺序与 web.xml文件中<Filter-mapping>标签的顺序有关
 <filter>
        <filter-name>AutoLoginFilter01</filter-name>
        <filter-class>com.filter.AutoLoginFilter</filter-class>
    </filter>
    <filter>
        <filter-name>AutoLoginFilter02</filter-name>
        <filter-class>com.filter.AutoLoginFilter</filter-class>
    </filter>
    <filter>
        <filter-name>AutoLoginFilter03</filter-name>
        <filter-class>com.filter.AutoLoginFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>AutoLoginFilter01</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <filter-mapping>
        <filter-name>AutoLoginFilter03</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <filter-mapping>
        <filter-name>AutoLoginFilter02</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
顺序为 1---->3----->2

Filter细节

  • Filter类中doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)方法

chain.doFilter(req,resp); //放行的方法

  • init方法的参数 FilterConfig , 可以用于获取filter在注册的名字 以及初始化参数。 其实这里的设计的初衷与ServletConfig是一样的。

  •   <url-pattern>/*</url-pattern> 写法格式与servlet一样。
    
  1. 全路径匹配
    /LoginServlet
  2. 以 / 开头,以 * 号结尾
    /LoginServlet/*
  3. 以 * 号开始,以后缀名结尾
    *.do *.jsp *.html

4.dispatch 标签的用法

<filter-mapping>
        <filter-name>AutoLoginFilter02</filter-name>
        <url-pattern>/*</url-pattern>
        <dispatch> REQUEST </dispatch>
    </filter-mapping>

REQUEST : 只要是请求过来,都拦截,默认就是REQUEST
FORWARD : 只要是转发都拦截。
ERROR : 页面出错发生跳转
INCLUDE : 包含页面的时候就拦截。

自动登录案例

案例分析

在这里插入图片描述
在这里插入图片描述

环境搭建

  • 数据库环境搭建
    建立一张用户表user,存放用户名,密码,等各种用户信息
  • jar包的导入,使用数据库连接池,beanutils工具类,jstl标签库,dbutils简化crud操作

这里是引用

代码实现

  • 登录页面 login.jsp 搭建
<html>
<head>
    <title>登录页面</title>
</head>
<body>
<form action="LoginServlet" method="post">
    <table>
        <tr>
            <td>用户名:</td>
            <td><input type="text" name="username" ></td>
        </tr>
        <tr>
            <td>密码:</td>
            <td><input type="password" name="password" ></td>
        </tr>
        <tr>
            <td>日期:</td>
            <td><input type="date" name="date" ></td>
        </tr>
        <tr>
            <td colspan="2"><input type="checkbox" name="auto" >自动登录</td>
        </tr>
        <tr>
            <td colspan="2"><input type="submit" name="登录" ></td>
        </tr>
    </table>
</form>
</body>
</html>
  • LoginServlet.java 类,判断登录成功或失败,是否选择了自动登录,选择了进行向客户端添加cookie
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        try {
            request.setCharacterEncoding("utf-8");
            //1、使用 beanutils 来获取参数,并封装在 user类中。封装在javaBean中
            //注意,只能序列化字符串,如果是日期类型的话,提供了一个接口,先注册自己的接口
//            Converter myDateConverter = new MyDateConverter();
            ConvertUtils.register(new MyDateConverter(), Date.class);
            Map map = request.getParameterMap();
            User user = new User();
            //将数据封装到User中去
            BeanUtils.populate( user,map);

            //2、查询数据库,判断是否用户名密码是否正确
            UserDao userDao = new UserDaoImpl();
            UserBean userBean = userDao.login(user); //如果数据库中有该用户,返回该用户的所有信息,封装成UserBean


            if (userBean != null){
                //判断要不要自动登录
                String auto = request.getParameter("auto");
                System.out.println(auto);

                if(auto != null){ //如果是要自动登录,向客户端存cookie
                    //登陆成功,将用户名密码存储到cookie中
                    System.out.println(user.getUsername()+"#"+user.getPassword());
                    Cookie cookie = new Cookie("user", user.getUsername()+"#"+user.getPassword());
                    cookie.setMaxAge(60*60*24*7);  //设置生存周期
                    response.addCookie(cookie);
                }
                //3.将用户信息UserBean保存在作用域中,用于前端页面的判断
                HttpSession session = request.getSession();
                System.out.println(userBean.toString());
                session.setAttribute("user",userBean);

                //4、跳转页面重定向
                response.sendRedirect("index.jsp");
            }else {
                response.getWriter().write("用户名或密码错误");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
  • 登录首页 index.jsp,根据seeion作用域中是否有UserBean进行判断
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
  <head>
    <title>首页</title>
  </head>
  <body>
    <c:if test="${not empty user }">
      您好,欢迎您,${user.username}
    </c:if>
    <c:if test="${empty user }">
      您好,请登录。
    </c:if>
  </body>
</html>

  • 过滤器AutoLoginFilter 过滤器的使用

过滤器的核心不是说拦截不给,还是说放行显示。核心是在放行之前,帮用户完成自动登录的功能

  • 过滤器实现的思路
  1. 先根据作用域中是否有UserBean,有,就不用取cookie了, 直接放行
  2. 如果session失效了,那么就取 cookie。
    1. 判断有无cookie,没有,直接放行
    2. 有,读取cookie,用户名和密码,进而实现登录的功能
      然后再将UserBean保存在session中,以至于方便下一次没过期前还能使用
//过滤时候调用这个方法
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
        try {
            //实现自动登录的功能
            //1、获取cookie
            HttpServletRequest request = (HttpServletRequest) req;
            Cookie[] cookies = request.getCookies();
            //自定义一个工具类,cookie有好多,要得到想要的,参数是cookies数组,还有想要的cookie名字,得到我们想要的cookie
            Cookie cookie = CookieUtil.getCookie(cookies, "user");
            User user = new User();

            HttpSession session = request.getSession();
            UserBean userBean = (UserBean)session.getAttribute("user");

            //判断作用域中是否还有userbean,浏览器不关闭的情况下直接访问首页,如果不判断会一直重新登录一遍,
            //没有必要,因为还在一个会话中,当前会话还没失效,直接放行就可以
            if(userBean != null){
                //当前会话还有效,浏览器没有关闭直接放行,不用再登录一遍了
                chain.doFilter(req,resp);
            }else {
                //当前会话失效,浏览器关闭了,
                //进而再根据cookie判断是否已经登录过

                if (cookie != null) { //说明不是第一次登录,执行自动登录的功能,
                    //判断还要不要登录
                    String value = cookie.getValue();
                    String[] split = value.split("#");
                    user.setUsername(split[0]);
                    user.setPassword(split[1]);
                    UserDao userDao = new UserDaoImpl();
                    userBean = userDao.login(user);

                    //将用户信息存到session中
                    session.setAttribute("user",userBean);

                    //放行
                    chain.doFilter(req,resp);

                }else{
                    //第一次登录,也放行
                    chain.doFilter(req,resp);
                }
            }
        } catch (SQLException e) {
            e.printStackTrace();
            chain.doFilter(req,resp);
        }
    }

BeanUtils的使用

BeanUtils作用就是讲在登录提交过来的信息封装到类中,简化了代码
BeanUtils的使用
BeanUtils.populate(bean, map);

注册自己的日期转换器
ConvertUtils.register(new MyDateConverter(), Date.class);

//转化数据
Map map = request.getParameterMap();
UserBean bean = new UserBean();

转化map中的数据,放置到bean对象身上
BeanUtils.populate(bean, map);

 			request.setCharacterEncoding("utf-8");
            //1、使用 beanutils 来获取参数,并封装在 user类中。封装在javaBean中
            //注意,只能序列化字符串,如果是日期类型的话,提供了一个接口,先注册自己的接口
//            Converter myDateConverter = new MyDateConverter();
            ConvertUtils.register(new MyDateConverter(), Date.class);
            //获得map集合
            Map map = request.getParameterMap();
            User user = new User();
            //将数据封装到User中去
            BeanUtils.populate( user,map);
注意:这里是只能快速的封装字符串数据,像日期的类型不可以直接封装,
但是BeanUtils提供了向外的接口,可以实现自定义的类去封装自己相封装的类型
public class MyDateConverter implements Converter {
	@Override
	// 将value 转换 c 对应类型
	// 存在Class参数目的编写通用转换器,如果转换目标类型是确定的,可以不使用c 参数
	public Object convert(Class c, Object value) {
		String strVal = (String) value;
		// 将String转换为Date --- 需要使用日期格式化
		DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
		try {
			Date date = dateFormat.parse(strVal);
			return date;
		} catch (ParseException e) {
			e.printStackTrace();
		}
		return null;
	}
}

Listener监听器

监听器的作用

就是监听某一事件的发生,状态的改变

监听器的原理(接口回调技术)

在没考虑接口回调的时候,如果A类中想要调用B类的方法,首先new B() 的对象,然后再调用B类的方法,或者直接将该方法定义成静态的,用类名.方法名调用。
前提:你要知道B类的名字,B类必须存在
如果A类在1998年写的,当时还不知道B类,B类在2020年写的,这种情况下没办法调用。 所以A类在建立的时候提供一个接口,并作为方法的参数。

A在执行循环,当循环到5的时候, 通知B。
事先先把一个对象传递给 A , 当A 执行到5的时候,通过这个对象,来调用B中的方法。 但是注意,不是直接传递B的实例,而是传递一个接口的实例过去。
体现了态的思想
在这里插入图片描述

八大类监听器

三大作用域监听器

  • ServletRequestListener 监听器

监听请求,浏览器发出的任何请求都会监听到
访问html,访问jsp,访问Servlet都会监听

  1. request的创建:就会加载监听器的实现类,并且调用requestDestroyed()方法
  2. 服务器对请求作出响应之后销毁,并调用监听器的实现类。并且调用requestInitialized()方法
  1. 使用–> 在web.xml文件中注册
<listener>
        <listener-class>com.listener.MyListener</listener-class>
</listener>
  1. 自定义实现类去实现该接口ServletRequestListener
public class MyListener implements ServletRequestListener {
    @Override
    public void requestDestroyed(ServletRequestEvent servletRequestEvent) {
      //在发生请求的时候调用该方法
    }

    @Override
    public void requestInitialized(ServletRequestEvent servletRequestEvent) {
        //服务器对请求做出相应之后就会销毁,代用该方法
    }
}
  • HttpSessionListener 监听器

会话监听器,只要会话的建立就可以监听----> 监听在线人数
html 不会监听、jsp会、Servlet会

  1. 创建HttpSession对象,getSession(),调用监听器的实现类,并且运行sessionCreated()方法
  2. session的销毁
    会话超时,会销毁session。
    服务器的非正常关闭,销毁session
    注意,服务器的正常关闭是使session钝化。
  1. 在web.xml文件中注册
<listener>
        <listener-class>com.listener.MyListener</listener-class>
</listener>
  1. 自定义实现类去实现该接口ServletRequestListener
public class MyListener implements HttpSessionListener {
    @Override
    public void sessionCreated(HttpSessionEvent httpSessionEvent) {
        //在创建会话(getSession())时候调用该方法
    }
    @Override
    public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
        //session销毁的时候调用
        //1.会话超时 2.非正常话关闭服务器
    }
}
  • ServletContextListener 监听器

监听ServletContext类创建的时候,以及ServletContext类销毁的时候
1.ServletContext 创建:当服务器启动的时候
2. ServletContext 销毁:关闭服务器时候、项目移除的时候

  1. 在web.xml中注册
<listener>
        <listener-class>com.listener.MyListener</listener-class>
</listener>
  1. 自定义实现类去实现该接口ServletRequestListener
public class MyListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        //ServletContext对象创建的时候调用该方法
    }

    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {
        //ServletContext对象销毁的时候调用该方法
    }
}

三大作用域中属性的添加、删除、替换状态的监听器

使用过程和前三个一样

  • ServleRequestAttributeListener

在request作用域中添加、删除、修改属性,分别调用监听器的三个方法

  • HttpSessionAttributeListener

在session作用域中添加、删除、修改属性,分别调用监听器的三个方法

  • ServletContextAttributeListener

在ServletContext作用域中添加、删除、修改属性,分别调用监听器的三个方法

两大作用域 绑定和解绑 and 钝化和活化

这两个作用域不用注册,直接使用JavaBean去实现他就可以
  • HttpSessionActivationListener 钝化,活化
  1. 用于监听现在session的值是钝话,还是活化,作用是监听值的钝化或者活化,并不是让session的值钝化或者活化。那怎么样让session的值钝化或者活化呢?
  2. 当前的会话中session的值会很多,如果一直不用会在内存中,消耗内存,这时候考虑把他存在硬盘上,就是钝化(序列化),等到再用的时候,再从硬盘中读取,就是活化(反序列化)

怎么样时session中的值钝化或者活化呢??

  • 正常关闭服务器
  • 配置文件
  1. 在Tomcat里面的 conf/context.xml 里面配置,对所有的项目生效
  2. 在conf/Catalina/localhost/context.xml 配置,对 localhost生效。 localhost:8080
  3. 在自己的web工程项目中的 META-INF/context.xml,只对当前的工程生效。
    maxIdleSwap : 1分钟不用就钝化
    directory : 钝化后的那个文件存放的目录位置。
<Context>
		<Manager className="org.apache.catalina.session.PersistentManager" maxIdleSwap="1">
			<Store className="org.apache.catalina.session.FileStore" directory="hou"/>
		</Manager>
	</Context>
  • HttpSessionBindingListener监听器
  1. 使用JavaBean去实现接口

在这里插入图片描述

  1. 什么时候绑定,什么时候解绑
//绑定
User user = new User();
user.setName("da");
session.setAttribute("a",User);
//解绑
session.removeAttribute("a");

案例:监听在线人数

  • 需求

当一个用户登录成功之后,显示人登录的名称,和当前在线的人数,以及在线人数的所有人的名字。

  • 分析
  1. 第一步:首先实现用户登录的功能
  1. 输入用户名和密码之后,查询数据库,看是否有该用户,没有返回null,提示用户名密码错误,有返回该用户的所有信息,进入登录页面
  1. 第二步:显示登录用户的名称
  1. 该名称都在javaBean对象中,怎么在这个页面将该对象中的数据读取出来?
    将在数据库查询的用户信息保存在作用域中,用el表达式读取数据。
  1. 第三步:显示在线人数,和在线人数的所有的名字
  1. 显示在线人数和所有用户,只要把所有的用户(javaBean)数据传过来,然后进行遍历每一个用户,再分别输出每一个用户的名字即可。
  2. 怎么样把所有用户传过来呢,显然要用一个集合,存储所有的用户(javaBean),在登录页面直接遍历该集合,然后使用size()函数获得集合中的有几个数据,进而就能求出所有在线用户的人数。
  1. 第四步:每添加一个用户,就把用户存到list集合中,然后再把集合传到登录页面,怎么实现呢?
  1. 使用监听器-------》三大作用域中值的状态监听 HttpSessionAttributeListener
  2. 为什么选中它呢?根据需求而做出的选择。
    当一个用户登录,他会打开浏览器,进行登录,也就是说一个浏览器窗口也只是提供给一个用户用,如果用户在在没退出的情况下再进入登录页面登录,就要替换之前登陆的用户。
    只有在不同的会话中(不同的会话值指的是不同的浏览器打开,或者同一浏览器关闭再打开,如果一个浏览器打开没关闭再接着打开统一浏览器,也是同一会话),才表示是另一个人在用,而不是同一个人有两个用户。
    显然,不同的会话表示不同的用户,同一会话两次登录会替换前一个用户,必须保证一个会话一个用户,而一个会话一个用户使用HttpSessionListener监听器是不合适的,如果一个用户打开了一个会话,直接输入登录页面地址,也会jsp会自动建立一个session,就算没登录也多了一个人,还有就是用户登录成功,点击退出登录,没办法立即减去一个用户,只能等到30分钟之后session自动销毁。
    所以要用HttpSessionAttributeListener
  1. 第五步:根据在session作用域中值的状态向list集合中添加用户,删除用户,和更改用户 。 可以在登录页面直接使用EL表达式获取
  1. list集合中的数据,是针对不同的会话,多以要把list结合存入比session更大的作用域中才可以
  2. 使用监听器,当向session中存用户,就在监听器中向list集合添加添加用户。
  3. list集合怎么来,在 ServletContext作用域中取。
    如果没有,就是第一次添加用户。创建集合 new ArrayList() 添加用户add(),
    如果有,直接在其基础上再添加用户
  4. 删除集合元素使用remove(),
  5. 替换集合中的元素,是先删除原来的元素,再添加新的元素,没有直接的替换方法
    在监听器中替换的方法se.getValue();是获得原来的旧值,获取新的值要使用session.getAttribute()获取
  6. 修改完之后,再向servletContext作用域中重新存值
  1. 第六步:就是第五步做法的第二种方法

将List集合封装在自定义UserList类中,向外提供添加。替换、删除的方法。

注意,要把属性和方法定义成静态的才可以,这样数据才在该类的所有实现类中才可以共享,
数据在内存中才会只有一份,这样的话就不用存在作用域中了,在登录页面只要导入这个类,
然后使用(类名.属性) 调用里面的方法或者属性就可以了。
  • 效果
    在这里插入图片描述
  • 代码实现
    MyServletListener.java 监听器
@WebListener
public class MyListener implements HttpSessionAttributeListener {
    @Override
    public void attributeAdded(HttpSessionBindingEvent se) {
        System.out.println("session添加值了");
        HttpSession session = se.getSession();
        ServletContext servletContext = session.getServletContext();
        List list = (List)servletContext.getAttribute("list");
        System.out.println(list);
        if(list != null){
            //list集合中有没有人,有,继续添加
            //向集合中添加一个用户
            list.add((User)se.getValue());
            servletContext.setAttribute("list",list);
        }else{
            //没有,添加第一个
            List<User> list1 = new ArrayList<>();
            boolean add = list1.add((User) se.getValue());
            servletContext.setAttribute("list",list1);
        }
    }

    @Override
    public void attributeRemoved(HttpSessionBindingEvent se) {
        System.out.println("sessionti移除值了");
        HttpSession session = se.getSession();
        ServletContext servletContext = session.getServletContext();
        List list = (List)servletContext.getAttribute("list");

        list.remove((User)se.getValue());
        servletContext.setAttribute("list",list);
    }

    @Override
    public void attributeReplaced(HttpSessionBindingEvent se) {
        System.out.println("session替换值了");
        //在同一个会话中,切换用户,当前用户就会被新用户替换
        HttpSession session = se.getSession();
        ServletContext servletContext = session.getServletContext();
        List list = (List)servletContext.getAttribute("list");

        //获得session中的旧对象
        User user = (User) se.getValue();
        //删除旧对象
        list.remove(user);
        //获得session中的新对象
        User user1 = (User)session.getAttribute("user");
        //向集合中添加新对象
        list.add(user1);
        servletContext.setAttribute("list",list);
    }
}

LoginServlet.java 登录

protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		response.setContentType("text/html;charset=utf-8");
		request.setCharacterEncoding("utf-8");
		String name = request.getParameter("username");
//		System.out.println(name); //没填的时候返回空字符串,不是返回null

		if(name != null && name != ""){
			//有用户在线
			HttpSession session = request.getSession();
			User user = new User();

			//将数据封装到User类中
			user.setName(name);

			//讲User保存到作用域中,在这里存值,会调用监听器中的attributeAdd()方法
			session.setAttribute("user", user);

			//跳转页面
			response.sendRedirect("index01.jsp");

		}else{
			response.getWriter().write("请输入用户名");
		}
	}

ExitServlet.java 退出登录

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.getSession().removeAttribute("username");//从session中移除对象
        response.sendRedirect("login.jsp");//重定向到用户登录页面
    }

index01.jsp 登录首页

<body>
<%
    List alist = (List) application.getAttribute("list");
%>
<c:if test="${not empty user }">
    欢迎您,${user.name } &nbsp;&nbsp;&nbsp;
    <a href="ExitServlet">退出登录</a><br>
    当前在线人数:<%=alist.size() %>&nbsp;<br>

    在线用户名单:<br>
    <select multiple="multiple" name="list" style="width:200px;height:250px">
        <c:forEach items="${list}" var="user1">
            <option>${user1.name }</option>
        </c:forEach>
    </select>
</c:if>

<c:if test="${empty user}">
    <a href="login.jsp">请输入用户名登录</a>
</c:if>

</body>

login.jsp登录页面

<form action="loginServlet" method="post">
	<table>
		<tr>
			<td>用户名</td>
			<td><input type="text" name="username"></td>
		</tr>
		<tr>
			<td colspan="2"><input type="submit" value="登录"></td>
		</tr>
	</table>
</form>
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值