跳到主要内容

监听器、过滤器、Servlet

· 阅读需 11 分钟
不如怀念
Web 前端工程师 (Web Front-end Engineer)

最后更新于 2017-08-31 11:52:00

在 Java Web 开发过程中,监听器、过滤器、Servlet 这三大内容是很重要的组件,也是实现很多功能的根本所在。

监听器

Web 监听器(Listener)是由 Servlet 规范提供一种特殊类,用于监听 ServletContext、HttpSession 和 ServletRequest 等域对象的创建与销毁事件,以及监听这些域对象中属性的创建、销毁、修改事件。

监听器可以在事件的发生前、发生后做一些必要的处理,一般可以用来统计在线人数和在线用户、统计网站访问量、系统启动时初始化信息等。

分类

  • 按监听对象划分
    • ServletContext : Application 整个应用
    • HttpSession : Session 一次会话
    • ServletRequest : Request 一次请求
  • 按监听事件划分
    • 域对象自身的创建和销毁事件
      • ServletContextListener
      • HttpSessionListener
      • ServletRequestListener
    • 域对象中属性的添加、删除、更改事件
      • ServletContextAttributeListener
      • HttpSessionAttributeListener
      • ServletRequestAttributeListener
    • 绑定到 HttpSession 中对象的状态的变化事件
      • HttpSessionBindingListener
      • HttpSessionActivationListener

域对象自身的创建和销毁事件分别由相应接口的实现类来处理,实现接口中 Initialized()Destroyed() 方法则可以完成监听域对象的创建和销毁。

域对象中属性的添加、删除和更改事件分别由相应接口的实现类来处理,实现接口中的 attributeAdded()attributeRemoved()attributeReplaced() 方法则可以完成监听属性的添加、删除和更改。

绑定到 HttpSession 中对象的状态变化由相应接口的实现类来处理,HttpSessionBindingListener 接口中规范了 valueBound()valueUnbound() 方法完成监听对象的绑定和解除绑定;而 HttpSessionActivationListener 接口中规范了 sessionWillPassivate()sessionDidActivate() 方法来完成监听对象的钝化和活化。所谓的钝化就是将对象序列化到存储设备中去,而活化则是将对象从存储设备中反序列化到内存当中去。

注册

在创建好监听器类后,需要在项目中设置才能起作用,可以在web.xml中去设置,也可以使用 @WebListener 注解注册。

package listener;

@WebListener // 此处使用了注解注册该监听器
public class AppListener implements ServletContextListener {

@Override // 销毁方法
public void contextDestroyed(ServletContextEvent arg0) { }

@Override // 初始化方法
public void contextInitialized(ServletContextEvent arg0) { }
}

<!-- 也可以在 web.xml 文件中注册该监听器 -->
<listener>
<listener-class>listener.AppListener</listener-class>
</listener>

由上面的 AppListener 监听器类可以看出,实现了 ServletContextListener 接口中 InitializedDestroyed 方法,分别在 application 创建也就是应用启动时执行和在应用关闭时执行。HttpSessionListener 和 ServletRequestListener 监听器接口中同样也规定了这两个方法。

启动顺序

如果是在 web.xml 中注册的多个监听器,则其启动顺序由注册顺序决定;如果使用的是注解注册,那么启动顺序将是不确定的。

如果 Web 项目中同时注册了监听器(Listener)、过滤器(Filter)和 Servlet,那么他们启动的优先级如下:

Listener > Filter > Servlet

过滤器

Web 过滤器(Filter)也是一个服务器端的组件,可以截取客户端的请求与响应信息,并对这些信息过滤。过滤器一般用来实现登陆认证和编码转换等功能。

工作原理

在用户发出请求之后,过滤器会首先将用户请求截取进行简单的处理,然后再将请求发送至 Web 资源,等待 Web 资源响应之后又会发送给过滤器,然后再次进行简单处理后发送给用户。

用户(发送请求)--> 过滤器 --> Web 资源(响应)--> 过滤器 --> 用户(接受响应)

生命周期

应用启动时,根据配置首先实例化过滤器,然后执行其 init() 方法初始化,随后在用户发送请求后执行 doFilter() 方法进行过滤处理,待应用关闭时则会执行 destory() 方法进行销毁。

分类

在 Servlet 2.5 中过滤器分为四类,Servlet 3.0 之后又加入一种异步处理的类型:

  • Request :用户直接访问和请求重定向时
  • Forward :服务器端转发时
  • Include :服务器端转发时
  • Error :跳转到异常或错误页面时
  • Async :异步处理时

Request 类型的过滤器将在用户直接访问页面或者发生请求重定向(sendRedirect)时会被调用;而 Forward 和 Include 类型的过滤器对应服务器端请求转发(getRequestDispatcher)的 forward()include() 方法,前者会直接转发到另一个页面,而后者会将转发页面的内容与原页面融合。另外,Async 类型的过滤器需要过滤器和 Servlet 开启支持异步处理时才有效。

所有类型的过滤器均是 Filter 接口的实现类。

注册

在创建好过滤器类后,需要在项目中设置才能起作用,可以在web.xml中去设置,也可以使用 @WebFilter 注解注册。

package filter;

// 使用注解注册
@WebFilter(filterName="firstFilter", urlPatterns={"/servlet/GetAndPost"},
dispatcherTypes={DispatcherType.REQUEST})
public class firstFilter implements Filter {

@Override // 销毁方法
public void destroy() { }

@Override // 过滤方法
public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2) {
// 放行(必须)
arg2.doFilter(arg0, arg1);
}

@Override // 初始化方法
public void init(FilterConfig arg0) { }
}

<!-- 也可以在 web.xml 文件中注册该过滤器 -->
<filter>
<filter-name>firstFilter</filter-name>
<filter-class>filter.firstFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>firstFilter</filter-name>
<url-pattern>/servlet/GetAndPost</url-pattern>
<dispatcher>REQUEST</dispatcher>
</filter-mapping>

一个过滤器是可以映射多个路径的,只需在注解中多配置几个 urlPatterns 数组的元素或者在 web.xml 中多添加几组 filter-mapping 标签即可。

init() 方法完成过滤器的初始化,可以用来读取 web.xml 文件中过滤器的参数;doFilter() 完成实际的过滤操作,当用户请求与过滤器相关联的 URL 时,Web 容器将先调用过滤器的该方法,其中 FilterChain 参数可以调用 chain.doFilter() 方法,将请求传递给下一个过滤器或目标资源;destory() 方法可以释放过滤器所占用的资源。

过滤器链

当多个过滤器映射同一个路径的话就会形成过滤器链,其执行的顺序对应于在 web.xml 文件中注册顺序,如果使用注解注册则顺序是不确定的。

过滤器链中 doFilter() 代码的执行顺序遵循:依次执行 chain.doFilter() 前的代码,最后传递给 Web 资源,待响应后又反向依次执行 chain.doFilter() 之后的代码。

// 第一个 doFilter
public void doFilter(request, response, chain) {
... // 执行顺序:1
chain.doFilter(request, response);
... // 执行顺序:4
}

// 第二个 doFilter
public void doFilter(request, response, chain) {
... // 执行顺序:2
chain.doFilter(request, response);
... // 执行顺序:3
}

Servlet

Servlet 是在服务器上运行的小程序,并且可以通过“请求-响应”编程模型来访问这个驻留在服务器内存中的 Servlet 小程序,其主要的功能在于可以动态的生成 Web 内容。

我们通常继承实现了 Http 协议的 HttpServlet 抽象类,通过重写 doGet()doPost() 方法来处理用户的请求。

生命周期

用户发送请求后,相应的 Servlet 开始实例化,并执行 init() 方法,完成初始化后开始执行service() 方法响应请求,待到服务器关闭时,将执行 destory() 方法销毁。

需要注意的是同一个 Servlet 只会在服务器内存中存在一份实例,当第二次请求发出后,不再实例化一个新的 Servlet,而是由第一次请求所得到的 Servlet 实例处理随后的请求。

另外,Servlet 不一定是在用户首次请求后才进行实例化,可以在注册时进行配置,当应用启动时就进行实例化。而且,当 Servlet 类文件被更改后,服务器会重新装载 Servlet,此时也会销毁原来的实例,重新实例化新的 Servlet 并初始化。

注册

在创建好过 Servlet 类后,需要在项目中设置才能起作用,可以在 web.xml 中去设置,也可以使用 @WebServlet 注解注册。

package servlet;

// 使用注解注册
@WebServlet(name="ServletLife", loadOnStartup=1, urlPatterns={"/servlet/ServletLife"},
initParams={
@WebInitParam(name="username",value="MrWang"),
@WebInitParam(name="password",value="1314")
})
public class ServletLife extends HttpServlet {

@Override // 初始化方法
public void init() { }

@Override // 销毁方法
public void destroy() { }

@Override
protected void doGet(request, response) { }

@Override
protected void doPost(request, response) { }
}

<!-- 也可以在 web.xml 文件中注册该 Servlet -->
<servlet>
<servlet-name>ServletLife</servlet-name>
<servlet-class>servlet.ServletLife</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>ServletLife</servlet-name>
<url-pattern>/servlet/GetAndPost</url-pattern>
</servlet-mapping>

doGet()doPost() 方法由 service 方法自动调用,具体调用哪个取决于用户提交请求的方式。load-on-startup 标签或者注解中的 loadOnStartup 属性可以设置 Servlet 在应用启动时就加载,数字越小代表优先级越高。