• 理解过滤器的作用
  • 掌握过滤器的考法技巧
  • 过滤器的应用场景

过滤器(Filter) 是 J2EE Servlet模块下的组件 Filter的作用是对URL进行统一的拦截处理 Filter通常用于应用程序层面进行全局处理

过滤链-执行过程

如下图所示:过滤器不仅对请求拦截还会对响应拦截处理

image.png

过滤器的三要素

  • 任何过滤器都要实现Filter接口
  • 在Filter接口的doFilter() 方法中编写过滤器的功能代码
  • 在web.xml 中对过滤器进行配置,说明拦截URL的范围

过滤器使用

创建一个过滤器,如下代码: 过滤器必须要实现Filter接口

  1. public class FilterSample1 implements Filter {
  2. @Override
  3. public void destroy() {
  4. System.out.println("过滤器被销毁");
  5. }
  6. @Override
  7. public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
  8. throws IOException, ServletException {
  9. System.out.println("过滤器已生效");
  10. //传递到下一个过滤链
  11. chain.doFilter(request, response);
  12. }
  13. @Override
  14. public void init(FilterConfig filterConfig) throws ServletException {
  15. System.out.println("过滤器初始化");
  16. }
  17. }

在web.xml中配置过滤器以及过滤器的拦截范围,注意url-pattern非常关键,如果配置/* 表示对所有的URL进行拦截,都会执行过滤器的doFilter()方法进行处理.

  1. <!-- 配置过滤器 -->
  2. <filter>
  3. <filter-name>sample1</filter-name>
  4. <filter-class>com.jakeprim.filter.FilterSample1</filter-class>
  5. </filter>
  6. <filter-mapping>
  7. <filter-name>sample1</filter-name>
  8. <url-pattern>/*</url-pattern> <!-- 过滤器的作用范围 /*表示对所有的URL进行拦截 -->
  9. </filter-mapping>

新建一个Servlet 和 index.html,分别去请求结果如下:
过滤器已生效
过滤器已生效

也就是说url-pattern 配置的/* 对所有的URL进行拦截处理,关于url-pattern 我会在下面详细讲解.

过滤器的生命周期

在tomcat启动的时候调用了Filter.init()方法,在请求URL的时候如果在拦截范围则执行doFilter()方法,当tomcat重启或关闭的时候执行destroy()方法

image.png

大家可以具体运行打印一下生命周期,就明白了,是非常简单的.

public class FilterSample1 implements Filter {

    @Override
    public void destroy() {
        System.out.println("过滤器被销毁");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        System.out.println("过滤器已生效");
        //传递到下一个过滤链
        chain.doFilter(request, response);
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("过滤器初始化");
    }

}

过滤器的特性

  • 过滤器对象在Web应用启动时被创建且全局唯一
  • 唯一的过滤器对象在并发环境中采用“多线程”提供服务

过滤器进阶

通过注解@WebFilter() 配置Filter

如下通过注解的形式,更加简单方便

@WebFilter(urlPatterns = "/*",filterName = "sample2")

配置过滤器和注解过滤器哪个先执行哪个后执行呢? 在上述两个过滤器中,先执行配置形式的,再执行注解形式的.

配置与注解如何选择?

  • 配置形式维护性更好,适合应用全局过滤
  • 注解形式开发体验更好,适用于小型项目敏捷开发

千万不要一半配置一半注解,这样会让程序难以维护

字符集过滤器

解决中文乱码的问题:

  • GET请求-server.xml增加URIEncoding=“UTF-8”
  • POST 请求- request.setCharacterEncoding(“UTF-8”)
  • 响应 response.setContentType(“text/html;charset=UTF-8”)

需要在代码中手动编写,而且要在每一个servlet进行添加,如果少写了一部分则会出现问题,可以通过过滤器对请求和响应进行统一的设置.

实现代码如下:通过urlPatterns = “/*”可以拦截所有的请求和响应进行对中文乱码的处理.

@WebFilter(filterName = "/character",urlPatterns = "/*")
public class CharacterEncodingFilter implements Filter {

    /**
     * Default constructor. 
     */
    public CharacterEncodingFilter() {
        // TODO Auto-generated constructor stub
    }

    /**
     * @see Filter#destroy()
     */
    public void destroy() {
        // TODO Auto-generated method stub
    }

    /**
     * @see Filter#doFilter(ServletRequest, ServletResponse, FilterChain)
     */
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        //解决post请求的中文乱码问题
        HttpServletRequest servletRequest = (HttpServletRequest)request;
        servletRequest.setCharacterEncoding("UTF-8");
        //解决响应中的中文乱码问题
        HttpServletResponse servletResponse = (HttpServletResponse)response;
        servletResponse.setContentType("text/html;charset=utf-8");

        // pass the request along the filter chain
        chain.doFilter(request, response);
    }

    /**
     * @see Filter#init(FilterConfig)
     */
    public void init(FilterConfig fConfig) throws ServletException {
        // TODO Auto-generated method stub
    }

}

在servlet的响应中,返回中文.

@WebServlet("/hello")
public class HelloServlet extends HttpServlet{

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().println("hello测试");
    }
}

结果如下:
hello测试

需要注意ServletRequest 和 HttpServletRequest,HttpServletRequest实现了ServletRequest接口.注意这里要强转一下,转成我们需要的具体的类.

image.png

过滤器参数化

  • 过滤器为了增强灵活性,允许配置信息放在web.xml.
  • 在web.xml中配置设置过滤器参数

在上述代码中,如果我们要将UTF-8编程GBK编码,就需要修改Filter类中的代码,而过滤器将可变的选项变成参数化.

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        //解决post请求的中文乱码问题
        HttpServletRequest servletRequest = (HttpServletRequest)request;
        servletRequest.setCharacterEncoding("UTF-8");
        //解决响应中的中文乱码问题
        HttpServletResponse servletResponse = (HttpServletResponse)response;
        servletResponse.setContentType("text/html;charset=utf-8");

        // pass the request along the filter chain
        chain.doFilter(request, response);
    }

在web.xml中配置参数,通过init-param ,param-name:参数名,param-value : 参数值,如果要将encoding变成GBK只需要修改web.xml即可.

<filter>
        <filter-name>character</filter-name>
        <filter-class>com.jakeprim.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value> <!-- 可变的值 -->
        </init-param>
</filter>
<filter-mapping>
        <filter-name>character</filter-name>
        <url-pattern>/*</url-pattern> <!-- 过滤器的作用范围 /*表示对所有的URL进行拦截 -->
</filter-mapping>

Filter类中的实现如下: 这样我们只需要修改web.xml中的配置即可修改encoding,而不用写死在程序内部在重新编译上线.


public class CharacterEncodingFilter implements Filter {

    /**
     * Default constructor. 
     */
    public CharacterEncodingFilter() {
        // TODO Auto-generated constructor stub
    }

    /**
     * @see Filter#destroy()
     */
    public void destroy() {
        // TODO Auto-generated method stub
    }

    /**
     * @see Filter#doFilter(ServletRequest, ServletResponse, FilterChain)
     */
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        //注意这里要强转一下
        //解决post请求的中文乱码问题
        HttpServletRequest servletRequest = (HttpServletRequest)request;
        servletRequest.setCharacterEncoding(encoding);//修改 如 GBK
        //解决响应中的中文乱码问题
        HttpServletResponse servletResponse = (HttpServletResponse)response;
        servletResponse.setContentType("text/html;charset="+encoding);//修改 如 GBK,这样不好控制 比较麻烦,  将可变的选项进行参数化设置


        // pass the request along the filter chain
        chain.doFilter(request, response);
    }

    /**
     * @see Filter#init(FilterConfig)
     */
    public void init(FilterConfig fConfig) throws ServletException {
        // TODO Auto-generated method stub
        encoding = fConfig.getInitParameter("encoding");//读取配置的参数
        System.out.println(encoding);
    }

    private String encoding;

}

多个参数在web.xml中罗列即可

<filter>
        <filter-name>character</filter-name>
        <filter-class>com.jakeprim.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>p1</param-name>
            <param-value>v1</param-value>
        </init-param>
        <init-param>
            <param-name>p2</param-name>
            <param-value>v2</param-value>
        </init-param>
    </filter>

既然能在web.xml中配置参数,那么也可以在注解中进行配置

@WebFilter(filterName = "/character",urlPatterns = "/*",initParams = {
        @WebInitParam(name = "encoding",value = "UTF-8"),
        @WebInitParam(name = "p1",value = "v1"),
        @WebInitParam(name = "p2",value = "v2")
})

以上通过外层的设置,使程序更容易维护.

url-pattern设置过滤范围

常用写法如下:

  • /index.jsp - 执行资源精准匹配
  • /servlet/* - 以前缀进行模糊匹配
  • *.jsp - 以后缀进行模糊匹配
  • /* - 匹配所有的URL

通过例子我们来看一下过滤范围的设置

@WebServlet("/servlet/sample1")
public class SampleServlet1 extends HttpServlet {
    private static final long serialVersionUID = 1L;

    /**
     * @see HttpServlet#HttpServlet()
     */
    public SampleServlet1() {
        super();
        // TODO Auto-generated constructor stub
    }

    /**
     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // TODO Auto-generated method stub
        response.getWriter().println("I'm"+this.getClass().getSimpleName());
    }

    /**
     * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // TODO Auto-generated method stub
        doGet(request, response);
    }

}

创建一个filter,进行打印日志:

public class UrlPatternFilter implements Filter{

    @Override
    public void destroy() {
        // TODO Auto-generated method stub

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest rHttpServletRequest = (HttpServletRequest)request;
        System.out.println("拦截到:"+rHttpServletRequest.getRequestURL());
        chain.doFilter(request, response);
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // TODO Auto-generated method stub

    }

}

设置只拦截/servlet下的URL


    <filter>
        <filter-name>UrlPatternFilter</filter-name>
        <filter-class>com.jakeprim.filter.UrlPatternFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>UrlPatternFilter</filter-name>
        <url-pattern>/servlet/*</url-pattern> <!-- 过滤器的作用范围 只用于/test.jsp /servlet/*  *.jsp-->
    </filter-mapping>

访问:http://localhost:8080/filter/servlet/sample1
打印结果:拦截到:http://localhost:8080/filter/servlet/sample1
访问:http://localhost:8080/filter/test.jsp
控制台中并没有结果打印,如果我们只想拦截test.jsp如何设置呢?

<filter-mapping>
        <filter-name>UrlPatternFilter</filter-name>
        <url-pattern>/test.jsp</url-pattern> <!-- 过滤器的作用范围 只用于/test.jsp /servlet/*  *.jsp-->
    </filter-mapping>

打印结果如下:拦截到:http://localhost:8080/filter/test.jsp
这时Filter就只拦截test.jsp了. 过滤器的作用范围很容易理解的,多实例操作几遍就明白了.但是我们需要注意映射的几个问题

注意几个映射的问题

  • / 指映射Web应用根路径,且只对Servlet生效

如下代码我们将servlet的url-pattern = “/“ 把filter的url-filter改成/ 就会访问到http://localhost:8080/filter/ 访问Servlet

@WebServlet("/")
public class SampleServlet2 extends HttpServlet {
    private static final long serialVersionUID = 1L;

    /**
     * @see HttpServlet#HttpServlet()
     */
    public SampleServlet2() {
        super();
        // TODO Auto-generated constructor stub
    }

    /**
     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // TODO Auto-generated method stub
        response.getWriter().append("Served at: ").append(request.getContextPath());
    }
}
  • 默认首页index.jsp会让/ 失效

我们在WebContent 目录下新建一个index.jsp, 这时候在访问http://localhost:8080/filter/ 就会访问index.jsp 而不会访问servlet了. 要注意这样的问题

如果我们想要即拦截servlet又想拦截jsp可以通过多个filter-mapping来进行设置,可以一一匹配

<filter-mapping>
        <filter-name>UrlPatternFilter</filter-name>
        <url-pattern>/index.html</url-pattern> <!-- 过滤器的作用范围 只用于/test.jsp /servlet/*  *.jsp-->
    </filter-mapping>
    <filter-mapping>
        <filter-name>UrlPatternFilter</filter-name>
        <url-pattern>/servlet/*</url-pattern> <!-- 过滤器的作用范围 只用于/test.jsp /servlet/*  *.jsp-->
    </filter-mapping>
    <filter-mapping>
        <filter-name>UrlPatternFilter</filter-name>
        <url-pattern>*.jsp</url-pattern> <!-- 过滤器的作用范围 只用于/test.jsp /servlet/*  *.jsp-->
    </filter-mapping>

/ 与 /* 含义不同, 前者指向根路径,后者代表所有

关于注解形式的设置,关于过滤器的配置推荐写到web.xml方便管理,不建议写到注解

@WebFilter(filterName = "UrlPatternFilter",urlPatterns = {
        "/","servlet/*","*.jsp"
})

过滤链

前面有讲到过过滤器的执行过程,下面来看如何设置过滤链的

  • 过滤链开发注意事项

每一个过滤器应具有单独职能
过滤器的执行顺序以 为准,写的靠前会先执行过滤器,同时web.xml配置的过滤器会比注解的过滤器先执行.
如果过滤器全部使用注解形式配置过滤器,注解形式的过滤链是按照过滤器的类名的字母升序的顺序依次执行的而不是filterName名称.(这样配置是非常不合理的,过滤器最好不要使用注解的形式进行配置,无法控制前后的执行顺序,影响系统的维护性)
调用chain.doFilter()将请求向后传递

我们再看一下下图:

image.png

我们通过一个例子来进行掩饰

创建三个Filter

public class FilterA implements Filter{

    @Override
    public void destroy() {
        // TODO Auto-generated method stub

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        System.out.println("Filter A");
        chain.doFilter(request, response);
        System.out.println("Back A");
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // TODO Auto-generated method stub

    }

}
public class FilterB implements Filter{

    @Override
    public void destroy() {
        // TODO Auto-generated method stub

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        System.out.println("Filter B");
        chain.doFilter(request, response);
        System.out.println("Back B");
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // TODO Auto-generated method stub

    }

}
public class FilterC implements Filter{

    @Override
    public void destroy() {
        // TODO Auto-generated method stub

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        System.out.println("Filter C");
        chain.doFilter(request, response);
        System.out.println("Back C");
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // TODO Auto-generated method stub

    }

}

然后我们在web.xml配置过滤链的执行顺序通过的先后顺序: A - B - C,拦截所有URL

 <filter>
      <filter-name>filterA</filter-name>
      <filter-class>com.jakeprim.filter.FilterA</filter-class>
  </filter>
  <filter>
      <filter-name>filterB</filter-name>
      <filter-class>com.jakeprim.filter.FilterB</filter-class>
  </filter>
   <filter>
      <filter-name>filterC</filter-name>
      <filter-class>com.jakeprim.filter.FilterC</filter-class>
  </filter>

  <filter-mapping>
      <filter-name>filterA</filter-name>
      <url-pattern>/*</url-pattern>
  </filter-mapping>


  <filter-mapping>
      <filter-name>filterB</filter-name>
      <url-pattern>/*</url-pattern>
  </filter-mapping>


  <filter-mapping>
      <filter-name>filterC</filter-name>
      <url-pattern>/*</url-pattern>
  </filter-mapping>

新建一个servlet

@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    /**
     * @see HttpServlet#HttpServlet()
     */
    public HelloServlet() {
        super();
        // TODO Auto-generated constructor stub
    }

    /**
     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // TODO Auto-generated method stub
        response.getWriter().append("Served at: ").append(request.getContextPath()).append("Hello");
        System.out.println("Hello");
    }

}

最后访问这个servlet:http://localhost:8080/filter-chain/hello
控制台打印如下:
从如下结果中我们就可以看出
顺序为 FilterA -》 FilterB -> FilterC -> HelloServlet,执行到servlet后然后返回浏览器响应. 跟我们上图的执行过程完全一致.

Filter A
Filter B
Filter C
Hello
Back C
Back B
Back A

我们通过 来调整一下执行顺序

    <filter-mapping>
        <filter-name>filterB</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <filter-mapping>
        <filter-name>filterA</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <filter-mapping>
        <filter-name>filterC</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

执行顺序如下:

Filter B
Filter A
Filter C
Hello
Back C
Back A
Back B

到现在为止,大家可能对过滤链有什么作用还存在疑虑,其实有很多案例都有可能会用到过滤链,比如:开发一个网站,要求这个网站只能在中国的IP访问,禁止国外的IP访问,这个时候我们就会用到过滤链了,我们在FilterB中进行判断如果是国外的IP就不执行chain``.doFilter(),doFilter()如果不执行就不会执行下一个过滤器了直接返回禁止访问的页面.
如下代码:

public class FilterB implements Filter{

    @Override
    public void destroy() {
        // TODO Auto-generated method stub

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
//        System.out.println("Filter B");
//        chain.doFilter(request, response);
//        System.out.println("Back B");
        boolean isIP = true;
        if (isIP) {
            response.setCharacterEncoding("UTF-8");
            response.setContentType("text/html;charset=utf-8");
            response.getWriter().println("非国内IP禁止访问");
        }else {
            chain.doFilter(request, response);
        }
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // TODO Auto-generated method stub

    }

}

返回结果:
同时过滤器A C 都不没有被执行,当前servlet也没有被执行.而是在FilterB中直接通过respone返回给浏览器了
image.png

如果不执行doFilter()就无法进行执行到servlet,可见过滤链的应用还是非常广泛的.

应用

通过使用过滤器,来判断是否是移动端和电脑端,分别显示不同的页面,过滤可以很简单的帮我们实现这个功能.
代码如下:

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        String trigetUri = httpServletRequest.getRequestURI();// /index.html
        System.out.println("trigetUri:" + trigetUri + httpServletRequest.getContextPath());
        if (trigetUri.indexOf("/desktop/index.html") != -1 || trigetUri.indexOf("/mobile/index.html") != -1) {
            chain.doFilter(request, response);
        } else if (trigetUri.indexOf("/index.html") != -1) {
            String userAgent = httpServletRequest.getHeader("User-Agent").toLowerCase();
            if (userAgent.indexOf("iphone") != -1 || userAgent.indexOf("android") != -1) {
                trigetUri = httpServletRequest.getContextPath() + "/mobile/index.html";
                httpServletResponse.sendRedirect(trigetUri);
            } else {
                trigetUri = httpServletRequest.getContextPath() + "/desktop/index.html";
                httpServletResponse.sendRedirect(trigetUri);
            }
        } else {
            chain.doFilter(request, response);
        }
    }