JavaWeb-过滤器

1、什么是过滤器

  
  过滤器是一个程序,它先于与之相关的Servlet页面运行在服务器上,它是Servlet技术中最激动人心的技术,但它并不是一个标准的Servlet,它不能处理用户请求,也不能对客户端生成响应。它主要用于对HttpServletRequest进行预处理,也可以对HttpServletResponse进行后处理

  过滤器可以附加到一个或多个Servlet、JSP页面或者是HTML静态页面上,然后检查进入这些资源的请求信息。它对这些请求进行拦截,从而实现一些特殊的功能,比如可以实现权限访问控制,过滤敏感词汇,设置统一字符集、压缩响应信息等功能。

1、过滤器位于客户端和web应用程序之间,用于检查和修改两者之间流过的请求和响应;
2、在请求到达Servlet或者JSP之前,过滤器截获请求;
3、在响应送给客户端之前,过滤器截获响应;
4、多个过滤器可以形成一个过滤器链,过滤器链中不同过滤器的先后顺序由部署文件web.xml中
   过滤器映射<filter-mapping>的顺序决定。

注意第三点:“在响应送给客户端之前,过滤器截获响应”!

2、过滤器的原理

  在Servlet作为过滤器使用时,它可以对客户的请求进行处理。处理完成之后,它会交给下一个过滤器处理,这样,客户的请求在过滤器链中逐个处理,直到请求发送到目标为止。例如,我们访问用户管理系统的某个页面,服务器在进行处理时需要做两项工作:①判断客户端的会话是否有效然后检查权限;②对提交的数据进行统一编码。这两项工作可以在由两个过滤器组成的过滤器链中进行处理。当过滤器处理成功后,再把提交的数据发送到最终目标。

可以看一下过滤器链在整个Web应用中的位置:
这里写图片描述

3、过滤器的使用

(1) 开发步骤

开发Servlet过滤器的步骤为:

1.编写实现Filter接口的Servlet类。
2.在web.xml中配置Filter

开发一个过滤器需要实现Filter接口,Filter接口定义了以下方法:

1、destory()由web容器调用,销毁此Filter
2、init(FilterConfig filterConfig)由Web容器调用,初始化此Filter
3、doFilter(ServletRequest request, ServletResponse response, FilterChain chain)具体处理过滤的代码

一个过滤器的代码类似如下,其中最重要的是在doFilter()方法中的实现自己对request和response的过滤:

public class MyFilter implements Filter {
    @Override
    public void destroy() {
        //销毁代码
    }

    @Override
    public void doFilter(ServletRequest arg0, ServletResponse arg1,
            FilterChain chain) throws IOException, ServletException {
        //具体的过滤细节
    }

    @Override
    public void init(FilterConfig arg0) throws ServletException {
        //初始化代码
    }
}

(2) Filter链

  在一个web应用中,可以开发编写多个Filter,这些Filter组合起来称之为一个Filter链。web服务器根据Filter在web.xml文件中的注册顺序,决定先调用哪个Filter,当第一个Filter的doFilter方法被调用时,web服务器会创建一个代表Filter链的FilterChain对象传递给该方法。

FilterTest1.java的代码:

public class FilterTest1 implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {

        // 做全局性的设置(对request和response进行一些预处理)
        request.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/html;charset=UTF-8");

        System.out.println("FilterTest1执行之前!!!");
        // 拦截下目标资源,然后放行(目标资源也会执行)
        chain.doFilter(request, response); // 放行
        System.out.println("FilterTest1执行之后!!!");
    }

    @Override
    public void destroy() {

    }
}

FilterTest2.java的代码:

public class FilterTest2 implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {

        System.out.println("FilterTest2执行之前!!!");
        // 拦截下目标资源,然后放行(目标资源也会执行)
        chain.doFilter(request, response); // 放行
        System.out.println("FilterTest2执行之后!!!");

    }

    @Override
    public void destroy() {

    }
}

在web.xml中配置过滤器,注意:Filter在web.xml文件中是有注册顺序。
我们可能这样配置,说实话我们经常这么做不是吗?

<filter>
    <filter-name>FilterTest1</filter-name>
    <filter-class>cn.itcast.web.filter.FilterTest1</filter-class>
</filter>
<filter-mapping>
    <filter-name>FilterTest1</filter-name>
     <!--“/*”表示拦截所有的请求 -->
    <url-pattern>/*</url-pattern>
</filter-mapping>

<filter>
    <filter-name>FilterTest2</filter-name>
    <filter-class>cn.itcast.web.filter.FilterTest2</filter-class>
</filter>
<filter-mapping>
    <filter-name>FilterTest2</filter-name>
     <!--“/*”表示拦截所有的请求 -->
    <url-pattern>/*</url-pattern>
</filter-mapping>

从以上配置中,关于Filter在web.xml文件中的注册顺序我们是一目了然的,即FilterTest1过滤器在前,FilterTest2过滤器在后。但我们有时候又会这样配置:

<filter>
    <filter-name>FilterTest1</filter-name>
    <filter-class>cn.itcast.web.filter.FilterTest1</filter-class>
</filter>

<filter>
    <filter-name>FilterTest2</filter-name>
    <filter-class>cn.itcast.web.filter.FilterTest2</filter-class>
</filter>

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

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

那这样是不是说只要哪个标签在前,哪个过滤器就在前呢?可惜并不是这样的,在web.xml中配置的过滤器的先后顺序是依的顺序而定。我们只要了解这点就好,没人会闲的蛋疼的考你这方面的知识。

网站首页index.jsp的代码如下:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
    <%
        System.out.println("index!!!");
    %>
</body>
</html>

最后在浏览器中输入访问服务器的地址http://localhost:8080/day19/index.jsp,将在Eclipse的控制台打印:
这里写图片描述
注意:

从这个例子我们可以观察到,过滤链在服务器处理完请求后,会拦截应答进行再一次的处理(如果过滤器有设置的话)!!并且过滤顺序跟一开始是相反的。

4、过滤器的部署

Filter的部署分为两个步骤:

1、注册Filter
2、映射Filter
     过滤器的部署细节与Servlet很相似,Servlet是要配置<servlet><servlet-mapping>,
而过滤器是要配置<filter><filter-mapping>

(1) 注册Filter

开发好Filter之后,需要在web.xml文件中进行注册,这样才能够被web服务器调用。

1、简单配置如下所示(假设我的Filter放在com.gavin.filter包下):

<filter>
    <filter-name>FilterName</filter-name>
    <filter-class>com.gavin.filter.FilterName</filter-class>
</filter>

2、在web.xml文件中注册Filter详细范例:

<filter>
    <filter-name>testFitler</filter-name>
    <filter-class>org.test.TestFiter</filter-class>
    <init-param>
        <param-name>word_file</param-name>  
        <param-value>/WEB-INF/word.txt</param-value>
    </init-param>
</filter>
1、<filter-name>用于为过滤器指定一个名字,该元素的内容不能为空。
2、<filter-class>元素用于指定过滤器的完整的限定类名。
3、<init-param>元素用于为过滤器指定初始化参数,它的子元素<param-name>指定参数的名字,<param-value>指定参数的值。在过滤器中,
可以使用FilterConfig接口对象来访问初始化参数。如果过滤器不需要指定初始化参数,那么<init-param>元素可以不配置。

(2) 映射Filter

在web.xml文件中注册了Filter之后,还要在web.xml文件中映射Filter。

<!--映射过滤器-->
<filter-mapping>
    <filter-name>testFitler</filter-name>
    <!--“/*”表示拦截所有的请求 -->
    <url-pattern>/*</url-pattern>
</filter-mapping>

<filter-mapping>元素用于设置一个Filter所负责拦截的资源。

一个Filter拦截的资源可通过两种方式来指定:Servlet名称资源访问的请求路径

上面的示例是资源访问的请求路径的形式,下面咱们使用Servlet名称来指定Filter拦截的资源,例如:

<filter-mapping>
    <filter-name>CharacterEncodingFilter</filter-name>
    <servlet-name>ServletDemo1</servlet-name>
</filter-mapping>

元素的解释:

1、<filter-name>子元素用于设置filter的注册名称。该值必须是在<filter>元素中声明过的过滤器的名字。
2、<url-pattern>设置filter所拦截的请求路径(过滤器关联的URL样式)。
3、<servlet-name>指定过滤器所拦截的Servlet名称。
4、<dispatcher>指定过滤器所拦截的资源被Servlet容器调用的方式,可以是REQUEST、INCLUDE、FORWARD和ERROR之一,
   默认REQUEST。用户可以设置多个<dispatcher>子元素用来指定Filter对资源的多种调用方式进行拦截。如下:

<filter-mapping>
    <filter-name>testFilter</filter-name>
    <url-pattern>/index.jsp</url-pattern>
    <dispatcher>REQUEST</dispatcher>
    <dispatcher>FORWARD</dispatcher>
</filter-mapping>

<dispatcher>子元素可以设置的值及其意义:
1request:当用户直接访问页面时,Web容器将会调用过滤器。如果目标资源是通过RequestDispatcher的include()或forward()方法访问或ERROR情况时,那么该过滤器就不会被调用。
2、include:如果目标资源是通过RequestDispatcher的include()方法访问时,那么该过滤器将被调用。除此之外,该过滤器不会被调用。
3、forward:如果目标资源是通过RequestDispatcher的forward()方法访问时,那么该过滤器将被调用,除此之外,该过滤器不会被调用。
4ERROR:  如若在A.jsp页面page指令中指定了error属性=examError.jsp,那么A.jsp中若出现了异常,会跳转到examError.jsp中处理。而在跳转到examError.jsp时,若过滤器配置了ERROR的dispather那么则会拦截,否则不会拦截。

5、过滤器的生命周期

(1) Filter的创建

  和我们编写的Servlet程序一样,Filter的创建和销毁由WEB服务器负责。 web应用程序启动时,web服务器将创建Filter的实例对象,并调用其init方法,完成对象的初始化功能,从而为后续的用户请求作好拦截的准备工作。

注意:

1filter对象只会创建一次,init方法也只会执行一次。
2、开发人员通过init方法的参数,可获得代表当前filter配置信息的FilterConfig对象。

(2) Filter的销毁

  destroy方法在Web容器卸载Filter对象之前被调用。该方法在Filter的生命周期中仅执行一次。在这个方法中,可以释放过滤器使用的资源。

面试题:Filter对象的生命周期。

1、Filter对象何时被创建?
    服务器一启动的时候,就会针对这个web应用将所有的Filter对象(拦截器)创建出来,并且以后访问的时候,都是使用同一个拦截器进行拦截。也即一个拦截器会被所有的请求所共享,每一次请求来了之后,都会导致doFilter()方法被调用一次,Filter对象只有一个,而doFilter()方法会被多次调用。
2、Filter对象在内存里面有几个?
    答:一个。服务器并不会针对请求创建新的Filter对象(拦截器)。
    Filter对象何时被摧毁?
    移除掉web服务器里面这个web应用(或停掉服务器),就会摧毁这个web应用对应的拦截器。

咱们从一个具体示例中来理解:

public class FilterTest1 implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("Filter被创建了!!!");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {

        // 做全局性的设置(对request和response进行一些预处理)
        request.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/html;charset=UTF-8");

        System.out.println("FilterTest1执行之前!!!");
        // 拦截下目标资源,然后放行(目标资源也会执行)
        chain.doFilter(request, response); // 放行
        System.out.println("FilterTest1执行之后!!!");

    }

    @Override
    public void destroy() {
        System.out.println("Filter被销毁了!!!");
    }

}

当启动服务器的时候,Eclipse的控制台会打印Filter被创建了!!!如下:
这里写图片描述
当停掉服务器时,Eclipse的控制台会打印Filter被销毁了!!!如下:
这里写图片描述

(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上下文对象的引用。

范例:利用FilterConfig得到filter配置信息

public class FilterTest1 implements Filter {

    /*
     * Filter对象在被创建时,服务器会调用init()方法,并传递进来这样一个对象:FilterConfig(代表Filter的配置信息)
     * FilterConfig对象封装了配置的初始化参数。
     */
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("Filter被创建了!!!");
        String value = filterConfig.getInitParameter("xxx"); // 获取指定初始化参数
        System.out.println(value);
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {

        // 做全局性的设置(对request和response进行一些预处理)
        request.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/html;charset=UTF-8");

        System.out.println("FilterTest1执行之前!!!");
        // 拦截下目标资源,然后放行(目标资源也会执行)
        chain.doFilter(request, response); // 放行
        System.out.println("FilterTest1执行之后!!!");

    }

    @Override
    public void destroy() {
        System.out.println("Filter被销毁了!!!");
    }
}

在web. xml中配置过滤器:

<filter>
    <filter-name>FilterTest1</filter-name>
    <filter-class>cn.itcast.web.filter.FilterTest1</filter-class>
    <init-param>
        <param-name>xxx</param-name>
        <param-value>yyyy</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>FilterTest1</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

这样在启动服务器的时候,Eclipse的控制台会打印:
这里写图片描述
如果我们现在想在FilterTest1类的doFilter方法中使用FilterConfig对象封装的配置初始化参数,那又该怎么做呢?我们可以像下面这样做哟:

public class FilterTest1 implements Filter {

    private FilterConfig config;

    /*
     * Filter对象在被创建时,服务器会调用init()方法,并传递进来这样一个对象:FilterConfig(代表Filter的配置信息)
     * FilterConfig对象封装了配置的初始化参数。
     */
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("Filter被创建了!!!");
        // String value = filterConfig.getInitParameter("xxx"); // 获取指定初始化参数
        // System.out.println(value);
        this.config = filterConfig;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {

        String value = this.config.getInitParameter("xxx");
        System.out.println(value);

        // 做全局性的设置(对request和response进行一些预处理)
        request.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/html;charset=UTF-8");

        System.out.println("FilterTest1执行之前!!!");
        // 拦截下目标资源,然后放行(目标资源也会执行)
        chain.doFilter(request, response); // 放行
        System.out.println("FilterTest1执行之后!!!");

    }

    @Override
    public void destroy() {
        System.out.println("Filter被销毁了!!!");
    }

}

6、过滤器的应用

  过滤器可以附加到一个或多个Servlet、JSP页面或者是HTML静态页面上,然后检查进入这些资源的请求信息。它对这些请求进行拦截,从而实现一些特殊的功能,比如可以实现权限访问控制,过滤敏感词汇,设置统一字符集、压缩响应信息等功能。

(1) 统一全站字符编码

通过配置参数charset指明使用何种字符编码,以处理Html Form请求参数的中文问题。

// 解决全站中文乱码
public class CharacterEncodingFilter implements Filter {

    private FilterConfig config;
    private String defaultCharset = "UTF-8"; //默认值

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
            throws IOException, ServletException {

        // 获取要设置的字符集
        String charset = this.config.getInitParameter("charset");
        if (charset == null) {
            charset = defaultCharset;
        }

        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) resp;

        request.setCharacterEncoding(charset);
        response.setCharacterEncoding(charset);
        response.setContentType("text/html;charset=" + charset);

        chain.doFilter(request, response);

    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        this.config = filterConfig;
    }

    @Override
    public void destroy() {

    }
}

在web.xml文件中配置以上过滤器:

<filter>
    <filter-name>CharacterEncodingFilter</filter-name>
    <filter-class> cn.itcast.web.filter.example.CharacterEncodingFilter</filter-class>
    <init-param>
        <param-name>charset</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>CharacterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

(2) 防止非法登录

  使用Session防止用户非法登录到某个页面,采用的是在登录页面将用户信息放入Session,在需要防止非法登录的页面取出登录的信息,如果没有登录用户的信息,则为非法登录,然后将其强制跳转到登录页面。

  那么就出现了一个问题,如果我们的Web应用有很多个需要防止非法登录的页面怎么办?一个个都这样处理肯定非常麻烦。这时候,过滤器的应用就让这个问题变得非常简单。

首先,我们编写CheckUserFilter,它的doFilter()代码如下:

public void doFilter(ServletRequest arg0, ServletResponse arg1,
        FilterChain chain) throws IOException, ServletException {
    HttpServletRequest request = (HttpServletRequest)arg0;
    HttpServletResponse response = (HttpServletResponse)arg1;
    User login_user = (User)request.getSession().getAttribute("login-user");
    if(login_user == null){
        // 说明用户没有登录,让他跳转到错误页面
        request.setAttribute("error", "请登录!");
        request.getRequestDispatcher("/LoginServlet").forward(request,response);
    }else{
        // 让其通过
        chain.doFilter(request, response);
    }
}

可以看到,在doFilter中,我们去判断Session中是否有登录用户的信息,如果有,让其通过;如果没有,则强制跳转到登录页面。然后我们对这个Filter进行部署,假设我们的主页面MainFrame,添加用户的页面AddUser和更新用户的页面UpdateUser都需要防止非法登录,则可以让其映射到所有的这些Servlet上,部署如下:

<filter>
    <filter-name>CheckUserFilter</filter-name>
    <filter-class>com.gavin.filter.CheckUserFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>CheckUserFilter</filter-name>
    <servlet-name>MainFrame</servlet-name>
</filter-mapping>
<filter-mapping>
    <filter-name>CheckUserFilter</filter-name>
    <servlet-name>AddUser</servlet-name>
</filter-mapping>
<filter-mapping>
    <filter-name>CheckUserFilter</filter-name>
    <servlet-name>UpdateUser</servlet-name>
</filter-mapping>

实验可得,在没有登录之前去访问MainFrame、AddUser或者UpdateUser,都会提示错误信息,然后跳转到登录页面。

(3) 禁止浏览器缓存所有动态页面

有3个HTTP响应头字段都可以禁止浏览器缓存当前页面,它们在Servlet中的示例代码如下:

response.setDateHeader(“Expires”,-1);
response.setHeader(“Cache-Control”,”no-cache”);
response.setHeader(“Pragma”,”no-cache”);

并不是所有的浏览器都能完全支持上面的三个响应头,因此最好是同时使用上面的三个响应头。

Expires数据头:值为GMT时间值,为-1指浏览器不要缓存页面。
Cache-Control响应头有两个常用值:
(1) no-cache:指浏览器不要缓存当前页面。
(2) max-age:xxx:指浏览器缓存页面xxx秒。

在Eclipse中新建一个day19_test的web项目,在该项目下创建网站首页index.jsp,代码为:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
    <%
        System.out.println("index!!!");
    %>
</body>
</html>

我们以IE 9浏览器作为测试工具,先清空IE浏览器的缓存,然后打开IE浏览器,输入访问服务器的地址http://localhost:8080/day19_test/index.jsp,这时查看IE浏览器的缓存目录,发现index.jsp这个web动态资源缓存在里面。
这里写图片描述
这时我们要禁止IE浏览器缓存所有动态页面,所以要编写一个禁止浏览器缓存所有动态页面的过滤器。

public class NoCacheFilter implements Filter {

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

    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
            throws IOException, ServletException {

        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) resp;

        response.setDateHeader("Expires",-1);
        response.setHeader("Cache-Control","no-cache");
        response.setHeader("Pragma","no-cache");

        chain.doFilter(request, response);

    }

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

    }
}

web.xml文件中的配置如下:

<filter>
    <filter-name>NoCacheFilter</filter-name>
    <filter-class>cn.itcast.web.filter.example.NoCacheFilter</filter-class>
</filter>
    <filter-mapping>
    <filter-name>NoCacheFilter</filter-name>
    <!-- 拦截所有jsp的访问  -->
    <url-pattern>*.jsp</url-pattern>
</filter-mapping>

这时再次清空IE浏览器的缓存,然后打开IE浏览器,输入访问服务器的地址http://localhost:8080/day19_test/index.jsp,会发现IE浏览器的默认缓存目录下没有缓存index.jsp页面。
这里写图片描述

(4) 控制浏览器缓存页面静态资源

有些动态页面中引用了一些图片或css文件以修饰页面效果,这些图片和css文件经常是不变化的,所以为减轻服务器的压力,可以使用filter控制浏览器缓存这些文件,以提升服务器的性能。

现在将day19_test该Web应用下的网站首页index.jsp修改为:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
    Hello World!!!<br/>
    <img src="${pageContext.request.contextPath }/images/Krystal.jpg">
</body>
</html>

这时我们就要创建一个控制浏览器缓存页面中的静态资源的过滤器。

// 控制浏览器缓存的过滤器
public class CacheFilter implements Filter {

    private FilterConfig config;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

        this.config = filterConfig;

    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
            throws IOException, ServletException {

        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) resp;

        // 1. 获取到用户想访问的资源
        String uri = request.getRequestURI();

        // 2. 获取该资源的缓存时间
        int expires = 0;
        if (uri.endsWith(".jpg")) {
            expires = Integer.parseInt(this.config.getInitParameter("jpg"));
        } else if (uri.endsWith(".css")) {
            expires = Integer.parseInt(this.config.getInitParameter("css"));
        } else {
            expires = Integer.parseInt(this.config.getInitParameter("js"));
        }

        // response.setDateHeader("expires", expires*60*1000); // 这个缓存时间值是1970-01-01 08:00:00到现在时刻的毫秒值,应该在当前时间值上加上一分钟,这是才缓存一分钟
        response.setDateHeader("expires", System.currentTimeMillis() + expires*60*1000);
        chain.doFilter(request, response);

    }

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

    }
}

在编写上述过滤器时一定要注意一个细节:Expires相应头的值为GMT时间值,即从1970-01-01 08:00:00到现在时刻的毫秒值。应该在当前时间值上加上一分钟,这是才缓存一分钟

web.xml文件中的配置如下:

<filter>
    <filter-name>CacheFilter</filter-name>
    <filter-class>cn.itcast.web.filter.example.CacheFilter</filter-class>
    <!-- 配置要缓存的web资源以及缓存时间,以分钟为单位 -->
    <init-param>
        <param-name>css</param-name>
        <param-value>10</param-value>
    </init-param>
    <init-param>
        <param-name>jpg</param-name>
        <param-value>1</param-value>
    </init-param>
    <init-param>
        <param-name>js</param-name>
        <param-value>20</param-value>
    </init-param>
</filter>
<!-- 配置要缓存的web资源的后缀-->
<filter-mapping>
    <filter-name>CacheFilter</filter-name>
    <url-pattern>*.jpg</url-pattern>
</filter-mapping>
<filter-mapping>
    <filter-name>CacheFilter</filter-name>
    <url-pattern>*.css</url-pattern>
</filter-mapping>
<filter-mapping>
    <filter-name>CacheFilter</filter-name>
    <url-pattern>*.js</url-pattern>
</filter-mapping>

注意:一个filter可以对应多个filter-mapping。

现在加上前面还有一个禁止浏览器缓存所有动态页面的过滤器,那么就有2个过滤器了,形成了一个过滤器链。打开IE浏览器,输入访问服务器的地址http://localhost:8080/day19_test/index.jsp,那么在IE浏览器默认的缓存目录里面将不会缓存index.jsp这个页面,但会缓存Krystal.jpg这个静态图片,而且缓存一分钟。如果在这一分钟之内,通过在IE浏览器的地址栏上敲回车键的行为去访问服务器,那么只会向服务器发送一次请求,即请求index.jsp这个动态Web资源,至于index.jsp页面中的Krystal.jpg静态图片,会从IE浏览器默认的缓存目录里面获取;如果通过点击刷新按钮的行为去访问服务器,那么将会向服务器发送二次请求第一次请求index.jsp这个动态Web资源,第二次请求index.jsp页面中的Krystal.jpg静态图片。因为刷新有两个作用:

1、不管你有没有缓存,都要向服务器发请求。
2、刷新就是把上次的事情再干一次。

但我们平时访问网站,不是通过刷新去访问的,都是点击超链接。


猜你喜欢

转载自blog.csdn.net/weixin_39190897/article/details/82454633