|
通常为了减轻数据库的压力和提升系统性能我们会将数据缓存至memcached中,传统的写法冗余而且高度耦合,本文试图通过Annotation+AOP无侵入式集成memcached。效果如下:
@Override
@ReadCacheAnnotation(clientName = CacheClientEnum.commodityclient, assignCacheKey = "${param0}", localExpire = 120)
public CommodityStyleVO getCommodityCacheModel(String commodityNo) {
return this.commodityMapper.getCommodityCacheModel(commodityNo);
}@Override
@UpdateCacheAnnotation(clientName = CacheClientEnum.commodityclient, assignCacheKey = "${param0}")
public CommodityStyleVO updateCommodityCacheModel(String commodityNo) {
return this.commodityMapper.updateCommodityCacheModel(commodityNo);
}仅仅通过一个注解就完美集成了memcached,而且如此透明灵活!!!
不要高兴太早,这里有一个很重要的问题需要解决:我们都知道memcached通过key-value值对的形式来存储数据的,如何指定key是集成memcached关键所在,而注解中(annotation)根本无法根据当前的入参植入一个动态key,怎么办?借鉴freemarker一些思想我们可以先在注解中定义表达式模板,然后利用java反射机制来产生动态key,这个完全可以做到,不是吗?
既然要定义表达式模板,我们需要制定表达式规则,举个例子:
@ReadCacheAnnotation(clientName = CacheClientEnum.cmsclient,
assignCacheKey = "brandslist+${param1.header(appversion)}+${param0.brand}")
public String brandSearch(CatBrandSearchVO searchVo,HttpServletRequest request){
return null;
}上面的assignCacheKey表达式将被解析成:
assignCacheKey = "brandslist"+request.getHeader("appversion")+searchVo.getBrand()
看到到这里,我想大家都明白Annotation+AOP无侵入式集成memcached的原理了吧。
在spring中使用Annotation+AOP进行增强仅需少量的代码即可实现,关于注解和AOP相关的基础知识这里不展开讨论。
我们这里只是简单贴出核心代码段,以便有更清晰的认识:
(1)ReadCacheAdvice - 环绕增强 ReadCacheAnnotation
@Aspect
@Component
public class ReadCacheAdvice extends CacheBase {
@Pointcut("@annotation(com.yougou.mobilemall.framework.cache.ReadCacheAnnotation)")
public void methodCachePointcut() {
}
@Around("methodCachePointcut()")
public Object methodCacheHold(final ProceedingJoinPoint joinPoint) throws Throwable {
ReadCacheAnnotation annotation =null;
IMemcachedCache memcachedCache = null;
Object result = null;
String cacheKey;
try {
// 获取目标方法
final Method method = this.getMethod(joinPoint);
annotation = method.getAnnotation(ReadCacheAnnotation.class);
memcachedCache = this.cacheManager.getCache(annotation.clientName().name());
// 是否启用本地缓存
cacheKey = this.getCacheKey(joinPoint.getArgs(), annotation.assignCacheKey());
if (annotation.localExpire() > 0) {
result = memcachedCache.get(cacheKey, annotation.localExpire());
} else {
result = memcachedCache.get(cacheKey);
}
if (result != null) {
return result;
}
} catch (Throwable ex) {
logger.error("Caching on " + joinPoint.toShortString() + " aborted due to an error.", ex);
return joinPoint.proceed();
}
// 缓存命中失败,执行方法从DB获取数据
result = joinPoint.proceed();
try {
// 将数据缓存到缓存服务器
if (result != null) {
if(annotation.remoteExpire()>0){
memcachedCache.put(cacheKey, result,annotation.remoteExpire());
}else{
memcachedCache.put(cacheKey, result);
}
}
} catch (Throwable ex) {
logger.error("Caching on " + joinPoint.toShortString() + " aborted due to an error.", ex);
}
return result;
}
}
(2)getCacheKey() - 利用表达式模板和java反射机制产生动态key
/**
* @param method
* @param assignCacheKey
* @return
* @throws IllegalArgumentException
*/
protected String getCacheKey(Object[] args, String cacheKeyExpression) throws NoSuchMethodException,
IllegalArgumentException {
if (cacheKeyExpression == null || cacheKeyExpression.trim().equals("")) {
logger.error("This assignCacheKey is not valid on a method.");
throw new IllegalArgumentException("This assignCacheKey is not valid on a method.");
}
// 解析assignCacheKey表达式,格式如: ${param0}+ hello + ${param1.name(key)}
StringBuffer sbCacheKey = new StringBuffer(128);
String[] params = cacheKeyExpression.replaceAll(" ", "").split("[+]");
for (int i = 0; i < params.length; i++) {
if (params == null || "".equals(params.trim())) {
continue;
}
Pattern pattern = Pattern.compile("^([$][{]).*[}]$");
Matcher matcher = pattern.matcher(params);
if (matcher.find()) {
// 根据参数获取参数值:${coupon.name}
String param = params.substring(2, params.length() - 1);
sbCacheKey.append(this.getArguValue(args, param));
} else {
sbCacheKey.append(params);
}
}
return Md5Encrypt.md5(sbCacheKey.toString());
}
/**
* 根据参数名获取参数值
*
* @param args
* @param param
* @return
* @throws IllegalArgumentException
* @throws NoSuchMethodException
*/
private String getArguValue(Object[] args, String params) throws NoSuchMethodException, IllegalArgumentException {
String[] arrParam = params.split("[.]");
if (arrParam[0] == null || "".equals(arrParam[0])) {
logger.error("This assignCacheKey is not valid on a method.");
new IllegalArgumentException("This assignCacheKey is not valid on a method.");
}
// 方法入参列表中匹配当前参数对象
int index = Integer.parseInt(arrParam[0].replaceAll("param", ""));
Object currObject = args[index];
try {
for (int i = 1; i < arrParam.length; i++) {
// 根据参数获取参数值:name(key)
String param=arrParam;
Pattern pattern = Pattern.compile("([(]).*[)]$");
Matcher matcher = pattern.matcher(param);
if (matcher.find()) {
String paramName = param.substring(0, param.indexOf('('));
String paramKey = param.substring(param.indexOf('(')+1, param.length() - 1);
currObject = BeanUtils.getMappedProperty(currObject, paramName, paramKey);
} else {
currObject = BeanUtils.getProperty(currObject, param);
}
}
} catch (Exception ex) {
logger.error("This assignCacheKey is not valid on a method.");
new IllegalArgumentException("This assignCacheKey is not valid on a method.");
}
return currObject!=null? currObject.toString():"";
} 我们的key是完全由使用者来决定的,这很大程度给予了使用者很大的自由性,这一点上我们甚至优于simple-spring-memcached,当然这也有一些弊端,我们无法在编译阶段对表达式模板进行验证,不熟悉表达式规则很容易出错。
版权声明:本文为博主原创文章,未经博主允许不得转载。 |
|