利用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呢?少写代码.专注自身业务逻辑实现(关注本身的业务 ...
随机推荐
- 关于C++构造函数初始化顺序
这里主要是说序列初始化成员变量时,存在这样的规则: 1. 先进行序列初始化,再进行构造函数函数体内的赋值等操作. 2. 序列初始化,不是简单的自左至右或自右至左,而是根据成员变量的定义顺序来初始化. ...
- 也谈自动化平台的搭建,另附高大上的名字---无人值守定时巡检系统(selenium+testng+ant+jenkins)
最近公司新项目改版,由于没有运维,开发则负责上线任务,并且都是手动上线,并行的项目多了,分支混乱,经常导致B项目上线覆盖A项目,导致系统不定时出现异常,老板知道了便扣了大家的绩效,作为测试这边必须想个 ...
- Eclipse 项目中有红色感叹号,怎么办?
/** * JDK1.5中增加了自动拆装箱的语言特性,在基本类型和包装类型之间可以相互地转换和运算. * 大家也都知道Java中==运算符是比较两个对象间的引用是否相同.在自动拆装箱与“==”运算符之 ...
- 点击document隐藏某个div
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- Visor 隐藏应用之一 CSS3 生成器
Visor 有很多隐藏功能,这些功能并不是系统的核心功能,只是为了测试一些技术,做了些对初学前端的工程师还算有用的功能.今天就介绍一下CSS3 生成器. 先介绍一下Note 组件: Note是基本图形 ...
- Whole life
Whole life the wonder such you bring the beautyI can see but i keep deep inside on itOh life i feel ...
- play(1) 第一个简单的应用
去年去了一家公司,公司要求要回使用play,所以在几天的时间内猛学习了一段时间play,发现play!有很多优点:简单,小巧,高开发效率,集成很多功能,容易调试.现在虽然已经不在那家公司,没有使用pl ...
- JS 日期格式化
<script type="text/javascript" src="http://code.jquery.com/jquery-latest.js"& ...
- TypeScript Handbook 2——接口1(翻译)
接口(Interfaces) One of TypeScript's core principles is that type-checking focuses on the 'shape' that ...
- Kindle使用的一些方法
最大的好处就是方便,买书便宜,到手我就买了六部书,十块钱不到,以纸书的价格一本都买不到,能够买一些一直想读一下,但又担心读不下去的书.而且买了之后完全不用担心书柜收纳不下了.另外很轻便,放在包里上下班 ...