前言
Filter
也是一种服务端小程序,它的功能是在请求到达对应的Servlet
实例以及从Servlet
返回的响应到达客户端的过程中,对Request
和Response
进行相应的处理,比较常见的应用如权限验证等。和Servlet
一样,Filter
只是一个实现了特定接口等普通Java
对象而已。Filter
采用了责任链的设计模式,关于责任链设计模式可以浏览
Listener
也是一种特殊的服务端小程序,它的功能是监听一些事件的开始与结束,以及某些类的属性变化。下面首先介绍Filter
Filter
一个类实现了Filter
接口就可以成为一个Filter
,以下是Filter
接口的定义。
- Filter接口
public interface Filter {
default void init(FilterConfig filterConfig) throws ServletException {
}
void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) throws IOException, ServletException;
default void destroy() {
}
}
其中init()
和destroy()
是Filter
的生命周期方法,分别在创建和销毁的时候被调用,这一点与 Servlet
类似。其中doFilter()
操作则是正在在执行过滤操作,让我现在web
应用里使用Filter
,看看能起到什么效果。
使用Filter
- 使用
idea
构建的web
项目结构
首先让我们定义一个需要被访问的HelloServlet
并在index.jsp
中给它提供一个访问入口。
HelloServlet
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("这里是HelloServlet的doGet()方法");
}
}
index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>$Title$</title>
</head>
<body>
<a href="HelloServlet">点击访问这里访问HelloServlet</a>
</body>
</html>
配置web.xml
,把HelloServlet
交给tomcat
管理。
web.xml
<servlet>
<servlet-name>hello</servlet-name>
<servlet-class>HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/HelloServlet</url-pattern>
</servlet-mapping>
再没有配置Filter
前,让我们先访问一下HelloServlet
,观察一下控制台的输出。
这里是HelloServlet的doGet()方法
可以看到可以成功调用到HelloServlet
的doGet()
方法。
现在,再让我们实现一个Filter
MyFilter
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void destroy() {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("这里是MyFilter的doFilter方法");
}
}
同Servlet
一样我们也需要把MyFilter
注册到tomcat
所管理的容器中去
web.xml
<filter>
<filter-name>myFilter</filter-name>
<filter-class>MyFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>myFilter</filter-name>
<url-pattern>/HelloServlet</url-pattern>
</filter-mapping>
配置方法和Servlet
差不多,<url-pattern>
指明了当访问该url
地址时,该Filter
的doFilter()
会被调用进行过滤操作。配置好了之后再让我们访问HelloServlet
。
- 配置
MyFilter
后控制台的输出结果。
Connected to server
[2020-04-18 01:57:17,405] Artifact springweb:war exploded: Artifact is being deployed, please wait...
[2020-04-18 01:57:17,671] Artifact springweb:war exploded: Artifact is deployed successfully
[2020-04-18 01:57:17,671] Artifact springweb:war exploded: Deploy took 266 milliseconds
这里是MyFilter的doFilter方法
我们可以看到当执行完MyFilter
的doFilter()
方法后,整个过程就结束了,Request
并没有继续传递给Servlet
,也就是说请求被MyFilter
拦截了。原因很简单,我们在doFilter()
中只是简单的打印了一行字符,然后就直接return
了,当然不会继续执行了。那如何才能将这个请求放行
呢?事实我们要注意到同时传递进来的还有一个FilterChain
实例,这个FilterChain
并不是我们创建的,而是tomcat
维护的一个Filter
链,我们可以把其看作是一个队列,当我们MyFilter
的执行逻辑处理完以后,我们应当将该请求递交给这个Chain
中的下一个Filter
处理,如果没有下一个Filter
了就交给这个Request
所想要访问的Servlet
处理。
- 调用
chain.doFilter()
将请求传递
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void destroy() {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("这里是MyFilter的doFilter方法");
filterChain.doFilter(servletRequest,servletResponse);
}
}
然后让我们再来访问一次HelloServlet
Connected to server
[2020-04-18 02:09:09,024] Artifact springweb:war exploded: Artifact is being deployed, please wait...
[2020-04-18 02:09:09,254] Artifact springweb:war exploded: Artifact is deployed successfully
[2020-04-18 02:09:09,254] Artifact springweb:war exploded: Deploy took 230 milliseconds
这里是MyFilter的doFilter方法
这里是HelloServlet的doGet()方法
可以看到请求被成功放行了。
之前我们提到过Filter
对Request
和Response
都可以进行处理,这里似乎只对Request
进行了处理,那么在调用 Servlet
的Service
方法后返回的Response
呢?让我们在filterCahin.doFilter(...)
下面加一行打印信息。
- 处理
response
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("Before:这里是MyFilter的doFilter方法");
filterChain.doFilter(servletRequest,servletResponse);
System.out.println("After:这里是MyFilter的doFilter方法");
}
- 控制台输出
Before:这里是MyFilter的doFilter方法
这里是HelloServlet的doGet()方法
After:这里是MyFilter的doFilter方法
看到这个输出,不难想到如下的执行流程。
在执行filterChain
的doFilter()
方法放行之前,对Request
进行处理,当从filterChain
的doFilter()
方法中返回时,再对Response
进行处理。这里只有一个Filter
,还不够有说服力,放我们再追加一个Filter
,来验证这个调用流程。
MyFiliter2
public class MyFilter2 implements Filter{
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void destroy() {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("Before:这里是MyFilter2的doFilter方法");
filterChain.doFilter(servletRequest,servletResponse);
System.out.println("After:这里是MyFilter2的doFilter方法");
}
}
把MyFilter2
也配置进tomcat
容器中去
web.xml
<filter>
<filter-name>myFilter2</filter-name>
<filter-class>MyFilter2</filter-class>
</filter>
<filter-mapping>
<filter-name>myFilter2</filter-name>
<url-pattern>/HelloServlet</url-pattern>
</filter-mapping>
再次访问HelloServlet
,控制台的输出信息如下
Before:这里是MyFilter的doFilter方法
Before:这里是MyFilter2的doFilter方法
这里是HelloServlet的doGet()方法
After:这里是MyFilter2的doFilter方法
After:这里是MyFilter的doFilter方法
也就是下图的执行流程
方法调用的次序如下
很明显这是一个递归执行的过程。
实现Filter
结合上述分析,和javax.servlet
下的Filter
和FilterChain
接口的定义,我们可以来模拟一下Filter
的执行流程。
- 定义
Filter
接口
interface Filter {
void doFilter(Request request, Response response, FilterChain filterChain);
}
- 定义演示用的
Request
和Response
类
class Request {
String msg;
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
class Response {
String msg;
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
- 实现一些
Filter
实例
class AFilter implements Filter {
@Override
public void doFilter(Request request, Response response, FilterChain filterChain) {
System.out.println("AFilter处理"+request.getMsg());
filterChain.doFilter(request, response);
System.out.println("AFilter处理"+response.getMsg());
}
}
class BFilter implements Filter {
@Override
public void doFilter(Request request, Response response, FilterChain filterChain) {
System.out.println("BFilter处理"+request.getMsg());
filterChain.doFilter(request, response);
System.out.println("BFilter处理"+response.getMsg());
}
}
class CFilter implements Filter {
@Override
public void doFilter(Request request, Response response, FilterChain filterChain) {
System.out.println("CFilter处理"+request.getMsg());
filterChain.doFilter(request, response);
System.out.println("CFilter处理"+response.getMsg());
}
}
class DFilter implements Filter {
@Override
public void doFilter(Request request, Response response, FilterChain filterChain) {
System.out.println("DFilter处理"+request.getMsg());
filterChain.doFilter(request, response);
System.out.println("DFilter处理"+response.getMsg());
}
}
- 实现
FilterChain
class FilterChain {
private List<Filter> filters;
private int pos;
public FilterChain(){
this.filters = new ArrayList<>();
this.pos = 0;
}
public FilterChain add(Filter filter) {
filters.add(filter);
return this;
}
public void doFilter(Request request, Response response) {
// 通过了所有的`Filter`
if (pos == filters.size()) {
System.out.println("通过了所有的Filter, 访问到了`Servlet的特定方法`");
return;
}
Filter filter = filters.get(pos);
pos++; //进入下一个`Filter`
filter.doFilter(request, response, this);
}
}
FilterChain
是Filter
执行中最关键一环,FilterChain
由tomcat
生成,这个过程可以简单的看作是扫描web.xml
文件,生成多个定义好的Filter
实例,然后再添加到一个FilterChain
中,FilterChain
中设置了一个变量pos
,用来指明当前到达第几个Filter
,若pos==filter.size()
,则说明通过了最后一个过滤器,接下去去访问Servlet
即可。整个过程同上面一样,是一个递归调用的过程。
让我们对整个环节进行测试。
- test
public class Test {
public static void main(String[] args) {
Request request = new Request();
Response response = new Response();
request.setMsg("请求1");
response.setMsg("响应1");
Filter afilier = new AFilter();
Filter bfilier = new BFilter();
Filter cfilier = new CFilter();
Filter dfilier = new DFilter();
FilterChain filterChain = new FilterChain();
filterChain.add(afilier)
.add(bfilier)
.add(cfilier)
.add(dfilier);
filterChain.doFilter(request,response);
}
}
- 执行结果
AFilter处理请求1
BFilter处理请求1
CFilter处理请求1
DFilter处理请求1
通过了所有的Filter, 访问到了`Servlet的特定方法`
DFilter处理响应1
CFilter处理响应1
BFilter处理响应1
AFilter处理响应1
Listener
Listener
通常监听ServletContext
(由tomcat
管理的Servlet
容器)、ServletRequest
、Session
三大对象。根据监听的内容又可以分为生命周期(创建、销毁)、属性变更两大部分。
监听生命周期
想要监听ServletContext
、ServletRequest
、Session
只要实现对应的ServletContextListener
、ServletRequestListener
、HttpSessionListener
接口就可以了。我们创建一个MyListener
类,一次性实现这3个接口进行演示。
-
MyListener
类
public class MyListener implements ServletRequestListener, ServletContextListener, HttpSessionListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("MyListener:" + "ServletContext被初始化" );
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("MyListener:" + "ServletContext被销毁" );
}
@Override
public void requestDestroyed(ServletRequestEvent sre) {
System.out.println("MyListener:" + "ServletRequest被初销毁" );
}
@Override
public void requestInitialized(ServletRequestEvent sre) {
System.out.println("MyListener:" + "ServletRequest被初始化" );
}
@Override
public void sessionCreated(HttpSessionEvent se) {
System.out.println("MyListener:" + "httpsession被创建" );
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
System.out.println("MyListener:" + "httpsession被销毁" );
}
}
同样,我们需要把MyListener
也注册进tomcat
的Servlet
容器里。
web.xml
<listener>
<listener-class>MyListener</listener-class>
</listener>
现在访问下主页,可以观察到控制台有如下打印信息
MyListener:ServletContext被初始化
MyListener:ServletRequest被初始化
MyListener:httpsession被创建
MyListener:ServletRequest被初销毁
可见ServletContext
这个象征着Servlet
容器的实例再tomcat
启动后就立即被实例化,当我们访问网站首页时,会先创建一个ServletRequest
对象,请求到达后会自动创建一个session
,然后销毁这个ServletRequest
。那我们如何销毁这个创建好的session
呢?
创建一个用于销毁session
的页面,并在主页上设立一个指向该页面的超链接。
destorysession.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<%
session.invalidate();
%>
</body>
</html>
- index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>$Title$</title>
</head>
<body>
<a href="destroysession.jsp">点击销毁session</a>
</body>
</html>
现在首先访问index.jsp
,再去访问destorysession.jsp
,后台打印信息如下。
MyListener:ServletRequest被初始化
MyListener:httpsession被创建
MyListener:ServletRequest被初销毁
MyListener:ServletRequest被初始化
MyListener:httpsession被销毁
MyListener:ServletRequest被初销毁
即使不去访问destorysession.jsp
, session
也会在一段时间后自动销毁。
属性监听
我们还可以设置监听器来监听ServletContext
、ServletRequest
、Session
对象中的属性的增加、删除、替换的操作。只要实现对应的ServletContextAttributeListener
、ServletRequestAttributeListener
、HttpSessionAttributeListener
接口即可,下面实现AttriListener
来测试所有属性监听相关的方法。
AttriListener
public class AttrListener implements ServletContextAttributeListener, ServletRequestAttributeListener, HttpSessionAttributeListener {
@Override
public void attributeAdded(ServletContextAttributeEvent scae) {
// 新添加的属性名
String name = scae.getName();
// 和该属性名关联的值
Object val = scae.getServletContext().getAttribute(name);
System.out.println("ServletContext AttributeAdded:" + name + " " + val);
}
@Override
public void attributeRemoved(ServletContextAttributeEvent scae) {
//被删除的属性名
String name = scae.getName();
System.out.println("ServletContext AttributeRemoved:" + name);
}
@Override
public void attributeReplaced(ServletContextAttributeEvent scae) {
// 被替换的属性名
String name = scae.getName();
// 该属性名的新值
Object val = scae.getServletContext().getAttribute(name);
System.out.println("ServletContext AttributeReplaced:" + name + " " + val);
}
@Override
public void attributeAdded(ServletRequestAttributeEvent srae) {
String name = srae.getName();
// 和该属性名关联的值
Object val = srae.getServletRequest().getAttribute(name);
System.out.println("ServletRequest AttributeAdded:" + name + " " + val);
}
@Override
public void attributeRemoved(ServletRequestAttributeEvent srae) {
String name = srae.getName();
System.out.println("ServletRequest AttributeRemoved:" + name);
}
@Override
public void attributeReplaced(ServletRequestAttributeEvent srae) {
String name = srae.getName();
// 该属性名的新值
Object val = srae.getServletRequest().getAttribute(name);
System.out.println("ServletRequest AttributeReplaced:" + name + " " + val);
}
@Override
public void attributeAdded(HttpSessionBindingEvent se) {
String name = se.getName();
// 和该属性名关联的值
Object val = se.getSession().getAttribute(name);
System.out.println("HttpSession AttributeAdded:" + name + " " + val);
}
@Override
public void attributeRemoved(HttpSessionBindingEvent se) {
String name = se.getName();
System.out.println("HttpSession AttributeRemoved:" + name);
}
@Override
public void attributeReplaced(HttpSessionBindingEvent se) {
String name = se.getName();
// 该属性名的新值
Object val = se.getSession().getAttribute(name);
System.out.println("HttpSession AttributeReplaced:" + name + " " + val);
}
}
每个方法的作用和使用方法见其上注释。
设计一个jsp
页面来验证监听器是否起作用。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<%
application.setAttribute("name", "tom");
application.setAttribute("name", "ben");
application.removeAttribute("name");
request.setAttribute("age", "22");
request.setAttribute("age", "23");
request.removeAttribute("age");
session.setAttribute("city", "beijing");
session.setAttribute("city", "shanghai");
session.removeAttribute("city");
%>
</body>
</html>
访问该页面查看控制台打印结果如下。
ServletContext AttributeAdded:name tom
ServletContext AttributeReplaced:name ben
ServletContext AttributeRemoved:name
ServletRequest AttributeAdded:age 22
ServletRequest AttributeReplaced:age 23
ServletRequest AttributeRemoved:age
HttpSession AttributeAdded:city beijing
HttpSession AttributeReplaced:city shanghai
HttpSession AttributeRemoved:city