定义
拦截所有访问web资源的请求或者响应(servlet、Jsp页面、HTML页面),从而实现我们自己的业务逻辑,这些逻辑可以是实现访问权限的控制、过滤敏感词、压缩响应等功能。
原理
过滤器是"链接"在容器的处理过程中的,它会在servlet处理器之前访问进入的请求,并且在响应信息返回客服端之前访问这些响应信息。这样就可以动态的修改请求和响应中的内容。
创建Filter
创建web项目:
或者像这样建立web项目:
添加Tomcat服务器:
如果没有SmartTomcat,只有Tomcat像下面图片中这样:
则选择Tomcat里面的local,并向下面这样配置:
启动程序:
添加java包和servlet依赖:
创建过滤器并实现它的方法:
在web.xml中注册Filter:
当我们在执行的时候,发现会报404错误,那是因为我们没有重写Filter里面的init方法,在用Filter的时候必须重写init方法,因为web 工程启动的时候执行构造器方法和init 初始化方法 ,如果不重写初始化方法,就不会走过滤器。
重写Filter里面的init方法,然后执行过滤器:
我们会发现在浏览器中什么都没有输出,但是控制台中输出了doFilter里面内容,说明我们的过滤器起作用了 ,他拦截了所有的请求。当我们在doFilter方法里面加上下面的逻辑,浏览器就会输出内容:
上面filterChain里面的doFilter方法它的作用就是请求放行到下一个资源,这个资源有可能是过滤器,也可能是客户端。
Filter的生命周期
上面每一步分别对应不同的方法:
实例化对应Filter的构造器方法,初始化对应init()方法、销毁对应的是destroy()方法
Filter它是一个接口,该接口里面有如下几个方法:
destroy()
当web服务器调用该方法时,表示过滤器将被销毁。
init()
当web服务器调用该方法时,表示过滤器将被注册到服务中
doFilter()
当服务器调用Filter中的doFilter()方法,它会将每一个请求或者响应传递给下一个资源
具体实现:
1.可以看到Filter是在应用启动是被创建和初始化的。
2.Filter是单例多线程的(创建和初始化只会被执行一次)
3.doFilter()方法无论是那一个线程访问,只要由该Filter进行过滤,那么就会执行该Filter的doFilter()方法,并且是每过滤一次就会执行一次doFilter()。
4.Filter中destroy方法是在应用被停止时调用的,它意味着销毁这个Filter。
5.由于Filter是单列多线程的,所以为了保证线程安全,不能在Filter中定义可修改的成员变量,因为每个线程均可修改这个成员变量,这样就会带来线程安全问题。
FilterConfig
FilterConfig指的是Filter在web.xml中的注册信息 ,它将注册信息进行封装,然后通过形参的方式传给初始化方法。
FilterConfig里面的方法:
演示使用:
package com.xihua;
import javax.servlet.*;
import java.io.IOException;
import java.util.Enumeration;
public class MyFilter implements Filter {
private FilterConfig filterConfig;
public MyFilter() {
System.out.println("执行了MyFilter的构造方法");
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig = filterConfig;
System.out.println("执行了init方法");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//获取过滤器的名字
String filterName = filterConfig.getFilterName();
System.out.println("过滤器的名字:" + filterName);
//获取过滤器中所有的初始化参数名称
Enumeration<String> names = filterConfig.getInitParameterNames();
while (names.hasMoreElements()){
String name = names.nextElement();
//获取过滤器中初始化参数对应的值
String value = filterConfig.getInitParameter(name);
System.out.println(name + "=" + value);
}
//获取全局域
ServletContext sc = filterConfig.getServletContext();
System.out.println("servletContext=" + sc);
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
System.out.println("执行了destory方法");
}
}
<?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.xihua.MyFilter</filter-class>
<init-param>
<param-name>school</param-name>
<param-value>xihua</param-value>
</init-param>
<init-param>
<param-name>researcher</param-name>
<param-value>jack</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>MyFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
运行结果:
<filter-mapping>
<filter-mapping>标签表示拦截匹配,也就是要拦截那些请求。
<filter-name>:要拦截的过滤器名称
<url-pattern>:拦截那些路径
<url-pattern>
注意Filter中在写拦截所有路径的时候只能写成/*,而不能写成/,因为写成/它就不走拦截器中的doFilter方法了。
在Servlet中/*即会拦截动态资源又会拦截静态资源,而/不会拦截动态资源
演示Filter拦截动态和静态资源如下:
package com.xihua;
import javax.servlet.*;
import java.io.IOException;
import java.util.Enumeration;
public class MyFilter implements Filter {
public MyFilter() {
System.out.println("执行了MyFilter的构造方法");
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("执行了init方法");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("执行doFilter之前");
System.out.println("执行doFilter之后");
}
@Override
public void destroy() {
System.out.println("执行了destory方法");
}
}
package com.xihua.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class SomeServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("执行SomeServlet");
}
}
<?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.xihua.MyFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>MyFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>SomeServlet</servlet-name>
<servlet-class>com.xihua.servlet.SomeServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>SomeServlet</servlet-name>
<url-pattern>/SomeServlet</url-pattern>
</servlet-mapping>
</web-app>
当将Filter中的拦截改为/时,过滤器将不起作用:
下面演示Servlet/*和/的区别:
在<filter-mapping>标签中可以不使用<url-pattern>标签,但是需要指定<servlet-name>,这样就能指定拦截某个servlet而不拦截其他请求:
<?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.xihua.MyFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>MyFilter</filter-name>
<!--<url-pattern>/*</url-pattern>-->
<servlet-name>SomeServlet</servlet-name>
</filter-mapping>
<servlet>
<servlet-name>SomeServlet</servlet-name>
<servlet-class>com.xihua.servlet.SomeServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>SomeServlet</servlet-name>
<url-pattern>/SomeServlet</url-pattern>
</servlet-mapping>
</web-app>
Filter里面的<dispatcher>标签
dispatcher表示分发器,表示过滤器所拦截的资源被servlet容器调用的方式,可以是REQUEST,INCLUDE,FORWARD,ERROR中的任何一个,默认是REQUEST。用户可以设置多个<dispatcher>子元素用来指定过滤器对资源的多种调用方式进行拦截。
FORWARD | 表示当前过滤器只会拦截由一个Servlet通过RequestDispatcher的forward()完成跳转 |
INCLUDE | 表示当前过滤器只会拦截由一个Servlet通过RequestDispatcher的include()完成跳转 |
REQUEST | 表示当前过滤器会拦截普通请求,但对于forward()与include()的跳转不进行拦截,REQUEST是默认的。 |
ERROR | 表示当跳转到指定的错误处理页面时,这个跳转请求会被当前过滤器拦截 |
下面演示上面四个子元素的区别:
两个servlet:SomeServlet、OtherServelt
package com.xihua.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class SomeServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("执行SomeServlet");
req.getRequestDispatcher("/otherServlet").forward(req,resp);
}
}
package com.xihua.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class OtherServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("执行了otherServlet");
}
}
在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>
<filter-name>MyFilter</filter-name>
<filter-class>com.xihua.MyFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>MyFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!--注册SomeServlet-->
<servlet>
<servlet-name>SomeServlet</servlet-name>
<servlet-class>com.xihua.servlet.SomeServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>SomeServlet</servlet-name>
<url-pattern>/someServlet</url-pattern>
</servlet-mapping>
<!--注册OtherServlet-->
<servlet>
<servlet-name>OtherServlet</servlet-name>
<servlet-class>com.xihua.servlet.OtherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>OtherServlet</servlet-name>
<url-pattern>/otherServlet</url-pattern>
</servlet-mapping>
</web-app>
执行someServlet请求,因为在someServlet中我们设定了一个请求转发,观察拦截器的拦截效果:
再来看一下在<filter>标签里面添加<dispatcher>标签,并设定子元素为FORWARD:
<filter-mapping>
<filter-name>MyFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>
将请求转发的关键字换成include:
出现上面的原因是因为:
请求转发中forward和include的区别在于响应的标准输出流开启时间不一样,forward会请求传递给最后一个servlet,当最后一个servet执行完以后才会开启/返回响应。而include它是将其他请求包在第一个请求里面,并由第一个请求开启/返回响应。
Filter对请求和响应的修改
多个Filter的执行顺序
两个Filter:MyFilter、UsFilter,在xml文件中先配置MyFilter,后配置UsFilter
<?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">
<!--注册MyFilter-->
<filter>
<filter-name>MyFilter</filter-name>
<filter-class>com.xihua.MyFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>MyFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!--注册UsFilter-->
<filter>
<filter-name>UsFilter</filter-name>
<filter-class>com.xihua.UsFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>UsFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!--注册SomeServlet-->
<servlet>
<servlet-name>SomeServlet</servlet-name>
<servlet-class>com.xihua.servlet.SomeServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>SomeServlet</servlet-name>
<url-pattern>/someServlet</url-pattern>
</servlet-mapping>
</web-app>
交换xml文件中的配置顺序:
总结:多个Filter的执行顺序,与Filter的配置顺序有关。
Filter的执行原理
Servlet的执行原理:在Servlet中有两个Map,这两个Map的key均为Servlet注册时的<url-pattern/>值,但value是不同的。第一个Map的value是Servlet实例对象的引用,第二个Map的value为<servlet-class/>的值,即Servlet类的全限定类名。
执行原理:
当对Servlet的请求到达Servlet容器时,会先对请求进行解析,使用该解析出的URL,作为比较对象,从第一个Map中查找是否有匹配的key,若不存在匹配的key,那么读取其value,即Servlet对象的引用,执行该Servlet的service()方法。
若不存在匹配的key ,那么再从第二个Map中查找是否有匹配的key。若存在,这读取其value,即读取其value,即要访问的Servlet的全限定类名。然后使用反射机制创建该Servlet实例,并将该实例写入到第一个Map中,然后在执行该Servlet的service()方法。
若第二个Map中也没有找到匹配的key,那么就跳转到错误处理页面404。
Filter的执行原理:
一个数组与一个Map :
一个Map:Map的key为<url-pattern/>的值,value为Filter实例对象的引用
一个数组:存在着与请求相匹配的所有Filter
执行原理:
当对某资源的请求到达Web容器时,会先对请求进行解析,使用解析出的URI作为比较对象,从Map中查找是否存在相匹配的key。若存在,那么读取其value,即Filter对象的引用,将该应用存入到数组中。然后继续向后查找,直到将Map查找完毕。这样在数组中就会存在按照查找顺序排好序的Filter引用。
数组初始化完毕后,开始按照数组元素顺序进行执行。所有数组中的Filter全部执行完毕后,再跳转到请求的目标资源。