使用Decorator设计模式增强response对象

对于页面中很少更新的数据,例如商品分类,为避免每次都要从数据库查询分类数据,因此可把分类数据缓存在内存或文件中,以此来减轻数据库压力,提高系统响应速度。
试想一下,如果我们真要写这样一个缓存过滤器,那就要把页面中很少更新的数据给缓存起来,紧接着我们就要问自己,用什么东西给他们存起来呢?可以用Map集合嘛!如果使用Map<K, V>集合,那么K和V又分别代表什么呢?我大概想:

  • K应该是String,也就是用户想访问的资源(uri),如/ServletDemo4等;
  • V应该是byte[],也就是缓存数据,因为归根究底,不管是什么格式的数据,最后存在计算机里面的就是字节数组了。

我们编写这样的一个缓存过滤器,代码应该就是:

package cn.liayun.web.filter.example;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;

public class WebCacheFilter implements Filter {
	
	/*
	 * Map<"/ServletDemo4", byte[]>
	 * 
	 * key:资源的uri,例如.../ServletDemo4
	 * value:该资源的缓存数据(字节数组)
	 */
	private Map<String, byte[]> map = new HashMap<String, byte[]>();

	@Override
	public void init(FilterConfig filterConfig) throws ServletException {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
			throws IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) resp;
		
		//1. 得到用户想访问的资源(uri)
		String uri = request.getRequestURI();
		
		//2. 看Map集合中是否保存了该资源的数据
		byte[] b = map.get(uri);
		
		//3. 如果保存了,则直接取出数据打给浏览器
		if (b != null) {
			response.getOutputStream().write(b);
			return;
		}
		
		//4. 如果没有保存资源对应的数据,则放行,让目标资源执行,这时还须写一个response的包装类,捕获目标资源的输出
		MyResponse myResponse = new MyResponse(response);
		chain.doFilter(request, myResponse);
		byte[] data = myResponse.getBuffer();
		
		//5. 以资源的uri为关键字,把资源对应的数据保存在Map集合中,已备于下次访问
		map.put(uri, data);
		
		//6. 输出数据给浏览器
		response.getOutputStream().write(data);
	}
	
	class MyResponse extends HttpServletResponseWrapper {
		
		//维护一个缓冲流
		private ByteArrayOutputStream bout = new ByteArrayOutputStream();
		private PrintWriter pw;
		
		private HttpServletResponse response;

		public MyResponse(HttpServletResponse response) {
			super(response);
			this.response = response;
		}

		@Override
		public ServletOutputStream getOutputStream() throws IOException {
			// myResponse.getOutputStream().write("写出数据");
			return new MyServletOutputStream(bout);
		}
		
		@Override
		public PrintWriter getWriter() throws IOException {
			/*
			 * myResponse.getWriter().write("java组件多。我个人......");
			 * 调用我的PrintWriter对象写出数据,由于PrintWriter是一个包装流,写出的数据太小的话,它是不会写到底层流bout里面去的,
			 * 而是写到了PrintWriter对象的缓冲里面。
			 * 为了确保数据能够写到底层流bout里面去,应该怎么做呢?
			 * 
			 * return new PrintWriter(bout);
			 */
//			return new PrintWriter(bout);
			
//			pw = new PrintWriter(bout);
			
			//为了解决中文乱码
			pw = new PrintWriter(new OutputStreamWriter(bout, response.getCharacterEncoding()));
			return pw;
		}
		
		//定义一个方法返回bout缓冲区对象里面的数据(得到底层流中的数据)
		public byte[] getBuffer() {
			if (pw != null) {
				pw.close();//确保数据一定会写到底层流bout里面去
			}
			return bout.toByteArray();
		}
		
	}
	
	class MyServletOutputStream extends ServletOutputStream {
		
		private ByteArrayOutputStream bout = null;
		
		public MyServletOutputStream(ByteArrayOutputStream bout) {
			this.bout = bout;
		}

		@Override
		public boolean isReady() {
			// TODO Auto-generated method stub
			return false;
		}

		@Override
		public void setWriteListener(WriteListener listener) {
			// TODO Auto-generated method stub
			
		}

		@Override
		public void write(int b) throws IOException {
			bout.write(b);
		}
		
	}

	@Override
	public void destroy() {
		// TODO Auto-generated method stub
		
	}

}

紧接着我们就要在web.xml中配置Web资源缓存过滤器了。

java函数缓存_Java-Web基础


我们只是对指定的Web资源——ServletDemo4进行过滤。ServletDemo4是一个用来显示分类的Servlet,具体代码是:

package cn.liayun.web.servlet;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

//显示分类的Servlet
@WebServlet("/ServletDemo4")
public class ServletDemo4 extends HttpServlet {
	
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		//查询数据,获取到分类信息
		String data = "分类信息数据";
		//输出分类信息
		response.getWriter().write(data);
		
		System.out.println("ServletDemo4执行了");
	}
	
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doGet(request, response);
	}

}

当有人首次访问ServletDemo4时,确实会查询数据库,获取到分类信息,并显示出分类信息,但当他再次点击刷新按钮时,ServletDemo4就不会从数据库中查询分类数据了,因为分类数据已经缓存在Map集合中了,这样是不是减轻了数据库压力,并提高系统的响应速度了呢?