第六章 过滤器Filter
Filter概述
Filter不用于客户端请求,只用于对request,response进行修改或对context,session,request事件进行监听。
1.概述
如上图,多个filter组成一个FilterChain。
2.Filter接口
3.Filter配置
防盗链Filter
代码详解:
1.编写过滤器
public class ImageRedirectFilter implements Filter {
public void init(FilterConfig config) throws ServletException {
}
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
// 禁止缓存
response.setHeader("Cache-Control", "no-store");
response.setHeader("Pragrma", "no-cache");
response.setDateHeader("Expires", 0);
// 链接来源地址
String referer = request.getHeader("referer");
if (referer == null || !referer.contains(request.getServerName())) {
request.getServerName()//获取你的网站的域名
/**
* 如果 链接地址来自其他网站,则返回错误图片
*/
request.getRequestDispatcher("/error.gif").forward(request,
response);
} else {
/**
* 图片正常显示
*/
chain.doFilter(request, response);
}
}
public void destroy() {
}
}
过滤器继承Filter接口,重载三个方法,分别是init(),doFilter(),destroy().其中doFilter()中必须执行
chain.doFilter(request, response);以便于执行接下来的过滤器。在doFilter()中判断浏览器请求的源地址和服务器的域名是否一致。
2.过滤器配置
通过配置<url-pattern>,说明只有当浏览器访问/images或者/upload/images目录时才执行该过滤器。
字符编码Filter
public class CharacterEncodingFilter implements Filter {
private String characterEncoding;
private String enable;
private boolean enabled;
@Override
public void init(FilterConfig config) throws ServletException {
characterEncoding = config.getInitParameter("characterEncoding");
enable = config.getInitParameter("enable");
enabled = "true".equalsIgnoreCase(enable.trim())
|| "1".equalsIgnoreCase(enable.trim());
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
if (enabled&&characterEncoding != null) {
request.setCharacterEncoding(characterEncoding);
response.setCharacterEncoding(characterEncoding);
}
chain.doFilter(request, response);
}
@Override
public void destroy() {
characterEncoding = null;
}
}
个人认为编码Filter应该处于第一个Filter,这样,在tomcat解析request参数前就对参数进行编码设置了。
日志记录Filter
public class LogFilter implements Filter {
private Log log = LogFactory.getLog(this.getClass());
private String filterName;
public void init(FilterConfig config) throws ServletException {
// 获取 Filter 的 name,配置在 web.xml 中
filterName = config.getFilterName();
log.info("启动 Filter: " + filterName);
}
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
long startTime = System.currentTimeMillis();
String requestURI = request.getRequestURI();
requestURI = request.getQueryString() == null ? requestURI
: (requestURI + "?" + request.getQueryString()); //判断请求中是否含有参数,如果有,则加入到requestURI中去。
chain.doFilter(request, response);
long endTime = System.currentTimeMillis();
log.info(request.getRemoteAddr() + " 访问了 " + requestURI + ", 总用时 "
+ (endTime - startTime) + " 毫秒。");//request.getRemoteAddr()返回客户端或代理服务器的IP地址
}
public void destroy() {
log.info("关闭 Filter: " + filterName);
}
}
web配置省略。
异常捕捉Filter
public class ExceptionHandlerFilter implements Filter {
public void destroy() {
}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
try { //捕获异常
chain.doFilter(request, response);
} catch (Exception e) { //得到异常后,根据不同异常作相应的处理
Throwable rootCause = e;
while (rootCause.getCause() != null) {
rootCause = rootCause.getCause();
}
String message = rootCause.getMessage();
message = message == null ? "异常:" + rootCause.getClass().getName()
: message;
request.setAttribute("message", message);
request.setAttribute("e", e);
if (rootCause instanceof AccountException) {//判断rootCause是否是AccountException的一个实例
request.getRequestDispatcher("/accountException.jsp").forward(
request, response);
} else if (rootCause instanceof BusinessException) {
request.getRequestDispatcher("/businessException.jsp").forward(
request, response);
} else {
request.getRequestDispatcher("/exception.jsp").forward(request,
response);
}
}
}
public void init(FilterConfig arg0) throws ServletException {
}
}
权限验证Filter
public class PrivilegeFilter implements Filter {
private Properties pp = new Properties();
public void init(FilterConfig config) throws ServletException {
// 从 初始化参数 中获取权 限配置文件 的位置
String file = config.getInitParameter("file");
String realPath = config.getServletContext().getRealPath(file);
try {
pp.load(new FileInputStream(realPath));
} catch (Exception e) {
config.getServletContext().log("读取权限控制文件失败。", e);
}
}
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
// 获取访问的路径,例如:admin.jsp
String requestURI = request.getRequestURI().replace(
request.getContextPath() + "/", "");
// 获取 action 参数,例如:add
String action = req.getParameter("action");
action = action == null ? "" : action;
// 拼接成 URI。例如:log.do?action=list
String uri = requestURI + "?action=" + action;
// 从 session 中获取用户权限角色。
String role = (String) request.getSession(true).getAttribute("role");
role = role == null ? "guest" : role;
boolean authentificated = false;
// 开始检查该用户角色是否有权限访问 uri
for (Object obj : pp.keySet()) {
String key = ((String) obj);
// 使用正则表达式验证 需要将 ? . 替换一下,并将通配符 * 处理一下
if (uri.matches(key.replace("?", "\\?").replace(".", "\\.")
.replace("*", ".*"))) {
// 如果 role 匹配
if (role.equals(pp.get(key))) {
authentificated = true;
break;
}
}
}
if (!authentificated) {
throw new RuntimeException(new AccountException(
"您无权访问该页面。请以合适的身份登陆后查看。"));
}
// 继续运行
chain.doFilter(req, res);
}
public void destroy() {
pp = null;
}
}
内容替换Filter
public class HttpCharacterResponseWrapper extends HttpServletResponseWrapper {
private CharArrayWriter charArrayWriter = new CharArrayWriter();
public HttpCharacterResponseWrapper(HttpServletResponse response) {
super(response);
}
@Override
public PrintWriter getWriter() throws IOException {
return new PrintWriter(charArrayWriter);//将response的输出目标变为charArrayWriter
}
public CharArrayWriter getCharArrayWriter() {
return charArrayWriter;
}
}
public class OutputReplaceFilter implements Filter {
private Properties pp = new Properties();
public void init(FilterConfig config) throws ServletException {
String file = config.getInitParameter("file");
String realPath = config.getServletContext().getRealPath(file);
try {
pp.load(new FileInputStream(realPath));
} catch (IOException e) {
}
}
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
// 自定义的 response
HttpCharacterResponseWrapper response = new HttpCharacterResponseWrapper(
(HttpServletResponse) res);
// 提交给 Servlet 或者下一个 Filter
chain.doFilter(req, response);
// 得到缓存在自定义 response 中的输出内容
String output = response.getCharArrayWriter().toString();
// 修改,替换
for (Object obj : pp.keySet()) {
String key = (String) obj;
output = output.replace(key, pp.getProperty(key));
}
// 输出
PrintWriter out = res.getWriter();
out.write(output);
out.println("<!-- Generated at " + new java.util.Date() + " -->");
}
public void destroy() {
}
}
代码解析:
1.定义一个类HttpCharacterResponseWrapper用于替换原来的response。
2.在过滤器中用res声明一个新的response:
HttpCharacterResponseWrapper response = new HttpCharacterResponseWrapper(
(HttpServletResponse) res);
3.调用chain.doFilter(req, response)时,如果有输出就会调用response.getWriter(), 此时调用的是覆盖后的PrintWriter(),该PrintWriter()将会把数据写到charArrayWriter中去。
4.执行完chain.doFilter(req, response)后,获取charArrayWriter中的数据:
String output = response.getCharArrayWriter().toString();
5.修改output数据
6.再通过原始的res来输出output中的数据
PrintWriter out = res.getWriter();
out.write(output);
以上代码的功能是先将res包装成response(此时response的输出目标不再是客户端,而是charArrayWriter缓存区),接着在读取charArrayWriter内的内用,再通过res的写出方法写到客户端。
GZIP压缩Filter
public class GZipFilter implements Filter {
public void destroy() {
}
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
String acceptEncoding = request.getHeader("Accept-Encoding");
System.out.println("Accept-Encoding: " + acceptEncoding);
if (acceptEncoding != null
&& acceptEncoding.toLowerCase().indexOf("gzip") != -1) {
// 如果客户浏览器支持 GZIP 格式, 则使用 GZIP 压缩数据
GZipResponseWrapper gzipResponse = new GZipResponseWrapper(response);
chain.doFilter(request, gzipResponse);
// 输出压缩数据
gzipResponse.finishResponse();
} else {
// 否则, 不压缩
chain.doFilter(request, response);
}
}
public void init(FilterConfig arg0) throws ServletException {
}
}
类GZipResponseWrapper代码如下:
public class GZipResponseWrapper extends HttpServletResponseWrapper {
// 默认的 response
private HttpServletResponse response;
// 自定义的 outputStream, 执行close()的时候对数据压缩,并输出
private GZipOutputStream gzipOutputStream;
// 自定义 printWriter,将内容输出到 GZipOutputStream 中
private PrintWriter writer;
public GZipResponseWrapper(HttpServletResponse response) throws IOException {
super(response);
this.response = response;
}
public ServletOutputStream getOutputStream() throws IOException {
if (gzipOutputStream == null)
gzipOutputStream = new GZipOutputStream(response);
return gzipOutputStream;
}
public PrintWriter getWriter() throws IOException {
if (writer == null)
writer = new PrintWriter(new OutputStreamWriter(
new GZipOutputStream(response), "UTF-8"));
return writer;
}
// 压缩后数据长度会发生变化 因此将该方法内容置空
public void setContentLength(int contentLength) {
}
public void flushBuffer() throws IOException {
gzipOutputStream.flush();
}
public void finishResponse() throws IOException {
if (gzipOutputStream != null)
gzipOutputStream.close();
if (writer != null)
writer.close();
}
}
类GZipOutputStream代码如下:
public class GZipOutputStream extends ServletOutputStream {
private HttpServletResponse response;
// JDK 自带的压缩数据的类
private GZIPOutputStream gzipOutputStream;
// 将压缩后的数据存放到 ByteArrayOutputStream 对象中
private ByteArrayOutputStream byteArrayOutputStream;
public GZipOutputStream(HttpServletResponse response) throws IOException {
this.response = response;
byteArrayOutputStream = new ByteArrayOutputStream();
gzipOutputStream = new GZIPOutputStream(byteArrayOutputStream);
}
public void write(int b) throws IOException {
gzipOutputStream.write(b);
}
public void close() throws IOException {
// 压缩完毕 一定要调用该方法
gzipOutputStream.finish();
// 将压缩后的数据输出到客户端
byte[] content = byteArrayOutputStream.toByteArray();
// 设定压缩方式为 GZIP, 客户端浏览器会自动将数据解压
response.addHeader("Content-Encoding", "gzip");
response.addHeader("Content-Length", Integer.toString(content.length));
// 输出
ServletOutputStream out = response.getOutputStream();
out.write(content);
out.close();
}
public void flush() throws IOException {
gzipOutputStream.flush();
}
public void write(byte[] b, int off, int len) throws IOException {
gzipOutputStream.write(b, off, len);
}
public void write(byte[] b) throws IOException {
gzipOutputStream.write(b);
}
}
代码详解:
1.在过滤器中有如下代码:
// 如果客户浏览器支持 GZIP 格式, 则使用 GZIP 压缩数据
GZipResponseWrapper gzipResponse = new GZipResponseWrapper(response);
chain.doFilter(request, gzipResponse);
// 输出压缩数据
gzipResponse.finishResponse();
第一行是将response包装,目的是压缩response中的数据
第三行是将response中压缩的数据输出到客户端
2.构造一个GZipResponseWrapper类,在此类中重写getWriter方法改变其输出目的地。
public PrintWriter getWriter() throws IOException {
if (writer == null)
writer = new PrintWriter(new OutputStreamWriter(
new GZipOutputStream(response), "UTF-8"));
return writer;
}
因此过滤器中gzipResponse获取的writer将会把数据输出到GZipOutputStream(response)流中。
3.GZipOutputStream流继承于ServletOutputStream,在此类中创建一个缓存区ByteArrayOutputStream并生成GZIPOutputStream的对象gzipOutputStream,并将该流的输出指向缓存区,该类的write方法自动将数据压缩并写到指定目的地。GZipOutputStream的输出都是通过GZIPOutputStream的gzipOutputStream写入到缓存区。
4.当过滤器执行chain.doFilter(request, gzipResponse)期间,假如有数据输出则会调用gzipResponse.getWriter()方法得到PrintWriter out对象。执行out.write("String"),则会进一步调用OutputStreamWriter的write()方法,又会进一步调用GZipOutputStream的write()方法,在
GZipOutputStream的write()方法中,调用GZIPOutputStream的write方法把需要输出的数据压缩并缓存到ByteArrayOutputStream中。
5.当过滤器执行gzipResponse.finishResponse()时,便会关闭PrintWriter writer对象对应的流,即进一步关闭OutputStreamWriter流,进一步关闭GZipOutputStream流,在GZipOutputStream的close方法中将缓存区的ByteArrayOutputStream通过response输出到客户端。
图像水印Filter
思路和上一节GZIP压缩是类似的。
1.WaterMarkFilter代码如下:
public class WaterMarkFilter implements Filter {
// 水印图片,配置在初始化参数中
private String waterMarkFile;
public void init(FilterConfig config) throws ServletException {
String file = config.getInitParameter("waterMarkFile");
waterMarkFile = config.getServletContext().getRealPath(file);
}
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
// 自定义的response
WaterMarkResponseWrapper waterMarkRes = new WaterMarkResponseWrapper(
response, waterMarkFile);
chain.doFilter(request, waterMarkRes);
// 打水印,输出到客户端浏览器
waterMarkRes.finishResponse();
}
public void destroy() {
}
}
2.WaterMarkResponseWrapper代码如下:
public class WaterMarkResponseWrapper extends HttpServletResponseWrapper {
// 水印图片位置
private String waterMarkFile;
// 原response
private HttpServletResponse response;
// 自定义servletOutputStream,用于缓冲图像数据
private WaterMarkOutputStream waterMarkOutputStream;
public WaterMarkResponseWrapper(HttpServletResponse response,
String waterMarkFile) throws IOException {
super(response);
this.response = response;
this.waterMarkFile = waterMarkFile;
this.waterMarkOutputStream = new WaterMarkOutputStream();
}
// 覆盖getOutputStream(),返回自定义的waterMarkOutputStream
public ServletOutputStream getOutputStream() throws IOException {
return waterMarkOutputStream;
}
public void flushBuffer() throws IOException {
waterMarkOutputStream.flush();
}
// 将图像数据打水印,并输出到客户端浏览器
public void finishResponse() throws IOException {
// 原图片数据
byte[] imageData = waterMarkOutputStream.getByteArrayOutputStream()
.toByteArray();
// 打水印后的图片数据
byte[] image = ImageUtil.waterMark(imageData, waterMarkFile);
// 将图像输出到浏览器
response.setContentLength(image.length);
response.getOutputStream().write(image);
waterMarkOutputStream.close();
}
}
3.WaterMarkOutputStream代码如下:
public class WaterMarkOutputStream extends ServletOutputStream {
// 缓冲图片数据
private ByteArrayOutputStream byteArrayOutputStream;
public WaterMarkOutputStream() throws IOException {
byteArrayOutputStream = new ByteArrayOutputStream();
}
public void write(int b) throws IOException {
byteArrayOutputStream.write(b);
}
public void close() throws IOException {
byteArrayOutputStream.close();
}
public void flush() throws IOException {
byteArrayOutputStream.flush();
}
public void write(byte[] b, int off, int len) throws IOException {
byteArrayOutputStream.write(b, off, len);
}
public void write(byte[] b) throws IOException {
byteArrayOutputStream.write(b);
}
public ByteArrayOutputStream getByteArrayOutputStream() {
return byteArrayOutputStream;
}
}
4.ImageUtil代码如下:
public class ImageUtil {
/**
*
* @param imageData
* JPG 图像文件
* @param waterMarkFile
* 水印图片
* @return 加水印后的图像数据
* @throws IOException
*/
public static byte[] waterMark(byte[] imageData, String waterMarkFile)
throws IOException {
// 水印图片的右边距 下边距
int paddingRight = 10;
int paddingBottom = 10;
// 原始图像
Image image = new ImageIcon(imageData).getImage();
int imageWidth = image.getWidth(null);
int imageHeight = image.getHeight(null);
// 水印图片
Image waterMark = ImageIO.read(new File(waterMarkFile));
int waterMarkWidth = waterMark.getWidth(null);
int waterMarkHeight = waterMark.getHeight(null);
// 如果图片尺寸过小,则不打水印,直接返回
if (imageWidth < waterMarkWidth + 2 * paddingRight
|| imageHeight < waterMarkHeight + 2 * paddingBottom) {
return imageData;
}
BufferedImage bufferedImage = new BufferedImage(imageWidth,
imageHeight, BufferedImage.TYPE_INT_RGB);
Graphics g = bufferedImage.createGraphics();
// 绘制原始图像
g.drawImage(image, 0, 0, imageWidth, imageHeight, null);
// 绘制水印图片
g.drawImage(waterMark, imageWidth - waterMarkWidth - paddingRight,
imageHeight - waterMarkHeight - paddingBottom, waterMarkWidth,
waterMarkHeight, null);
g.dispose();
// 转成JPEG格式
ByteArrayOutputStream out = new ByteArrayOutputStream();
JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out);
encoder.encode(bufferedImage);
byte[] data = out.toByteArray();
out.close();
return data;
}
}
代码详解:
1.过滤器中如下代码:
// 自定义的response
WaterMarkResponseWrapper waterMarkRes = new WaterMarkResponseWrapper(
response, waterMarkFile);
对response进行包装,使得response的输出数据放入缓存,并对图像数据添加水印
2.chain.doFilter(request, waterMarkRes)执行过程中,如果输出图片,则会调用waterMarkRes.getOutputStream()方法,由于在WaterMarkResponseWrapper中重写了该方法,故得到的是waterMarkOutputStream流。waterMarkOutputStream流中将数据写入到缓存区byteArrayOutputStream流中。
3.过滤器执行waterMarkRes.finishResponse()时,将会把waterMarkOutputStream中的数据取出并添加水印后,通过response.getOutputStream().write(image)将图片写出。
缓存Filter
具体代码详见《JavaWeb整合开发王者归来》
XSLT转换Filter
具体代码详见《JavaWeb整合开发王者归来》
文件上传Filter
1.UploadRequestWrapper代码如下
public class UploadRequestWrapper extends HttpServletRequestWrapper {
private static final String MULTIPART_HEADER = "Content-type";
// 是否是上传文件
private boolean multipart;
// map,保存所有的域
private Map<String, Object> params = new HashMap<String, Object>();
@SuppressWarnings("all")
public UploadRequestWrapper(HttpServletRequest request) {
super(request);
// 判断是否为上传文件
multipart = request.getHeader(MULTIPART_HEADER) != null
&& request.getHeader(MULTIPART_HEADER).startsWith(
"multipart/form-data");
if (multipart) {
try {
// 使用apache的工具解析
DiskFileUpload upload = new DiskFileUpload();
upload.setHeaderEncoding("utf8");
// 解析,获得所有的文本域与文件域
List<FileItem> fileItems = upload.parseRequest(request);
for (Iterator<FileItem> it = fileItems.iterator(); it.hasNext();) {
// 遍历
FileItem item = it.next();
if (item.isFormField()) {
// 如果是文本域,直接放到map里
params.put(item.getFieldName(), item.getString("utf8"));
} else {
// 否则,为文件,先获取文件名称
String filename = item.getName().replace("\\", "/");
filename = filename
.substring(filename.lastIndexOf("/") + 1);
// 保存到系统临时文件夹中
File file = new File(System
.getProperty("java.io.tmpdir"), filename);
// 保存文件内容
OutputStream ous = new FileOutputStream(file);
ous.write(item.get());
ous.close();
// 放到map中
params.put(item.getFieldName(), file);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
public Object getAttribute(String name) {
// 如果为上传文件,则从map中取值
if (multipart && params.containsKey(name)) {
return params.get(name);
}
return super.getAttribute(name);
}
@Override
public String getParameter(String name) {
// 如果为上传文件,则从map中取值
if (multipart && params.containsKey(name)) {
return params.get(name).toString();
}
return super.getParameter(name);
}
public static void main(String[] args) {
System.out.println(System.getProperties().toString().replace(", ",
"\r\n"));
}
}
以上代码自定义了request并重写其相关方法。并且判断request中的数据类型,如果是multipart,则把数据名和对应的值放入map集合。通过request.getParameter()和request.getAttribute()方法便可方便获取request的数据。