SpringBoot Redis入门(三)——自定义MyCacheable缓存注解,实现对返回结果缓存

2024-01-08 12:16:33
  • 自定义一个MyCacheable注解,以实现与Spring @Cacheable相同功能。

实现步骤:

  1. 创建MyCacheable注解;
  2. 创建拦截器;
  3. 验证;

1、创建MyCacheable注解

MyCacheable.java

import java.lang.annotation.*;

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface MyCacheable {

    /**
     * 缓存分组名称
     *
     * @return
     */
    String value() default "";

    /**
     * 缓存 key 值
     *
     * @return
     */
    String key() default "";
}

value:与 Cacheable 的含义一致,只是我们的只支持字符,不支持数组;
key: 与 Cacheable的含义一样,是我们存取绑在的key;
这样我们简单的MyCacheable注解就定义完了。

拦截器定义

MyCacheableAnnotationInterceptor.java


import lombok.Getter;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodClassKey;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.redis.core.RedisTemplate;

import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 1. 在执行方法前,将 @MyCacheable 注解入栈
 * 2. 在执行方法后,将 @MyCacheable 注解出栈
 *
 * @author yudao
 */
public class MyCacheableAnnotationInterceptor implements MethodInterceptor {


    @Autowired
    RedisTemplate redisTemplate;

    /**
     * MyCacheable 空对象
     */
    static final MyCacheable MY_CACHEABLE_NULL = MyCacheableAnnotationInterceptor.class.getAnnotation(MyCacheable.class);

    @Getter
    private final Map<MethodClassKey, MyCacheable> MyCacheableCache = new ConcurrentHashMap<>();

    public MyCacheableAnnotationInterceptor(RedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        Logger log = LoggerFactory.getLogger(MyCacheableAnnotationInterceptor.class);
        log.debug("MyCacheableAnnotationInterceptor 拦截器:" + methodInvocation.getMethod().getName());
        // 获取方法上注解
        MyCacheable myCacheable = this.findAnnotation(methodInvocation);

        //如果没有MyCacheable注解,则执行方法体
        if (myCacheable == null) {
            // 执行逻辑
            return methodInvocation.proceed();
        }
        //获取到MyCacheable注解了,先试着从缓存中获取值;
        String cacheName = myCacheable.value();
        // 我们示例就没有Spring的强大能支持SpringEL表达式,
        // 我们的只能是没有任何解析,直接拿来就用了。
        String key = myCacheable.key();
        Object result = redisTemplate.opsForHash().get(cacheName, key);
        // 没获取到结果,则执行方法,
        if (result == null) {
            result = methodInvocation.proceed();
            // 空值是否缓存,看自身业务了。
            if (result != null) {
                redisTemplate.opsForHash().put(cacheName, key, result);
            }
        }
        return result;
    }

    private MyCacheable findAnnotation(MethodInvocation methodInvocation) {
        // 1. 从缓存中获取
        Method method = methodInvocation.getMethod();
        Object targetObject = methodInvocation.getThis();
        Class<?> clazz = targetObject != null ? targetObject.getClass() : method.getDeclaringClass();
        MethodClassKey methodClassKey = new MethodClassKey(method, clazz);
        MyCacheable myCacheable = MyCacheableCache.get(methodClassKey);
        if (myCacheable != null) {
            return myCacheable != MY_CACHEABLE_NULL ? myCacheable : null;
        }

        // 2.1 从方法中获取
        myCacheable = AnnotationUtils.findAnnotation(method, MyCacheable.class);
        // 2.2 从类上获取
        if (myCacheable == null) {
            myCacheable = AnnotationUtils.findAnnotation(clazz, MyCacheable.class);
        }
        // 2.3 添加到缓存中
        MyCacheableCache.put(methodClassKey, myCacheable != null ? myCacheable : MY_CACHEABLE_NULL);
        return myCacheable;
    }

}

项目配置了这个拦截器后,所有添加了@MyCacheable的方法类类在执行方法前都会进入该拦截器,在该方法中解析方法上是否存在MyCacheable注解,若有则先从缓存获取一波结果,能获取到直接返回,获取不到在执行查询方法,获取到后存入缓存。

之后,我们来把拦截器装配起来。

拦截器装配

MyCacheableAnnotationAdvisor.java

/**
 * {@link MyCacheable} 注解的 Advisor 实现类
 */
@Getter
@EqualsAndHashCode(callSuper = true)
public class MyCacheableAnnotationAdvisor extends AbstractPointcutAdvisor {

    private final Advice advice;

    private final Pointcut pointcut;

    public MyCacheableAnnotationAdvisor(MyCacheableAnnotationInterceptor interceptor) {
        this.advice = interceptor;
        this.pointcut = this.buildPointcut();
    }

    protected Pointcut buildPointcut() {
        Pointcut classPointcut = new AnnotationMatchingPointcut(MyCacheable.class, true);
        Pointcut methodPointcut = new AnnotationMatchingPointcut(null, MyCacheable.class, true);
        return new ComposablePointcut(classPointcut).union(methodPointcut);
    }

}

拦截器配置

MyCacheableConfig.java

import com.luo.chengrui.labs.lab03.mycache.MyCacheableAnnotationAdvisor;
import com.luo.chengrui.labs.lab03.mycache.MyCacheableAnnotationInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Role;
import org.springframework.data.redis.core.RedisTemplate;

/**
 * redis配置
 */
@Configuration
public class MyCacheableConfig {

    @Bean
    @Role(2)
    MyCacheableAnnotationAdvisor myCacheableAnnotationAdvisor(RedisTemplate redisTemplate) {
        return new MyCacheableAnnotationAdvisor(new MyCacheableAnnotationInterceptor(redisTemplate));
    }
}

使用示例

    /**
     * @return
     */
    @MyCacheable(value = "USER_INFO", key = "01")
    public Object getUserInfo3() {
        System.out.println("未获取到缓存,从数据库获取.............");
        Map<String, Object> user = new HashMap<>();
        user.put("username", "01");
        user.put("usercode", "zhangsan");
        user.put("sex", "男");
        user.put("createtime", DateUtils.format(new Date()));

        return user;
    }
    @Test
    public void testMyCacheable() throws InterruptedException {
        System.out.println(userService.getUserInfo3());
        System.out.println(userService.getUserInfo3());
    }

运行结果:

在这里插入图片描述
存储效果:
在这里插入图片描述

综上,Spring的Cacheable注解的基本逻辑也就是如此了,只是它提供了其他强大的Key解析功能,sync线程中步功能和condition条件缓存功能。

文章来源:https://blog.csdn.net/ldz_wolf/article/details/135451175
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。