美文网首页MacOS, Java和IDEA程序员
Java Web开发中的过滤器(Filter)

Java Web开发中的过滤器(Filter)

作者: SpaceCat | 来源:发表于2019-12-10 23:03 被阅读0次

    1、Filter介绍

    Filter技术是servlet 2.3新增加的功能。servlet2.3是sun公司于2000年10月发布的,它的开发者包括许多个人和公司团体,充分体现了sun公司所倡导的代码开放性原则。在众多参与者的共同努力下,servlet2.3比以往功能都强大了许多,而且性能也有了大幅提高。
    通过Filter功能,用户可以改变一个request和修改一个response。Filter不是一个servlet,它不能产生一个response,但是,它能够在一个request到达servlet之前预处理request,也可以在response离开servlet时处理response。
    这样,Filter可以对所有的发往Java服务器的请求(包括对html,js,图片等静态资源的请求)做拦截,从而实现一些特殊的功能,如URL级别的权限访问控制、过滤敏感词汇、压缩响应信息等。

    2、Filter执行机制

    Servlet API中提供了一个Filter接口,开发web应用时,如果编写的Java类实现了这个接口,则把这个java类称之为过滤器Filter。通过Filter类,可以实现用户在访问某个目标资源之前,对访问的请求和响应进行拦截。
    Filter接口中有方法void doFilter(ServletRequest req,ServletResponse res,FilterChain chain)用来执行filter的工作。每一个filter从doFilter()方法中得到当前的request及response对象。在这个方法里,可以进行任何的针对request及response的操作,包括收集数据,包装数据等。filter调用chain.doFilter()方法将请求从该filter中放行。之后,有可能请求到了要访问的资源对象,也有可能把控制权交给了下一个filter。
    当我们编写好Filter,并配置对哪个web资源进行拦截后,WEB服务器每次在调用web资源的service方法之前,都会先调用一下filter的doFilter方法,因此,在该方法内编写代码可达到如下目的:

    • 调用目标资源之前,让一段代码执行。
    • 是否调用目标资源(即是否让用户访问web资源)。
    • 调用目标资源之后,让一段代码执行。

    web服务器在调用doFilter方法时,会传递一个filterChain对象进来,filterChain对象是filter接口中最重要的一个对象,它也提供了一个doFilter方法,开发人员可以根据需求决定是否调用此方法,调用该方法,则web服务器就会调用web资源的service方法,即web资源就会被访问,否则web资源不会被访问。

    3、Filter入门示意

    这里我们编写一个简单的Filter示意代码。
    首先,新建一个Filter类,实现javax.servlet.Filter接口。
    com.lfqy.web.filter.FilterTrial01

    package com.lfqy.web.filter;
    
    import javax.servlet.*;
    import javax.servlet.http.HttpServletRequest;
    import java.io.IOException;
    
    /**
     * Created by chengxia on 2019/11/4.
     * filter的三种典型应用:
     1、可以在filter中根据条件决定是否调用chain.doFilter(request, response)方法,即是否让目标资源执行
     2、在让目标资源执行之前,可以对request\response作预处理,再让目标资源执行
     3、在目标资源执行之后,可以捕获目标资源的执行结果,从而实现一些特殊的功能
     */
    public class FilterTrial01 implements Filter {
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
            System.out.println("The filter is initializing...");
        }
    
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            //设置request和response的编码格式
            servletRequest.setCharacterEncoding("UTF-8");
            servletResponse.setCharacterEncoding("UTF-8");
            //设置请求的contenttype
            servletResponse.setContentType("text/html;charset=UTF-8");
    
            System.out.println("Before the request is handled...");
            System.out.println("url: " + ((HttpServletRequest)servletRequest).getRequestURL());
            //让目标资源执行,放行
            filterChain.doFilter(servletRequest,servletResponse);
            System.out.println("After the request is handled...");
        }
    
        @Override
        public void destroy() {
            System.out.println("The filter is destroying...");
        }
    }
    

    在这个filter的实现类中,在请求的执行前后,分别输出提示信息和请求的url到控制台。
    然后,在配置文件中,添加这个filter的定义。
    WEB-INF/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_3_1.xsd"
             version="3.1">
        <filter>
            <filter-name>FilterTrial01</filter-name>
            <filter-class>com.lfqy.web.filter.FilterTrial01</filter-class>
        </filter>
        <filter-mapping>
            <filter-name>FilterTrial01</filter-name>
            <!-- "/*"表示拦截所有的请求 -->
            <url-pattern>*.jsp</url-pattern>
            <url-pattern>*.html</url-pattern>
        </filter-mapping>
    </web-app>
    

    从这个配置文件的内容,可以看出,filter的配置分为两部分:一部分是定义filter的名称和filter实现类的关联关系,另外一部分是定义filter所拦截请求的过滤规则。这里我们配置了两个请求url的拦截规则,也就是拦截所有对jsp和html页面的请求(前面已经说明filter可以拦截对静态资源的请求)。前一部分我们称为注册Filter,后一部分我们称为映射Filter。
    这里需要注意urlpattern的配置规则:

    • /开头和以/*结尾的是用来做路径映射的。
    • 以前缀*.开头的是用来做扩展映射的。
    • /是用来定义default servlet映射的。
    • 剩下的都是用来定义详细映射的。比如: /aa/bb/cc.jsp

    所以,如果urlpattern配置成/*.jsp会报错,因为它既属于路径映射,又属于后缀映射,应用服务器会困惑。

    最后,编写两个测试页面。
    index.jsp

    <%--
      Created by IntelliJ IDEA.
      User: chengxia
      Date: 2019/11/4
      Time: 7:47 AM
      To change this template use File | Settings | File Templates.
    --%>
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
      <head>
        <title>Trial Page for Filter</title>
      </head>
      <body>
      <h3>This is a filter page.</h3>
      </body>
    </html>
    

    test.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Gogogo</title>
    </head>
    <body>
    <h1>gogogo</h1>
    </body>
    </html>
    

    这样,启动服务器之后,分别访问http://localhost:8080/test.htmlhttp://localhost:8080/index.jsp,可以看到控制台输出如下:

    Before the request is handled...
    url: http://localhost:8080/test.html
    After the request is handled...
    Before the request is handled...
    url: http://localhost:8080/index.jsp
    After the request is handled...
    

    4、Filter其它说明

    4.1 Filter链

    在一个web应用中,可以开发编写多个Filter,这些Filter组合起来称之为一个Filter链。
    web服务器根据Filter在web.xml文件中的注册顺序,决定先调用哪个Filter,当第一个Filter的doFilter方法被调用时,web服务器会创建一个代表Filter链的FilterChain对象传递给该方法。在doFilter方法中,开发人员如果调用了FilterChain对象的doFilter方法,则web服务器会检查FilterChain对象中是否还有filter,如果有,则调用第2个filter,如果没有,则调用目标资源。

    4.2 Filter生命周期

    4.2.1 创建

    Filter的创建和销毁由应用服务器负责。 web应用启动时,应用服务器将创建Filter的实例对象,并调用其init方法,完成对象的初始化功能,从而为后续的用户请求作好拦截的准备工作,filter对象只会创建一次,init方法也只会执行一次。通过init方法的参数,可获得代表当前filter配置信息的FilterConfig对象。

    4.2.2 销毁

    Web容器调用destroy方法销毁Filter。destroy方法在Filter的生命周期中仅执行一次。在destroy方法中,可以释放过滤器使用的资源。

    4.2.3 FilterConfig接口

    用户在配置filter时,可以使用<init-param>为filter配置一些初始化参数,当web容器实例化Filter对象,调用其init方法时,会把封装了filter初始化参数的filterConfig对象传递进来。因此开发人员在编写filter时,通过filterConfig对象的方法,就可获得:

    • String getFilterName():得到filter的名称。
    • String getInitParameter(String name): 返回在部署描述中指定名称的初始化参数的值。如果不存在返回null.
    • Enumeration getInitParameterNames():返回过滤器的所有初始化参数的名字的枚举集合。
    • public ServletContext getServletContext():返回Servlet上下文对象的引用。

    如下是一个带参数的filter的例子。
    com.lfqy.web.filter.FilterInitParamTrial

    package com.lfqy.web.filter;
    
    import javax.servlet.*;
    import javax.servlet.http.HttpServletRequest;
    import java.io.IOException;
    
    /**
     * Created by chengxia on 2019/11/4.
     * filter的三种典型应用:
     1、可以在filter中根据条件决定是否调用chain.doFilter(request, response)方法,即是否让目标资源执行
     2、在让目标资源执行之前,可以对request\response作预处理,再让目标资源执行
     3、在目标资源执行之后,可以捕获目标资源的执行结果,从而实现一些特殊的功能
     */
    public class FilterInitParamTrial implements Filter {
        private String filterName;
        private String filterFunc;
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
            System.out.println("The filter is initializing...");
            filterName = filterConfig.getInitParameter("FilterName");
            filterFunc = filterConfig.getInitParameter("Function");
        }
    
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            //设置request和response的编码格式
            servletRequest.setCharacterEncoding("UTF-8");
            servletResponse.setCharacterEncoding("UTF-8");
            //设置请求的contenttype
            servletResponse.setContentType("text/html;charset=UTF-8");
    
            System.out.println(filterName + " for function " + filterFunc + "executing. Before the request is handled...");
            System.out.println("url: " + ((HttpServletRequest)servletRequest).getRequestURL());
            //让目标资源执行,放行
            filterChain.doFilter(servletRequest,servletResponse);
            System.out.println(filterName + " for function " + filterFunc + "executing. After the request is handled...");
        }
    
        @Override
        public void destroy() {
            System.out.println("The filter is destroying...");
        }
    }
    

    WEB-INF/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_3_1.xsd"
             version="3.1">
        <filter>
            <filter-name>FilterTrial01</filter-name>
            <filter-class>com.lfqy.web.filter.FilterTrial01</filter-class>
        </filter>
        <filter>
            <filter-name>FilterInitParamTrial</filter-name>
            <filter-class>com.lfqy.web.filter.FilterInitParamTrial</filter-class>
            <init-param>
                <description>测试Filter配置参数01</description>
                <param-name>FilterName</param-name>
                <param-value>PaopaoFilter</param-value>
            </init-param>
            <init-param>
                <description>测试Filter配置参数02</description>
                <param-name>Function</param-name>
                <param-value>Test filter init param...</param-value>
            </init-param>
        </filter>
        <filter-mapping>
            <filter-name>FilterTrial01</filter-name>
            <!-- "/*"表示拦截所有的请求 -->
            <url-pattern>*.jsp</url-pattern>
        </filter-mapping>
        <filter-mapping>
            <filter-name>FilterInitParamTrial</filter-name>
            <!-- "/*"表示拦截所有的请求 -->
            <url-pattern>*.html</url-pattern>
        </filter-mapping>
    </web-app>
    

    重启tomcat服务器,配置生效之后,访问http://localhost:8080/test.html,控制台输出如下:

    PaopaoFilter for function Test filter init param...executing. Before the request is handled...
    url: http://localhost:8080/test.html
    PaopaoFilter for function Test filter init param...executing. After the request is handled...
    

    可见,过滤器中配置的初始参数已经被拿到。

    4.3 映射Filter时的dispatcher属性

    <dispatcher>指定过滤器所拦截的资源被 Servlet 容器调用的方式,可以是REQUEST,INCLUDE,FORWARD和ERROR之一,默认REQUEST。用户可以设置多个<dispatcher> 子元素用来指定 Filter 对资源的多种调用方式进行拦截。如下:

    <filter-name>filterName</filter-name>
       <url-pattern>/*</url-pattern>
       <dispatcher>REQUEST</dispatcher>
       <dispatcher>FORWARD</dispatcher>
    </filter-mapping>
    

    <dispatcher>子元素可以设置的值及其意义:

    • REQUEST:当用户直接访问页面时,Web容器将会调用过滤器。如果目标资源是通过RequestDispatcher的include()forward()方法访问时,那么该过滤器就不会被调用。
    • INCLUDE:如果目标资源是通过RequestDispatcher的include()方法访问时,那么该过滤器将被调用。除此之外,该过滤器不会被调用。
    • FORWARD:如果目标资源是通过RequestDispatcher的forward()方法访问时,那么该过滤器将被调用,除此之外,该过滤器不会被调用。
    • ERROR:如果目标资源是通过声明式异常处理机制调用时,那么该过滤器将被调用。除此之外,过滤器不会被调用。

    5、Filter使用场景示例

    5.1 装饰器模式

    5.1.1 定义

    装饰器模式,又名包装(Wrapper)模式。装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案。装饰模式是在不必改变原类文件和使用继承的情况下,动态的扩展一个对象的功能。在装饰器模式中,会创建一个包装对象,用来装饰包裹真实的对象。

    5.1.2 使用场景说明

    当某个对象的方法不适应业务需求时,通常有2种方式可以对方法进行增强:

    • 编写子类,覆盖需增强的方法。
    • 使用装饰器设计模式对方法进行增强。

    在实际应用中遇到需增强对象的方法时,到底选用哪种方式比较好呢?这个没有具体的定式,只能是根据具体的需求来采用具体的方式,不过有一种情况下,必须使用Decorator设计模式:即被增强的对象,开发人员只能得到它的对象,无法得到它的class文件。比如request、response对象,开发人员之所以在servlet中能通过sun公司定义的HttpServletRequest\response接口去操作这些对象,是因为Tomcat服务器厂商编写了request、response接口的实现类。web服务器在调用servlet时,会用这些接口的实现类创建出对象,然后传递给servlet程序。此种情况下,由于开发人员根本不知道服务器厂商编写的request、response接口的实现类是哪个?在程序中只能拿到服务器厂商提供的对象,因此就只能采用Decorator设计模式对这些对象进行增强。

    5.1.3 装饰器模式的实现

    在实现装饰器模式时,首先看需要被增强对象继承了什么接口或父类,编写一个类也去继承这些接口或父类。然后,在类中定义一个变量,变量类型即需增强对象的类型,同时,在类中定义一个构造函数,构造函数的参数是需增强的对象。最后,覆盖需增强的方法,编写增强的代码。
    这里需要注意,装饰器模式中,必须重新编写接口或者父类的所有方法。对于其中,根本不需要增强的方法,只需要简单调用被增强对象(装饰器类中的成员变量)的同名方法即可。

    5.2 Servlet API中对于请求对象和响应对象的默认装饰器实现

    Servlet API中提供了一个request对象的Decorator设计模式的默认实现类HttpServletRequestWrapper,HttpServletRequestWrapper类实现了request接口中的所有方法,但这些方法的内部实现都是仅仅调用了一下所包装的的 request对象的对应方法,以避免用户在对request对象进行增强时需要实现request接口中的所有方法。
    Servlet API中也提供了response对象的Decorator设计模式的默认实现类HttpServletResponseWrapper,HttpServletResponseWrapper类实现了response接口中的所有方法,但这些方法的内部实现都是仅仅调用了一下所包装的的 response对象的对应方法,以避免用户在对response对象进行增强时需要实现response接口中的所有方法。
    这两个现成的装饰器类中,都定义了各自的包装对象,实现了request对象和reponse对象对应接口的方法。只不过在方法的实现中,都是简单调用对应包装对象的方法。这样的好处是,当我们自己实现装饰器类的时候,只需要继承这两个线程的装饰器类,然后,重写我们想扩展功能的方法即可。

    5.2.1 使用Decorator模式包装request对象解决get请求中的中文参数乱码问题

    5.2.1.1 问题原因

    Tomcat 7之前的版本,对于URL中字符的编码都是使用iso8859-1。对于Get请求来说,参数是放在URL中的,所以,对于GET请求,在获得请求参数的时候,如果没有进行合适的转码操作,取出的参数值就可能是乱码。Tomcat 8以后默认的URL编码格式是utf-8。
    在Tomcat配置文件conf/server.xml中设置URIEncoding的值为UTF-8:

    <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443"
               URIEncoding="utf-8"/>
    

    但是不建议这么做。因为这样相当于应用层的代码依赖于Tomcat的设置,有损可移植性。
    这里用的是Tomcat 8,为了说明这个例子,这里将Tomcat默认的URL编码方式改成了``

    <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443"
               URIEncoding="iso-8859-1"/>
    

    5.2.1.2 代码示例

    首先,写一个解决编码问题的filter实现类,它的作用就是拦截请求,然后,将请求做一个转码包装,然后放行。
    com.lfqy.web.filter.CharacterEncodingFilter

    package com.lfqy.web.filter;
    
    import com.lfqy.web.wrapper.CharacterEncodingRequest;
    import javax.servlet.*;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    /**
     * Created by chengxia on 2019/11/13.
     */
    public class CharacterEncodingFilter implements Filter {
    
        private FilterConfig filterConfig = null;
        //设置默认的字符编码
        private String defaultCharset = "UTF-8";
    
        public void init(FilterConfig filterConfig){
            //在初始化时,得到过滤器的配置信息
            this.filterConfig = filterConfig;
        }
        public void doFilter(ServletRequest req, ServletResponse resp,
                             FilterChain chain) throws IOException, ServletException {
    
            HttpServletRequest request = (HttpServletRequest) req;
            HttpServletResponse response = (HttpServletResponse) resp;
            //得到在web.xml中配置的字符编码
            String charset = filterConfig.getInitParameter("charset");
            if(charset==null){
                charset = defaultCharset;
            }
            request.setCharacterEncoding(charset);
            response.setCharacterEncoding(charset);
            response.setContentType("text/html;charset="+charset);
    
            CharacterEncodingRequest requestWrapper = new CharacterEncodingRequest(request);
            chain.doFilter(requestWrapper, response);
        }
    
        public void destroy(){
            //do nothing in destory.
        }
    }
    

    写一个请求对象的包装类,在包装类中,重写获取请求参数的getParameter方法,如果是get请求,就转码之后,再返回值。
    com.lfqy.web.wrapper.CharacterEncodingRequest

    package com.lfqy.web.wrapper;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletRequestWrapper;
    
    /**
     * Created by chengxia on 2019/11/11.
     */
    public class CharacterEncodingRequest extends HttpServletRequestWrapper {
        //定义一个成员变量,该对象就是被增强对象
        private HttpServletRequest req;
        //定义一个构造函数,其参数是被增强对象
        public CharacterEncodingRequest(HttpServletRequest req){
            super(req);
            this.req = req;
        }
    
        @Override
        public String getParameter(String name){
            try{
                //获取参数的值
                String value= this.req.getParameter(name);
                if(value==null){
                    return null;
                }
                //如果不是以get方式提交数据的,就直接返回获取到的值
                if(!this.req.getMethod().equalsIgnoreCase("get")) {
                    return value;
                }else{
                    //如果是以get方式提交数据的,就对获取到的值进行转码处理
                    value = new String(value.getBytes("ISO8859-1"),this.req.getCharacterEncoding());
                    return value;
                }
            }catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }
    

    接下来写一个jsp页面,用来分别发送GET请求和POST请求:
    EncodingFilterTest.jsp

    <%--
      Created by IntelliJ IDEA.
      User: chengxia
      Date: 2019/11/13
      Time: 7:53 AM
      To change this template use File | Settings | File Templates.
    --%>
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <%--引入jstl标签库 --%>
    <%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
    <html>
    <head>
        <title>Encoding Filter Test</title>
    </head>
    <body>
    <%--使用c:url标签构建url,构建好的url存储在reqGetUrl变量中--%>
    <c:url value="/EncodingTestServlet" scope="page" var="reqGetUrl">
        <%--构建的url的附带的中文参数 ,参数名是:petname,值是:于泡泡--%>
        <c:param name="petname" value="于泡泡"></c:param>
    </c:url>
    <%--使用get的方式访问 --%>
    <a href="${reqGetUrl}">超链接(get方式请求)</a>
    <hr/>
    <%--使用post方式提交表单 --%>
    <form action="${pageContext.request.contextPath}/EncodingTestServlet" method="post">
        用户名:<input type="text" name="petname" value="于泡泡" />
        <input type="submit" value="post方式提交">
    </form>
    
    </body>
    </html>
    

    为了接收前面的POST请求,需要再写一个Servlet。
    com.lfqy.web.servlet.EncodingTestServlet

    package com.lfqy.web.servlet;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.io.PrintWriter;
    
    /**
     * Created by chengxia on 2019/11/13.
     */
    public class EncodingTestServlet extends HttpServlet {
        public void doGet(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
            //接收参数
            String petname = request.getParameter("petname");
            //获取请求方式
            String method = request.getMethod();
            //获取输出流
            PrintWriter out = response.getWriter();
            out.write("请求的方式:"+method);
            out.write("<br/>");
            out.write("接收到的参数:"+petname);
        }
    
        public void doPost(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
            doGet(request, response);
        }
    }
    

    最后看下配置文件WEB-INF/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_3_1.xsd"
             version="3.1">
        <filter>
            <filter-name>FilterTrial01</filter-name>
            <filter-class>com.lfqy.web.filter.FilterTrial01</filter-class>
        </filter>
        <filter>
            <filter-name>FilterInitParamTrial</filter-name>
            <filter-class>com.lfqy.web.filter.FilterInitParamTrial</filter-class>
            <init-param>
                <description>测试Filter配置参数01</description>
                <param-name>FilterName</param-name>
                <param-value>PaopaoFilter</param-value>
            </init-param>
            <init-param>
                <description>测试Filter配置参数02</description>
                <param-name>Function</param-name>
                <param-value>Test filter init param...</param-value>
            </init-param>
        </filter>
        <filter>
            <filter-name>CharacterEncodingFilter</filter-name>
            <filter-class>com.lfqy.web.filter.CharacterEncodingFilter</filter-class>
            <init-param>
                <description>设置编码信息</description>
                <param-name>charset</param-name>
                <param-value>utf-8</param-value>
            </init-param>
        </filter>
        <filter-mapping>
            <filter-name>FilterTrial01</filter-name>
            <!-- "/*"表示拦截所有的请求 -->
            <url-pattern>*.jsp</url-pattern>
        </filter-mapping>
        <filter-mapping>
            <filter-name>FilterInitParamTrial</filter-name>
            <!-- "/*"表示拦截所有的请求 -->
            <url-pattern>*.html</url-pattern>
        </filter-mapping>
        <filter-mapping>
            <filter-name>CharacterEncodingFilter</filter-name>
            <!-- "/*"表示拦截所有的请求 -->
            <url-pattern>/*</url-pattern>
        </filter-mapping>
    
        <servlet>
            <servlet-name>EncodingTestServlet</servlet-name>
            <servlet-class>com.lfqy.web.servlet.EncodingTestServlet</servlet-class>
        </servlet>
        <servlet-mapping>
            <servlet-name>EncodingTestServlet</servlet-name>
            <url-pattern>/EncodingTestServlet</url-pattern>
        </servlet-mapping>
    
        <servlet>
            <servlet-name>PostUrlParamTestServlet</servlet-name>
            <servlet-class>com.lfqy.web.servlet.PostUrlParamTestServlet</servlet-class>
        </servlet>
        <servlet-mapping>
            <servlet-name>PostUrlParamTestServlet</servlet-name>
            <url-pattern>/PostUrlParamTestServlet</url-pattern>
        </servlet-mapping>
    </web-app>
    

    启动项目在Tomcat服务器上运行,然后访问http://localhost:8080/EncodingFilterTest.jsp,效果如下:

    测试页面
    这样,无论点击链接还是点击按钮,都会能够得到正确的请求参数并回显。
    GET请求
    POST请求

    5.2.2 使用Decorator模式包装request对象实现HTML字符的转义

    用户在浏览器页面输入的内容,如果其中含有HTML的中需要转义的字符,需要进行转义之后才能够正常显示。这里就介绍如何通过装饰器实现该功能。
    首先,新建一个装饰器类,用于实现请求内容中参数值的转义。
    com.lfqy.web.wrapper.HTMLCharacterEscapeRequest

    package com.lfqy.web.wrapper;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletRequestWrapper;
    
    /**
     * Created by chengxia on 2019/11/11.
     */
    public class HTMLCharacterEscapeRequest extends HttpServletRequestWrapper {
        //定义一个成员变量,该对象就是被增强对象
        private HttpServletRequest req;
        //定义一个构造函数,其参数是被增强对象
        public HTMLCharacterEscapeRequest(HttpServletRequest req){
            super(req);
            this.req = req;
        }
    
        @Override
        public String getParameter(String name){
    
            //获取参数的值
            String value= this.req.getParameter(name);
            if(value==null || value.length() ==0 ){
                return value;
            }
            return filter(value);
        }
    
        private String filter(String msg){
            if (msg == null){
                return null;
            }
            char content[] = new char[msg.length()];
            msg.getChars(0, msg.length(), content, 0);
            StringBuffer result = new StringBuffer(content.length + 50);
            for (int i = 0; i < content.length; i++) {
                switch (content[i]) {
                    case '<':
                        result.append("&lt;");
                        break;
                    case '>':
                        result.append("&gt;");
                        break;
                    case '&':
                        result.append("&amp;");
                        break;
                    case '"':
                        result.append("&quot;");
                        break;
                    default:
                        result.append(content[I]);
                }
            }
            return result.toString();
        }
    }
    

    新建一个filter实现类。
    com.lfqy.web.filter.HTMLCharacterEscapeFilter

    package com.lfqy.web.filter;
    
    import com.lfqy.web.wrapper.CharacterEncodingRequest;
    import com.lfqy.web.wrapper.HTMLCharacterEscapeRequest;
    
    import javax.servlet.*;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    /**
     * Created by chengxia on 2019/11/13.
     */
    public class HTMLCharacterEscapeFilter implements Filter {
    
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
            //do nothing
        }
    
        public void doFilter(ServletRequest req, ServletResponse resp,
                             FilterChain chain) throws IOException, ServletException {
    
            HttpServletRequest request = (HttpServletRequest) req;
            HttpServletResponse response = (HttpServletResponse) resp;
    
            HTMLCharacterEscapeRequest requestWrapper = new HTMLCharacterEscapeRequest(request);
            chain.doFilter(requestWrapper, response);
        }
    
        public void destroy(){
            //do nothing in destory.
        }
    }
    

    写一个jsp页面,用来测试。
    HTMLCharacterEscapeTest.jsp

    <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
    <!DOCTYPE HTML>
    <html>
    <head>
        <title>Html Character Escape Test</title>
    </head>
    
    <body>
    <form action="${pageContext.request.contextPath}/HTMLCharacterEscapeTestServlet" method="post">
        留言:
        <textarea rows="8" cols="70" name="message">
            <script type="text/javascript">
                alert("This is my house.")
            </script>
            <a href="http://www.poorage.com">An Unknown Website</a>
        </textarea>
        <input type="submit" value="发表">
    </form>
    </body>
    </html>
    

    写一个Servlet,用于接收测试页面发送的请求。
    com.lfqy.web.servlet.HTMLCharacterEscapeTestServlet

    package com.lfqy.web.servlet;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.io.PrintWriter;
    
    /**
     * Created by chengxia on 2019/11/13.
     */
    public class HTMLCharacterEscapeTestServlet extends HttpServlet {
        public void doGet(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
            //接收参数
            String msg = request.getParameter("message");
            //获取输出流
            PrintWriter out = response.getWriter();
            out.write("message got:"+"<br/>"+msg);
        }
    
        public void doPost(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
            doGet(request, response);
        }
    }
    

    最后,在配置文件中,配置Servlet和过滤器。
    WEB-INF/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_3_1.xsd"
             version="3.1">
        <filter>
            <filter-name>FilterTrial01</filter-name>
            <filter-class>com.lfqy.web.filter.FilterTrial01</filter-class>
        </filter>
        <filter>
            <filter-name>FilterInitParamTrial</filter-name>
            <filter-class>com.lfqy.web.filter.FilterInitParamTrial</filter-class>
            <init-param>
                <description>测试Filter配置参数01</description>
                <param-name>FilterName</param-name>
                <param-value>PaopaoFilter</param-value>
            </init-param>
            <init-param>
                <description>测试Filter配置参数02</description>
                <param-name>Function</param-name>
                <param-value>Test filter init param...</param-value>
            </init-param>
        </filter>
        <filter>
            <filter-name>CharacterEncodingFilter</filter-name>
            <filter-class>com.lfqy.web.filter.CharacterEncodingFilter</filter-class>
            <init-param>
                <description>设置编码信息</description>
                <param-name>charset</param-name>
                <param-value>utf-8</param-value>
            </init-param>
        </filter>
        <filter>
            <filter-name>HTMLCharacterEscapeFilter</filter-name>
            <filter-class>com.lfqy.web.filter.HTMLCharacterEscapeFilter</filter-class>
        </filter>
        <filter-mapping>
            <filter-name>FilterTrial01</filter-name>
            <!-- "/*"表示拦截所有的请求 -->
            <url-pattern>*.jsp</url-pattern>
        </filter-mapping>
        <filter-mapping>
            <filter-name>FilterInitParamTrial</filter-name>
            <!-- "/*"表示拦截所有的请求 -->
            <url-pattern>*.html</url-pattern>
        </filter-mapping>
        <filter-mapping>
            <filter-name>CharacterEncodingFilter</filter-name>
            <!-- "/*"表示拦截所有的请求 -->
            <url-pattern>/*</url-pattern>
        </filter-mapping>
        <filter-mapping>
            <filter-name>HTMLCharacterEscapeFilter</filter-name>
            <!-- "/*"表示拦截所有的请求 -->
            <url-pattern>/*</url-pattern>
        </filter-mapping>
    
        <servlet>
            <servlet-name>EncodingTestServlet</servlet-name>
            <servlet-class>com.lfqy.web.servlet.EncodingTestServlet</servlet-class>
        </servlet>
        <servlet-mapping>
            <servlet-name>EncodingTestServlet</servlet-name>
            <url-pattern>/EncodingTestServlet</url-pattern>
        </servlet-mapping>
    
        <servlet>
            <servlet-name>PostUrlParamTestServlet</servlet-name>
            <servlet-class>com.lfqy.web.servlet.PostUrlParamTestServlet</servlet-class>
        </servlet>
        <servlet-mapping>
            <servlet-name>PostUrlParamTestServlet</servlet-name>
            <url-pattern>/PostUrlParamTestServlet</url-pattern>
        </servlet-mapping>
    
        <servlet>
            <servlet-name>HTMLCharacterEscapeTestServlet</servlet-name>
            <servlet-class>com.lfqy.web.servlet.HTMLCharacterEscapeTestServlet</servlet-class>
        </servlet>
        <servlet-mapping>
            <servlet-name>HTMLCharacterEscapeTestServlet</servlet-name>
            <url-pattern>/HTMLCharacterEscapeTestServlet</url-pattern>
        </servlet-mapping>
    </web-app>
    

    启动服务器之后,访问http://localhost:8080/HTMLCharacterEscapeTest.jsp,效果如下:

    留言测试页面
    点击页面上的发表,效果如下。
    留言测试结果页面
    可以看到,前面的留言内容中的字符都已经被转义了。

    5.2.3 使用Decorator模式包装request对象实现敏感字符过滤

    首先,新建一个filter实现类,在初始化方法中,读取敏感词列表,加载到内存。同时,实现一个内部类,包装request类,重新实现获取参数值的方法。
    com.lfqy.web.filter.DirtyWordsFilter

    package com.lfqy.web.filter;
    
    import javax.servlet.*;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletRequestWrapper;
    import javax.servlet.http.HttpServletResponse;
    import java.io.*;
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * Created by chengxia on 2019/11/13.
     */
    public class DirtyWordsFilter implements Filter {
    
        private FilterConfig config = null;
        private List<String> dirtyWords;
    
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
            this.config = filterConfig;
            dirtyWords = getDirtyWords();
        }
    
        @Override
        public void doFilter(ServletRequest req, ServletResponse resp,
                             FilterChain chain) throws IOException, ServletException {
    
            HttpServletRequest request = (HttpServletRequest) req;
            HttpServletResponse response = (HttpServletResponse) resp;
            DirtyWordsRequest dirtyRequest = new DirtyWordsRequest(request);
    
            chain.doFilter(dirtyRequest, response);
        }
    
        @Override
        public void destroy() {
    
        }
    
        /**
         * @Method: getDirtyWords
         * @Description: 获取敏感字符
         * @Anthor:SpaceCatt
         *
         * @return
         */
        private List<String> getDirtyWords(){
            List<String> dirtyWords = new ArrayList<String>();
            String dirtyWordPath = config.getInitParameter("DirtyWordsFilePath");
            InputStream inputStream = config.getServletContext().getResourceAsStream(dirtyWordPath);
            InputStreamReader is = null;
            try {
                is = new InputStreamReader(inputStream,"UTF-8");
            } catch (UnsupportedEncodingException e2) {
                e2.printStackTrace();
            }
            BufferedReader reader = new BufferedReader(is);
            String line;
            try {
                while ((line = reader.readLine())!= null) {//如果 line为空说明读完了
                    dirtyWords.add(line);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            return dirtyWords;
        }
    
        /**
         * @ClassName: DirtyWordsRequest
         * @Description: 使用Decorator模式包装request对象,实现敏感字符过滤功能
         * @author: spacecat
         * @date: 2019-12-6 上午08:56:35
         *
         */
        class DirtyWordsRequest extends HttpServletRequestWrapper {
            private HttpServletRequest request;
            public DirtyWordsRequest(HttpServletRequest request) {
                super(request);
                this.request = request;
            }
            /* 重写getParameter方法,实现对敏感字符的过滤
             * @see javax.servlet.ServletRequestWrapper#getParameter(java.lang.String)
             */
            @Override
            public String getParameter(String name) {
    
                String value = this.request.getParameter(name);
                if(value==null){
                    return null;
                }
    
                for(String dirtyWord : dirtyWords){
                    if(value.contains(dirtyWord)){
                        System.out.println("内容中包含敏感词:"+dirtyWord+",将会被替换成****");
                        //替换敏感字符
                        value = value.replace(dirtyWord, "****");
                    }
                }
                return value;
            }
        }
    }
    

    然后,写一个测试页面,提交带敏感词的表单内容。
    DirtyWordsFilterTest.jsp

    <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
    <!DOCTYPE HTML>
    <html>
    <head>
        <title>Html Character Escape Test</title>
    </head>
    
    <body>
    <form action="${pageContext.request.contextPath}/HTMLCharacterEscapeTestServlet" method="post">
        留言:
        <textarea rows="8" cols="70" name="message">
            这是我的留言,包含敏感词1,敏感词2等敏感词。哈哈
        </textarea>
        <input type="submit" value="发表">
    </form>
    </body>
    </html>
    

    这里表单的数据直接提交到com.lfqy.web.servlet.HTMLCharacterEscapeTestServlet即可,无需新写。
    最后修改配置文件。
    WEB-INF/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_3_1.xsd"
             version="3.1">
        <filter>
            <filter-name>FilterTrial01</filter-name>
            <filter-class>com.lfqy.web.filter.FilterTrial01</filter-class>
        </filter>
        <filter>
            <filter-name>FilterInitParamTrial</filter-name>
            <filter-class>com.lfqy.web.filter.FilterInitParamTrial</filter-class>
            <init-param>
                <description>测试Filter配置参数01</description>
                <param-name>FilterName</param-name>
                <param-value>PaopaoFilter</param-value>
            </init-param>
            <init-param>
                <description>测试Filter配置参数02</description>
                <param-name>Function</param-name>
                <param-value>Test filter init param...</param-value>
            </init-param>
        </filter>
        <filter>
            <filter-name>CharacterEncodingFilter</filter-name>
            <filter-class>com.lfqy.web.filter.CharacterEncodingFilter</filter-class>
            <init-param>
                <description>设置编码信息</description>
                <param-name>charset</param-name>
                <param-value>utf-8</param-value>
            </init-param>
        </filter>
        <filter>
            <filter-name>DirtyWordsFilter</filter-name>
            <filter-class>com.lfqy.web.filter.DirtyWordsFilter</filter-class>
            <init-param>
                <description>设置包含敏感词的配置文件路径</description>
                <param-name>DirtyWordsFilePath</param-name>
                <param-value>/WEB-INF/DirtyWords.txt</param-value>
            </init-param>
        </filter>
        <filter>
            <filter-name>HTMLCharacterEscapeFilter</filter-name>
            <filter-class>com.lfqy.web.filter.HTMLCharacterEscapeFilter</filter-class>
        </filter>
        <filter-mapping>
            <filter-name>FilterTrial01</filter-name>
            <!-- "/*"表示拦截所有的请求 -->
            <url-pattern>*.jsp</url-pattern>
        </filter-mapping>
        <filter-mapping>
            <filter-name>FilterInitParamTrial</filter-name>
            <!-- "/*"表示拦截所有的请求 -->
            <url-pattern>*.html</url-pattern>
        </filter-mapping>
        <filter-mapping>
            <filter-name>CharacterEncodingFilter</filter-name>
            <!-- "/*"表示拦截所有的请求 -->
            <url-pattern>/*</url-pattern>
        </filter-mapping>
        <filter-mapping>
            <filter-name>HTMLCharacterEscapeFilter</filter-name>
            <!-- "/*"表示拦截所有的请求 -->
            <url-pattern>/*</url-pattern>
        </filter-mapping>
        <filter-mapping>
            <filter-name>DirtyWordsFilter</filter-name>
            <!-- "/*"表示拦截所有的请求 -->
            <url-pattern>/*</url-pattern>
        </filter-mapping>
    
        <servlet>
            <servlet-name>EncodingTestServlet</servlet-name>
            <servlet-class>com.lfqy.web.servlet.EncodingTestServlet</servlet-class>
        </servlet>
        <servlet-mapping>
            <servlet-name>EncodingTestServlet</servlet-name>
            <url-pattern>/EncodingTestServlet</url-pattern>
        </servlet-mapping>
    
        <servlet>
            <servlet-name>PostUrlParamTestServlet</servlet-name>
            <servlet-class>com.lfqy.web.servlet.PostUrlParamTestServlet</servlet-class>
        </servlet>
        <servlet-mapping>
            <servlet-name>PostUrlParamTestServlet</servlet-name>
            <url-pattern>/PostUrlParamTestServlet</url-pattern>
        </servlet-mapping>
    
        <servlet>
            <servlet-name>HTMLCharacterEscapeTestServlet</servlet-name>
            <servlet-class>com.lfqy.web.servlet.HTMLCharacterEscapeTestServlet</servlet-class>
        </servlet>
        <servlet-mapping>
            <servlet-name>HTMLCharacterEscapeTestServlet</servlet-name>
            <url-pattern>/HTMLCharacterEscapeTestServlet</url-pattern>
        </servlet-mapping>
    </web-app>
    

    启动tomcat之后,访问http://localhost:8080/DirtyWordsFilterTest.jsp,效果如下:

    留言敏感词测试页面
    点击发表,可以看到关键词已经都被替换:
    留言敏感词测试结果页面
    在实际中,我们经常将上面的三个过滤器合并成一个。如下:
    com.lfqy.web.filter.TripleInOneFilter
    package com.lfqy.web.filter;
    
    import javax.servlet.*;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletRequestWrapper;
    import javax.servlet.http.HttpServletResponse;
    import java.io.*;
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * Created by chengxia on 2019/11/13.
     */
    public class TripleInOneFilter implements Filter {
    
        private FilterConfig config = null;
        private List<String> dirtyWords;
        //设置默认的字符编码
        private String defaultCharset = "UTF-8";
    
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
            this.config = filterConfig;
            dirtyWords = getDirtyWords();
        }
    
        @Override
        public void doFilter(ServletRequest req, ServletResponse resp,
                             FilterChain chain) throws IOException, ServletException {
    
            HttpServletRequest request = (HttpServletRequest) req;
            HttpServletResponse response = (HttpServletResponse) resp;
            //得到在web.xml中配置的字符编码
            String charset = config.getInitParameter("charset");
            if(charset==null){
                charset = defaultCharset;
            }
            request.setCharacterEncoding(charset);
            response.setCharacterEncoding(charset);
            response.setContentType("text/html;charset="+charset);
    
            TripleInOneRequest filteredRequest = new TripleInOneRequest(request);
    
            chain.doFilter(filteredRequest, response);
        }
    
        @Override
        public void destroy() {
    
        }
    
        /**
         * @Method: getDirtyWords
         * @Description: 获取敏感字符
         * @Anthor:SpaceCat
         *
         * @return
         */
        private List<String> getDirtyWords(){
            List<String> dirtyWords = new ArrayList<String>();
            String dirtyWordPath = config.getInitParameter("DirtyWordsFilePath");
            InputStream inputStream = config.getServletContext().getResourceAsStream(dirtyWordPath);
            InputStreamReader is = null;
            try {
                is = new InputStreamReader(inputStream,"UTF-8");
            } catch (UnsupportedEncodingException e2) {
                e2.printStackTrace();
            }
            BufferedReader reader = new BufferedReader(is);
            String line;
            try {
                while ((line = reader.readLine())!= null) {//如果 line为空说明读完了
                    dirtyWords.add(line);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            return dirtyWords;
        }
    
        /**
         * @ClassName: TripleInOneRequest
         * @Description: 使用Decorator模式包装request对象,实现敏感字符过滤功能
         * @author: spacecat
         * @date: 2019-12-6 上午08:56:35
         *
         */
        class TripleInOneRequest extends HttpServletRequestWrapper {
            private HttpServletRequest request;
            public TripleInOneRequest(HttpServletRequest request) {
                super(request);
                this.request = request;
            }
            /* 重写getParameter方法,实现对敏感字符的过滤
             * @see javax.servlet.ServletRequestWrapper#getParameter(java.lang.String)
             */
            @Override
            public String getParameter(String name) {
    
                //获取参数的值
                String value= this.request.getParameter(name);
                if(value==null){
                    return null;
                }
    
                //如果不是以get方式提交数据的,就直接返回获取到的值;如果是get方法,就需要进行编码转换
                if(!this.request.getMethod().equalsIgnoreCase("get")) {
                    return value;
                }else{
                    try {
                        //如果是以get方式提交数据的,就对获取到的值进行转码处理
                        value = new String(value.getBytes("ISO8859-1"),this.request.getCharacterEncoding());
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
    
                //html字符转义
                value = filter(value);
    
                //敏感词过滤
                for(String dirtyWord : dirtyWords){
                    if(value.contains(dirtyWord)){
                        System.out.println("内容中包含敏感词:"+dirtyWord+",将会被替换成****");
                        //替换敏感字符
                        value = value.replace(dirtyWord, "****");
                    }
                }
                return value;
            }
    
            public String filter(String value) {
                if (value == null){
                    return null;
                }
                char content[] = new char[value.length()];
                value.getChars(0, value.length(), content, 0);
                StringBuffer result = new StringBuffer(content.length + 50);
                for (int i = 0; i < content.length; i++) {
                    switch (content[i]) {
                        case '<':
                            result.append("&lt;");
                            break;
                        case '>':
                            result.append("&gt;");
                            break;
                        case '&':
                            result.append("&amp;");
                            break;
                        case '"':
                            result.append("&quot;");
                            break;
                        default:
                            result.append(content[I]);
                    }
                }
                return (result.toString());
            }
        }
    }
    

    在配置文件中添加的配置信息如下:
    WEB-INF/web.xml

    <filter>
        <filter-name>DirtyWordsFilter</filter-name>
        <filter-class>com.lfqy.web.filter.DirtyWordsFilter</filter-class>
        <init-param>
            <description>设置编码信息</description>
            <param-name>charset</param-name>
            <param-value>utf-8</param-value>
        </init-param>
        <init-param>
            <description>设置包含敏感词的配置文件路径</description>
            <param-name>DirtyWordsFilePath</param-name>
            <param-value>/WEB-INF/DirtyWords.txt</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>TripleInOneFilter</filter-name>
        <!-- "/*"表示拦截所有的请求 -->
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    

    5.2.4 使用Decorator设计模式包装response对象实现压缩响应正文内容

    通过filter向目标页面传递一个自定义的response对象。在自定义的response对象中,重写getOutputStream方法和getWriter方法,使目标资源调用此方法输出页面内容时,获得的是我们自定义的ServletOutputStream对象。在我们自定义的ServletOuputStream对象中,重写write方法,使写出的数据写出到一个buffer中(这里实际上是写到了java.io.ByteArrayOutputStream中,这个流自带一个自动增长的buffer,所有到输出到该流的数据都会先到buffer)。当页面完成输出后,通过调用自定义response对象的getBuffer()方法,在filter中就可得到页面写出的数据,从而我们可以调用GzipOuputStream对数据进行压缩后再写出给浏览器,以此完成响应正文件压缩功能。
    首先,实现一个自定义的过滤器类。
    com.lfqy.web.filter.GzipFilter

    package com.lfqy.web.filter;
    
    import javax.servlet.*;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletRequestWrapper;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpServletResponseWrapper;
    import java.io.*;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.zip.GZIPOutputStream;
    
    /**
     * Created by chengxia on 2019/11/13.
     */
    public class GzipFilter implements Filter {
    
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
        }
    
        @Override
        public void doFilter(ServletRequest req, ServletResponse resp,
                             FilterChain chain) throws IOException, ServletException {
    
            HttpServletRequest request = (HttpServletRequest) req;
            HttpServletResponse response = (HttpServletResponse) resp;
            BufferedResponse bufResp = new BufferedResponse(response);
            chain.doFilter(request, bufResp);
            //拿出缓存中的数据,压缩后再打给浏览器
            byte out[] = bufResp.getBuffer();
            System.out.println("原始大小:" + out.length);
    
            ByteArrayOutputStream bout = new ByteArrayOutputStream();
            //压缩输出流中的数据
            GZIPOutputStream gout = new GZIPOutputStream(bout);
            gout.write(out);
            gout.close();
    
            byte gzip[] = bout.toByteArray();
            System.out.println("压缩后的大小:" + gzip.length);
    
            response.setHeader("content-encoding", "gzip");
            response.setContentLength(gzip.length);
            response.getOutputStream().write(gzip);
        }
    
        @Override
        public void destroy() {
    
        }
    
        /**
         * @ClassName: BufferedResponse
         * @Description:
         * 使用Decorator模式包装response对象,输出到带缓存的ByteArrayOutputStream功能
         * 同时,实现了getBuffer方法,使能够拿到缓冲区的内容
         * @author: spacecat
         * @date: 2019-12-6 上午08:56:35
         *
         */
        class BufferedResponse extends HttpServletResponseWrapper {
    
            private ByteArrayOutputStream bout = new ByteArrayOutputStream();
            private PrintWriter pw;
            private HttpServletResponse response;
            public BufferedResponse(HttpServletResponse response) {
                super(response);
                this.response = response;
            }
            /**
             * 这个方法是我们压缩之后,再向浏览器写数据的时候,要调用的。
             * */
            @Override
            public ServletOutputStream getOutputStream() throws IOException {
                return new MyServletOutputStream(bout);
            }
            /**
             * 这个方法是框架输出到response对象时调用的
             * */
            @Override
            public PrintWriter getWriter() throws IOException {
                pw = new PrintWriter(new OutputStreamWriter(bout,this.response.getCharacterEncoding()));
                return pw;
            }
    
            public byte[] getBuffer(){
                try{
                    if(pw!=null){
                        pw.close();
                    }
                    if(bout!=null){
                        bout.flush();
                        return bout.toByteArray();
                    }
    
    
                    return null;
                }catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        }
    
        class MyServletOutputStream extends ServletOutputStream{
    
            private ByteArrayOutputStream bout;
            public MyServletOutputStream(ByteArrayOutputStream bout){
                this.bout = bout;
            }
    
            @Override
            public void write(int b) throws IOException {
                this.bout.write(b);
            }
    
            @Override
            public boolean isReady() {
                return false;
            }
    
            @Override
            public void setWriteListener(WriteListener writeListener) {
            }
        }
    }
    

    修改配置文件,说明那些需要压缩。
    WEB-INF/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_3_1.xsd"
             version="3.1">
        <filter>
            <filter-name>FilterTrial01</filter-name>
            <filter-class>com.lfqy.web.filter.FilterTrial01</filter-class>
        </filter>
        <filter>
            <filter-name>FilterInitParamTrial</filter-name>
            <filter-class>com.lfqy.web.filter.FilterInitParamTrial</filter-class>
            <init-param>
                <description>测试Filter配置参数01</description>
                <param-name>FilterName</param-name>
                <param-value>PaopaoFilter</param-value>
            </init-param>
            <init-param>
                <description>测试Filter配置参数02</description>
                <param-name>Function</param-name>
                <param-value>Test filter init param...</param-value>
            </init-param>
        </filter>
        <filter>
            <filter-name>CharacterEncodingFilter</filter-name>
            <filter-class>com.lfqy.web.filter.CharacterEncodingFilter</filter-class>
            <init-param>
                <description>设置编码信息</description>
                <param-name>charset</param-name>
                <param-value>utf-8</param-value>
            </init-param>
        </filter>
        <filter>
            <filter-name>DirtyWordsFilter</filter-name>
            <filter-class>com.lfqy.web.filter.DirtyWordsFilter</filter-class>
            <init-param>
                <description>设置编码信息</description>
                <param-name>charset</param-name>
                <param-value>utf-8</param-value>
            </init-param>
            <init-param>
                <description>设置包含敏感词的配置文件路径</description>
                <param-name>DirtyWordsFilePath</param-name>
                <param-value>/WEB-INF/DirtyWords.txt</param-value>
            </init-param>
        </filter>
        <filter>
            <filter-name>TripleInOneFilter</filter-name>
            <filter-class>com.lfqy.web.filter.TripleInOneFilter</filter-class>
            <init-param>
                <description>设置包含敏感词的配置文件路径</description>
                <param-name>DirtyWordsFilePath</param-name>
                <param-value>/WEB-INF/DirtyWords.txt</param-value>
            </init-param>
        </filter>
        <filter>
            <filter-name>GzipFilter</filter-name>
            <filter-class>com.lfqy.web.filter.GzipFilter</filter-class>
        </filter>
        <filter>
            <filter-name>HTMLCharacterEscapeFilter</filter-name>
            <filter-class>com.lfqy.web.filter.HTMLCharacterEscapeFilter</filter-class>
        </filter>
        <filter-mapping>
            <filter-name>FilterTrial01</filter-name>
            <!-- "/*"表示拦截所有的请求 -->
            <url-pattern>*.jsp</url-pattern>
        </filter-mapping>
        <filter-mapping>
            <filter-name>FilterInitParamTrial</filter-name>
            <!-- "/*"表示拦截所有的请求 -->
            <url-pattern>*.html</url-pattern>
        </filter-mapping>
        <filter-mapping>
            <filter-name>CharacterEncodingFilter</filter-name>
            <!-- "/*"表示拦截所有的请求 -->
            <url-pattern>/*</url-pattern>
        </filter-mapping>
        <filter-mapping>
            <filter-name>HTMLCharacterEscapeFilter</filter-name>
            <!-- "/*"表示拦截所有的请求 -->
            <url-pattern>/*</url-pattern>
        </filter-mapping>
        <filter-mapping>
            <filter-name>DirtyWordsFilter</filter-name>
            <!-- "/*"表示拦截所有的请求 -->
            <url-pattern>/*</url-pattern>
        </filter-mapping>
        <filter-mapping>
            <filter-name>TripleInOneFilter</filter-name>
            <!-- "/*"表示拦截所有的请求 -->
            <url-pattern>/*</url-pattern>
        </filter-mapping>
        <!--jsp文件的输出的内容都经过压缩过滤器压缩后才输出 -->
        <filter-mapping>
            <filter-name>GzipFilter</filter-name>
            <url-pattern>*.jsp</url-pattern>
            <!-- 配置过滤器的拦截方式-->
            <!-- 对于在Servlet中通过
                request.getRequestDispatcher("jsp页面路径").forward(request, response)
            方式访问的Jsp页面的要进行拦截 -->
            <dispatcher>FORWARD</dispatcher>
            <!--对于直接以URL方式访问的jsp页面进行拦截,过滤器的拦截方式默认就是 REQUEST-->
            <dispatcher>REQUEST</dispatcher>
        </filter-mapping>
        <!--js文件的输出的内容都经过压缩过滤器压缩后才输出 -->
        <filter-mapping>
            <filter-name>GzipFilter</filter-name>
            <url-pattern>*.js</url-pattern>
        </filter-mapping>
        <!--css文件的输出的内容都经过压缩过滤器压缩后才输出 -->
        <filter-mapping>
            <filter-name>GzipFilter</filter-name>
            <url-pattern>*.css</url-pattern>
        </filter-mapping>
        <!--html文件的输出的内容都经过压缩过滤器压缩后才输出 -->
        <filter-mapping>
            <filter-name>GzipFilter</filter-name>
            <url-pattern>*.html</url-pattern>
        </filter-mapping>
    
        <servlet>
            <servlet-name>EncodingTestServlet</servlet-name>
            <servlet-class>com.lfqy.web.servlet.EncodingTestServlet</servlet-class>
        </servlet>
        <servlet-mapping>
            <servlet-name>EncodingTestServlet</servlet-name>
            <url-pattern>/EncodingTestServlet</url-pattern>
        </servlet-mapping>
    
        <servlet>
            <servlet-name>PostUrlParamTestServlet</servlet-name>
            <servlet-class>com.lfqy.web.servlet.PostUrlParamTestServlet</servlet-class>
        </servlet>
        <servlet-mapping>
            <servlet-name>PostUrlParamTestServlet</servlet-name>
            <url-pattern>/PostUrlParamTestServlet</url-pattern>
        </servlet-mapping>
    
        <servlet>
            <servlet-name>HTMLCharacterEscapeTestServlet</servlet-name>
            <servlet-class>com.lfqy.web.servlet.HTMLCharacterEscapeTestServlet</servlet-class>
        </servlet>
        <servlet-mapping>
            <servlet-name>HTMLCharacterEscapeTestServlet</servlet-name>
            <url-pattern>/HTMLCharacterEscapeTestServlet</url-pattern>
        </servlet-mapping>
    </web-app>
    

    这样,启动tomcat服务器之后,访问http://localhost:8080/DirtyWordsFilterTest.jsp,页面正常显示,服务器控制台输出如下:

    Before the request is handled...
    url: http://localhost:8080/DirtyWordsFilterTest.jsp
    原始大小:376
    压缩后的大小:300
    After the request is handled...
    

    5.2.5 使用Decorator设计模式包装response对象实现缓存数据到内存

    对于页面中很少更新的数据,例如商品分类,为避免每次都要从数据库查询分类数据,因此可把分类数据缓存在内存或文件中,以此来减轻数据库或者磁盘的压力,提高系统响应速度。
    原理上,这里也是用自定义的Response对象将输出先放到缓存,然后,缓存的数据放到一个映射结构中。这样,后续如果在内存中命中,就不用重新查询数据库或者访问磁盘了。
    首先写一个自定义过滤器实现类。
    com.lfqy.web.filter.WebResourceCachedFilter

    package com.lfqy.web.filter;
    
    import javax.servlet.*;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpServletResponseWrapper;
    import java.io.ByteArrayOutputStream;
    import java.io.IOException;
    import java.io.OutputStreamWriter;
    import java.io.PrintWriter;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.zip.GZIPOutputStream;
    
    /**
     * Created by chengxia on 2019/11/13.
     */
    public class WebResourceCachedFilter implements Filter {
    
        //用一个map结构缓存web资源
        private Map<String,byte[]> map = new HashMap<String,byte[]>();
    
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
        }
    
        @Override
        public void doFilter(ServletRequest req, ServletResponse resp,
                             FilterChain chain) throws IOException, ServletException {
    
            HttpServletRequest request = (HttpServletRequest) req;
            HttpServletResponse response = (HttpServletResponse) resp;
            //1.得到用户请求的uri
            String uri = request.getRequestURI();
            //2.看缓存中有没有uri对应的数据
            byte b[] = map.get(uri);
            //3.如果缓存中有,直接拿缓存的数据打给浏览器,程序返回
            if(b!=null){
                //根据字节数组和指定的字符编码构建字符串
                String webResourceHtmlStr = new String(b,response.getCharacterEncoding());
                System.out.println(webResourceHtmlStr);
                response.getOutputStream().write(b);
                return;
            }
            //4.如果缓存没有,让目标资源执行,并捕获目标资源的输出
            BufferedResponse myresponse = new BufferedResponse(response);
            chain.doFilter(request, myresponse);
            //获取缓冲流中的内容的字节数组
            byte out[] = myresponse.getBuffer();
            //5.把资源的数据以用户请求的uri为关键字保存到缓存中
            map.put(uri, out);
            //6.把数据打给浏览器
            response.getOutputStream().write(out);
        }
    
        @Override
        public void destroy() {
    
        }
    
        /**
         * @ClassName: BufferedResponse
         * @Description:
         * 使用Decorator模式包装response对象,输出到带缓存的ByteArrayOutputStream功能
         * 同时,实现了getBuffer方法,使能够拿到缓冲区的内容
         * @author: spacecat
         * @date: 2019-12-6 上午08:56:35
         *
         */
        class BufferedResponse extends HttpServletResponseWrapper {
    
            private ByteArrayOutputStream bout = new ByteArrayOutputStream();
            private PrintWriter pw;
            private HttpServletResponse response;
            public BufferedResponse(HttpServletResponse response) {
                super(response);
                this.response = response;
            }
            /**
             * 这个方法是我们压缩之后,再向浏览器写数据的时候,要调用的。
             * */
            @Override
            public ServletOutputStream getOutputStream() throws IOException {
                return new MyServletOutputStream(bout);
            }
            /**
             * 这个方法是框架输出到response对象时调用的
             * */
            @Override
            public PrintWriter getWriter() throws IOException {
                pw = new PrintWriter(new OutputStreamWriter(bout,this.response.getCharacterEncoding()));
                return pw;
            }
    
            public byte[] getBuffer(){
                try{
                    if(pw!=null){
                        pw.close();
                    }
                    if(bout!=null){
                        bout.flush();
                        return bout.toByteArray();
                    }
    
    
                    return null;
                }catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        }
    
        class MyServletOutputStream extends ServletOutputStream{
    
            private ByteArrayOutputStream bout;
            public MyServletOutputStream(ByteArrayOutputStream bout){
                this.bout = bout;
            }
    
            @Override
            public void write(int b) throws IOException {
                this.bout.write(b);
            }
    
            @Override
            public boolean isReady() {
                return false;
            }
    
            @Override
            public void setWriteListener(WriteListener writeListener) {
            }
        }
    }
    

    修改配置文件,指定缓存那些页面。
    WEB-INF/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_3_1.xsd"
             version="3.1">
        <filter>
            <filter-name>FilterTrial01</filter-name>
            <filter-class>com.lfqy.web.filter.FilterTrial01</filter-class>
        </filter>
        <filter>
            <filter-name>FilterInitParamTrial</filter-name>
            <filter-class>com.lfqy.web.filter.FilterInitParamTrial</filter-class>
            <init-param>
                <description>测试Filter配置参数01</description>
                <param-name>FilterName</param-name>
                <param-value>PaopaoFilter</param-value>
            </init-param>
            <init-param>
                <description>测试Filter配置参数02</description>
                <param-name>Function</param-name>
                <param-value>Test filter init param...</param-value>
            </init-param>
        </filter>
        <filter>
            <filter-name>CharacterEncodingFilter</filter-name>
            <filter-class>com.lfqy.web.filter.CharacterEncodingFilter</filter-class>
            <init-param>
                <description>设置编码信息</description>
                <param-name>charset</param-name>
                <param-value>utf-8</param-value>
            </init-param>
        </filter>
        <filter>
            <filter-name>DirtyWordsFilter</filter-name>
            <filter-class>com.lfqy.web.filter.DirtyWordsFilter</filter-class>
            <init-param>
                <description>设置编码信息</description>
                <param-name>charset</param-name>
                <param-value>utf-8</param-value>
            </init-param>
            <init-param>
                <description>设置包含敏感词的配置文件路径</description>
                <param-name>DirtyWordsFilePath</param-name>
                <param-value>/WEB-INF/DirtyWords.txt</param-value>
            </init-param>
        </filter>
        <filter>
            <filter-name>TripleInOneFilter</filter-name>
            <filter-class>com.lfqy.web.filter.TripleInOneFilter</filter-class>
            <init-param>
                <description>设置包含敏感词的配置文件路径</description>
                <param-name>DirtyWordsFilePath</param-name>
                <param-value>/WEB-INF/DirtyWords.txt</param-value>
            </init-param>
        </filter>
        <filter>
            <filter-name>GzipFilter</filter-name>
            <filter-class>com.lfqy.web.filter.GzipFilter</filter-class>
        </filter>
        <filter>
            <filter-name>WebResourceCachedFilter</filter-name>
            <filter-class>com.lfqy.web.filter.WebResourceCachedFilter</filter-class>
        </filter>
        <filter>
            <filter-name>HTMLCharacterEscapeFilter</filter-name>
            <filter-class>com.lfqy.web.filter.HTMLCharacterEscapeFilter</filter-class>
        </filter>
        <filter-mapping>
            <filter-name>FilterTrial01</filter-name>
            <!-- "/*"表示拦截所有的请求 -->
            <url-pattern>*.jsp</url-pattern>
        </filter-mapping>
        <filter-mapping>
            <filter-name>FilterInitParamTrial</filter-name>
            <!-- "/*"表示拦截所有的请求 -->
            <url-pattern>*.html</url-pattern>
        </filter-mapping>
        <filter-mapping>
            <filter-name>CharacterEncodingFilter</filter-name>
            <!-- "/*"表示拦截所有的请求 -->
            <url-pattern>/*</url-pattern>
        </filter-mapping>
        <filter-mapping>
            <filter-name>HTMLCharacterEscapeFilter</filter-name>
            <!-- "/*"表示拦截所有的请求 -->
            <url-pattern>/*</url-pattern>
        </filter-mapping>
        <filter-mapping>
            <filter-name>DirtyWordsFilter</filter-name>
            <!-- "/*"表示拦截所有的请求 -->
            <url-pattern>/*</url-pattern>
        </filter-mapping>
        <filter-mapping>
            <filter-name>TripleInOneFilter</filter-name>
            <!-- "/*"表示拦截所有的请求 -->
            <url-pattern>/*</url-pattern>
        </filter-mapping>
        <!--jsp文件的输出的内容都经过压缩过滤器压缩后才输出 -->
        <filter-mapping>
            <filter-name>GzipFilter</filter-name>
            <url-pattern>*.jsp</url-pattern>
            <!-- 配置过滤器的拦截方式-->
            <!-- 对于在Servlet中通过
                request.getRequestDispatcher("jsp页面路径").forward(request, response)
            方式访问的Jsp页面的要进行拦截 -->
            <dispatcher>FORWARD</dispatcher>
            <!--对于直接以URL方式访问的jsp页面进行拦截,过滤器的拦截方式默认就是 REQUEST-->
            <dispatcher>REQUEST</dispatcher>
        </filter-mapping>
        <!--js文件的输出的内容都经过压缩过滤器压缩后才输出 -->
        <filter-mapping>
            <filter-name>GzipFilter</filter-name>
            <url-pattern>*.js</url-pattern>
        </filter-mapping>
        <!--css文件的输出的内容都经过压缩过滤器压缩后才输出 -->
        <filter-mapping>
            <filter-name>GzipFilter</filter-name>
            <url-pattern>*.css</url-pattern>
        </filter-mapping>
        <!--html文件的输出的内容都经过压缩过滤器压缩后才输出 -->
        <filter-mapping>
            <filter-name>GzipFilter</filter-name>
            <url-pattern>*.html</url-pattern>
        </filter-mapping>
        
        <filter-mapping>
            <filter-name>WebResourceCachedFilter</filter-name>
            <!-- 映射需要缓存输出的JSP页面,这几个页面都只是单纯作为输入UI,不会有太多的变化,因此可以缓存输出 -->
            <url-pattern>/DirtyWordsFilterTest.jsp</url-pattern>
            <url-pattern>/test.jsp</url-pattern>
            <url-pattern>/test2.jsp</url-pattern>
        </filter-mapping>
        
        <servlet>
            <servlet-name>EncodingTestServlet</servlet-name>
            <servlet-class>com.lfqy.web.servlet.EncodingTestServlet</servlet-class>
        </servlet>
        <servlet-mapping>
            <servlet-name>EncodingTestServlet</servlet-name>
            <url-pattern>/EncodingTestServlet</url-pattern>
        </servlet-mapping>
    
        <servlet>
            <servlet-name>PostUrlParamTestServlet</servlet-name>
            <servlet-class>com.lfqy.web.servlet.PostUrlParamTestServlet</servlet-class>
        </servlet>
        <servlet-mapping>
            <servlet-name>PostUrlParamTestServlet</servlet-name>
            <url-pattern>/PostUrlParamTestServlet</url-pattern>
        </servlet-mapping>
    
        <servlet>
            <servlet-name>HTMLCharacterEscapeTestServlet</servlet-name>
            <servlet-class>com.lfqy.web.servlet.HTMLCharacterEscapeTestServlet</servlet-class>
        </servlet>
        <servlet-mapping>
            <servlet-name>HTMLCharacterEscapeTestServlet</servlet-name>
            <url-pattern>/HTMLCharacterEscapeTestServlet</url-pattern>
        </servlet-mapping>
    </web-app>
    

    启动服务器之后,第二次访问http://localhost:8080/DirtyWordsFilterTest.jsp,服务器控制台输出如下:

    Before the request is handled...
    url: http://localhost:8080/DirtyWordsFilterTest.jsp
    
    <!DOCTYPE HTML>
    <html>
    <head>
        <title>Html Character Escape Test</title>
    </head>
    
    <body>
    <form action="/HTMLCharacterEscapeTestServlet" method="post">
        留言:
        <textarea rows="8" cols="70" name="message">
            这是我的留言,包含敏感词1,敏感词2等敏感词。哈哈
        </textarea>
        <input type="submit" value="发表">
    </form>
    </body>
    </html>
    原始大小:376
    压缩后的大小:300
    After the request is handled...
    

    注意,在最后的两个例子中,并没有调用chain.doFilter(filteredRequest, response);,也就是说,请求经过这两个filter处理之后,就不会继续往别的过滤器传递了。这里需要特别注意。

    参考资料

    相关文章

      网友评论

        本文标题:Java Web开发中的过滤器(Filter)

        本文链接:https://www.haomeiwen.com/subject/nhyngctx.html