AOP+memcached无侵入式集成

来源:互联网 发布:刘清华踮脚看航母 知乎 编辑:程序博客网 时间:2024/06/06 16:51

        通常为了减轻数据库的压力和提升系统性能我们会将数据缓存至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@Componentpublic 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[i] == null || "".equals(params[i].trim())) {continue;}Pattern pattern = Pattern.compile("^([$][{]).*[}]$");Matcher matcher = pattern.matcher(params[i]);if (matcher.find()) {// 根据参数获取参数值:${coupon.name}String param = params[i].substring(2, params[i].length() - 1);sbCacheKey.append(this.getArguValue(args, param));} else {sbCacheKey.append(params[i]);}}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[i];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,当然这也有一些弊端,我们无法在编译阶段对表达式模板进行验证,不熟悉表达式规则很容易出错。

原创粉丝点击