Filter&Listener

作者: h2coder | 来源:发表于2022-11-18 15:37 被阅读0次

Filter过滤器

概念

  • Filter表示过滤器,是JavaWeb的三大组件之一,Servlet、Filter、Listener。Filter和Listener都是辅助Servlet的组件。
image-20221102185648847.png

作用

  • 过滤器可以将资源的请求进行拦截,实现一些特殊功能的通用操作
    • 统一编码处理(POST请求中文乱码,字符集统一设置为UTF-8)
    • VIP功能(只能允许VIP用户访问)
    • 敏感字处理(敏感字替换为*等)

Filter快速入门

image-20221102203804502.png
  • 注解方式

    • 定义Demo1Servlet类,继承于Servlet
    • 类头添加@WebServlet注解,并设置value值为 /demo1
    • 在doGet和doPost方法中,向控制台打印一句话
@WebServlet(value = "/demo1")
public class Demo1Servlet extends HttpServlet {
    private static final long serialVersionUID = -4692985281158768196L;

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("访问web资源 demo1");
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
}
  • 新建Demo1Filter类,实现Filter接口
  • 类头添加@WebFilter注解,并设置value值为demo1(需要拦截的资源路径)
  • 复写doFilter方法,chain.doFilter代表放行(不拦截),在放行前后打印一句话
  • 访问该项目的demo1路径的Servlet
@WebFilter(value = "/demo1")
public class Demo1Filter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("Demo1Filter 过滤前逻辑...");
        chain.doFilter(request, response);
        System.out.println("Demo1Filter 过滤后逻辑...");
    }

    @Override
    public void destroy() {
    }
}
  • XML方式

    • 类头没有注解,其他部分和注解方式一致
public class Demo3Filter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("Demo3Filter 过滤前逻辑...");
        chain.doFilter(request, response);
        System.out.println("Demo3Filter 过滤后逻辑...");
    }

    @Override
    public void destroy() {
    }
}
  • web.xml中配置Filter
  • filter标签,配置过滤器的名称
  • filter-mapping标签,配置拦截路径
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <!-- XML方式配置Filter过滤器 -->
    <!-- 配置过滤器的名称 -->
    <filter>
        <filter-name>demo3_filter</filter-name>
        <filter-class>com.itheima.filter.Demo3Filter</filter-class>
    </filter>
    <!-- 配置拦截路径 -->
    <filter-mapping>
        <filter-name>demo3_filter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
</web-app>
  • 结果
Demo1Filter 过滤前逻辑...
访问web资源 demo1
Demo1Filter 过滤后逻辑...

Filter执行流程

规律

  • 访问web资源的时候,首先会经过过滤器执行放行前代码
  • 然后再访问web资源
  • 最后再返回到过滤器执行放行后代码

思考

  • 放行后访问对应资源,资源访问完成后,还会回到Filter中吗?

  • 如果回到Filter中,是重头执行还是执行放行后的逻辑呢?

    • 放行后逻辑
  • 如果过滤器代码如下,资源还会被访问吗?(不调用chain.doFilter)

    • 不会,不调用chain.doFilter代表不放行(拦截)
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    System.out.println("Demo1Filter 过滤前逻辑...");
    System.out.println("Demo1Filter 过滤后逻辑...");
}

Filter的拦截路径配置

  • /*,表示任意匹配,拦截所有的请求
  • /demo1,表示精确匹配,就只能拦截访问demo1路径的请求,其他资源不会被拦截
  • /user/*,表示目录匹配,只会拦截/user/目录下的所有请求
  • *.jsp,表示后缀匹配,只会拦截后缀为.jsp的请求
//任意匹配
@WebFilter(value = "/*")
//精确匹配
//@WebFilter(value = "/demo1")
//目录匹配
//@WebFilter(value = "/user/*")
//后缀名拦截
//@WebFilter(value = "*.jsp")
public class Demo1Filter implements Filter {
    //...
}

Filter过滤器的优先级

  • 注解方式配置,按照拦截器的名称进行自然排序
  • XML方式配置,按照XML配置的先后顺序

Filter过滤器链

image-20221102204934232.png
  • 再定义一个Demo2Filter类,并将Demo1Filter和Demo2Filter的路径匹配都改为/*,表示拦截所有
@WebFilter(value = "/*")
public class Demo1Filter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("Demo1Filter 过滤前逻辑...");
        chain.doFilter(request, response);
        System.out.println("Demo1Filter 过滤后逻辑...");
    }

    @Override
    public void destroy() {
    }
}

@WebFilter(value = "/*")
public class Demo2Filter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("Demo2Filter 过滤前逻辑...");
        chain.doFilter(request, response);
        System.out.println("Demo2Filter 过滤后逻辑...");
    }

    @Override
    public void destroy() {
    }
}
  • 结果
Demo1Filter 过滤前逻辑...
Demo2Filter 过滤前逻辑...
访问web资源 demo1
Demo2Filter 过滤后逻辑...
Demo1Filter 过滤后逻辑...

Filter实现全局,POST请求中文乱码问题

如果每个Servlet处理前,都需要先设置一次编码,就会出现大量的重复代码,并且后续如果需要修改,就需要多出。而Filter过滤器就可以实现统一处理

image-20221102211814132.png image-20221102212044223.png
@WebFilter(value = "/*")
public class EncodingFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        if (request instanceof HttpServletRequest) {
            HttpServletRequest req = (HttpServletRequest) request;
            String method = req.getMethod();
            //解决POST请求中文乱码问题
            if (method.equalsIgnoreCase("POST")) {
                request.setCharacterEncoding("UTF-8");
            }
        }
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {
    }
}

Filter实现登录权限拦截

image-20221102212826343.png
  • LoginServlet,处理登录
/**
 * 登录
 */
@WebServlet(value = "/loginServlet")
public class LoginServlet extends HttpServlet {
    private static final long serialVersionUID = -2612492748416781981L;

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //解决POST中文乱码问题(已在EncodingFilter过滤器中统一处理)
        //request.setCharacterEncoding("UTF-8");

        //获取请求参数
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        String remember = request.getParameter("remember");

        //是否勾选了记住我
        boolean isRemember = "1".equals(remember);

        //调用业务层查询用户信息
        UserService userService = new UserService();
        User user = userService.login(username, password);

        //判断用户信息是否为null
        if (user != null) {
            //保存到session域,数据进行共享,使其他登录过的页面,才可以获取到数据
            HttpSession session = request.getSession();
            session.setAttribute("username", user.getUsername());
            //不使用request域存储数据,那么使用重定向
            response.sendRedirect("brand.jsp");
        } else {
            //如果为null,则为登录失败,跳转到登录页面,提示错误信息
            //只有这个页面进行错误提示,所以使用请求转发
            request.setAttribute("loginMessage", "登录失败,用户名或密码错误");
            request.getRequestDispatcher("login.jsp").forward(request, response);
        }
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
}
  • LoginFilter,处理登录权限
/**
 * 登录过滤器,当访问需要登录后的页面,如果未登录,则拦截请求,跳转到登录页面
 */
@WebFilter("/*")
public class LoginFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        if (request instanceof HttpServletRequest) {
            HttpServletRequest req = (HttpServletRequest) request;
            //不需要的权限的资源的请求地址(白名单)
            String[] whiteUris = {"css", "imgs", "login.jsp", "register.jsp", "checkCodeServlet", "loginServlet", "registerServlet"};
            //获取请求路径
            String requestURI = req.getRequestURI();
            System.out.println("正在访问的资源 = " + requestURI);
            for (String uri : whiteUris) {
                //请求的资源,不需要登录,那么放行
                if (requestURI.contains(uri)) {
                    chain.doFilter(request, response);
                    return;
                }
            }
            //获取Session
            HttpSession session = req.getSession();
            //如果登录过,获取不为null
            Object username = session.getAttribute("username");
            //未登录,跳转到登录页面
            if (username == null) {
                //设置错误提示
                req.setAttribute("loginMessage", "请先登录");
                req.getRequestDispatcher("login.jsp").forward(request, response);
            } else {
                //已登录,放行
                chain.doFilter(request, response);
            }
        } else {
            chain.doFilter(request, response);
        }
    }

    @Override
    public void destroy() {
    }
}

Listener监听器

概念

  • Listener代表监听器,JavaWeb三大组件(Servlet、Filter、Listener)之一
image-20221102214313741.png

作用

  • Listener监听器可以监听Application、Session、Request这3个对象的创建、销毁,以及给其添加、修改、删除属性时,自动执行代码的组件

Listener监听器的分类

  • JavaWeb中提供了8个监听器

ServletContextListener使用

ServletContextListener监听器,对ServletContext对象的创建、销毁进行监听

  • Web应用启动时互调contextInitialized,销毁时调用contextDestroyed
  • 注意:程序重启时,会先销毁,再重新创建
/**
 * 程序上下文生命周期监听
 * 注意:程序重启时,会先销毁,再重新创建
 */
@WebListener
public class ContextLoaderListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("ContextLoaderListener 程序启动");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("ContextLoaderListener 程序销毁");
    }
}

HttpSessionListener使用

HttpSessionListener监听,对Session对象的创建、销毁进行监听

20221118154235.png

需求

  • 实现在线人数统计

问题

  • 浏览器关闭后,Session并没有马上销毁,所以导致监听器的sessionDestroyed方法,并不会及时回调

原因

  • Session有一个无操作多长时间后自动销毁的时间,默认为30分钟,可以将时间调小,例如为1分钟来进行测试

实现代码

  • web.xml,配置Session的自动销毁时间,单位为分钟
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <!--
        浏览器关闭后,Session并没有马上销毁,所以导致监听器的sessionDestroyed方法,并不会及时回调
        Session有一个无操作多长时间后自动销毁的时间,默认为30分钟,可以将时间调小,例如为1分钟来进行测试
    -->
    <!-- session的最大存活时间,单位为分钟,默认为30分钟 -->
    <session-config>
        <session-timeout>1</session-timeout>
    </session-config>
</web-app>
  • OnlineCountListener,在线人数统计监听器,继承于HttpSessionListener
/**
 * 在线人数统计监听器
 */
@WebListener
public class OnlineCountListener implements HttpSessionListener {
    public static final String ONLINE_COUNT_KEY = "onlineCount";

    @Override
    public void sessionCreated(HttpSessionEvent event) {
        ServletContext servletContext = event.getSession().getServletContext();
        System.out.println("有一位用户上线了,session id = " + event.getSession().getId());
        Integer onlineCount = (Integer) servletContext.getAttribute(ONLINE_COUNT_KEY);
        //第一个人登录,获取到的是null,那么初始化数量为1
        if (onlineCount == null) {
            onlineCount = 1;
        } else {
            //后续人数都增加1
            onlineCount = onlineCount + 1;
        }
        //更新到全局 ServletContext 中
        servletContext.setAttribute(ONLINE_COUNT_KEY, onlineCount);
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent event) {
        ServletContext servletContext = event.getSession().getServletContext();
        System.out.println("有一位用户下线了,session id = " + event.getSession().getId());
        Integer onlineCount = (Integer) servletContext.getAttribute(ONLINE_COUNT_KEY);
        if (onlineCount == null) {
            onlineCount = 0;
        } else {
            //有用户下线,那么数量减少1
            onlineCount = onlineCount - 1;
        }
        //更新到全局 ServletContext 中
        servletContext.setAttribute(ONLINE_COUNT_KEY, onlineCount);
    }
}
  • 05_online_count.jsp,JSP页面,输出在线人数
<%--
  Created by IntelliJ IDEA.
  User: If
  Date: 2022/11/18
  Time: 15:10
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>在线人数</title>
</head>
<body>
<h1>当前有<span><%=config.getServletContext().getAttribute("onlineCount")%></span>人在线</h1>
</body>
</html>

相关文章

  • Filter&Listener

    Filter:过滤器 Listener:监听器

  • Filter&Listener

    Filter:过滤器 概念 web中的过滤器:当访问服务器的资源时,过滤器可以将请求拦截下来,完成一些特殊的功能。...

  • Filter&Listener

    Filter过滤器 概念 Filter表示过滤器,是JavaWeb的三大组件之一,Servlet、Filter、L...

  • 第18讲.Filter&Listener

    学习摘要: 今天讲的内容web的两大组件:Filter(过滤器)和Listener(监听器),重点掌握Filter...

网友评论

    本文标题:Filter&Listener

    本文链接:https://www.haomeiwen.com/subject/miepxdtx.html