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

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