过滤器

这里写图片描述

一、概述

过滤器,又叫拦截器,属于Servlet中的技术,Since Servlet2.3。使用Filter技术可以对web服务器管理的所有web资源,例如JSP、Servlet、静态资源等进行拦截,从而实现一些特殊的功能(后面会有八个案例)。
这里写图片描述

二、快速入门

1)过滤器编写步骤

① 在Web工程中新建Java类实现Filter接口,并实现doFilter()方法【可打印一句话,来证明能够进行拦截】

public class FilterDemo1 implements Filter {
    // 应用被加载时完成过滤器的实例化
    public FilterDemo1() {
        System.out.println("调用了默认构造方法");
    }

    // 初始化:服务器传入FilterConfig参数
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("调用初始化方法");
    }

    // 用户每次访问被过滤资源都会调用过滤器的doFilter方法
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        System.out.println("FilterDemo1第一次拦截");
        // 对request、response进行预处理,代码放在chain.doFilter()前
        chain.doFilter(request, response);// 放行
        // 对响应信息进行拦截,代码放在chain.doFilter()后
        System.out.println("FilterDemo1第二次拦截");
    }

    // 应用从服务器上卸载时调用销毁方法
    public void destroy() {
        System.out.println("调用销毁方法");
    }
}

② 在web.xml中配置Filter(filter、filter-mapping元素),指定拦截范围【类似Servlet配置,配置完成后,访问一个页面,看看拦截效果】:

<filter>
    <filter-name>FilterDemo1</filter-name>
    <filter-class>org.flyne.filter.FilterDemo1</filter-class>
</filter>

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

2)doFilter()方法

WEB服务器每次在调用web资源之前,都会先调用doFilter方法,因此,在该方法内编写代码可达到如下目的:

① 调用目标资源之前,对request、response进行预处理

② 是否调用目标资源,即是否让用户访问web资源

Web服务器在调用doFilter方法时,会传递一个filterChain对象进来,它也提供了一个doFilter方法,我们可以根据需求决定是否调用此方法,调用该方法,则web资源就会被访问(放行),否则web资源不会被访问(一直处于拦截状态)。

③ 调用目标资源之后,对响应信息进行过滤

3)Filter链(FilterChain)

① 在一个web应用中,可以开发编写多个Filter,这些Filter组合起来称之为一个Filter链。

② Web服务器根据Filter在web.xml文件中的注册顺序(按filter-mapping元素的顺序),决定先调用哪个Filter,当第一个Filter的doFilter方法被调用时,Web服务器会创建一个代表Filter链的FilterChain对象传递给该方法。

在doFilter方法中,如果调用了FilterChain对象的doFilter方法,则web服务器会检查FilterChain对象中是否还有filter,如果有,则调用第2个filter,如果没有,则调用目标资源。

三、一些细节问题

1)Filter生命周期

诞生(init):过滤器的实例是在应用被加载时就完成的实例化,并初始化的。
存活(doFilter):和应用的生命周期一致的。针对拦截范围内的资源访问,每次访问都会调用void doFIlter(request,response.chain)进行拦截。
死亡(destroy):应用被卸载(undeploy)。
如上面的FilterDemo1,当部署(Deploy)该应用到服务器时,就会产生FilterDemo1的实例,并调用初始化方法;最后将该应用从服务器卸载(undeploy)时,调用destroy方法。

2)FilterConfig接口【类似ServletConfig】

在配置Filter时,可以使用为filter配置一些初始化参数,当web容器实例化Filter对象,调用其init方法时,会把封装了filter初始化参数的filterConfig对象传递进来。filterConfig中的方法如下:

String getFilterName():得到Filter名称
String getInitParameter(name):返回中配置的参数,不存在返回null
3)子元素的配置

在配置拦截范围()时,可以配置多个子元素,使得Filter将会拦截客户端直接请求的资源(REQUEST)、请求转发的目标资源(FORWARD)、包含的目标资源(INCLUDE)和中指定的目标资源(ERROR)。默认值为REQUEST。
四、基础案例(4个)

1)SetCharacterEncodingFilter:解决POST请求参数和响应输出的中文乱码

public class SetCharacterEncodingFilter implements Filter {

    private String encoding;//编码可以通过<init-param>元素配置

    public void init(FilterConfig filterConfig) throws ServletException {
        encoding = filterConfig.getInitParameter("encoding");
        if(encoding == null){//如果用户忘记配置,默认encoding为UTF-8
            encoding = "UTF-8";
        }
    }

    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        //只能解决POST请求参数乱码问题
        request.setCharacterEncoding(encoding);
        //指定输出编码(最后带上,后面会有说明)
        response.setCharacterEncoding(encoding);
        //指定输出流编码及客户端应使用的码表
        response.setContentType("text/html;charset="+encoding);
        chain.doFilter(request, response);
    }

    public void destroy() {

    }
}
------------------------web.xml配置------------------------
<filter>
    <filter-name>SetCharacterEncodingFilter</filter-name>
    <filter-class>org.flyne.examples.SetCharacterEncodingFilter</filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>SetCharacterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

2)NoCacheFilter:禁止客户端缓存动态资源

public void doFilter(ServletRequest req, ServletResponse resp,
        FilterChain chain) throws IOException, ServletException {
    //响应头为HTTP协议里的,需转换一下
    HttpServletRequest request = null;
    HttpServletResponse response = null;
    try{
        request = (HttpServletRequest)req;
        response = (HttpServletResponse)resp;
    }catch(Exception e){
        throw new RuntimeException("not-http request or response");
    }

    response.setHeader("Expires", "0");
    response.setHeader("Cache-Control", "no-cache");
    response.setHeader("Pragma", "no-cache");
    chain.doFilter(request, response);
}
------------------------web.xml配置------------------------
<filter>
    <filter-name>NoCacheFilter</filter-name>
    <filter-class>org.flyne.examples.NoCacheFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>NoCacheFilter</filter-name>
    <url-pattern>/servlet/*</url-pattern>
    <url-pattern>*.jsp</url-pattern>
</filter-mapping>

3)SetCacheExpiresFilter:控制静态资源缓存时间

public class SetCacheExpiresFilter implements Filter {

    private int htmlExp;//HTML的缓存时间,单位为小时,下同
    private int cssExp;//CSS的缓存时间
    private int jsExp;//JS的缓存时间

    public void init(FilterConfig filterConfig) throws ServletException {
        htmlExp = Integer.parseInt(filterConfig.getInitParameter("html"));
        cssExp = Integer.parseInt(filterConfig.getInitParameter("css"));
        jsExp = Integer.parseInt(filterConfig.getInitParameter("js"));
    }

    public void doFilter(ServletRequest req, ServletResponse resp,
            FilterChain chain) throws IOException, ServletException {
        // 响应头为HTTP协议里的,需转换一下
        HttpServletRequest request = null;
        HttpServletResponse response = null;
        try {
            request = (HttpServletRequest) req;
            response = (HttpServletResponse) resp;
        } catch (Exception e) {
            throw new RuntimeException("not-http request or response");
        }

        //根据请求资源的后缀名确定缓存时间
        String uri = request.getRequestURI();
        String extName = uri.substring(uri.lastIndexOf(".")+1);
        long expTime = 0;
        if("html".equals(extName)){
            expTime = System.currentTimeMillis()+htmlExp*60*60*1000;

        }else if("css".equals(extName)){
            expTime = System.currentTimeMillis()+cssExp*60*60*1000;
        }else if("js".equals(extName)){
            expTime = System.currentTimeMillis()+jsExp*60*60*1000;
        }
        response.setDateHeader("Expires", expTime);

        chain.doFilter(request, response);
    }

    public void destroy() {
    }
}
------------------------web.xml配置------------------------
<filter>
    <filter-name>SetCacheExpiresFilter</filter-name>
    <filter-class>org.flyne.examples.SetCacheExpiresFilter</filter-class>
    <init-param>
        <param-name>html</param-name>
        <param-value>1</param-value>
    </init-param>
    <init-param>
        <param-name>css</param-name>
        <param-value>2</param-value>
    </init-param>
    <init-param>
        <param-name>js</param-name>
        <param-value>3</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>SetCacheExpiresFilter</filter-name>
    <url-pattern>*.html</url-pattern>
    <url-pattern>*.css</url-pattern>
    <url-pattern>*.js</url-pattern>
</filter-mapping>

4)AutoLoginFilter :用户自动登录
当用户登陆时,将表单提交到LoginServlet处理,如果用户勾选了记住我,则将用户的登陆信息存入Cookie:Cookie名为“logInfo”,值为(用户名的base64加密结果_密码的md5加密结果)。下面的自动登录过滤器基于以上信息:

public class AutoLoginFilter implements Filter {

    //表现层同service层打交道
    private UserService service = new UserServiceImpl();

    public void init(FilterConfig filterConfig) throws ServletException {
    }

    public void doFilter(ServletRequest req, ServletResponse resp,
            FilterChain chain) throws IOException, ServletException {
        // 转为Http协议的request和response
        HttpServletRequest request = null;
        HttpServletResponse response = null;
        try {
            request = (HttpServletRequest) req;
            response = (HttpServletResponse) resp;
        } catch (Exception e) {
            throw new RuntimeException("not-http request or response");
        }

        HttpSession session = request.getSession();
        // 判断用户有没有登录:只管没有登录的
        User sUser = (User) session.getAttribute("user");
        if (sUser == null) {
            Cookie[] cookies = request.getCookies();
            for (int i = 0; cookies != null && i < cookies.length; i++) {
                Cookie cookie = cookies[i];
                //名为logInfo的Cookie记录了登录信息(用户名、密码)
                if ("logInfo".equals(cookie.getName())) {
                    String value = cookie.getValue();
                    //Cookie中的用户名是经过Base64加密的,所以需要解密
                    String username = SecurityUtil.base64Decode(value.split("_")[0]);
                    String password = value.split("_")[1];
                    //Cookie中的密码是md5加密后的,所以第三个参数为true
                    User user = service.login(username, password, true);
                    //通过则在session中设置登陆标记
                    if(user!=null){
                        session.setAttribute("user", user);
                    }
                    break;
                }
            }
        }
        chain.doFilter(request, response);
    }

    public void destroy() {
    }
}

五、Filter高级开发:装饰设计模式
在Filter.doFilter中可以得到代表用户请求和响应的request、response对象,因此在编程中可以使用装饰模式(Decorator)对request、response对象进行包装,改写其中的某些方法,增强功能,再把包装对象传给目标资源,从而实现一些特殊需求。
在《数据库连接池(DataSource)》一文中讲过改写Connection.close功能的几种方法。在Filter的高级开发中也需要对request、response的进行功能上的改写。
Sun公司提供了HttpServletRequestWrapper、HttpServletResponseWrapper两个对HttpServletRequest、HttpServletRequest对象的简单包装类【作用同《数据库连接池(DataSource)》一文中的ConnectionAdapter 】如果要改写request或response对象的功能,只需继承HttpServletRequestWrapper、HttpServletResponseWrapper类覆写对应方法即可。
六、高级案例(4个)
1)SetCharacterEncodingFilter:解决全站中文乱码
上面的第一个案例解决了Post请求参数和响应输出的中文乱码问题,本案例是它的增强版:增加了get请求参数的中文乱码解决。
解决方法是改写request对象的getParameter方法。如何改写request对象的getParameter方法:装饰设计模式,见代码中的MyHttpServletRequest类。

class EncodingHttpServletRequest extends HttpServletRequestWrapper{

    public EncodingHttpServletRequest(HttpServletRequest request) {
        super(request);
    }
    //改写request对象的getParameter方法
    public String getParameter(String name) {
        String value = super.getParameter(name);
        if(value == null) return null;
        //解决get请求方式时的乱码问题
        if("get".equalsIgnoreCase(super.getMethod())){
            try {
                return new String(value.getBytes("ISO-8859-1"),super.getCharacterEncoding());
            } catch (UnsupportedEncodingException e) {
                throw new RuntimeException(e);
            }
        }
        return value;
    }
}

public class SetCharacterEncodingFilter implements Filter {
        ………………
        //doFilter()
        request.setCharacterEncoding(encoding);//解决POST请求参数的乱码
        response.setCharacterEncoding(encoding);//输出流编码
        request.setContentType("text/html;charset="+encoding);

        EncodingHttpServletRequest ecRequest = new EncodingHttpServletRequest(request);
        //往下传递的request对象是改写了getParameter方法的包装对象
        chain.doFilter(ecRequest, request);}
        ………………
}

2)DirtyWordsFilter:过滤脏话
核心还是对request对象中getParameter方法的改写:

class DirtyWordsHttpServletRequest extends HttpServletRequestWrapper{

    //脏话库
    private String[] strs = {"日","插","草","操"};
    public DirtyWordsHttpServletRequest(HttpServletRequest request) {
        super(request);
    }

    public String getParameter(String name) {
        String value = super.getParameter(name);
        if(value==null) return null;
        //遍历脏话库,对脏话用*替换
        for(String s:strs){
            value = value.replace(s, "*");
        }
        return value;
    }
}

3)HtmlFilter:过滤html标记
思路同DirtyWordsFilter,htmlFilter直接从Tomcat下的webapps\examples\WEB-INF\classes\util\HTMLFilter.java拷贝。

class HtmlHttpServletRequest extends HttpServletRequestWrapper{

    public HtmlHttpServletRequest(HttpServletRequest request) {
        super(request);
    }

    public String getParameter(String name) {
        String value = super.getParameter(name);
        if(value == null) return value;
        //对HTML标记中的标记符号转义
        value = htmlFilter(value);
        return value;
    }
    //下面的一大段代码拷贝自webapps\examples\WEB-INF\classes\ util\HTMLFilter.java
    private String htmlFilter(String message) {
        if (message == null)
            return (null);

        char content[] = new char[message.length()];
        message.getChars(0, message.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());
    }
}

4)GzipFilter:全站压缩(有难度)
下面的代码在一个普通的Java类中演示了Gzip压缩的过程:
String str = “压缩我吧!!!啊啊啊!!!!!!!!!!!”;

// 获得字节数组
byte[] b = str.getBytes("utf-8");
System.out.println("压缩前大小:" + b.length);

//☆☆☆☆☆对数据进行压缩☆☆☆☆☆
ByteArrayOutputStream out = new ByteArrayOutputStream();
GZIPOutputStream gout = new GZIPOutputStream(out);
gout.write(b);
gout.close(); //这句话一定要有

b = out.toByteArray();
System.out.println("压缩后大小:" + b.length);

在web工程中对响应输出进行压缩前需要判断客户端是否支持Gzip压缩(请求头的Accept-Encoding字段是否含有gzip压缩),如果支持gzip压缩则进行压缩,并在响应头字段中告诉客户端响应消息为gzip压缩。
因为是对响应输出进行压缩,所以压缩代码应放在chain.doFilter()之后。全站压缩的最大难点在于如何拦截目标资源的响应输出。假设目标资源为一个Servlet,且Servlet中的相应输出代码如下:
ServletOutputStream out = response.getOutputStream();
out.write(b);

则截取该Servlet的响应输出思路如下:
① 继承HttpServletResponseWrapper,改写getOutputStream方法,返回一个自己定义的输出流。
② 该输出流继承ServletOutputStream,并覆写其中的write(int)方法,每次都向指定的地方输出(很关键,即下文的ByteArrayOutputStream对象),这样就完成了响应消息的截获。
下面的代码演示了这一过程,在改写response功能时,定义了一个ByteArrayOutputStream对象baos,并提供了获取该字节数组的getBufferedBytes方法。在getOutputStream()中,返回一个包装了baos的ServletOutputStream对象。

class GzipHttpServletResponse extends HttpServletResponseWrapper{
    //截获输出流,放入baos
    private ByteArrayOutputStream baos = new ByteArrayOutputStream();

    private PrintWriter pw = null;

    public GzipHttpServletResponse(HttpServletResponse response) {
        super(response);
    }

    //返回包装了baos的ServletOutputStream对象,用户每次的输出都会被“截获”到baos中
    public ServletOutputStream getOutputStream() throws IOException {
        return new MyServletOutputStream(baos);
    }

    //截获字符流:放到baos中
    public PrintWriter getWriter() throws IOException {
        pw = new PrintWriter(new OutputStreamWriter(baos,super.getCharacterEncoding()));
        return pw;
    }

    //获取截获的字节数组
    public byte[] getBufferedBytes() {
        try {
            if(pw!=null){
                pw.close();
            }
            baos.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return baos.toByteArray();
    }
}

//改写ServletOutputStream --> 包装流
class MyServletOutputStream extends ServletOutputStream{

    private ByteArrayOutputStream baos = new ByteArrayOutputStream();

    public MyServletOutputStream(ByteArrayOutputStream baos){
        this.baos = baos;
    }
    public void write(int b) throws IOException {
        baos.write(b);
    }
}

过滤器doFilter方法中的部分代码:

GzipHttpServletResponse gzipResponse = new GzipHttpServletResponse(response);

chain.doFilter(request, gzipResponse);

byte b[] = gzipResponse.getBufferedBytes();//得到原始的数据为编码的关键点
System.out.println("压缩前大小:"+b.length);
//判断客户是否支持gzip压缩
String acceptEncoding = request.getHeader("Accept-Encoding");
if(acceptEncoding!=null&&acceptEncoding.contains("gzip")){  //支持
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    GZIPOutputStream gout = new GZIPOutputStream(out);
    gout.write(b);
    gout.close();

    b = out.toByteArray();//压缩后的数据
    System.out.println("压缩后大小:"+b.length);

    //告知浏览器压缩方式
    response.setHeader("Content-Encoding", "gzip");
    response.setContentLength(b.length);//告知客户端,正文的长度
}
response.getOutputStream().write(b);

在配置web.xml的过滤范围时要注意,一般只过滤字符文件(.jsp,.html,.css,.js等),因为.jpg,.png,*.zip等都已经是压缩过的文件了(再压缩没意义,只会浪费服务器资源)。

<filter>
    <filter-name>GzipFilter</filter-name>
    <filter-class>org.flyne.filter.advanced.GzipFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>GzipFilter</filter-name>
    <url-pattern>*.jsp</url-pattern>
    <url-pattern>*.html</url-pattern>
    <url-pattern>*.js</url-pattern>
    <url-pattern>*.css</url-pattern>
</filter-mapping>
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值