零. 引言
为什么使用缓存? 当网站流量逐渐增大, 数据库 IO 将比较早出现瓶颈, 而使用缓存, 可以使数据库瓶颈晚点到来, 从而提升网站性能。 Java Web 项目如何使用缓存? 缓存首先是要获取返回给页面的内容, 然后写入缓存(MemCached、 Redis等缓存), 本文使用 MemCached 作为示例。
二. 代码示例
要截获页面返回的内容,整体的思路是先把原始返回的页面内容写入到一个字符Writer,然后再组装成字符串并进行分析,最后再返回给客户端。代码如下:
用于包装 ServletOutputStream:
public class FilterServletOutputStream extends ServletOutputStream{ // 不需要太多方法, 可以用 OutputStream 代替 private DataOutputStream stream; public FilterServletOutputStream(OutputStream output) { stream = new DataOutputStream(output); } public void write(int b) throws IOException { stream.write(b); } public void write(byte[] b) throws IOException { stream.write(b); } public void write(byte[] b, int off, int len) throws IOException { stream.write(b, off, len); } }
获取内容包装类:
import java.io.*; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.TreeMap; import javax.servlet.ServletOutputStream; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponseWrapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Created by zhuyb on 15/10/21. */ public class MyHttpServletResponseWrapper extends HttpServletResponseWrapper implements Serializable{ private static final long serialVersionUID = -7207188787151521963L; private static final Logger LOG = LoggerFactory.getLogger(MyHttpServletResponseWrapper.class); private int statusCode = SC_OK; private int contentLength; private String contentType; private final Map<String, List<Serializable>> headersMap = new TreeMap<String, List<Serializable>>(String.CASE_INSENSITIVE_ORDER); private final List cookies = new ArrayList(); private ByteArrayOutputStream outstr; private PrintWriter writer; private boolean disableFlushBuffer = true; /** * Creates a GenericResponseWrapper */ public MyHttpServletResponseWrapper(final HttpServletResponse response) { super(response); // 输出 HTML 结果的地方 this.outstr = new ByteArrayOutputStream(); } // 获取数据 public byte[] getData() { if (writer != null) writer.flush(); return outstr.toByteArray(); } /** * Gets the outputstream. */ public ServletOutputStream getOutputStream() { return new FilterServletOutputStream(outstr); } /** * Sets the status code for this response. */ public void setStatus(final int code) { statusCode = code; super.setStatus(code); } /** * Send the error. If the response is not ok, most of the logic is bypassed and the error is sent raw * Also, the content is not cached. * * @param i the status code * @param string the error message * @throws IOException */ public void sendError(int i, String string) throws IOException { statusCode = i; super.sendError(i, string); } /** * Send the error. If the response is not ok, most of the logic is bypassed and the error is sent raw * Also, the content is not cached. * * @param i the status code * @throws IOException */ public void sendError(int i) throws IOException { statusCode = i; super.sendError(i); } /** * Send the redirect. If the response is not ok, most of the logic is bypassed and the error is sent raw. * Also, the content is not cached. * * @param string the URL to redirect to * @throws IOException */ public void sendRedirect(String string) throws IOException { statusCode = HttpServletResponse.SC_MOVED_TEMPORARILY; super.sendRedirect(string); } /** * Returns the status code for this response. */ public int getStatus() { return statusCode; } /** * Sets the content length. */ public void setContentLength(final int length) { this.contentLength = length; super.setContentLength(length); } /** * Gets the content length. */ public int getContentLength() { return contentLength; } /** * Sets the content type. */ public void setContentType(final String type) { this.contentType = type; super.setContentType(type); } /** * Gets the content type. */ public String getContentType() { return contentType; } /** * Gets the print writer. 最终使用输出流写回浏览器 */ public PrintWriter getWriter() throws IOException { if (writer == null) { writer = new PrintWriter(new OutputStreamWriter(getOutputStream(), getCharacterEncoding()), true); } return writer; } /** * @see javax.servlet.http.HttpServletResponseWrapper#addHeader(java.lang.String, java.lang.String) */ @Override public void addHeader(String name, String value) { List<Serializable> values = this.headersMap.get(name); if (values == null) { values = new LinkedList<Serializable>(); this.headersMap.put(name, values); } values.add(value); super.addHeader(name, value); } /** * @see javax.servlet.http.HttpServletResponseWrapper#setHeader(java.lang.String, java.lang.String) */ @Override public void setHeader(String name, String value) { final LinkedList<Serializable> values = new LinkedList<Serializable>(); values.add(value); this.headersMap.put(name, values); super.setHeader(name, value); } /** * @see javax.servlet.http.HttpServletResponseWrapper#addDateHeader(java.lang.String, long) */ @Override public void addDateHeader(String name, long date) { List<Serializable> values = this.headersMap.get(name); if (values == null) { values = new LinkedList<Serializable>(); this.headersMap.put(name, values); } values.add(date); super.addDateHeader(name, date); } /** * @see javax.servlet.http.HttpServletResponseWrapper#setDateHeader(java.lang.String, long) */ @Override public void setDateHeader(String name, long date) { final LinkedList<Serializable> values = new LinkedList<Serializable>(); values.add(date); this.headersMap.put(name, values); super.setDateHeader(name, date); } /** * @see javax.servlet.http.HttpServletResponseWrapper#addIntHeader(java.lang.String, int) */ @Override public void addIntHeader(String name, int value) { List<Serializable> values = this.headersMap.get(name); if (values == null) { values = new LinkedList<Serializable>(); this.headersMap.put(name, values); } values.add(value); super.addIntHeader(name, value); } /** * @see javax.servlet.http.HttpServletResponseWrapper#setIntHeader(java.lang.String, int) */ @Override public void setIntHeader(String name, int value) { final LinkedList<Serializable> values = new LinkedList<Serializable>(); values.add(value); this.headersMap.put(name, values); super.setIntHeader(name, value); } /** * Adds a cookie. */ public void addCookie(final Cookie cookie) { cookies.add(cookie); super.addCookie(cookie); } /** * Gets all the cookies. */ public Collection getCookies() { return cookies; } /** * Flushes buffer and commits response to client. */ public void flushBuffer() throws IOException { flush(); // doing this might leads to response already committed exception // when the PageInfo has not yet built but the buffer already flushed // Happens in Weblogic when a servlet forward to a JSP page and the forward // method trigger a flush before it forwarded to the JSP // disableFlushBuffer for that purpose is 'true' by default // EHC-447 if (!disableFlushBuffer) { super.flushBuffer(); } } /** * Resets the response. */ public void reset() { super.reset(); cookies.clear(); headersMap.clear(); statusCode = SC_OK; contentType = null; contentLength = 0; } /** * Resets the buffers. */ public void resetBuffer() { super.resetBuffer(); } /** * Flushes all the streams for this response. */ public void flush() throws IOException { if (writer != null) { writer.flush(); } outstr.flush(); } /** * Is the wrapped reponse's buffer flushing disabled? * @return true if the wrapped reponse's buffer flushing disabled */ public boolean isDisableFlushBuffer() { return disableFlushBuffer; } /** * Set if the wrapped reponse's buffer flushing should be disabled. * @param disableFlushBuffer true if the wrapped reponse's buffer flushing should be disabled */ public void setDisableFlushBuffer(boolean disableFlushBuffer) { this.disableFlushBuffer = disableFlushBuffer; } }
缓存过滤器:
public class CacheFilter implements Filter { public void destroy() { } public void init(FilterConfig filterConfig) throws ServletException { } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse res = (HttpServletResponse) response; String fullUrl = req.getRequestURL().toString(); // 带上 debug 参数不使用缓存, 方便后续开发调试 if (req.getParameter("debug") != null) { cacheble = false; MemcachedCacheManager.getCache("someproject_page_cache").put(key, ""); } String key = fullUrl.replaceAll("(\\?|&)debug=1", "").hashCode(); // 如果页面路径包含 wenniuwuren 则缓存 boolean cacheble = “http://blog.csdn.net/wenniuwuren”.contains(“wenniuwuren"); MemCached pageCache = MemcachedCacheManager.getCache("jobmd_page_cache"); if (cacheble) { if (pageCache != null) { String html = (String) pageCache.get(key); if (StringUtils.isNotBlank(html)) { res.getWriter().write(html); return; } // 使用我们自定义的响应包装器来包装原始的 ServletResponse MyHttpServletResponseWrapper myHttpServletResponseWrapper = new MyHttpServletResponseWrapper(res); // 注意此处传入的是 myHttpServletResponseWrapper, 而不是 response 参数, 这样才能获取页面返回内容 chain.doFilter(req, myHttpServletResponseWrapper); if (myHttpServletResponseWrapper.getStatus() == 0 || myHttpServletResponseWrapper.getStatus() == 200) { if (myHttpServletResponseWrapper.getData().length > 0) { String data = new String(myHttpServletResponseWrapper.getData()); // 当然在这之前可以进行订制化处理数据, 然后返回浏览器。 页面装饰框架 sitemesh, 就是在这对 HTML 装饰处理后返回页面的 // 写到浏览器 res.getWriter().write(data); // 放入缓存, 缓存 7200 秒 pageCache.put(key, data, 7200); return; } else { // 没数据则, 缓存清空 pageCache.put(key, ""); chain.doFilter(request, response); } } } else { // 没缓存则继续执行 chain.doFilter(request, response); } } else { // 不缓存页面的处理流程 if (fullUrl.contains("debug=1")) { pageCache.remove(key); } chain.doFilter(request, response); } } }
web.xml 配置过滤器:
<filter> <filter-name>cache</filter-name> <filter-class>com.wenniuwuren.CacheFilter</filter-class> <init-param> <param-name>cachePath</param-name> <param-value>/var/cache/project</param-value> </init-param> </filter> <filter-mapping> <filter-name>cache</filter-name> <url-pattern>*.*</url-pattern> </filter-mapping>
获取结果如下图: outstr 得到了页面返回的 HTML 代码:
版权声明:本文为博主原创文章,未经博主允许不得转载。
时间: 2024-10-26 08:16:16