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>。但是这样设计好像没什么用,因为只要有一个URL匹配就会被拦截。
上面是对于单个Filter配置多个mapping的情况,而后面还有多个Filter对应多个mapping,这种多个Filter组合起来就称之为一个Filter链,此时的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设置的过滤器被初始化时,它得到的字符串为空。
3、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对象销毁时执行该方法(仅执行一次)。
4、多个过滤器的执行顺序
如果在一个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配置就把注解注释掉即可)
package com.thr; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.annotation.WebInitParam; import java.io.IOException; import java.util.Enumeration; /** * @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初始化和销毁时的顺序我也不知道是根据什么排的序。
5、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... }