Filter(过滤器)的使用
1、Filter简介
Filter称之为过滤器。我们生活中的过滤器有过滤网、净水器、空气净化器等。而Web中过滤器是用来对Web服务器管理的Web资源进行拦截(如JSP, Servlet, 静态图片文件或静态html文件等),从而完成一些特殊的功能。比如:实现URL级别的权限访问控制(最常用)、字符编码、登录限制、过滤敏感词汇、文件压缩,跨域设置等一些高级功能。
在Servlet API中提供了一个Filter接口,开发web应用时,如果编写的Java类实现了这个接口,则把这个java类称之为过滤器Filter。通过Filter技术,开发人员可以实现用户在访问某个目标资源之前,对访问的请求和响应进行拦截,如下所示:
上面图片的理解就是:当一个请求过来时,首先会被过滤器拦截下来,若满足条件,则进入下一步,执行完毕之后,又返回Filter,最后再返回给客户端。若不满足条件,则直接返回。
2、Filter开发步骤
Filter开发步骤很简单,分为两个步骤:
- 编写Java类实现Filter接口,并实现其doFilter方法(也以重写init方法与destroy方法)。
- 在web.xml 文件中使用
<filter>
和<filter-mapping>
元素配置filter,并设置它所能拦截的资源。当然我们也可以通过注解来实现。
下面举个简单的例子来说明下:字符编码拦截。我还记的在之前的博客中,如果要把含有中文的数据输出到网页中而不出现乱码的话,就必须在代码中下面这些信息。
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html");
如果页面不多的话还好,如果页面多了的话,就要写很多重复。而用过滤器只需写一遍即可。
使用Filter代码示例:
package com.thr;
import javax.servlet.*;
import java.io.IOException;
/**
* @author tanghaorong
* @date 2020-05-02
* @desc 创建一个实现Filter的实现类
*/
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("Filter初始化,只初始化一次...");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
//对request和response进行一些预处理
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
System.out.println("拦截前执行...");
filterChain.doFilter(request,response);
System.out.println("拦截后执行...");
}
@Override
public void destroy() {
System.out.println("Filter销毁...");
}
}
在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">
<!--配置过滤器-->
<filter>
<filter-name>MyFilter</filter-name>
<filter-class>com.thr.MyFilter</filter-class>
</filter>
<!--映射过滤器-->
<filter-mapping>
<filter-name>MyFilter</filter-name>
<!--/* :表示拦截所有的请求 -->
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
拦截的规则和Servlet的匹配规则一样,它们的拦截规则如下:
- 以指定资源匹配。例如:/index.jsp、/login
- 以目录匹配。例如:/servlet/* 、 /abc/def
- 以后缀名匹配,例如:*.jsp 、 *.do
- 通配符,拦截所有web资源:/*
注意的点也是一样的:/abc/*.do、/*.do、abc*.do 这些都是非法的,这样的拦截规则是无效的。
我们也可以为一个过滤器配置多个映射,也就是一个Filter对应多个
上面是对于单个Filter配置多个mapping的情况,而后面还有多个Filter对应多个mapping,这种多个Filter组合起来就称之为一个Filter链,此时的Filter的执行顺序是不确定的。因为这个地方难以理解,所以后面会详细介绍。
3、Filter的注解配置
当然我们也可以不在web.xml中配置,可以使用注解——@WebFilter。@WebFilter 用于将一个类声明为过滤器,该注解将会在部署时被容器处理,容器将根据具体的属性配置将相应的类部署为过滤器。该注解包含的所有属性如下:
属性名 | 类型 | 描述 |
---|---|---|
filterName | String | 指定过滤器的 name 属性,等价于<filter-name> |
value | String[] | 该属性等价于 urlPatterns 属性。但是两者不应该同时使用。 |
urlPatterns | String[] | 指定一组过滤器的 URL 匹配模式。等价于<url-pattern> 标签。 |
servletNames | String[] | 指定过滤器将应用于哪些 Servlet。取值是 @WebServlet 中的 name 属性的取值,或者是 web.xml 中<servlet-name> 的取值。 |
dispatcherTypes | DispatcherType | 指定过滤器的转发模式。具体取值包括:ASYNC、ERROR、FORWARD、INCLUDE、REQUEST。 |
initParams | WebInitParam[] | 指定一组过滤器初始化参数,等价于<init-param> 标签。 |
asyncSupported | boolean | 声明过滤器是否支持异步操作模式,等价于<async-supported> 标签。 |
description | String | 该过滤器的描述信息,等价于 <description> 标签。 |
displayName | String | 该过滤器的显示名,通常配合工具使用,等价于<display-name> 标签。 |
smallIcon | String | 此Filter的小图标。 |
largeIcon | String | 此Filter的大图标。 |
使用@WebFilter的一个简单举例,还是用上面MyFilter过滤器。不过在测试的发现了一个问题,就是使用了注解配置Filter,但是继续在web.xml文件中配置该Filter(配置信息一样),好像是不会报错的。这个我也不知道是怎么回事,每种方式我都去试了一下,结果不会出现问题,可能过滤器是支持这种方式的吧,但是Servlet就不行会报错。(如果有大神知道可以多多指出,毕竟我还是个菜鸟)
/**
* @author tanghaorong
* @date 2020-05-03
* @desc 使用注解配置Filter
*/
@WebFilter(filterName = "myFilter",value = "/*")
public class MyFilter implements Filter {
code...
}
上面使用注解和web.xml同时配置不报错的原因是:服务器在启动时首先是会加载web.xml的文件,Filter的映射据<filter-mapping>
的顺序来映射的,由于上面的代码使用注解和web.xml同时配置了同一个Filter,相当于配置两次<filter>
和<filter-mapping>
。故这个Filter会初始化两次,由于先加载的web.xml文件,所以当@WebFilter设置的过滤器被初始化时,它得到的字符串为空。
4、Filter的生命周期
在Filter接口中定义了三个方法,这三个都是与Filter的生命相关的方法,我们看一下源码:
package javax.servlet;
import java.io.IOException;
public interface Filter {
void init(FilterConfig var1) throws ServletException;
void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) throws IOException, ServletException;
void destroy();
}
对于这三个方法的介绍:
- init(FilterConfig var1):表示Filter对象的初始化方法,在Filter对象创建时执行(只执行一次),并且传入一个FilterConfig类型的参数,该参数封装了Filter的
<init-param>
初始化参数。 - doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3):表示Filter执行过滤的核心方法,如果某资源在已经被配置到这个Filter进行过滤的话,那么每次访问这个资源都会执行doFilter方法,注意 这里的request和response没有http需要强制转换。
- destory():表示Filter销毁方法,当Filter对象销毁时执行该方法(仅执行一次)。
5、多个过滤器的执行顺序
如果在一个Web应用中,编写了多个Filter,那么这些Filter组合起来称之为一个Filter链。此时的Web服务器根据Filter在web.xml文件中的<filter-mapping>
定义顺序执行。如果是注解的话则根据类名先后顺序执行。如果web.xml和注解混合使用,则先加载web.xml然后注解。当第一个Filter的doFilter方法被调用时,Web服务器会创建一个代表Filter链的FilterChain对象传递给该方法。在doFilter方法中,开发人员如果调用了FilterChain对象的doFilter方法,则Web服务器会检查FilterChain对象中是否还有Filter,如果还有,则调用第2个Filter,依次类推,直到没有可以调用目标资源。
举个例子好好理解一下。分别创建A、B、C、D 四个Filter。其中A和D过滤器用注解配置,B和C过滤器用web.xml配置。
AFilter的代码示例如下:(因为DFilter代码大致相同,所以我就贴一个,就类名和打印时的名称不一样罢了。如果使用web.xml配置就把注解注释掉即可)
/**
* @author tanghaorong
* @date 2020-05-03
* @desc 使用注解配置AFilter
*/
@WebFilter(filterName = "AFilter",value = "/*")
public class AFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("初始过滤器名称:AFilter");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
System.out.println("AFilter拦截前执行...");
filterChain.doFilter(request,response);
System.out.println("AFilter拦截后执行...");
}
@Override
public void destroy() {
System.out.println("AFilter销毁...");
}
}
BFilter和CFilter在web.xml的配置如下:(注意:执行顺序跟<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">
<!--配置过滤器-->
<filter>
<filter-name>CFilter</filter-name>
<filter-class>com.thr.CFilter</filter-class>
</filter>
<filter>
<filter-name>BFilter</filter-name>
<filter-class>com.thr.BFilter</filter-class>
</filter>
<!--映射过滤器-->
<!--注意:这里CFilter在BFilter之前-->
<filter-mapping>
<filter-name>CFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>BFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
运行后的结果如下:
①、这是初始化的顺序截图:
每次运行的结果都是这个,不知道过滤器初始化创建的顺序根据什么来的执行的?不过这不是重点,重点在下面。
②、这是过滤器运行顺序截图:
可以看到,过滤器的执行结果符合我们的预期。CFilter和BFilter是在web.xml中声明的,且CFilter的定义在BFilter之前,所以CFilter在BFilter之前执行。而注解则是根据类来执行的。并且可以发现每当过滤器调用filterChain.doFilter(request,response)之后,检查到后面还有过滤器没有执行,则继续执行后面的过滤器,当检查到没有过滤器后,再依次执行过滤器后面的操作。
③、这是过滤器销毁顺序截图:
销毁的执行顺序和初始化是一样的。
我后面又试了试很多种方法,注解和web.xml混合配置,全注解配置,全web.xml配置。
可以的出一个结论:web.xml方式是根据mapping中先后顺序执行。而注解方式则是根据类名先后顺序排序的。但是Filter初始化和销毁时的顺序我也不知道是根据什么排的序,无论这么测试输出的格式都是一样的。
6、FilterConfig对象的使用
还记得在Servlet中有个ServletConfig,它代表的是当前Servlet的一些初始化参数。所以FilterConfig也是一样的,当我们在配置Filter的时候,使用<init-param>
为Filter配置一些初始化参数后,在Filter对象被创建并且调用其init方法时,会把封装了Filter初始化参数的FilterConfig对象传递进来。因此通过FilterConfig对象的方法就可获取初始化配置信息。方法如下:
- String getFilterName():返回Filter的名称。
- String getInitParameter(String name): 返回在部署描述中指定名称的初始化参数的值。如果不存在返回null.
- Enumeration getInitParameterNames():返回过滤器的所有初始化参数的名字的枚举集合。
- ServletContext getServletContext():返回Servlet上下文对象的引用。
使用FilterConfig获取Filter的初始化配置信息:
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">
<!--配置过滤器-->
<filter>
<filter-name>MyFilter</filter-name>
<filter-class>com.thr.MyFilter</filter-class>
<!--配置初始化信息-->
<init-param>
<param-name>name</param-name>
<param-value>tanghaorong</param-value>
</init-param>
<init-param>
<param-name>password</param-name>
<param-value>123456</param-value>
</init-param>
</filter>
<!--映射过滤器-->
<filter-mapping>
<filter-name>MyFilter</filter-name>
<!--/* :表示拦截所有的请求 -->
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
FilterConfig中的代码:
package com.thr;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
import java.util.Enumeration;
/**
* @author tanghaorong
* @date 2020-05-03
* @desc FilterConfig获取Filter初始化配置信息
*/
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("Filter初始化,只初始化一次...");
//获取过滤器的名字
String filterName = filterConfig.getFilterName();
//获取在web.xml文件中配置的初始化参数
String initParam1 = filterConfig.getInitParameter("name");
String initParam2 = filterConfig.getInitParameter("password");
System.out.println(filterName);
System.out.println(initParam1);
System.out.println(initParam2);
//返回过滤器的所有初始化参数的名字的枚举集合。
Enumeration<String> initParameterNames = filterConfig.getInitParameterNames();
while (initParameterNames.hasMoreElements()) {
String name = (String) initParameterNames.nextElement();
String value = filterConfig.getInitParameter(name);
System.out.println(name + "=" + value );
}
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
System.out.println("拦截前执行...");
filterChain.doFilter(request,response);
System.out.println("拦截后执行...");
}
@Override
public void destroy() {
System.out.println("Filter销毁...");
}
}
使用注解获取:
package com.thr;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.annotation.WebInitParam;
/**
* @author tanghaorong
* @date 2020-05-03
* @desc 使用注解配置Filter初始化参数,并且用FilterConfig获取
*/
@WebFilter(filterName = "myFilter",value = "/*",initParams = {
@WebInitParam(name = "name", value = "tanghaorong"),/*这里配置初始化的参数*/
@WebInitParam(name = "password", value = "123456")/*相当于<init-param>*/
})
public class MyFilter implements Filter {
//此段代码和上面是一样的
code...
}