GAE提供了簡單實用的API和開發(fā)工具,結(jié)合已有的
開發(fā)框架,Java開發(fā)人員可以很容易開發(fā)出自己的業(yè)務(wù)應(yīng)用系統(tǒng)。
本次先介紹頁面部分的性能優(yōu)化技巧,只需要進行簡單的
設(shè)置和少量的編碼,即可獲得不錯的性能提高。后續(xù)的文章
文中提到的技巧已經(jīng)在本博客取得驗證,從后來的統(tǒng)計數(shù)據(jù)中
可以看到,首頁的處理時間從平均400ms減少到了平均26ms,性能提高了15倍!
發(fā)表于:http://hoverblog./blog?key=aglob3ZlcmJsb2dyCwsSBEJsb2cYuRcM
指定GAE的靜態(tài)文件配置
在一般的httpd +
tomcat的架構(gòu)中,客戶端對圖片、css文件以及js文件等靜態(tài)資源文件,會根據(jù)文件的lsat
modified屬性,盡量只會請求一次,只有當(dāng)文件進行了更新之后,才會重新請求新的文件。
但是在GAE里面,如果你不進行靜態(tài)文件的設(shè)置,默認
情況下,是無法享受上面所提的好處的。下面來看看設(shè)置的文件/WEB-INF/appengine-web.xml:
<?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>
進行了上面的設(shè)置之后,你的應(yīng)用可以得到較為明顯的性
能提升。
利用Memcache服務(wù)進行頁面緩存
GAE提供了Memcache服務(wù),可以將經(jīng)常使用到
數(shù)據(jù)暫時存儲在Memcache中,可以大大減少請求的處理時間,提高頁面響應(yīng)速度。下面提供幾個代碼例子,利用Servlet
Filter技術(shù),可以對經(jīng)常訪問的頁面進行緩存操作。
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();
}
}
}
因需要在多處地方訪問Cache,因此這里使用了
Singleton模式,可以在不同的Action中訪問同一個Cache實例。
WebCacheFilter
WebCacheFilter.java
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 content
ByteArrayOutputStream 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() {
}
}
在初始化的時候,調(diào)用
CacheSingleton.init()方法,初始化Memecache的調(diào)用接口。
WebCacheFilter只處理HTTP
GET請求,對后臺數(shù)據(jù)的修改、刪除、新增等操作,應(yīng)該使用HTTP POST方式來提交數(shù)據(jù)。
下面將此Filter所用到的其他輔助類列在下面:
FilterServletOutputStream.java
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);
}
}
GenericResponseWrapper.java
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();
}
}
PageInfo.java
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
在web.xml中,配置
WebCacheFilter,對經(jīng)常訪問的頁面進行緩存。下面是我的博客的配置:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java./xml/ns/javaee"
xmlns:xsi="http://www./2001/XMLSchema-instance"
xsi:schemaLocation="http://java./xml/ns/javaee
http://java./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>
頁面緩存的使用限制
WebCacheFilter會緩存整個頁面的全部元
素,如果頁面中存在用戶相關(guān)的代碼,例如根據(jù)用戶的身份不同而現(xiàn)實不同的內(nèi)容的話,可能會出現(xiàn)不希望出現(xiàn)的后果。
假設(shè)你的頁面中,判斷如果是管理員的話,顯示編輯鏈
接:
jsp文件:
<s:if test="admin">
<a href="edit-blog?key=<s:property value="key"/>">
<s:text name="edit"/>
</a>
</s:if>
如果管理員先訪問了頁面,則緩存中保存的頁面中,就包
含了“編輯”的鏈接。當(dāng)另外一個普通用戶訪問同一個url時,從頁面緩存中獲得了前面管理員所看到的頁面,因為,普通用戶也看到了“編輯”的鏈接。
因此,在利用WebCacheFilter進行緩存的
頁面中,盡量避免太多的動態(tài)屬性顯示。數(shù)據(jù)的編輯、維護工作應(yīng)該在專門的頁面中進行。