Head First JSP---随笔十(过滤器的威力)

过滤器和包装类

过滤器允许你拦截请求。最棒的是,servlet对此一无所知


过滤器

3.3 描述Web容器请求处理模型;编写和配置过滤器;创建请求或响应包装器;给定一个设计问题,描述如何应用过滤器或包装器。
11.1 给定一个场景描述,列出了一系列问题,选择能够解决这些问题的模式。你必须了解的模式包括:
1. 拦截过滤器
2. 模型-视图-控制器
3. 前端控制器
4. 服务定位器
5. 业务委托
6. 传输对象
11.1 对于以下设计模式,将各模式与使用该模式可能带来的好处相匹配:
1. 拦截过滤器
2. 模型-视图-控制器
3. 服务定位器
4. 业务委托
5. 传输对象


过滤器

能够过滤一切东西!并且生命周期与servlet很类似。
这里写图片描述


过滤器是模块化,在DD中配置

顺序由DD来控制!
这里写图片描述


过滤器在3个方面很像servlet

  1. 容器知道过滤器API。过滤器有自己的API。如果一个Java类实现了Filter接口,对容器来说就有很大不同,这个类会从原先一个普通的Java类摇身一变而成为一个正式的J2EE过滤器。过滤器API的其他成员允许过滤器访问ServletContext,而且可以与其他过滤器链接。
  2. 容器管理过滤器的生命周期。就像servlet一样,过滤器也有一个生命周期。类似于servlet过滤器有init()和destroy()方法。对应于servlet的doGet()/doPost()方法,过滤器则有一个doFilter()方法。
  3. 都在DD中声明。Web应用可以有很多的过滤器,一个给定请求可能导致执行多个过滤器。针对请求要运行哪些过滤器,以及运行的顺序如何,这些都要在DD中声明。

建立请求跟踪过滤器

这里写图片描述


过滤器的生命周期

每个过滤器都必须实现Filter接口中的三个方法:init()、doFilter()和destroy()。

  1. 首先要有一个init()。容器决定实例化一个过滤器时,就要把握住机会,在init()方法中完成调用过滤器之前的所有初始化任务。前一页显示了最常见的实现;也就是保存FilterConfig对象的一个引用,以备过滤器以后使用。
  2. 真正的工作在doFilter()中完成。每次容器认为应该对当前请求应用过滤器时,就会调用doFilter()方法。doFilter()方法有3个参数:ServletRequest、ServletResponse、FilterChain,过滤器的功能要在doFilter()方法中实现。如果过滤器要把用户记录到一个文件中,就要在doFilter()中完成。你想压缩响应输出吗?也要在doFilter()中实现。
  3. 最后是destroy()。容器决定删除一个过滤器实例时,会调用destroy()方法,这样你就有机会在真正撤销实例之前完成所需的所有清理工作。

过滤器“入栈”

多个过滤器同时工作!
这里写图片描述


声明和确定过滤器顺序

在DD中配置过滤器时,通常会做3件事:

  1. 声明过滤器
  2. 将过滤器映射到你想过滤的Web资源
  3. 组织这些映射,创建过滤器调用序列

如下(简单地说,就是可以过滤url和servlet,顺序是先查找与之匹配的所有url再查找servlet将其按查找顺序组成链):
这里写图片描述


2.4版本中,过滤器可以应用于请求分派器

除了对url和servlet的过滤,还过滤转发、包含、请求分派和错误处理!
这里写图片描述


用一个响应端过滤器压缩输出

在servlet完成其工作并从(虚拟)栈弹出后,过滤器还会得到机会执行!
这里写图片描述
不过事情没有那么简单:
这里写图片描述
那该如何解决这个问题呢?往下看!


可以实现自己的响应

容器已经实现了HttpServletResponse接口:doFilter()和service()方法就是以这样一个响应作为参数。
这里写图片描述
实现HttpServletResponse接口也太麻烦了吧,因为方法好多啊!
这里写图片描述
如何解决?下面!


包装器

servlet API中的包装器类功能极其强大,它们为你要包装的东西实现了所需的所有方法,并将所有调用委托给底层的请求或响应对象。如果想创建定制请求或响应对象,只需派生某个便利请求或响应“包装器”类。包装器包装了实际请求或响应对象,而且把调用委托给(传给)实际的对象,还允许你对定制请求或响应做所需的额外处理

4个“便利”类:

  1. ServletRequestWrapper
  2. HttpServletRequestWrapper
  3. ServletResponseWrapper
  4. HttpServletResponseWrapper

这其实就是装饰器模式


简单的包装器例子

这里写图片描述


包装器第二版本

这里写图片描述


具体的代码

过滤器代码:

package com.example.web;

import java.io.IOException;
import java.util.zip.GZIPOutputStream;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class CompressioinFilter implements Filter{

    private ServletContext ctx;
    private FilterConfig cfg;

    /**
     * init方法保存配置对象,并保存
     * servlet上下文对象的一个直接
     * 引用
     */
    @Override
    public void init(FilterConfig cfg) throws ServletException {
        this.cfg = cfg;
        ctx = cfg.getServletContext();
        ctx.log(cfg.getFilterName()+" initialized.");
    }

    /**
     * 这个过滤器的核心是用装饰器包装响应对象,它
     * 用一个压缩I/O流包装输出流,当且仅当客户包
     * 含一个Accept-Encoding首部(具体为gzip)时,才会完成输出流的压缩。
     */
    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain fc)
            throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) resp;
        String valid_encodings = request.getHeader("Accept-Encoding");
        if(valid_encodings.indexOf("gzip")>-1) {
            CompressioinResponseWrapper wrappedResp = new CompressioinResponseWrapper(response);
            wrappedResp.setHeader("Content-Encoding","gzip");
            //链接下一个组件
            fc.doFilter(request, wrappedResp);
            //GZIP压缩流必须“结束”,这也会刷新输出GZIP
            //流缓冲区,将所有数据发送到原来的响应流
            //容器处理余下的工作
            GZIPOutputStream gzos = wrappedResp.getGZIPOutputStream();
            gzos.flush();

            ctx.log(cfg.getFilterName() + ": finished the request.");
        }else {
            ctx.log(cfg.getFilterName() + ": no encoding performed.");
            fc.doFilter(request, response);
        }
    }

    //销毁
    @Override
    public void destroy() {
        cfg = null;
        ctx = null;
    }

}

包装器代码:

package com.example.web;

import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.zip.GZIPOutputStream;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;

public class CompressioinResponseWrapper extends HttpServletResponseWrapper {

    //servlet响应的压缩输出流
    private GZIPServletOutputStream servletGzipOS = null;
    //压缩输出流的PrintWriter对象
    private PrintWriter pw = null;

    CompressioinResponseWrapper(HttpServletResponse response) {
        //super构造函数完成装饰器的职责
        //保存所装饰对象的一个引用,在这
        //里被装饰的对象就是HTTP响应对象
        super(response);
    }




    //忽略这个方法——输出会得到压缩
    @Override
    public void setContentLength(int len) {}

    public GZIPOutputStream getGZIPOutputStream() {
        //过滤器使用这个装饰器方法为压缩过
        //滤器提供一个GZIP输出流的句柄,以便
        //过滤器“完成”和刷新输出GZIP流
        return this.servletGzipOS.internalGzipOS;
    }

    private Object streamUsed = null;

    //允许访问所装饰的servlet输出流
    @Override
    public ServletOutputStream getOutputStream() throws IOException {
        if(streamUsed!=null&&streamUsed!=pw) {
            //仅当servlet还没有访问打印书写器时
            //允许servlet访问servlet输出流
            throw new IllegalStateException();
        }
        if(servletGzipOS==null) {
            //用我们的压缩输出流包装原来的servlet输出流
            servletGzipOS = new GZIPServletOutputStream(getResponse().getOutputStream());
            streamUsed = servletGzipOS;
        }
        return servletGzipOS;
    }

    @Override
    public PrintWriter getWriter() throws IOException {
        if(streamUsed!=null&&streamUsed!=servletGzipOS) {
            //当且仅当servlet还没有访问servlet输出
            //流时,允许servlet访问打印书写器
            throw new IllegalStateException();
        }
        if(pw==null) {
            /**
             * 要建立一个打印书写器,必须首先包装servlet输出流
             * 然后把压缩servlet输出流包装在另外两个输出流装饰器
             * 中:首先OutputStreamWrite把字符转换为字节,再用
             * PrintWriter包装OutputStreamWriter对象。
             */
            servletGzipOS = new GZIPServletOutputStream(getResponse().getOutputStream());
            OutputStreamWriter osw = new OutputStreamWriter(servletGzipOS, getResponse().getCharacterEncoding());
            pw = new PrintWriter(osw);
            streamUsed = pw;
        }
        return pw;
    }
}

辅助类:

package com.example.web;

import java.io.IOException;
import java.util.zip.GZIPOutputStream;

import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;

public class GZIPServletOutputStream extends ServletOutputStream{
    //保存原始GZIP流的一个引用
    GZIPOutputStream internalGzipOS;

    @Override
    public boolean isReady() {
        return false;
    }

    @Override
    public void setWriteListener(WriteListener arg0) {

    }

    //装饰器构造函数
    GZIPServletOutputStream(ServletOutputStream sos) throws IOException {
        this.internalGzipOS = new GZIPOutputStream(sos);
    }

    @Override
    public void write(int param) throws IOException {
        //这个方法把write()调用委托给GZIP压缩流,
        //从而实现压缩装饰,GZIP压缩流包装了原来的
        //ServletOutputStream
        internalGzipOS.write(param);
    }

}

本章完。

猜你喜欢

转载自blog.csdn.net/qq_37340753/article/details/81477330
今日推荐