Spring AOP+注解的方式实现缓存的获取

一、概述

在学习Spring AOP+注解的方式实现缓存的获取,你需要掌握几个知识点
1、为什么要用AOP+注解的方式实现缓存的获取
2、你需要了解Spring框架的搭建
3、你需要了解Spring如何做到获取方法参数的参数名
4、你需要了解反射的概念
5、你需要了解注解的基本知识

以上所列的2、4、5条都可以很快了解,但是对第3条,你需要好好学习下了,我就选择1、3来说下我的理解,如果有错误,请谅解,毕竟我水平不高。

1、为什么要用AOP+注解的方式实现缓存的获取?

首先使用AOP,我理解就是为了解耦合,如果我们在代码实现中,业务逻辑代码和缓存的操作掺杂在一起,这样会产生耦合,并且你会发现大量的缓存操作代码冗余,
而且灵活性也不高(如果我换了一个缓存框架,那么每个涉及到的缓存代码都需要修改)
使用注解是因为比较方便。
所以选择使用AOP+注解的方式实现缓存的获取

2、Spring如何做到获取方法参数的参数名

请参考我的博客http://blog.csdn.net/zx2612/article/details/74090658

二、缘由

为什么要写这篇文章?
最近在工作中,同事实现了这个功能,所以为了学习,我也选择回家写一次,看的再多,不如做一次。

三、实现

实现的基本原理:利用Spring AOP(其实是使用AspectJ,将方法上的注解作为切点,将通知织入),然后定义了一个@CacheParam注解,用来动态获取缓存的key,和动态控制是否清除缓存,使用@CacheManage来标识那个方法应该被拦截。对于注解的处理有点像SpringMVC的@RequestMapping和@PathVariable(当然我水平有限,如果存在错误和不足,请指教)

代码如下:

1、定义注解

/**
 * 指定某个方法参数的名称
 */
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CacheParam {
    String value() default  "";
}

/**
 * 标识在方法上面,用于动态操作缓存
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CacheManage {

    /**
     * 缓存的key
     * */
    String key();

    /**
     * 缓存组件的配置文件
     * 可能需要从多个缓存组件中获取数据
     */
    String configFile() default "cache.conf";

    boolean isClearCache() default false;

    String clearCacheFlag() default  "";
}

2、定义基本的缓存操作接口

/**
 * 使用注解方式操作缓存需要自己实现该接口的方法
 * 因为缓存组件有很多种,操作的Api也不一样
 */
public interface CacheOperate {

    public Object  get(String key,String configFile);

    public  Object  update(String key,Object value,String configFile);

    public  void del(String key,String configFile);

    public boolean existCache(String key,String configFile);

}

3、实现工具类

/**
 * 缓存反射工具类
 */
public class CacheUtil {
    private static final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();

    /**
     * 从method的参数列表中检测指定的CacheParam注解的value属性值,如果为空则使用Spring封装好的ParameterNameDiscoverer,去读取参数的名称
     * <p>
     * 对于参数名称的获取,如果编译成Class文件时带上了参数名称信息,则可以正常获取,否则只有类似arg0,arg1这样的信息
     * <p>
     * Spring获取参数名称至少有如下几种方式:
     * 1、通过jdk8提供的getParameters方法
     * 2、通过ASM直接解析Class文件,从局部变量表中读取
     *
     * @param method
     * @param type
     * @param startIndex
     * @return
     */
    public static String getParameterName(Method method, Class type, int startIndex) {

        String paramName = null;
        Annotation[][] annotationArrays = method.getParameterAnnotations();

        for (int index = startIndex; index < annotationArrays.length; index++) {
            Annotation[] annotations = annotationArrays[index];
            for (Annotation a : annotations) {
                if (a.getClass().isAssignableFrom(type)) {
                    CacheParam cacheParam = CacheParam.class.cast(a);
                    paramName = cacheParam.value();

                    if (paramName == null || "".equals(paramName)) {
                        paramName = parameterNameDiscoverer.getParameterNames(method)[index];
                    }

                    break;
                }
            }
        }

        return paramName;
    }


    public static String getParameterName(Method method, Class type) {
        return getParameterName(method, type, 0);
    }

    public static Map<String, Object> getAllParamters(Method method, Class type, Object[] args) {
        Map<String, Object> paramMap = new HashMap<String, Object>();

        Annotation[][] annotationArrays = method.getParameterAnnotations();
        for (int index = 0; index < annotationArrays.length; index++) {
            Annotation[] annotations = annotationArrays[index];
            for (Annotation a : annotations) {

                if (type.isAssignableFrom(a.getClass())) {

                    CacheParam cacheParam = CacheParam.class.cast(a);
                    String paramName = cacheParam.value();
                    if (paramMap.containsKey(paramName)) {
                        throw new IllegalStateException("method:" + method.getName() + "参数列表中的@CacheParam定义的属性值重复");
                    }

                    if (paramName == null || "".equals(paramName)) {
                        paramName = parameterNameDiscoverer.getParameterNames(method)[index];
                    }
                    paramMap.put(paramName, args[index]);
                    break;
                }
            }
        }
        return paramMap;
    }


    public static <T> T getTargetBean(ApplicationContext ac, Class<T> type) {
        return ac.getBean(type);
    }


    public static boolean isPlaceholder(String value) {
        value = StringUtils.trimAllWhitespace(value);
        return value != null && value.startsWith("{") && value.endsWith("}");
    }

    public static String getCacheKey(String key) {
        return key.replaceAll("[{}]", "");
    }

    public static <T> T checkAndCastKeyValue(String key, Class<T> targetType, Map<String, Object> paramValues, boolean isRequired) {

        String keyTemp = key;

        if (CacheUtil.isPlaceholder(keyTemp)) {
            keyTemp = CacheUtil.getCacheKey(keyTemp);
        }

        Object keyObj = paramValues.get(keyTemp);

        if (keyObj == null) {
            if (isRequired) {
                throw new IllegalStateException("无@CacheParam(" + key + ")对应的参数");
            } else {
                return  null;
            }
        }

        if (!keyObj.getClass().isAssignableFrom(targetType)) {
            throw new IllegalStateException("方法中@CacheParam(" + key + ")对应的参数值必须为"+targetType+"类型");
        }

        return targetType.cast(keyObj);
    }
}

4、定义切面

/**
 * 使用注解的方式获取缓存
 * 可以动态指定某个缓存的key
 * 可以动态指定是否要清空某个缓存
 * <p>
 * 动态指定的实现方式类似于SpringMVC中的RequestMapping和PathVariable
 */
@Component
@Aspect
public class CacheAspact {

    @Resource
    private ApplicationContext ac;

    @Around("@annotation(xyz.zhangxing.spring.extend.CacheManage)")
    public Object aspact(ProceedingJoinPoint joinPoint) throws Throwable {

        Signature s = joinPoint.getSignature();
        MethodSignature ms = (MethodSignature) s;
        Method method = ms.getMethod();
        Map<String, Object> paramValues = CacheUtil.getAllParamters(method, CacheParam.class, joinPoint.getArgs());

        CacheManage cacheManage = method.getAnnotation(CacheManage.class);

        CacheOperate cacheOperate = CacheUtil.getTargetBean(ac, CacheOperate.class);


        String key = CacheUtil.checkAndCastKeyValue(cacheManage.key(), String.class, paramValues, true);
        String configFile = cacheManage.configFile();

        Boolean clearCacheFlag = CacheUtil.checkAndCastKeyValue(cacheManage.clearCacheFlag(), Boolean.class, paramValues, false);
        if (clearCacheFlag == null) {
            clearCacheFlag = cacheManage.isClearCache();
        }

        Object result = null;

        //如果需要清除缓存,或者缓存不存在
        if (clearCacheFlag||!cacheOperate.existCache(key,configFile)) {
            result= joinPoint.proceed();
            cacheOperate.update(key,result,configFile);
        }else{
            result= cacheOperate.get(key,configFile);
        }
        return result;
    }
}

5、测试

@Component
public class TestCacheOperate implements CacheOperate {

private Map<String, Object> map = new ConcurrentHashMap<String, Object>();

public Object get(String key, String configFile) {
    return map.get(key);
}

public Object update(String key, Object value, String configFile) {
    if (value == null) {
        return null;
    }
    return map.put(key, value);
}

public void del(String key, String configFile) {
    map.remove(key);
}

public boolean existCache(String key, String configFile) {
    return map.containsKey(key) ? get(key, configFile) != null : false;
}

}

@Component("test")
public class TestAspact {

    @CacheManage(key = "{name}",clearCacheFlag = "{clearFlag}")
    public String  test(String value,@CacheParam("name") String name,@CacheParam("clearFlag") boolean clearFlag) {
        return value;
    }
}

public class Test {
    public static void main(String[] args) {
        ApplicationContext ac=new ClassPathXmlApplicationContext("classpath:spring.conf.xml");
       TestAspact ta= ac.getBean("test",TestAspact.class);
        System.out.println(ta.test("第一次的值","key",false));
        System.out.println(ta.test("第二次的值","key",false));
        System.out.println(ta.test("第三次的值","key",true));
        System.out.println(ta.test("第四次的值","key",false));
    }
}

结果如下:
第一次的值
第一次的值
第三次的值
第三次的值

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值