使用 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 实现相同的功能,代码会更整洁。

相关文章
相关标签/搜索