过滤器和包装类
过滤器允许你拦截请求。最棒的是,servlet对此一无所知。
过滤器
3.3 描述Web容器请求处理模型;编写和配置过滤器;创建请求或响应包装器;给定一个设计问题,描述如何应用过滤器或包装器。
11.1 给定一个场景描述,列出了一系列问题,选择能够解决这些问题的模式。你必须了解的模式包括:
1. 拦截过滤器
2. 模型-视图-控制器
3. 前端控制器
4. 服务定位器
5. 业务委托
6. 传输对象
11.1 对于以下设计模式,将各模式与使用该模式可能带来的好处相匹配:
1. 拦截过滤器
2. 模型-视图-控制器
3. 服务定位器
4. 业务委托
5. 传输对象
过滤器
能够过滤一切东西!并且生命周期与servlet很类似。
过滤器是模块化,在DD中配置
顺序由DD来控制!
过滤器在3个方面很像servlet
- 容器知道过滤器API。过滤器有自己的API。如果一个Java类实现了Filter接口,对容器来说就有很大不同,这个类会从原先一个普通的Java类摇身一变而成为一个正式的J2EE过滤器。过滤器API的其他成员允许过滤器访问ServletContext,而且可以与其他过滤器链接。
- 容器管理过滤器的生命周期。就像servlet一样,过滤器也有一个生命周期。类似于servlet过滤器有init()和destroy()方法。对应于servlet的doGet()/doPost()方法,过滤器则有一个doFilter()方法。
- 都在DD中声明。Web应用可以有很多的过滤器,一个给定请求可能导致执行多个过滤器。针对请求要运行哪些过滤器,以及运行的顺序如何,这些都要在DD中声明。
建立请求跟踪过滤器
过滤器的生命周期
每个过滤器都必须实现Filter接口中的三个方法:init()、doFilter()和destroy()。
- 首先要有一个init()。容器决定实例化一个过滤器时,就要把握住机会,在init()方法中完成调用过滤器之前的所有初始化任务。前一页显示了最常见的实现;也就是保存FilterConfig对象的一个引用,以备过滤器以后使用。
- 真正的工作在doFilter()中完成。每次容器认为应该对当前请求应用过滤器时,就会调用doFilter()方法。doFilter()方法有3个参数:ServletRequest、ServletResponse、FilterChain,过滤器的功能要在doFilter()方法中实现。如果过滤器要把用户记录到一个文件中,就要在doFilter()中完成。你想压缩响应输出吗?也要在doFilter()中实现。
- 最后是destroy()。容器决定删除一个过滤器实例时,会调用destroy()方法,这样你就有机会在真正撤销实例之前完成所需的所有清理工作。
过滤器“入栈”
多个过滤器同时工作!
声明和确定过滤器顺序
在DD中配置过滤器时,通常会做3件事:
- 声明过滤器
- 将过滤器映射到你想过滤的Web资源
- 组织这些映射,创建过滤器调用序列
如下(简单地说,就是可以过滤url和servlet,顺序是先查找与之匹配的所有url再查找servlet将其按查找顺序组成链):
2.4版本中,过滤器可以应用于请求分派器
除了对url和servlet的过滤,还过滤转发、包含、请求分派和错误处理!
用一个响应端过滤器压缩输出
在servlet完成其工作并从(虚拟)栈弹出后,过滤器还会得到机会执行!
不过事情没有那么简单:
那该如何解决这个问题呢?往下看!
可以实现自己的响应
容器已经实现了HttpServletResponse接口:doFilter()和service()方法就是以这样一个响应作为参数。
实现HttpServletResponse接口也太麻烦了吧,因为方法好多啊!
如何解决?下面!
包装器
servlet API中的包装器类功能极其强大,它们为你要包装的东西实现了所需的所有方法,并将所有调用委托给底层的请求或响应对象。如果想创建定制请求或响应对象,只需派生某个便利请求或响应“包装器”类。包装器包装了实际请求或响应对象,而且把调用委托给(传给)实际的对象,还允许你对定制请求或响应做所需的额外处理。
4个“便利”类:
- ServletRequestWrapper
- HttpServletRequestWrapper
- ServletResponseWrapper
- 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);
}
}