首页 / 资讯中心 / 文章详情

JavaWeb过滤器与监听器

JavaWeb过滤器与监听器 ★ FEATURED ARTICLE
第一部分过滤器Filter基础1.1 过滤器的概念⭐核心强调过滤器是JavaWeb三大组件之一与Servlet很相似。它是用来拦截请求的而不是处理请求的。请求处理流程 当用户请求某个Servlet时会先执行部署在这个请求上的Filter。如果Filter放行→ 继续执行用户请求的Servlet如果Filter不放行→ Servlet将不会被执行⭐核心强调Filter来决定是否调用Servlet当执行完成Servlet的代码后还会回到Filter执行后面的代码。很多人错误地认为请求输出给客户端就算结束了其实很多事情需要在给客户端响应之后才能完成单个过滤器示意图用户端(浏览器) ──请求── 过滤器Filter ──放行── Servlet ─响应── ─响应──多个过滤器示意图浏览器 ── Filter 1 ── Filter 2 ── Servlet ── ── ── 执行顺序1前 → 2前 → Servlet → 2后 → 1后1.2 快速入门示例1. 实现 Filter 接口public class MyFilter implements Filter { Override public void init(FilterConfig filterConfig) throws ServletException {} Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println(Hello Filter!!!); // 放行让请求继续往下走访问目标资源 filterChain.doFilter(servletRequest, servletResponse); System.out.println(filter 执行完毕了!); } Override public void destroy() {} }2. 在 web.xml 中配置filter filter-namefirstFilter/filter-name filter-classcn.tx.filter.MyFilter/filter-class /filter filter-mapping filter-namefirstFilter/filter-name url-pattern/*/url-pattern !-- 拦截所有请求 -- /filter-mapping⭐核心强调url-pattern和 Servlet 的匹配规则一模一样完全路径匹配以/开头如/aaa、/aaa/bbb目录匹配以/开头如/aaa/*、/*扩展名匹配不能以/开头如*.do、*.action1.3 生命周期与核心对象Filter 的三大方法方法执行时机执行次数init(FilterConfig)服务器启动时创建实例后调用仅一次doFilter(...)用户每次访问目标资源时调用多次destroy()服务器关闭销毁实例前调用仅一次FilterConfig 的作用与 ServletConfig 相似getServletContext()获取 ServletContextgetFilterName()获取 Filter 名称getInitParameter(String name)获取初始化参数getInitParameterNames()获取所有初始化参数名称web.xml 配置初始化参数filter filter-namefirstFilter/filter-name filter-classcn.tx.filter.MyFilter/filter-class init-param param-nameencode/param-name param-valueUTF-8/param-value /init-param /filterFilterChain 的作用FilterChain只有一个方法doFilter(ServletRequest, ServletResponse)。⭐核心强调调用该方法的意思是我当前 Filter放行了但不代表其他人其他过滤器也放行。它表示执行下一个过滤器的doFilter方法或者执行目标资源。1.4 过滤器执行顺序⭐核心强调一个目标资源可以指定多个过滤器执行顺序取决于web.xml 中的部署顺序谁先配谁先执行。示例配置!-- FilterA 先部署 -- filter filter-namefilterA/filter-name filter-classcn.txt.filter.FilterA/filter-class /filter filter-mapping.../filter-mapping !-- FilterB 后部署 -- filter filter-namefilterB/filter-name filter-classcn.txt.filter.FilterB/filter-class /filter filter-mapping.../filter-mapping// Filter A 的 doFilter 方法 public void doFilter(...) { System.out.println(filter A 被执行了); filterChain.doFilter(request, response); // 【关键点放行进入下一个环节】 System.out.println(filter A 执行完毕了); // 这一步会在最后执行 } // Filter B 的 doFilter 方法 public void doFilter(...) { System.out.println(filter B 被执行了); filterChain.doFilter(request, response); // 【关键点放行进入下一个环节】 System.out.println(filter B 执行完毕了); // 这一步会在倒数第二执行 } // Servlet / Controller 的方法 public void doGet(...) { System.out.println(servlet 被执行了); }执行结果filter A 被执行了 filter B 被执行了 servlet 被执行了 filter B 执行完毕了 filter A 执行完毕了1.5 拦截目标资源的两种方式方式一通过 URL 匹配url-pattern/*/url-pattern !-- 拦截所有 -- url-pattern/admin/*/url-pattern !-- 只拦截admin下的资源 --方式二通过 Servlet 名称匹配filter-mapping filter-namemyfilter/filter-name servlet-namemyservlet/servlet-name !-- 专门拦截名为myservlet的请求 -- /filter-mapping1.6 应用场景预处理在目标资源执行前做准备工作如设置编码通常都会放行。条件拦截判断是否放行如校验用户是否登录、IP是否被禁用。后处理在目标资源执行后对输出的数据进行特殊处理。第二部分过滤器实战 —— 解决 POST 中文乱码前端页面form action/filter_demo/test/post methodpost 姓名input typetext namename/br input typesubmit value提交 /formWeb.xml 配置filter filter-nameencodeFilter/filter-name filter-classcn.tx.filter.EncoderFilter/filter-class init-param param-nameencode/param-name param-valueUTF-8/param-value /init-param /filter filter-mapping filter-nameencodeFilter/filter-name url-pattern/*/url-pattern /filter-mappingFilter 代码public class EncodeFilter implements Filter { private String encode; Override public void init(FilterConfig filterConfig) throws ServletException { this.encode filterConfig.getInitParameter(encode); } Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { // 设置请求和响应的编码 servletRequest.setCharacterEncoding(encode); servletResponse.setContentType(text/html;charSet this.encode); servletResponse.setCharacterEncoding(this.encode); // 放行 filterChain.doFilter(servletRequest, servletResponse); } Override public void destroy() {} }⭐核心强调把编码设置写在 Filter 里拦截所有请求相当于把重复代码抽成了通用拦截层比在每个 Servlet 里写request.setCharacterEncoding优雅得多。第三部分监听器Listener3.1 监听器概述⭐核心强调JavaWeb 被监听的事件源为三大域对象ServletContext、HttpSession、ServletRequest。分类监听域对象**创建与销毁**的监听器监听域对象**操作域属性**的监听器监听HttpSession的监听器3.2 常用监听器接口监听器接口监听对象触发时机ServletContextListener整个应用启动/关闭时触发HttpSessionListener会话创建/销毁 Session 时触发ServletRequestListener请求创建/销毁 Request 时触发方法说明contextInitialized(ServletContextEvent evt)ServletContext 对象被创建后调用contextDestroyed(ServletContextEvent evt)ServletContext 对象被销毁前调用sessionCreated(HttpSessionEvent evt)HttpSession 对象被创建后调用sessionDestroyed(HttpSessionEvent evt)HttpSession 对象被销毁前调用requestInitialized(ServletRequestEvent evt)ServletRequest 对象被创建后调用requestDestroyed(ServletRequestEvent evt)ServletRequest 对象被销毁前调用3.3 监听器使用配置Web.xml 配置listener listener-classcn.tx.listener.MyListener/listener-class /listenerListener 代码实现多个接口public class MyListener implements ServletContextListener, HttpSessionListener, ServletRequestListener { public MyListener() {} // 请求到达时触发Filter/Servlet 之前每个请求一次 public void requestInitialized(ServletRequestEvent sre) { System.out.println(requestInitialized); } // 请求结束时触发Filter/Servlet 之后每个请求一次 public void requestDestroyed(ServletRequestEvent sre) { System.out.println(requestDestroyed); } // 应用启动时触发整个应用只执行一次(大上面) public void contextInitialized(ServletContextEvent sce) { System.out.println(contextInitialized); } // 应用关闭时触发整个应用只执行一次(终结时出现) public void contextDestroyed(ServletContextEvent sce) { System.out.println(contextDestroyed); } // 首次 getSession() 创建 Session 时触发每个 Session 一次 public void sessionCreated(HttpSessionEvent se) { System.out.println(sessionCreated); } // Session 超时 / invalidate() / 应用关闭时触发每个 Session 一次 public void sessionDestroyed(HttpSessionEvent se) { System.out.println(sessionDestroyed); }}第四部分实战 —— 使用 HttpSessionListener 统计在线人数4.1 前端页面index.jsp首页% page contentTypetext/html;charsetUTF-8 languagejava % html head title企业信息官网/title /head body h1欢迎登陆学员/h1 h2当前在线人数${count}/h2 hr a hreflogout.jsp退出/a /body /htmllogout.jsp退出页销毁 Session% page contentTypetext/html;charsetUTF-8 languagejava % html head titleTitle/title /head body % // 销毁当前会话 request.getSession().invalidate(); % h1已退出/h1 /body /html4.2 监听器代码public class MyListener implements ServletContextListener, HttpSessionListener { // 定义一个在线人数初始为 0 private Integer count 0; public MyListener() {} // Servlet 容器初始化时把在线人数存入上下文 public void contextInitialized(ServletContextEvent sce) { ServletContext context sce.getServletContext(); context.setAttribute(count, count); System.out.println(contextInitialized); } public void contextDestroyed(ServletContextEvent sce) { System.out.println(contextDestroyed); } // 如果创建了一个 session那么在线人数加 1 public void sessionCreated(HttpSessionEvent se) { onLineCount(se.getSession().getServletContext(), true); System.out.println(sessionCreated); } // 如果销毁了一个 session那么在线人数减 1 public void sessionDestroyed(HttpSessionEvent se) { onLineCount(se.getSession().getServletContext(), false); System.out.println(sessionDestroyed); } /** * 添加或删除在线人数 * param context 上下文对象Application 域 * param isAdd true加1false减1 */ private void onLineCount(ServletContext context, boolean isAdd) { if(isAdd) { count (Integer) context.getAttribute(count); context.setAttribute(count, count); } else { count (Integer) context.getAttribute(count); context.setAttribute(count, --count); } } }⭐核心强调在线人数必须存在ServletContextApplication 域所有用户共享。统计的逻辑是有人新建 Session 就 1有人销毁 Session 就 -1。注意sessionDestroyed的方法体里源码文档中写的是--count字符串这是笔误正确逻辑应该是先减减再存入即--count如我上面代码所示。附录核心易错点速查表类别易错点正确理解Filter本质以为 Filter 是用来处理请求的Filter 是拦截请求的决定是否放行请求结束点以为响应给客户端就结束了响应后还会回到 Filter 执行后续代码FilterChain以为调用就代表所有 Filter 都放行了只代表当前 Filter 放行执行下一个Filter顺序以为是按名字或类名排序严格按web.xml 中部署的顺序URL匹配扩展名匹配加了/如/*.do扩展名匹配不能以/开头如*.do监听器作用域把在线人数存在 request 或 session 里必须存在Application (ServletContext)Session销毁手动关浏览器不一定会立刻销毁 Session默认是超时销毁或调用invalidate()编码乱码Servlet 里挨个设setCharacterEncoding用 Filter 统一拦截设置⭐总结过滤器Filter是把关者负责在请求到达目标资源前进行拦截、过滤或预处理核心是FilterChain.doFilter()的放行机制。监听器Listener是观察员不拦截请求专门监听三大域对象的生命周期和属性变化。两者结合既能保证应用数据流转的安全如统一编码又能实现全局状态的追踪如在线人数统计。
阅读完成 · 觉得有帮助?
咨询建站