1.play framework中Cache实现默认提供了Ehcache和Memcache两种实现。Ehcache是单节点的,而Memcached是
可以集群的。涉及到的类很少,具体如下:
2.CacheImpl 实现接口
定义了从缓存中增删改查的各种接口方法:
public interface CacheImpl {
public void add(String key, Object value, int expiration);
public boolean safeAdd(String key, Object value, int expiration);
public void set(String key, Object value, int expiration);
public boolean safeSet(String key, Object value, int expiration);
public void replace(String key, Object value, int expiration);
public boolean safeReplace(String key, Object value, int expiration);
public Object get(String key);
public Map<String, Object> get(String[] keys);
public long incr(String key, int by);
public long decr(String key, int by);
public void clear();
public void delete(String key);
public boolean safeDelete(String key);
public void stop();
}
3.Cache 代理类
作用:play启动时会调用Cache.init方法,停止时调用Cache.stop方法。init方法中会根据配置文件决定使用Ehcache还是Memcache
/**
* Initialize the cache system.
*/
public static void init() {
if(forcedCacheImpl != null) {
cacheImpl = forcedCacheImpl;
return;
}
if (Play.configuration.getProperty("memcached", "disabled").equals("enabled")) {
try {
//使用Memcache
cacheImpl = MemcachedImpl.getInstance(true);
Logger.info("Connected to memcached");
} catch (Exception e) {
Logger.error(e, "Error while connecting to memcached");
Logger.warn("Fallback to local cache");
//使用Ehcache
cacheImpl = EhCacheImpl.newInstance();
}
} else {
cacheImpl = EhCacheImpl.newInstance();
}
}
stop方法调用具体实现类(Ehcache或者Memcache)的stop方法来关闭资源
/**
* Stop the cache system.
*/
public static void stop() {
cacheImpl.stop();
}
4.Ehcache 实现类
使用单例模式(不太严谨,因为实在启动的时候调用,不会出现并发的情况)来初始化Ehcache对象并提够服务。
构造方法如下:
构造方法如下:
private static EhCacheImpl uniqueInstance;
CacheManager cacheManager;
net.sf.ehcache.Cache cache;
private static final String cacheName = "play";
private EhCacheImpl() {
this.cacheManager = CacheManager.create();
this.cacheManager.addCache(cacheName);
this.cache = cacheManager.getCache(cacheName);
}
5.Memcache 实现类
同样使用单例模式(不太严谨,因为实在启动的时候调用,不会出现并发的情况)来初始化Memcache对象并提够服务。
单例方法如下:里面有一段逻辑不理解,期待大牛指点,嘿嘿。
public static MemcachedImpl getInstance(boolean forceClientInit) throws IOException {
if (uniqueInstance == null) {
uniqueInstance = new MemcachedImpl();
} else if (forceClientInit) {
//这部分不理解,期待大牛指点,嘿嘿。
// When you stop the client, it sets the interrupted state of this thread to true. If you try to reinit it with the same thread in this state,
// Memcached client errors out. So a simple call to interrupted() will reset this flag
Thread.interrupted();
uniqueInstance.initClient();
}
return uniqueInstance;
}
initClient方法如下:只有主机、用户名、密码配置项,配置项较少(没有超时时间,连接池等配置项),难于精确控制。
public void initClient() throws IOException {
System.setProperty("net.spy.log.LoggerImpl", "net.spy.memcached.compat.log.Log4JLogger");
List<InetSocketAddress> addrs;
if (Play.configuration.containsKey("memcached.host")) {
//单节点
addrs = AddrUtil.getAddresses(Play.configuration.getProperty("memcached.host"));
} else if (Play.configuration.containsKey("memcached.1.host")) {
int nb = 1;
String addresses = "";
//集群配置
while (Play.configuration.containsKey("memcached." + nb + ".host")) {
addresses += Play.configuration.get("memcached." + nb + ".host") + " ";
nb++;
}
addrs = AddrUtil.getAddresses(addresses);
} else {
throw new ConfigurationException("Bad configuration for memcached: missing host(s)");
}
if (Play.configuration.containsKey("memcached.user")) {
//用户名
String memcacheUser = Play.configuration.getProperty("memcached.user");
//密码
String memcachePassword = Play.configuration.getProperty("memcached.password");
if (memcachePassword == null) {
throw new ConfigurationException("Bad configuration for memcached: missing password");
}
// Use plain SASL to connect to memcached
AuthDescriptor ad = new AuthDescriptor(new String[]{"PLAIN"},
new PlainCallbackHandler(memcacheUser, memcachePassword));
ConnectionFactory cf = new ConnectionFactoryBuilder()
.setProtocol(ConnectionFactoryBuilder.Protocol.BINARY)
.setAuthDescriptor(ad)
.build();
client = new MemcachedClient(cf, addrs);
} else {
client = new MemcachedClient(addrs);
}
}
6.CacheFor 注解类
/**
* Cache an action's result.
*
* <p>If a time is not specified, the results will be cached for 1 hour by default.
*
* <p>Example: <code>@CacheFor("1h")</code>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface CacheFor {
String value() default "1h";
String id() default "";
}
在controller类的方法上使用。例如:@CacheFor("1h")。当用浏览器访问这个方法的url时,play会从Cache的缓存中查找,如果没有,就会把result放到缓存中。如果一小时内在收到同样的url请求,就会直接从Cache中获取并直接返回
(根据代码推测而来,目前还在学习中,有误请指教,谢谢)。
以下只复制了部分代码:
//判断必须是GET或者HEAD请求,并且被CacheFor注解的方法
// Check the cache (only for GET or HEAD)
if ((request.method.equals("GET") || request.method.equals("HEAD")) && actionMethod.isAnnotationPresent(CacheFor.class)) {
cacheKey = actionMethod.getAnnotation(CacheFor.class).id();
if ("".equals(cacheKey)) {
cacheKey = "urlcache:" + request.url + request.querystring;
}
actionResult = (Result) play.cache.Cache.get(cacheKey);
}
//如果缓存中不存在,添加结果到缓存中并返回
if (actionResult == null) {
ControllerInstrumentation.initActionCall();
try {
inferResult(invokeControllerMethod(actionMethod));
} catch(Result result) {
actionResult = result;
// Cache it if needed
if (cacheKey != null) {
play.cache.Cache.set(cacheKey, actionResult, actionMethod.getAnnotation(CacheFor.class).value());
}
} catch (InvocationTargetException ex) {
7.小结
play中的Cache实现相对简单(简单也有还未明白的地方),大概逻辑应该清楚了,具体细节有很多还未明白,求指导。