GAE提供了簡單實(shí)用的API和開發(fā)工具,結(jié)合已有的開發(fā)框架,Java開發(fā)人員可以很容易開發(fā)出自己的業(yè)務(wù)應(yīng)用系統(tǒng)。 本次先介紹頁面部分的性能優(yōu)化技巧,只需要進(jìn)行簡單的設(shè)置和少量的編碼,即可獲得不錯(cuò)的性能提高。后續(xù)的文章 文中提到的技巧已經(jīng)在本博客取得驗(yàn)證,從后來的統(tǒng)計(jì)數(shù)據(jù)中可以看到,首頁的處理時(shí)間從平均400ms減少到了平均26ms,性能提高了15倍! 發(fā)表于:http://hoverblog.appspot.com/blog?key=aglob3ZlcmJsb2dyCwsSBEJsb2cYuRcM 在一般的httpd +tomcat的架構(gòu)中,客戶端對圖片、css文件以及js文件等靜態(tài)資源文件,會根據(jù)文件的lsatmodified屬性,盡量只會請求一次,只有當(dāng)文件進(jìn)行了更新之后,才會重新請求新的文件。 但是在GAE里面,如果你不進(jìn)行靜態(tài)文件的設(shè)置,默認(rèn)情況下,是無法享受上面所提的好處的。下面來看看設(shè)置的文件/WEB-INF/appengine-web.xml: 進(jìn)行了上面的設(shè)置之后,你的應(yīng)用可以得到較為明顯的性能提升。 GAE提供了Memcache服務(wù),可以將經(jīng)常使用到數(shù)據(jù)暫時(shí)存儲在Memcache中,可以大大減少請求的處理時(shí)間,提高頁面響應(yīng)速度。下面提供幾個(gè)代碼例子,利用ServletFilter技術(shù),可以對經(jīng)常訪問的頁面進(jìn)行緩存操作。 因需要在多處地方訪問Cache,因此這里使用了Singleton模式,可以在不同的Action中訪問同一個(gè)Cache實(shí)例。 WebCacheFilter.java 在初始化的時(shí)候,調(diào)用CacheSingleton.init()方法,初始化Memecache的調(diào)用接口。 WebCacheFilter只處理HTTPGET請求,對后臺數(shù)據(jù)的修改、刪除、新增等操作,應(yīng)該使用HTTP POST方式來提交數(shù)據(jù)。 下面將此Filter所用到的其他輔助類列在下面: FilterServletOutputStream.java GenericResponseWrapper.java PageInfo.java 在web.xml中,配置WebCacheFilter,對經(jīng)常訪問的頁面進(jìn)行緩存。下面是我的博客的配置: WebCacheFilter會緩存整個(gè)頁面的全部元素,如果頁面中存在用戶相關(guān)的代碼,例如根據(jù)用戶的身份不同而現(xiàn)實(shí)不同的內(nèi)容的話,可能會出現(xiàn)不希望出現(xiàn)的后果。 假設(shè)你的頁面中,判斷如果是管理員的話,顯示編輯鏈接: 如果管理員先訪問了頁面,則緩存中保存的頁面中,就包含了“編輯”的鏈接。當(dāng)另外一個(gè)普通用戶訪問同一個(gè)url時(shí),從頁面緩存中獲得了前面管理員所看到的頁面,因?yàn)椋胀ㄓ脩粢部吹搅?#8220;編輯”的鏈接。 因此,在利用WebCacheFilter進(jìn)行緩存的頁面中,盡量避免太多的動態(tài)屬性顯示。數(shù)據(jù)的編輯、維護(hù)工作應(yīng)該在專門的頁面中進(jìn)行。指定GAE的靜態(tài)文件配置
<?xml version="1.0" encoding="utf-8"?><appengine-web-app xmlns="http://appengine.google.com/ns/1.0"><application>yourappid</application><version>1</version><static-files><include path="/**.jpg"/><include path="/**.png"/><include path="/**.gif"/><include path="/**.ico"/><include path="/**.css"/><include path="/**.js"/></static-files></appengine-web-app>
利用Memcache服務(wù)進(jìn)行頁面緩存
CacheSingleton.java
package hover.blog.servlet;import javax.cache.Cache;import javax.cache.CacheException;import javax.cache.CacheFactory;import javax.cache.CacheManager;import javax.servlet.ServletException;import java.util.Map;/*** @author Hover* @version 1.0*/public class CacheSingleton {private static final CacheSingleton instance = new CacheSingleton();private Cache cache;private CacheSingleton() {}public static CacheSingleton getInstance() {return instance;}public void init(Map props) throws ServletException {try {CacheFactory factory = CacheManager.getInstance().getCacheFactory();cache = factory.createCache(props);} catch (CacheException e) {throw new ServletException("cache error: " + e.getMessage(), e);}}public Cache getCache() {return cache;}public void clear() {if (cache != null) {cache.clear();}}}
WebCacheFilter
package hover.blog.servlet;import javax.cache.Cache;import javax.servlet.*;import javax.servlet.http.Cookie;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.BufferedOutputStream;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.util.Collections;import java.util.List;/*** @author Hover* @version 1.0*/@SuppressWarnings("unchecked")public class WebCacheFilter implements Filter {public static final String PAGE_PREFIX = "/page";public void init(FilterConfig config) throws ServletException {CacheSingleton.getInstance().init(Collections.emptyMap());}public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,FilterChain chain) throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest) servletRequest;HttpServletResponse response = (HttpServletResponse) servletResponse;Cache cache = CacheSingleton.getInstance().getCache();if ("post".equalsIgnoreCase(request.getMethod()) || cache == null) {chain.doFilter(servletRequest, servletResponse);} else {String requestPath = request.getRequestURI();String queryString = request.getQueryString();if (queryString != null && queryString.length() > 0) {requestPath += "?" + queryString;}String cachePath = PAGE_PREFIX + requestPath;PageInfo page = null;try {page = (PageInfo) cache.get(cachePath);}catch (Exception e) {// type mis-match}if (page == null) { // on cache contentByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();GenericResponseWrapper wrapper = new GenericResponseWrapper(response, byteArrayOutputStream);chain.doFilter(request, wrapper);wrapper.flush();page = new PageInfo(wrapper.getStatus(), wrapper.getContentType(), wrapper.getHeaders(),wrapper.getCookies(), byteArrayOutputStream.toByteArray());if (page.getStatus() == HttpServletResponse.SC_OK) {cache.put(cachePath, page);}}response.setStatus(page.getStatus());String contentType = page.getContentType();if (contentType != null && contentType.length() > 0) {response.setContentType(contentType);}for (Cookie cookie : (List) page.getCookies()) {response.addCookie(cookie);}for (String[] header : (List) page.getResponseHeaders()) {response.setHeader(header[0], header[1]);}response.setContentLength(page.getBody().length);BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());out.write(page.getBody());out.flush();}}public void destroy() {}}
package hover.blog.servlet;import javax.servlet.ServletOutputStream;import java.io.IOException;import java.io.OutputStream;/*** @author Hover* @version 1.0*/public class FilterServletOutputStream extends ServletOutputStream {private OutputStream stream;public FilterServletOutputStream(final OutputStream stream) {this.stream = stream;}/*** Writes to the stream.*/public void write(final int b) throws IOException {stream.write(b);}/*** Writes to the stream.*/public void write(final byte[] b) throws IOException {stream.write(b);}/*** Writes to the stream.*/public void write(final byte[] b, final int off, final int len) throws IOException {stream.write(b, off, len);}}
package hover.blog.servlet;import javax.servlet.ServletOutputStream;import javax.servlet.http.Cookie;import javax.servlet.http.HttpServletResponse;import javax.servlet.http.HttpServletResponseWrapper;import java.io.*;import java.util.ArrayList;import java.util.Collection;import java.util.List;import java.util.logging.Logger;/*** @author Hover* @version 1.0*/@SuppressWarnings("unchecked")public class GenericResponseWrapper extends HttpServletResponseWrapper implements Serializable {private static final Logger LOG = Logger.getLogger(GenericResponseWrapper.class.getName());private int statusCode = SC_OK;private int contentLength;private String contentType;private final List headers = new ArrayList();private final List cookies = new ArrayList();private ServletOutputStream outstr;private PrintWriter writer;/*** Creates a GenericResponseWrapper*/public GenericResponseWrapper(final HttpServletResponse response, final OutputStream outstr) {super(response);this.outstr = new FilterServletOutputStream(outstr);}/*** Gets the outputstream.*/public ServletOutputStream getOutputStream() {return 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);}/*** Sets the status code for this response.*/public void setStatus(final int code, final String msg) {statusCode = code;LOG.warning("Discarding message because this method is deprecated.");super.setStatus(code);}/*** 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(outstr, getCharacterEncoding()), true);}return writer;}/*** Adds a header.*/public void addHeader(final String name, final String value) {final String[] header = new String[]{name, value};headers.add(header);super.addHeader(name, value);}/*** @see #addHeader*/public void setHeader(final String name, final String value) {addHeader(name, value);}/*** Gets the headers.*/public List getHeaders() {return headers;}/*** Adds a cookie.*/public void addCookie(final Cookie cookie) {cookies.add(cookie);super.addCookie(cookie);}/*** Gets all the cookies.*/public List getCookies() {return cookies;}/*** Flushes buffer and commits response to client.*/public void flushBuffer() throws IOException {flush();super.flushBuffer();}/*** Resets the response.*/public void reset() {super.reset();cookies.clear();headers.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();}}
package hover.blog.servlet;import java.io.Serializable;import java.util.List;/*** @author Hover* @version 1.0*/public class PageInfo implements Serializable {private int status;private String contentType;private List responseHeaders;private List cookies;private byte[] body;public PageInfo() {}public PageInfo(int status, String contentType, List responseHeaders, List cookies, byte[] body) {this.status = status;this.contentType = contentType;this.responseHeaders = responseHeaders;this.cookies = cookies;this.body = body;}public int getStatus() {return status;}public void setStatus(int status) {this.status = status;}public String getContentType() {return contentType;}public void setContentType(String contentType) {this.contentType = contentType;}public List getResponseHeaders() {return responseHeaders;}public void setResponseHeaders(List responseHeaders) {this.responseHeaders = responseHeaders;}public List getCookies() {return cookies;}public void setCookies(List cookies) {this.cookies = cookies;}public byte[] getBody() {return body;}public void setBody(byte[] body) {this.body = body;}}
在web.xml中配置WebCacheFilter
<?xml version="1.0" encoding="UTF-8"?><web-app xmlns="http://java.sun.com/xml/ns/javaee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://java.sun.com/xml/ns/javaeehttp://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"version="2.5"><filter><filter-name>webCache</filter-name><filter-class>hover.blog.servlet.WebCacheFilter</filter-class></filter><filter-mapping><filter-name>webCache</filter-name><url-pattern>/main</url-pattern></filter-mapping><filter-mapping><filter-name>webCache</filter-name><url-pattern>/blog</url-pattern></filter-mapping><filter-mapping><filter-name>webCache</filter-name><url-pattern>/category</url-pattern></filter-mapping></web-app>
頁面緩存的使用限制
jsp文件:<s:if test="admin"><a href="edit-blog?key=<s:property value="key"/>"><s:text name="edit"/></a></s:if>