Created new caching mechanism to easily add cache support

原文地址:http://www.jroller.com/RickHigh/entry/created_new_caching_mechanism_to

Created new caching mechanism to easily add cache support without writing a lot of additional code.

You just annotated the methods that you want to cache with:



@CacheManagement(durationInSeconds=1000, type=CacheType.IN_MEMORY)



Based on the cache type, the system picks the right cache service to use (in memory (google collection API), local (ehcache) and distributed (memcached).

Wrote a CachingAspect that inspects a CacheManagement annotation and then decides which of three types of caches to use (in-memory no config cache, local cache [ehcache], distributed cache [memcached]).

Annotation:


public enum CacheType {
LOCAL, IN_MEMORY, DISTRIBUTED;
}


@Configurable
public aspect CachingAspect {

protected final Logger log = Logger.getLogger(getClass());

@Autowired
@Qualifier("distributedCacheService")
private CacheService distributedCacheService;

@Autowired
@Qualifier("localCacheService")
private CacheService localCacheService;

@Autowired
@Qualifier("inMemoryCacheService")
private CacheService inMemoryCacheService;

@Value("#{ systemProperties['ourappprefix.cache.local']=='yes' ? true : false}")
private boolean useLocalCacheMechanism = false;

pointcut cacheStuff(): execution(@CacheManagement public * com.somecompany.someproject..*.*(..));

Object around() : cacheStuff() {
Signature signature = thisJoinPoint.getSignature();
String methodName = signature.getName();
String objectName = signature.getDeclaringType().getSimpleName();

if (log.isDebugEnabled()) {
log.debug(String.format("CachingAspect in method %s of method %s",
methodName, objectName));
}

if (!useLocalCacheMechanism) {
log.debug("Not using local cache");
return proceed();
}

String cacheName = new StringBuilder(methodName.length()
+ objectName.length() + 1).append(objectName).append(':')
.append(methodName).toString();

MethodSignature msig = (MethodSignature) thisJoinPoint.getSignature();
CacheManagement annotation = msig.getMethod().getAnnotation(
CacheManagement.class);
CacheType type = annotation.type();
int durationInSeconds = annotation.durationInSeconds();

if (log.isDebugEnabled()) {
String msg = String.format(
"CachingAspect %s :: type = %s, duration in seconds = %s",
cacheName, type, durationInSeconds);
log.debug(msg);
}

Object[] args = thisJoinPoint.getArgs();
String[] params = msig.getParameterNames();

StringBuilder keyBuilder = new StringBuilder(64);
int index = 0;
for (Object arg : args) {
if (arg instanceof HttpServletRequest
|| arg instanceof HttpServletResponse) {
continue;
}
if (arg.getClass().getName().startsWith("org.springframework")) {
continue;
}
keyBuilder.append(params[index]).append(':').append(arg)
.append(',');
index++;
}
String key = keyBuilder.toString();

if (log.isDebugEnabled()) {
String msg = String.format("CachingAspect %s :: key = %s",
cacheName, key);
log.debug(msg);
}

CacheService service = getCacheService(type);
Serializable item = service.getItem(cacheName, key);
if (item == null) {
log.info(String.format("Cache Miss with cache %s on key %s",
cacheName, key));
item = (Serializable) proceed();

service.putItem(cacheName, key, item, durationInSeconds, annotation.initialSize());
} else {
log.info(String.format("Cache Hit with cache %s on key %s",
cacheName, key));
}

return item;
}

private CacheService getCacheService(CacheType type) {
CacheService service = null;

switch (type) {
case DISTRIBUTED:
service = this.distributedCacheService;
break;

case IN_MEMORY:
service = this.inMemoryCacheService;
break;
case LOCAL:
service = this.localCacheService;
break;

default:
break;
}
return service;
}
}


The aspect is longer than I would like. I'd like to refactor some of the above and put it into a Java class.


Then we inject these three service implementations:


import java.io.Serializable;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

import org.apache.log4j.Logger;
import org.springframework.stereotype.Component;

import com.google.common.collect.MapMaker;


@Component("inMemoryCacheService")
public class InMemoryCacheService implements CacheService {

private Map<String, Map<String, Serializable>> masterMap = new ConcurrentHashMap<String, Map<String, Serializable>>();

protected final Logger log = Logger.getLogger(getClass());

@Override
public Serializable getItem(String cacheName, String key) {
log.info(String.format("Getting key %s out of cache %s", key, cacheName));

Map<String, Serializable> map = masterMap.get(cacheName);
if (map==null) {
return null;
}
return map.get(key);
}


@Override
public void putItem(String cacheName, String key, Serializable item, int duration, int initialSize) {
log.info(String.format("Putting key into cache key %s duration %s", key, duration));
Map<String, Serializable> map = masterMap.get(cacheName);
if (map==null) {
map = new MapMaker()
.concurrencyLevel(64)
.softValues().initialCapacity(initialSize)
.expiration(duration, TimeUnit.SECONDS).makeMap();
masterMap.put(cacheName, map);
}
map.put(key, item);
}
}


The in-memory one is nice because it does not require any config. It is great for small caches.

Notice the above uses google collections API.

Here is an implementation that uses ehcache

import java.io.Serializable;
import java.net.URL;

import org.apache.log4j.Logger;
import org.springframework.stereotype.Component;

import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;


@Component("localCacheService")
public class EhcacheCacheService implements CacheService {

private CacheManager manager;
protected final Logger log = Logger.getLogger(getClass());


public EhcacheCacheService() {
URL url = getClass().getResource("/META-INF/ehcache-someappprefix.xml");
manager = new CacheManager(url);
}

@Override
public Serializable getItem(String cacheName, String key) {
if (log.isInfoEnabled()) log.info(String.format("Getting key %s out of cache %s", key, cacheName));
Element element = manager.getCache(cacheName).get(key);
return element== null ? null : element.getValue();
}

@Override
public void putItem(String cacheName, String key, Serializable item,
int duration, int initialSize) {
if (log.isInfoEnabled()) log.info(String.format("Putting key %s into cache %s", key, cacheName));
manager.getCache(cacheName).put(new Element(key, item));
}

}

It is nice because we can have a larger cache which is more configurable, a disk based cache and even a JGroup backed invalidation cache. The downside is that it has to be configured.

Then last but not least a distributed memcached instance.

import java.io.Serializable;

import net.spy.memcached.AddrUtil;
import net.spy.memcached.BinaryConnectionFactory;
import net.spy.memcached.MemcachedClient;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component("distributedCacheService")
public class MemcachedCacheService implements CacheService {

protected final Logger log = Logger.getLogger(getClass());

@Value("#{ systemProperties['someappprefix.cache.memcached.servers']}")
private String addresses;

private MemcachedClient client;



public MemcachedCacheService() throws Exception {
if (addresses == null || "".equals(addresses.trim())) {
log.warn("Memcached servers not configured using localhost");
addresses = "localhost:11211";
}
client = new MemcachedClient(new BinaryConnectionFactory(),
AddrUtil.getAddresses(this.addresses));
}

@Override
public Serializable getItem(String cacheName, String key) {
if (log.isInfoEnabled()) log.info(String.format("Getting key %s out of cache %s", key, cacheName));
String realKey = realKeyGen(cacheName, key);
return (Serializable) client.get(realKey);
}

@Override
public void putItem(String cacheName, String key, Serializable item,
int duration, int initialSize) {
if (log.isInfoEnabled()) log.info(String.format("Putting key %s into cache %s", key, cacheName));
String realKey = realKeyGen(cacheName, key);
client.add(realKey, duration, item);

}

private String realKeyGen(String cacheName, String key) {
String realKey = new StringBuilder(cacheName.length() + key.length() +1).append(cacheName).append(':').append(key).toString();
return realKey;
}

}


This is nice because you have an extremely large cache that is managed by memcached (a distributed hash map where the first hash gives you the server where there is a server which is essentially a large distributed hash map).


I have to order the Aspects so that the caching happens after security authentication as follows:

@Configurable
public aspect AuthenticationAspect {

declare precedence: SecureAllJSONEndpointsAspect, AuthenticationAspect, JsonFilterAspect, CachingAspect, Moved301IfTrailingSlashFoundAspect;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值