主题:
这份代码是开发中常见的代码,查询数据库某个主表的数据,为了提高性能,做一次缓存,每次调用时先拿缓存数据,有则直接返回,没有才向数据库查数据,降低数据库压力。

public Merchant loadCachedMerchant(String merchantId) {
String key = this.createCacheKey(merchantId);
Merchant merchant = (Merchant) this.memCachedClient.get(key);// 先看缓存
if (merchant == null) {
merchant = this.merchantDao.searchMerchantByMerchantId1(merchantId);//候查数据库
if (merchant != null) {
merchant = extraCacheMerchant(merchant);
this.memCachedClient.put(key, merchant, 5 * 60);
}
} return merchant;
}

然而一般我门在项目初始或在发展的过程中,并不能很好的规划出哪些业务时需要缓存的,而我们发现这个先取缓存再数据库的逻辑是共通的,那么我们就想到了面向切面开发来解决这个问题也是很合适的。

构想下,可以想到我们可以利用注解,对那些需要有这段缓存逻辑的方法单独提供一个注解,这样我们就能找到这些切面,也就是有这个特定注解的方法,比如叫@ServiceCache

这个@ServiceCache注解需要的参数有缓存key的组成方式,以及有效时间。

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ServiceCache {
int value() default -1; int expire() default 60; ServiceCache.Key key() default ServiceCache.Key.JSON; String[] includeKeys() default {}; boolean sync() default false; boolean nullPattern() default true; public static enum Key {
JSON,
TO_STRING; private Key() {
}
}
}

这里又加了一个sync参数用来控制同步执行,这个同步的参数是用来解决什么问题的呢?我们都明白这里使用缓存的方式是为了解决对数据库的频繁调用的问题,这些并发的调用可能导致数据库的压力,那么在我们的逻辑中:先检查缓存,在没有的情况下会去数据库找,而有一种极端的情况是,当大量请求并发时,到这个判断逻辑检查缓存同时发现没有,也就是数据库的数据尚未放入缓存,此时这些并发都会去数据库找,那么数据库依然有风险出现并发调用。

@Aspect
public class ServiceCacheAnnotationAspect {
private static Logger log = LoggerFactory.getLogger(ServiceCacheAnnotationAspect.class);
private ICache serviceCache;
private Object syncLock = new Object(); public ServiceCacheAnnotationAspect() {
}
/**
* 切面匹配为class使用了@Service 或 @Repository 并且方法使用了自定义的@ServiceCache
**/
@Around("(@within(org.springframework.stereotype.Service)||
    @within(org.springframework.stereotype.Repository))&&
    @annotation(com.xiaoka.freework.cache.annotation.ServiceCache)")
private Object cacheProcess(ProceedingJoinPoint jp) throws Throwable {
Class targetClz = jp.getTarget().getClass();
String methodName = jp.getSignature().getName();
if(!(jp.getSignature() instanceof MethodSignature)) {
log.warn("该方法接口无法启用缓存功能: {}", jp.getSignature().toLongString());
return jp.proceed();
} else {
MethodSignature methodSign = (MethodSignature)jp.getSignature();
ServiceCache sc = ServiceCacheUtils.single().findServiceCache(targetClz, methodSign.getMethod());
if(sc == null) {
return jp.proceed();
} else {
int expire = sc.value() >= 0?sc.value():sc.expire();//获取定义的过期时间
if(expire > 0) {
String cacheKey = ServiceCacheUtils.single().buildCacheKey(sc, targetClz, methodName, jp.getArgs());
Object rval = null;
if(sc.sync()) {
Object var9 = this.syncLock;
synchronized(this.syncLock) {// 这里做了同步
rval = this.cacheInvoke(sc, jp, cacheKey, expire);//这里实现我们核心逻辑
}
} else {
rval = this.cacheInvoke(sc, jp, cacheKey, expire);
} return rval instanceof ServiceCacheAnnotationAspect.Blank?null:rval;
} else {
return jp.proceed();
}
}
}
} private Object cacheInvoke(ServiceCache sc, ProceedingJoinPoint jp, String cacheKey, int expire) throws Throwable {
log.debug("Load from cache for key : {}", cacheKey);
Object rval = this.serviceCache.get(cacheKey);
if(rval == null) {//缓存中没有,就要去数据库拿,拿完就放缓存
log.info("Miss from cache, load backend for key : {}", cacheKey);
rval = jp.proceed();//执行目标方法
rval = rval == null && sc.nullPattern()?ServiceCacheAnnotationAspect.Blank.INST:rval;
if(rval != null) {
this.serviceCache.put(cacheKey, rval, expire);
}
} return rval;
} public void setServiceCache(ICache serviceCache) {
this.serviceCache = serviceCache;
ServiceCacheUtils.single().setCache(serviceCache);
} private static class Blank implements Serializable {
private static final long serialVersionUID = 3203712628835590212L;
private static final ServiceCacheAnnotationAspect.Blank INST = new ServiceCacheAnnotationAspect.Blank(); private Blank() {
}
}
}

实际使用中的例子是这样的:

@ServiceCache(expire = 600, includeKeys = { "name" })
public CarProvinceEntity selectProvinceByName(String name) {
return commonDao.mapper(CarProvinceEntity.class).source(Source.SLAVE)
.sql("selectByName").session().selectOne(name);
}

如此,对于开发业务的人员来说就比较方便了。通过aop结合注解,可以在项目中做一些切面的事情已经成为很多项目底层框架的一部分,比如计算方法耗时,打日志,国际化等等的业务。

相关知识点链接:

http://www.cnblogs.com/killbug/p/5271291.html

http://samter.iteye.com/blog/410618
http://itindex.net/detail/29812-aop
http://www.ibm.com/developerworks/cn/java/j-lo-aopi18n/index.html

--------------------20161020 补充线-----------------------

发现,实际项目编码时经常用到aop的思想,利用spring实现非常方便,所以再补充一个简易的例子:

因为业务方法调用可能抛出异常,但是有些方法在有异常情况下也不应该阻断流程,所以要try catch住,所以就写一个注解统一将这个需求做掉。

注解:

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface NoThrowException { }

注解解析,利用spring切面匹配注解,代理实际业务方法,catch住异常。

@Aspect
public class NoThrowExceptionAspect { @Around("@annotation(aspect.test.annotation.NoThrowException)")
public void tryAround(ProceedingJoinPoint joinPoint){
try{
joinPoint.proceed();
} catch (Throwable throwable) {
// throwable.printStackTrace();
}
}
}

实际使用:

 @NoThrowException
public void dost() throws Exception { System.out.printf("do st");
throw new Exception("TEST");
}

以上非常简单明了,spring的很多功能后续要深入使用。

遇到瓶颈,要静下心来,加油!

利用spring AOP 和注解实现方法中查cache-我们到底能走多远系列(46)的更多相关文章

  1. js中this和回调方法循环-我们到底能走多远系列(35)

    我们到底能走多远系列(35) 扯淡: 13年最后一个月了,你们在13年初的计划实现了吗?还来得及吗? 请加油~ 主题: 最近一直在写js,遇到了几个问题,可能初入门的时候都会遇到吧,总结下. 例子: ...

  2. 服务调用方案(Spring Http Invoker) - 我们到底能走多远系列(40)

    我们到底能走多远系列(40) 扯淡:  判断是否加可以效力于这家公司,一个很好的判断是,接触下这公司工作几年的员工,了解下生活工作状态,这就是你几年后的状态,如果满意就可以考虑加入了. 主题: 场景: ...

  3. 定时任务管理中心(dubbo+spring)-我们到底能走多远系列47

    我们到底能走多远系列47 扯淡: 又是一年新年时,不知道上一年你付出了多少,收获了多少呢?也许你正想着老板会发多少奖金,也许你正想着明年去哪家公司投靠. 这个时间点好好整理一下,思考总结一下,的确是个 ...

  4. Spring mvc源码url路由-我们到底能走多远系列(38)

    我们到底能走多远系列38 扯淡: 马航的事,挺震惊的.还是多多珍惜身边的人吧. 主题: Spring mvc 作为表现层的框架,整个流程是比较好理解的,毕竟我们做web开发的,最早也经常接触的就是一个 ...

  5. Bean实例化(Spring源码阅读)-我们到底能走多远系列(33)

    我们到底能走多远系列(33) 扯淡: 各位:    命运就算颠沛流离   命运就算曲折离奇   命运就算恐吓着你做人没趣味   别流泪 心酸 更不应舍弃   ... 主题: Spring源码阅读还在继 ...

  6. 初始化IoC容器(Spring源码阅读)-我们到底能走多远系列(31)

    我们到底能走多远系列(31) 扯淡: 有个问题一直想问:各位你们的工资剩下来会怎么处理?已婚的,我知道工资永远都是不够的.未婚的你们,你们是怎么分配工资的? 毕竟,对自己的收入的分配差不多体现了自己的 ...

  7. 利用Spring AOP自定义注解解决日志和签名校验

    转载:http://www.cnblogs.com/shipengzhi/articles/2716004.html 一.需解决的问题 部分API有签名参数(signature),Passport首先 ...

  8. (转)利用Spring AOP自定义注解解决日志和签名校验

    一.需解决的问题 部分API有签名参数(signature),Passport首先对签名进行校验,校验通过才会执行实现方法. 第一种实现方式(Origin):在需要签名校验的接口里写校验的代码,例如: ...

  9. 化繁就简,如何利用Spring AOP快速实现系统日志

    1.引言 有关Spring AOP的概念就不细讲了,网上这样的文章一大堆,要讲我也不会比别人讲得更好,所以就不啰嗦了. 为什么要用Spring AOP呢?少写代码.专注自身业务逻辑实现(关注本身的业务 ...

随机推荐

  1. 如何通过命令行创建和设置一个MySQL用户

    我想要在MySQL服务器上创建一个新的用户帐号,并且赋予他适当的权限和资源限制.如何通过命令行的方式来创建并且设置一个MySQL用户呢? 要访问一个MySQL服务器,你需要使用一个用户帐号登录其中方可 ...

  2. 【PCB】【AD使用】Altium Designer 的entry sheet ,offsheet和port作用

    Altium Designer之多图纸设计 1.图纸结构 图纸包括两种结构关系: 一种是层次式图纸,该连接关系是纵向的,也就是某一层次的图纸只能和相邻的上级或下级有关系: 另一种是扁平式图纸,该连接关 ...

  3. Bootstrap <基础二十三>页面标题(Page Header)

    页面标题(Page Header)是个不错的功能,它会在网页标题四周添加适当的间距.当一个网页中有多个标题且每个标题之间需要添加一定的间距时,页面标题这个功能就显得特别有用.如需使用页面标题(Page ...

  4. Bootstrap<基础六> 表单

    Bootstrap 通过一些简单的 HTML 标签和扩展的类即可创建出不同样式的表单. 表单布局 Bootstrap 提供了下列类型的表单布局: 垂直表单(默认) 内联表单 水平表单 垂直或基本表单 ...

  5. Bootstrap<基础三> 排版

    Bootstrap 使用 Helvetica Neue. Helvetica. Arial 和 sans-serif 作为其默认的字体栈. 使用 Bootstrap 的排版特性,您可以创建标题.段落. ...

  6. Web服务及http协议

    HTTP:HyperText Transfer Protocol--超文本传输协议 超链接:能够在文档之间跳转的文本 早起的Web:仅仅是能够实现在文档之间跳转的一种协议 http/0.9:仅支持纯文 ...

  7. CSS兼容问题实用建议

    CSS兼容问题,是美工最头痛的问题.做测试时,用谷哥和360浏览器(最新)都没有什么问题,用 IE6/IE8测试,问题就冒出来了.微软现在出IE10,我电脑上已经无法用IE6准确测试,IE-TESTE ...

  8. C++ 基础算法之二分查找

    前提: 有序数组! int binary_search(int* a, int len, int goal) { ; ; while(low <= high) { ; if(a[middle] ...

  9. JQuery基础总结上

    最近在慕课网学习JQuery基础课程,发现只是跟随网站的课程学习而不去自己总结扩展的话,很难达到真正学会理解的地步. 于是先在网站上草草过了一遍课程,然后借着今天的这个时间边记录边重新整理学习一下. ...

  10. MongooseJS 4.6.4 发布,MongoDB 连接包

    MongooseJS 4.6.4  发布了,MongooseJS 是基于 node.js,使用 JavaScript 编程,连接 MongoDB 数据库的软件包,使MongoDB 的文档数据模型变得优 ...