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

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. SQL指定字段指定顺序排序

    SELECT * FROM [ProjectInfo]ORDER BY (CASE DepartmentName WHEN 'AAA' THEN 1 WHEN 'BBB' THEN 2 WHEN 'C ...

  2. java时间戳

    1.时间戳的定义 时间戳是指文件属性里的创建.修改.访问时间. 数字时间戳技术是数字签名技术一种变种的应用.在电子商务交易文件中,时间是十分重要的信息.在书面合同中,文件签署的日期和签名一样均是十分重 ...

  3. App Store最新审核标准,中文版

    App store最新审核标准(2015.3)公布 1. 条款和条件 1.1 为App Store开发程序,开发者必须遵守 Program License Agreement (PLA).人机交互指南 ...

  4. Eclipse智能提示及快捷键

    1.java智能提示 (1). 打开Eclipse,选择打开" Window - Preferences". (2). 在目录树上选择"Java-Editor-Conte ...

  5. SQLite常用函数

    length(column_name) 取得相应栏位的长度 substr(column_name, start, length) 截取某个栏位相应长度的值

  6. Qt中2D绘图问题总结(二)----------坐标系统

    坐标系统 使用QPainter绘制时使用到逻辑坐标,然后转换成绘图设备的物理坐标. 逻辑坐标到物理坐标的映射由QPainter的worldTransform()函数.QPainter的viewport ...

  7. FFmpeg:初步编译使用[Android]

    1.安装NDK:http://dl.google.com/android/ndk/android-ndk-r9-linux-x86.tar.bz2 sudo gedit ~/.bashrc 末尾添加: ...

  8. UnitTesting中的ClassInitialize,ClassCleanup,TestInitialize,TestClearup

    ClassInitialize 标识一个包含代码的方法,这些代码必须在测试类中的任意测试运行之前使用,并用于分配测试类所使用的资源.此类不能被继承. http://msdn.microsoft.com ...

  9. tomcat8的配置

    本文章只适合初入javaweb的新人. 点击tomcat8的bin目录下的startup.bat,启动tomcat.在浏览器中输入http://localhost:8080  打开项目管理页,点击Ma ...

  10. Java中静态内部类的理解

    class A { public void func() { A a=new A(); C c=a.new C(); } public static void main(String[] args) ...