【JavaWeb学习】过滤器Filter

一、简介

  Filter也称之为过滤器,它是Servlet技术中最激动人心的技术,WEB开发人员通过Filter技术,对web服务器管理的所有web资源:例如Jsp, Servlet, 静态图片文件或静态 html 文件等进行拦截,从而实现一些特殊的功能。例如实现URL级别的权限访问控制、过滤敏感词汇、压缩响应信息等一些高级功能。

  Servlet API中提供了一个Filter接口,开发web应用时,如果编写的Java类实现了这个接口,则把这个java类称之为过滤器Filter。通过Filter技术,开发人员可以实现用户在访问某个目标资源之前,对访问的请求和响应进行拦截,如下所示:

  

Filter是如何实现拦截的? 

  Filter接口中有一个doFilter方法,当开发人员编写好Filter,并配置对哪个web资源进行拦截后,WEB服务器每次在调用web资源的service方法之前,都会先调用一下filter的doFilter方法,因此,在该方法内编写代码可达到如下目的:

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

二、Filter开发入门

1、编写java类实现Filter接口,并实现其doFilter方法。

 1 public class FilterDemo1 implements Filter{
 2     @Override
 3     public void init(FilterConfig arg0) throws ServletException {    
 4         System.out.println("Filter Init...");
 5     }
 6     @Override
 7     public void doFilter(ServletRequest req, ServletResponse resp,
 8             FilterChain chain) throws IOException, ServletException {
 9         //调用目标资源前
10         System.out.println("doFiliter之前 ...");
11         //调用目标资源
12         chain.doFilter(req, resp);
13         //调用目标资源后
14         System.out.println("doFiliter之后 ...");
15     }
16     @Override
17     public void destroy() {
18         System.out.println("Filter destroy...");
19     }
20 }

2、在 web.xml 文件中使用<filter>和<filter-mapping>元素对编写的filter类进行注册,并设置它所能拦截的资源。

<filter>
    <filter-name>FilterDemo1</filter-name>
    <filter-class>cn.qust.web.filter.demo.FilterDemo1</filter-class>
</filter>
<filter-mapping>
    <filter-name>FilterDemo1</filter-name>
    <url-pattern>/index.jsp</url-pattern>
</filter-mapping>

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

  如下面的filter注册顺序,FilterDemo2和FilterDemo1内容基本一致。

 1 <filter>
 2     <filter-name>FilterDemo1</filter-name>
 3     <filter-class>cn.qust.web.filter.demo.FilterDemo1</filter-class>
 4 </filter>
 5 <filter-mapping>
 6     <filter-name>FilterDemo1</filter-name>
 7     <url-pattern>/index.jsp</url-pattern>
 8 </filter-mapping>
 9 <filter>
10     <filter-name>FilterDemo2</filter-name>
11     <filter-class>cn.qust.web.filter.demo.FilterDemo2</filter-class>
12 </filter>
13 <filter-mapping>
14     <filter-name>FilterDemo2</filter-name>
15     <url-pattern>/*</url-pattern>
16 </filter-mapping>

当访问index.jsp页面的时候,结果如下

三、Filter的生命周期

init(FilterConfig filterConfig) throws ServletException:

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

开发人员通过init方法的参数,可获得代表当前filter配置信息的FilterConfig对象。

destroy()

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

 

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

  

 1 public class FilterDemo2 implements Filter{
 2     @Override
 3     public void init(FilterConfig config) throws ServletException {
 4         String filterName = config.getFilterName();
 5         System.out.println(filterName + " Init params : ");
 6         //获取初始配置参数
 7         Enumeration<String> initParameterNames = config.getInitParameterNames();
 8         while(initParameterNames.hasMoreElements()){
 9             String name = initParameterNames.nextElement();
10             String value = config.getInitParameter(name);
11             System.out.println(name + ":" + value);
12         }
13     }
14     
15     @Override
16     public void doFilter(ServletRequest req, ServletResponse resp,
17             FilterChain chain) throws IOException, ServletException {
18         System.out.println("FilterDemo2 doFiliter2之前 ...");
19         chain.doFilter(req, resp);
20         System.out.println("FilterDemo2 doFiliter2之后 ...");
21     }
22     @Override
23     public void destroy() {
24         System.out.println("FilterDemo2 destroy...");
25     }
26 
27 }

 

在web.xml注册filter的时候配置好参数:

<filter>
    <filter-name>FilterDemo2</filter-name>
    <filter-class>cn.qust.web.filter.demo.FilterDemo2</filter-class>
    <init-param>
        <param-name>description</param-name>
        <param-value>filter过滤器的初始配置参数</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>FilterDemo2</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
    

 

在tomcat的启动信息里可以看到打印结果:

五、Filter的常见应用

1、同一全站字符编码

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

  1)在Filter实现中对request和response进行编码设置

 1 public class CharacterEncodingFilter implements Filter{
 2 
 3     private FilterConfig config = null;
 4     private String defaultCharset = "UTF-8";
 5     
 6     @Override
 7     public void doFilter(ServletRequest request, ServletResponse response,
 8             FilterChain chain) throws IOException, ServletException {
 9         //获取配置信息里的字符编码charset
10         String charset = this.config.getInitParameter("charset");
11         //如果没有配置字符编码,那么使用默认字符编码
12         if(charset==null){
13             charset = this.defaultCharset;
14         }
15         
16        17         18         //设置编码    
19         request.setCharacterEncoding(charset);
20         response.setCharacterEncoding(charset);
21         response.setContentType("text/html;charset="+charset);
22         
23         chain.doFilter(request, response);
24     }
25 
26     @Override
27     public void init(FilterConfig config) throws ServletException {
28         this.config = config;
29     }
30     @Override
31     public void destroy() {
32     }
33 
34 }

  2)web.xml注册Filter,并配置参数charset

 1 <!-- 同一全站字符编码过滤器1 --> 
 2 <filter>
 3     <filter-name>CharacterEncodingFilter</filter-name>
 4     <filter-class>cn.qust.web.filter.CharacterEncodingFilter</filter-class>
 5     <init-param>
 6         <param-name>charset</param-name>
 7         <param-value>UTF-8</param-value>
 8     </init-param>
 9 </filter>
10 <filter-mapping>
11     <filter-name>CharacterEncodingFilter</filter-name>
12     <url-pattern>/*</url-pattern>
13 </filter-mapping>

 

  测试

<%@ 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>表单不同的请求方式</title>
</head>
<body>
    <form action="servlet/ServletDemo1" method="get">
        国家:<input type="text" name="country"><br/>
        <input type="submit" value="GET提交">
    </form>
    <hr/>
    <form action="servlet/ServletDemo1" method="post">
        国家:<input type="text" name="country"><br/>
        <input type="submit" value="POST提交">
    </form>
</body>
</html>

 

  

//测试统一全站字符编码
public class ServletDemo1 extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        String country = request.getParameter("country");
        response.getWriter().write("国家:" + country);
    }

    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }
}

 

但是get请求时会乱码,post请求则正常。

改进后的编码过滤器:

 1 package cn.qust.web.filter;
 2 
 3 import java.io.IOException;
 4 import java.io.UnsupportedEncodingException;
 5 
 6 import javax.servlet.Filter;
 7 import javax.servlet.FilterChain;
 8 import javax.servlet.FilterConfig;
 9 import javax.servlet.ServletException;
10 import javax.servlet.ServletRequest;
11 import javax.servlet.ServletResponse;
12 import javax.servlet.http.HttpServletRequest;
13 import javax.servlet.http.HttpServletRequestWrapper;
14 import javax.servlet.http.HttpServletResponse;
15 
16 /*真正解决全站乱码问题
17  * 
18  * 当某个对象的方法不适应业务需求时,通常有2种方式可以对方法进行增强:
19     .编写子类,覆盖需增强的方法
20     .使用Decorator设计模式对方法进行增强(包装模式)
21  * 疑问:在实际应用中遇到需增强对象的方法时,到底选用哪种方式呢?
22     没有具体的定式,不过有一种情况下,必须使用Decorator设计模式:即被增强的对象,开发人员只能得到它的对象,无法得到它的class文件。
23     比如request、response对象,开发人员之所以在servlet中能通过sun公司定义的HttpServletRequest\response接口去操作这些对象,是因为Tomcat服务器厂商编写了request、response接口的实现类。web服务器在调用servlet时,会用这些接口的实现类创建出对象,然后传递给servlet程序。
24     此种情况下,由于开发人员根本不知道服务器厂商编写的request、response接口的实现类是哪个?在程序中只能拿到服务器厂商提供的对象,因此就只能采用Decorator设计模式对这些对象进行增强。
25 
26  */
27 
28 /*
29  * Filter高级应用:Decorator设计模式
30  * 涉及到的代码文件:CharacterEncodingFilter2.java    text.jsp    ServletDemo2.java
31  * 测试:在text.jsp中的textArea中输入文字,提交之后有ServletDemo2正常显示出来
32  */
33 public class CharacterEncodingFilter2 implements Filter {
34     private FilterConfig config = null;
35     private String defaultCharset = "UTF-8";
36     
37     @Override
38     public void doFilter(ServletRequest request, ServletResponse response,
39             FilterChain chain) throws IOException, ServletException {
40         
41         //获取配置信息里的字符编码charset
42         String charset = this.config.getInitParameter("charset");
43         //如果没有配置字符编码,那么使用默认字符编码
44         if(charset==null){
45             charset = this.defaultCharset;
46         }
47         //设置编码    
48         request.setCharacterEncoding(charset);
49         response.setCharacterEncoding(charset);
50         response.setContentType("text/html;charset="+charset);
51         
52         chain.doFilter(new MyRequest((HttpServletRequest) request), response);
53     }
54 
55     @Override
56     public void init(FilterConfig filterConfig) throws ServletException {
57         this.config = filterConfig;
58     }
59     @Override
60     public void destroy() {
61     }
62     //request的内部包装类
63     class MyRequest extends HttpServletRequestWrapper{
64         private HttpServletRequest request = null;
65         public MyRequest(HttpServletRequest request) {
66             super(request);
67             this.request = request;
68         }
69         //要增强的方法
70         @Override
71         public String getParameter(String name) {
72             String value = request.getParameter(name);
73             String method = request.getMethod();
74             if(value!=null && method.equalsIgnoreCase("GET")){
75                 try {
76                     value = new String(value.getBytes("iso-8859-1"),request.getCharacterEncoding());
77                 } catch (UnsupportedEncodingException e) {
78                     e.printStackTrace();
79                 }
80             }
81             return value;
82         }
83     }
84 }

 

2、禁止浏览器缓存所有动态页面

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

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

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

Expires数据头:值为GMT时间值,为-1指浏览器不要缓存页面

Cache-Control响应头有两个常用值: 

no-cache指浏览器不要缓存当前页面。

max-age:xxx指浏览器缓存页面xxx秒

 1 package cn.qust.web.filter;
 2 
 3 import java.io.IOException;
 4 
 5 import javax.servlet.Filter;
 6 import javax.servlet.FilterChain;
 7 import javax.servlet.FilterConfig;
 8 import javax.servlet.ServletException;
 9 import javax.servlet.ServletRequest;
10 import javax.servlet.ServletResponse;
11 import javax.servlet.http.HttpServletRequest;
12 import javax.servlet.http.HttpServletResponse;
13 
14 
15 /*
16  * Filter应用2:禁止浏览器缓存页面
17  * 涉及到的代码文件:NoCacheFilter.java   index.jsp
18  * 测试:在浏览器缓存文件夹中查看。
19  * 实验结果:失败。缓存文件夹中依然存在着缓存文件
20  */
21 public class NoCacheFilter implements Filter{
22 
23     @Override
24     public void destroy() {
25     }
26 
27     @Override
28     public void doFilter(ServletRequest req, ServletResponse resp,
29             FilterChain chain) throws IOException, ServletException {
30         
31         HttpServletRequest request = (HttpServletRequest) req;
32         HttpServletResponse response = (HttpServletResponse) resp;
33         
34         response.setDateHeader("Expires",-1);
35         response.setHeader("Cache-Control","no-cache");
36         response.setHeader("Pragma","no-cache");
37         
38         chain.doFilter(request, response);
39     }
40 
41     @Override
42     public void init(FilterConfig arg0) throws ServletException {
43         // TODO Auto-generated method stub
44     }
45 }

 

3、控制浏览器缓存页面中的静态资源

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

 1 package cn.qust.web.filter;
 2 
 3 import java.io.IOException;
 4 import java.text.SimpleDateFormat;
 5 import java.util.Date;
 6 import java.util.Enumeration;
 7 import java.util.HashMap;
 8 import java.util.Map;
 9 import java.util.TimeZone;
10 
11 import javax.servlet.Filter;
12 import javax.servlet.FilterChain;
13 import javax.servlet.FilterConfig;
14 import javax.servlet.ServletException;
15 import javax.servlet.ServletRequest;
16 import javax.servlet.ServletResponse;
17 import javax.servlet.http.HttpServletRequest;
18 import javax.servlet.http.HttpServletResponse;
19 
20 
21 /*
22  * Filter应用3:控制浏览器缓存页面中的静态资源的过滤器
23  * 涉及的代码文件:CacheFilter.java    image.jsp
24  * 测试:打开image.jsp页面,查看缓存文件夹中的缓存
25  */
26 public class CacheFilter implements Filter {
27     private Map<String, Integer> expires = null;
28     private FilterConfig config = null;
29     @Override
30     public void destroy() {
31     }
32 
33     @Override
34     public void doFilter(ServletRequest req, ServletResponse resp,
35             FilterChain chain) throws IOException, ServletException {
36         HttpServletRequest request = (HttpServletRequest) req;
37         HttpServletResponse response = (HttpServletResponse) resp;
38         //获取请求地址的uri
39         String uri = request.getRequestURI();
40         //获取文件后缀
41         String fileType = uri.substring(uri.lastIndexOf(".")+1);
42         //缓存时间
43         long expires = -1;
44         if(this.expires.containsKey(fileType)){
45             expires = System.currentTimeMillis() + this.expires.get(fileType) ;
46             //时间差8个小时
47             expires += 8*60*60*1000;
48         }
49         //设置缓存时间
50         response.setDateHeader("Expires", expires);    
51         System.out.println("Expires:" + expires);
52         //调用目标资源
53         chain.doFilter(request, response);
54     }
55 
56     @Override
57     public void init(FilterConfig arg0) throws ServletException {
58         this.config = arg0;
59         //保存缓存文件类型及对应的缓存时间
60         this.expires = new HashMap<String, Integer>();
61         //把配置信息存放到map中
62         Enumeration<String> names = this.config.getInitParameterNames();
63         while(names.hasMoreElements()){
64             String fileType = names.nextElement();
65             Integer expireTime = Integer.valueOf(this.config.getInitParameter(fileType));//单位为分钟
66             expireTime = expireTime * 60 * 1000;
67             this.expires.put(fileType, expireTime);//单位为秒
68         }
69         System.out.println(this.expires.toString());
70     }
71 }

 

4、实现URL级别的权限认证

5、实现用户自动登录

posted @ 2017-01-31 23:16  刘一二  阅读(371)  评论(0编辑  收藏  举报