利用spring AOP 和注解实现方法中查cache-我们到底能走多远系列(46)
主题:
这份代码是开发中常见的代码,查询数据库某个主表的数据,为了提高性能,做一次缓存,每次调用时先拿缓存数据,有则直接返回,没有才向数据库查数据,降低数据库压力。
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)的更多相关文章
- js中this和回调方法循环-我们到底能走多远系列(35)
我们到底能走多远系列(35) 扯淡: 13年最后一个月了,你们在13年初的计划实现了吗?还来得及吗? 请加油~ 主题: 最近一直在写js,遇到了几个问题,可能初入门的时候都会遇到吧,总结下. 例子: ...
- 服务调用方案(Spring Http Invoker) - 我们到底能走多远系列(40)
我们到底能走多远系列(40) 扯淡: 判断是否加可以效力于这家公司,一个很好的判断是,接触下这公司工作几年的员工,了解下生活工作状态,这就是你几年后的状态,如果满意就可以考虑加入了. 主题: 场景: ...
- 定时任务管理中心(dubbo+spring)-我们到底能走多远系列47
我们到底能走多远系列47 扯淡: 又是一年新年时,不知道上一年你付出了多少,收获了多少呢?也许你正想着老板会发多少奖金,也许你正想着明年去哪家公司投靠. 这个时间点好好整理一下,思考总结一下,的确是个 ...
- Spring mvc源码url路由-我们到底能走多远系列(38)
我们到底能走多远系列38 扯淡: 马航的事,挺震惊的.还是多多珍惜身边的人吧. 主题: Spring mvc 作为表现层的框架,整个流程是比较好理解的,毕竟我们做web开发的,最早也经常接触的就是一个 ...
- Bean实例化(Spring源码阅读)-我们到底能走多远系列(33)
我们到底能走多远系列(33) 扯淡: 各位: 命运就算颠沛流离 命运就算曲折离奇 命运就算恐吓着你做人没趣味 别流泪 心酸 更不应舍弃 ... 主题: Spring源码阅读还在继 ...
- 初始化IoC容器(Spring源码阅读)-我们到底能走多远系列(31)
我们到底能走多远系列(31) 扯淡: 有个问题一直想问:各位你们的工资剩下来会怎么处理?已婚的,我知道工资永远都是不够的.未婚的你们,你们是怎么分配工资的? 毕竟,对自己的收入的分配差不多体现了自己的 ...
- 利用Spring AOP自定义注解解决日志和签名校验
转载:http://www.cnblogs.com/shipengzhi/articles/2716004.html 一.需解决的问题 部分API有签名参数(signature),Passport首先 ...
- (转)利用Spring AOP自定义注解解决日志和签名校验
一.需解决的问题 部分API有签名参数(signature),Passport首先对签名进行校验,校验通过才会执行实现方法. 第一种实现方式(Origin):在需要签名校验的接口里写校验的代码,例如: ...
- 化繁就简,如何利用Spring AOP快速实现系统日志
1.引言 有关Spring AOP的概念就不细讲了,网上这样的文章一大堆,要讲我也不会比别人讲得更好,所以就不啰嗦了. 为什么要用Spring AOP呢?少写代码.专注自身业务逻辑实现(关注本身的业务 ...
随机推荐
- Bootstrap<基础十三> 按钮组
按钮组允许多个按钮被堆叠在同一行上.当你想要把按钮对齐在一起时,这就显得非常有用.你可以通过Bootstrap 按钮(Button) 插件 添加可选的 JavaScript 单选框和复选框样式行为. ...
- Android的RecyclerView
简介 RecyclerView是support-v7中用来替换ListView的组件.RecyclerView 小组件比 ListView 更高级且更具灵活性. 此小组件是一个用于显示庞大数据集的容器 ...
- 依然同上~ 点击获取当前option的value与text
$(".tjbtn").click(function(){ $('#leader').each(function(index, ele){ var leader = ele.val ...
- 作业七:团队项目——Alpha版本冲刺阶段006
今日进展:完成主体代码. 今日安排:对程序主体进行编写.
- FB
转眼间,开始工作到现在好几年,忙着功能,忙着补漏填坑,忙着项目,现在回顾着开始的理想,一时有点恍惚,然后鄙视了下自己居然还在“理想”中…… 那就开始吧,做点什么呢? DX9/DX11的支持是必须的,S ...
- 【python】操作excel——xlrd xlwt xlutils
from xlutils.copy import copy import xlrd # import xlutils #打开已存在的excel rb=xlrd.open_workbook('D:\\1 ...
- 【转】 shell 判断语句
转自:http://see.sl088.com/wiki/Shell_%E4%B8%AD%E6%8B%AC%E5%8F%B7 test 和 [] test -z string 判定字串是否為 0 ?若 ...
- 关于Spring注解
* @author 小郑 1 * @content ejb3注解的API定义在javax.persistence.*包里面. 2 * 注释说明: 3 * @E ...
- .NET3.5项目转.NET2.0项目技巧
最近有一个项目,一开始开发是用VS2008(.NET3.5)开发的,该项目是一个Windorm客户端软件,由于在大规模的推广过程中,发现在安装.NET3.5Framework的时候浪费了太多时间,而且 ...
- nodejs常用模块之url
统一资源定位符(url)充当http服务器用来处理来自客户端的请求的一个地址标签.它为把一个请求发到正确的服务器的特定端口上,并访问合适的数据提供了所有需要的信息.