使用 AOP 和註解實現方法緩存

因爲項目中散落着各類使用緩存的代碼,這些緩存代碼與業務邏輯代碼交織耦合在一塊兒既編寫重複又難以維護,所以打算將這部分緩存代碼抽取出來造成一個註解以便使用。java

這樣的需求最適合用 AOP 技術來解決了,來看看如何在 spring 框架下使用 AOP 技術:spring

開啓註解掃描apache

首先開啓 Spring 註解掃描:緩存

<context:component-scan base-package="your.package" /> 以及開啓 @AspectJ 切面註解掃描:框架

<aop:aspectj-autoproxy proxy-target-class="true" /> 編寫註解less

而後使用 Java 語法編寫一個註解:學習

package your.package;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 方法級緩存
 * 標註了這個註解的方法返回值將會被緩存
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MethodCache {

    /**
     * 緩存過時時間,單位是秒
     */
    int expire();

}

切面(Aspect)代理

最後是編寫一個切面。注:若是你對切面的概念已經很清楚,能夠跳過本小結。code

什麼是切面?通俗來講就是「什麼時候何地發生何事」,其組成以下:component

Aspect = Advice (what & when) + Pointcut (where)

其執行過程以下:

通知(Advice)

通知(Advice)定義了 什麼時候(when) 發生 何事(what) 。

Spring AOP 的切面(Aspect)能夠搭配下面五種通知(Adive)註解使用:

通知 描述 @Before The advice functionality takes place before the advised method is invoked. @After The advice functionality takes place after the advised method completes, regardless of the outcome. @AfterReturning The advice functionality takes place after the advised method successfully completes. @AfterThrowing The advice functionality takes place after the advised method throws an exception. @Around The advice wraps the advised method, providing some functionality before and after the advised method is invoked. 切點(Pointcut)

切點(Pointcut)定義了切面在 何處(where) 執行。

Spring AOP 的切點(Pointcut)使用 AspectJ 的「切點表達式語言(Pointcut Expression Language)」進行定義。但要注意的是,Spring 僅支持其中一個子集:

切點表達式的語法以下:

完成切面

使用註解來建立切面,是 AspectJ 5 所引入的關鍵特性。在 AspectJ 5 以前,編寫 AspectJ 切面須要學習一種 Java 語言的擴展,很不友好。在此咱們使用註解來實現咱們的切面:

package your.package;

import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.kingdee.finance.cache.service.centralize.CentralizeCacheService;

/**
 * 方法級緩存攔截器
 */
@Aspect
@Component
public class MethodCacheInterceptor {

    private static final Logger logger = LoggerFactory.getLogger("METHOD_CACHE");
    private static final String CACHE_NAME = "Your unique cache name";

    @Autowired
    private CentralizeCacheService centralizeCacheService;

    /**
     * 搭配 AspectJ 指示器「@annotation()」可使本切面成爲某個註解的代理實現
     */
    @Around("@annotation(your.package.MethodCache)")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        String cacheKey = getCacheKey(joinPoint);
        Serializable serializable = centralizeCacheService.get(CACHE_NAME, cacheKey);
        if (serializable != null) {
            logger.info("cache hit,key [{}]", cacheKey);
            return serializable;
        } else {
            logger.info("cache miss,key [{}]", cacheKey);
            Object result = joinPoint.proceed(joinPoint.getArgs());
            if (result == null) {
                logger.error("fail to get data from source,key [{}]", cacheKey);
            } else {
                MethodCache methodCache = getAnnotation(joinPoint, MethodCache.class);
                centralizeCacheService.put(CACHE_NAME, methodCache.expire(), cacheKey, (Serializable) result);
            }
            return result;
        }
    }

    /**
     * 根據類名、方法名和參數值獲取惟一的緩存鍵
     * @return 格式爲 "包名.類名.方法名.參數類型.參數值",相似 "your.package.SomeService.getById(int).123"
     */
    private String getCacheKey(ProceedingJoinPoint joinPoint) {
        return String.format("%s.%s", 
                 joinPoint.getSignature().toString().split("\\s")[1], StringUtils.join(joinPoint.getArgs(), ","));
    }

    private <T extends Annotation> T getAnnotation(ProceedingJoinPoint jp, Class<T> clazz) {
        MethodSignature sign = (MethodSignature) jp.getSignature();
        Method method = sign.getMethod();
        return method.getAnnotation(clazz);
    }

}

要注意的是,目前該實現存在兩個限制:

方法入參必須爲基本數據類型或者字符串類型,使用其它引用類型的參數會致使緩存鍵構造有誤; 方法返回值必須實現 Serializable 接口; 投入使用

例如,使用本註解爲一個「按 ID 查詢列表」的方法加上五分鐘的緩存:

@MethodCache(expire = 300)
public List<String> listById(String id) {
    // return a string list.
}

總結

使用 AOP 技術,你能夠在一個地方定義全部的通用邏輯,並經過 聲明式(declaratively) 的方式進行使用,而沒必要修改各個業務類的實現。這種代碼解耦技術使得咱們的業務代碼更純粹、僅包含所需的業務邏輯。相比繼承(inheritance)和委託(delegation),AOP 實現相同的功能,代碼會更整潔。

相關文章
相關標籤/搜索