AOP+memcached无侵入式集成

1 篇文章 0 订阅
1 篇文章 0 订阅

        通常为了减轻数据库的压力和提升系统性能我们会将数据缓存至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[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,当然这也有一些弊端,我们无法在编译阶段对表达式模板进行验证,不熟悉表达式规则很容易出错。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值