程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> 利用spring AOP 和注解實現方法中查cache-我們到底能走多遠系列(46),aopcache-

利用spring AOP 和注解實現方法中查cache-我們到底能走多遠系列(46),aopcache-

編輯:JAVA綜合教程

利用spring AOP 和注解實現方法中查cache-我們到底能走多遠系列(46),aopcache-


主題:
這份代碼是開發中常見的代碼,查詢數據庫某個主表的數據,為了提高性能,做一次緩存,每次調用時先拿緩存數據,有則直接返回,沒有才向數據庫查數據,降低數據庫壓力。

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

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved