> 文章列表 > Filter过滤器和Listener监听器在Servlet的应用

Filter过滤器和Listener监听器在Servlet的应用

Filter过滤器和Listener监听器在Servlet的应用

文章目录

  • Filter过滤器
    • Filter过滤器的实现原理
    • 多个Filter的执行顺序
    • Filter过滤器在Servlet的应用
  • Listener监听器
    • Listener监听器的介绍
    • Listener监听器在Servlet中的应用


在这里插入图片描述

Filter过滤器


Filter过滤器的实现原理

      过滤器,顾名思义。我们可以根据需要制作特定的过滤器在某些物质进行处理时进行过滤达到我们想要的物质处理效果;当然,过滤器可以有多个,对某些物质进行一层又一层的筛选。想拿到预期的物质效果就可以让所有的物质都经过这一个或多个过滤器,而不必对每个物质进行手动的”筛选“。
      例如,我们现在有多个处理用于请求的Servlet,我们可能需要对每一个Servlet设置浏览器get请求发送内容的编码格式以及响应内容的编码格式防止出现乱码,或者需要对每一个Servlet接收在接收到用户请求时进行用户身份合法性的校验(即验证服务器端是否存在用户登陆成功后创建的Session对象或者因用户长时间未操作应用而导致Session对象事项而引起的菲方访问操作),那么这些重复的代码都需要在不同的Servlet中进行编写,如果Servlet数量少,还好说,但是当项目的规模起来后,在每一个Servlet中都添加这种相同的代码,工作量更大而且似乎代码显得更low了😀😀😀!!!,就像下图介绍的一样:
在这里插入图片描述
      于是现在我们想能不能将这些不同Servlet程序中的公共的代码(像设置编码格式、验证用户的合法性…)提取出来只写一遍,而让某些指定路径下加收请求的Servlet在处理用户请求之前都能先执行这些公共的程序呢?于是就有了接下的Filter过滤器
      Servlet规范中定义的Filter过滤器就是一种很好的公共代码存储并按规执行的小型代码仓库。Filter过滤器的优先级比我们自己编写的Servlet的优先级高,因此在执行我们自己的Servlet之前都会先判断执行用户请求的Servlet路径下是否有对应的过滤器,如果有,那么会先执行这一个或者多个过滤器,当过滤器中的代码执行结束后,才回去执行我们的Servlet程序去处理客户的请求并响应,并且在响应过程中还会经过请求来时经过的Filter,最后才将响应结果相应到客户端。看下面一张图或许可以加深理解😂:
在这里插入图片描述

多个Filter的执行顺序

  • 在web.xml中配置的Filter,按照Filter-Mapping定义的先后顺序先后执行对应的Filter。

          我们知道,当用户的请求路径满足多个Filter的路径或者其子路径时,这些个Filter会按照先后顺序依次执行响应部分的代码,例如,在web.xml文件中定义了FilterA和FilterB,并且FilterB的filter-mapping定义在FilterA之前,那么用户在发送请求时应当先执行FilterB在执行FilterA,在剩下的过滤器执行结束后执行用户请求路径对应的Servlet,执行结束后按照过滤器后执行的先返回经过的顺序再次经过过滤器,即先经过过滤器A,然后再经过过滤器B,最后响应结果到达客户端。这个流程如下图所示:
    在这里插入图片描述

web.xml文件中的配置应当是这样的:
按照filter-mapping定义的先后顺序先后执行对应的filter

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"version="6.0"><!--  Filter1在web.xml文件中的配置  --><filter><filter-name>filter2</filter-name><filter-class>com.shuai.javaweb.servlet.Filter2</filter-class></filter><filter-mapping><filter-name>filter2</filter-name><url-pattern>/request/a</url-pattern><url-pattern>/request/b</url-pattern></filter-mapping><!--  Filter2在web.xml文件中的配置  --><filter><filter-name>filter1</filter-name><filter-class>com.shuai.javaweb.servlet.Filter1</filter-class></filter><filter-mapping><filter-name>filter1</filter-name><url-pattern>/request/a</url-pattern><url-pattern>/request/b</url-pattern></filter-mapping>
</web-app>
  • 注解方式标注路径的Filter,按照定义的Filter类名的字典序大小顺序执行,字典序小的先执行

      Servlet中Filter过滤器注解的标注格式如下:

@WebFilter({"/request/*})   //只要用户发送的请求是/request或者/request的子请求就执行这个过滤器	
public class FilterA implements Filter {
}

      现在某个用户的请求需要经过FilterA和FilterB,这两个过滤器没有在web.xml文件中进行配置,而是采用注解的形式标注了Filter的路径信息。由于类名FilterA的字典序小于FilterB,因此用户的请求会先经过FilterA,然后在经过FIlterB,到达处理请求的Servlet;Servlet执行结束后则先经过FilterB然后经过FilterA最后响应结果到达客户端。

Filter过滤器在Servlet的应用

在Servlet中定义一个过滤器

  • 定义一个类实现jakarta.servlet.Filter接口
import jakarta.servlet.Filter;
public class MyFilter implements Filter{...}
  • 重写接口中的方法
    查看Filter的源码可以发现,其中的init方法和doDestroy方法被default修饰,我们知道,接口中的方法被default修饰,实现接口的子类可以选择性的进行该方法的实现,因此如果用不到这两个方法,我们可以不进行实现,但是必须对doFilter方法进行实现。
import jakarta.servlet.Filter;
public class MyFilter implements Filter{@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {...}
}
  • 在doFilter方法中编写过滤代码,手动调用FilterChain对象的doFilter方法
import jakarta.servlet.Filter;
public class MyFilter implements Filter{@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {//编写用户请求的过滤代码//手动调用FilterChain对象的doFilter方法,让过滤器接着向下执行chain.doFilter(servletRequest,servletResponse);//编写用户响应的过滤代码}
}
  • 在web.xml文件中或者采用注解的方式配置Filter的路径信息
    <filter><filter-name>filterA</filter-name><filter-class>com.shuai.javaweb.servlet.Filter1</filter-class></filter><filter-mapping><filter-name>filterA</filter-name><url-pattern>/request/a</url-pattern><url-pattern>/request/b</url-pattern></filter-mapping>
import jakarta.servlet.Filter;
@Webservlet({"\\request/a","/request/b"})	//这种配置方式等同于上边的注解方式
public class MyFilter implements Filter{}

过滤器的生命周期
      与Servlet的生命周期类似,Filter类对象在Tomcat服务器启动时创建并且只创建一次,在服务器关闭之前会被销毁。与Servlet对象不同的是,服务器在启动时会默认创建监听器对象,而对于Servlet,服务器在启动时不会默认创建对象,只有接收到用户请求时才会创建对象。
Servlet中使用过滤器的优点以及过滤器在Servlet中的应用场景
      在Servlet中,过滤器常用于设置servlet请求和响应对象的请求内容和响应内容的编码格式,防止出现乱码问题;也常用于判断用户身份的合法性(检查服务器端用户在登录时创建的session对象是否存在或者是否过期给出响应)。过滤器的使用减少了项目中某些必要代码的冗余度,提高了开发的效率。并且过滤器的执行顺序是在程序运行阶段动态组合的,这很符合项目开发过程中的ocp原则,增强了代码的可扩展性。Filter过滤器很好的利用了责任链设计模式的优点,将动态组合程序的调用顺序的核心思想体现了出来。

  • 常见应用1:设置请求对象以及响应对象的编码格式,解决乱码问题
@WebFilter({"/request/*"})
public class Filter2 implements Filter {@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest) servletRequest;HttpServletResponse response = (HttpServletResponse) servletResponse;request.setCharacterEncoding("UTF-8");response.setContentType("text/html;charset=UTF-8");chain.doFilter(request,response);   //接着向下执行//...}
}
  • 常见应用2:判断用户身份的合法性
public class Filter2 implements Filter {@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest) servletRequest;HttpServletResponse response = (HttpServletResponse) servletResponse;HttpSession session = request.getSession(false);if (session != null && session.getAttribute("username") != null) {chain.doFilter(request, response);   //接着向下执行} else {response.getWriter().print("illegal-user");}//...}
}

Listener监听器

Listener监听器的介绍

      Servlet中的监听器与JavaScript中的时间监听类似,当某些情况下触发某个条件,就执行一端特定的代码。Servlet中常用的监听器接口有以下7个:

  • ServletContextListener/ServletContextAttributeListener
  • ServletRequestListener/ServletRequestAttributeListener
  • HttpSessionListener/HttpSessionAttributeListener
  • HttpSessionBindingListener

Listener监听器在Servlet中的应用


在Servlet中定义一个监听器

  • 选定监听器类型
  • 编写一个监听器类实现这个类型的监听器接口并且覆写接口中具体监听动作的方法
  • 采用注解或者在web.xml文件中配置这个类为监听器类,配置的方法如下:
@WebListener   //注解方式配置这个类时监听器类
public class MyHttpSessionListener implements HttpSessionListener {...@Override...}
<!--在web.xml文件中配置这个监听器类-->
<?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"><listener><listener-class>com.shuai.web.listener.session.MyHttpSessionListener</listener-class></listener>
</web-app>

监听器对象的具体用法

  • ServletContextListener和ServletContextAttributeListener
       ServletContextListener监听了用户域对象ServletContext的创建和销毁,用户编写的监听器类在实现这个接口后可以覆写接口中的两个方法。当用户域对象创建或者销毁时会分别执行这两个方法中的代码。
       ServletContextAttributeListener监听了对用户域中属性的操作动作,这个借口中包含了向应用域中添加属性、替换属性、删除属性的三个方法。这三个方法在属性发生变化时会对应执行
//ServletContextListener的使用
import jakarta.servlet.ServletContextEvent;
import jakarta.servlet.ServletContextListener;
import jakarta.servlet.annotation.WebListener;@WebListener    //添加监听器注解。
public class MyServletContextListener implements ServletContextListener {/*** ServletContext应用域对象创建时会执行这个方法* @param sce*/@Overridepublic void contextInitialized(ServletContextEvent sce) {System.out.println("ServletContextListener监听器中的contextInitialized方法执行");}/*** ServletContext对象销毁时会执行这个方法* @param sce*/@Overridepublic void contextDestroyed(ServletContextEvent sce) {System.out.println("ServletContextListener监听器中的contextDestroy方法执行");}
}
//ServletContextAttributeListener的使用
package com.shuai.web.listener.servletcontext;import jakarta.servlet.ServletContextAttributeEvent;
import jakarta.servlet.ServletContextAttributeListener;
import jakarta.servlet.annotation.WebListener;@WebListener
public class MyServletContextAttributeListener implements ServletContextAttributeListener {/*** 向应用域对象ServletContext对象中添加属性时会执行这个方法* @param scae*/@Overridepublic void attributeAdded(ServletContextAttributeEvent scae) {System.out.println("应用域对象中添加了属性:" + scae.getName()+"-"+scae.getValue());}/*** 更改ServletContext域的属性时会执行这个方法* @param scae*/@Overridepublic void attributeRemoved(ServletContextAttributeEvent scae) {System.out.println("应用域对象中删除了属性:"+scae.getName()+"-"+scae.getValue());}/*** 删除应用域ServletContext的属性时会执行这个方法* @param scae*/@Overridepublic void attributeReplaced(ServletContextAttributeEvent scae) {System.out.println("应用域对象中替换了属性:" + scae.getName()+"-"+scae.getValue());}
}
  • ServletRequestListener和ServletRequestAttributeListener
    与应用域以及应用域属性的监听器类似,ServletRequestListener监听了用户请求对象ServletRequest的创建和销毁;ServletRequestAttributeListener监听了请求域中属性的操作动作
    二者的使用如下:
//ServletRequestListener的使用
import jakarta.servlet.ServletRequestEvent;
import jakarta.servlet.ServletRequestListener;
import jakarta.servlet.annotation.WebListener;@WebListener
public class MyServletRequestListener implements ServletRequestListener {/*** ServletRequest对象销毁时调用* @param sre*/@Overridepublic void requestDestroyed(ServletRequestEvent sre) {System.out.println("ServletRequest对象销毁了:" + sre);}/*** ServletRequest对象创建时调用* @param sre*/@Overridepublic void requestInitialized(ServletRequestEvent sre) {System.out.println("ServletRequest对象创建了:" + sre);}
}
//ServletRequestAttributeListener的使用
import jakarta.servlet.ServletRequestAttributeEvent;
import jakarta.servlet.ServletRequestAttributeListener;
import jakarta.servlet.annotation.WebListener;
@WebListener
public class MyServletRequestAttributeListener implements ServletRequestAttributeListener {@Overridepublic void attributeAdded(ServletRequestAttributeEvent srae) {System.out.println("ServletRequest中添加了属性:" + srae.getName() + "-" + srae.getValue());}@Overridepublic void attributeRemoved(ServletRequestAttributeEvent srae) {System.out.println("ServletRequest中删除了属性:" + srae.getName() + srae.getValue());}@Overridepublic void attributeReplaced(ServletRequestAttributeEvent srae) {System.out.println("ServletRequest中替换了属性(333):" + srae.getName() + srae.getValue());}
}
  • HttpSessionListener和HttpSessionAttributeListener
    与应用域以及应用域属性的监听器类似,HttpSessionListener监听了用户域Session对象的创建和销毁;HttpSessionAttributeListener监听了用户域中属性的操作动作
    二者的使用如下:
package com.shuai.web.listener.session;
//HttpSessionListener的使用
import jakarta.servlet.annotation.WebListener;
import jakarta.servlet.http.HttpSessionEvent;
import jakarta.servlet.http.HttpSessionListener;
@WebListener
public class MyHttpSessionListener implements HttpSessionListener {@Overridepublic void sessionCreated(HttpSessionEvent se) {System.out.println("Session对象被创建了");}@Overridepublic void sessionDestroyed(HttpSessionEvent se) {System.out.println("Session对象被销毁了");}
}
//HttpSessionAttributeListener的使用
import jakarta.servlet.annotation.WebListener;
import jakarta.servlet.http.HttpSessionAttributeListener;
import jakarta.servlet.http.HttpSessionBindingEvent;@WebListener
public class MyHttpSessionAttributeListener implements HttpSessionAttributeListener {@Overridepublic void attributeAdded(HttpSessionBindingEvent se) {System.out.println("Session用户域中添加了属性:" + se.getName() + "-" + se.getValue());}@Overridepublic void attributeRemoved(HttpSessionBindingEvent se) {System.out.println("Session用户域中删除了属性:" + se.getName() + se.getValue());}@Overridepublic void attributeReplaced(HttpSessionBindingEvent se) {System.out.println("Session用户域中替换了属性:" + se.getName() + se.getValue());}
}
  • HttpSessionBindingListener
    与上边的监听器类型不同,这个监听器再使用注解或者配置来说明这是一个监听器类。只需要让一个类实现这个监听器接口,这时这个类的对象添加到对应的Session域或者从响应的Session域中移除都会被监听到。这个监听接口常用于监听网站已登录的在线人数
//HttpSessionBindingListener的使用
import jakarta.servlet.http.HttpSessionBindingEvent;
import jakarta.servlet.http.HttpSessionBindingListener;public class MyHttpSessionBindingListener implements HttpSessionBindingListener {@Overridepublic void valueBound(HttpSessionBindingEvent event) {System.out.println("对象被绑定到session域中了");}@Overridepublic void valueUnbound(HttpSessionBindingEvent event) {System.out.println("对象从session域中解绑了");}
}