spring boot 自定redis缓存注解

6 篇文章 0 订阅
2 篇文章 0 订阅

功能要求

自定义缓存注解,使用自定义缓存的注解时可以将方法的返回值缓存到redis中。
类似与Spring cache的功能。

pom

项目的依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>


<dependency>
   <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
</dependency>
<dependency>
     <groupId>com.alibaba.fastjson2</groupId>
     <artifactId>fastjson2</artifactId>
 </dependency>
 <dependency>
     <groupId>org.projectlombok</groupId>
     <artifactId>lombok</artifactId>
 </dependency>

项目中redis配置

# redis 配置
redis:
  # 地址
  host: localhost
  # 端口,默认为6379
  port: 6379
  # 数据库索引
  database: 0
  # 密码
  password:
  # 连接超时时间
  timeout: 10s
  lettuce:
    pool:
      # 连接池中的最小空闲连接
      min-idle: 0
      # 连接池中的最大空闲连接
      max-idle: 8
      # 连接池的最大数据库连接数
      max-active: 8
      # #连接池最大阻塞等待时间(使用负值表示没有限制)
      max-wait: -1ms

redis配置

在项目中创建config文件夹,在此文件夹中创建关于redis的相关配置。
配置可以直接使用
如下

redis序列化器

/**
 * Redis使用FastJson序列化
 *
 * @author 张小三 
 */
public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T> {
    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

    private Class<T> clazz;


    public FastJson2JsonRedisSerializer(Class<T> clazz) {
        super();
        this.clazz = clazz;
    }

    @Override
    public byte[] serialize(T t) throws SerializationException {
        if (t == null) {
            return new byte[0];
        }
        return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName).getBytes(DEFAULT_CHARSET);
    }

    @Override
    public T deserialize(byte[] bytes) throws SerializationException {
        if (bytes == null || bytes.length <= 0) {
            return null;
        }
        String str = new String(bytes, DEFAULT_CHARSET);

        return JSON.parseObject(str, clazz, JSONReader.Feature.SupportAutoType);
    }

redis配置


/**
 * redis配置
 *
 * @author 张健泰
 */
@Configuration
@EnableCaching
@AutoConfigureBefore(RedisAutoConfiguration.class)
public class RedisConfig extends CachingConfigurerSupport
{
    @Bean
    @SuppressWarnings(value = { "unchecked", "rawtypes" })
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory)
    {
        RedisTemplate<Object, Object> template = new RedisTemplate<Object, Object>();
        template.setConnectionFactory(connectionFactory);

        FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);

        // 使用StringRedisSerializer来序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(serializer);

        // Hash的key也采用StringRedisSerializer的序列化方式
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(serializer);

        template.afterPropertiesSet();
        return template;
    }
}

redis操作

将redis的相关操作封装成类,以组件的形式注入到spring 容器中。

@SuppressWarnings(value = {"unchecked", "rawtypes"})
@Component(value = "RedisService")
@Repository
public class RedisService {
    @Autowired
    public RedisTemplate redisTemplate;

    /**
     * 缓存基本的对象,Integer、String、实体类等
     *
     * @param key   缓存的键值
     * @param value 缓存的值
     */
    public <T> void setCacheObject(final String key, final T value) {
        redisTemplate.opsForValue().set(key, value);
    }

    /**
     * 缓存基本的对象,Integer、String、实体类等
     *
     * @param key      缓存的键值
     * @param value    缓存的值
     * @param timeout  时间
     * @param timeUnit 时间颗粒度
     */
    public <T> void setCacheObject(final String key, final T value, final Long timeout, final TimeUnit timeUnit) {
        redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
    }

    /**
     * 设置有效时间
     *
     * @param key     Redis键
     * @param timeout 超时时间
     * @return true=设置成功;false=设置失败
     */
    public boolean expire(final String key, final long timeout) {
        return expire(key, timeout, TimeUnit.SECONDS);
    }

    /**
     * 设置有效时间
     *
     * @param key     Redis键
     * @param timeout 超时时间
     * @param unit    时间单位
     * @return true=设置成功;false=设置失败
     */
    public boolean expire(final String key, final long timeout, final TimeUnit unit) {
        return redisTemplate.expire(key, timeout, unit);
    }

    /**
     * 获取有效时间
     *
     * @param key Redis键
     * @return 有效时间
     */
    public long getExpire(final String key) {
        return redisTemplate.getExpire(key);
    }

    /**
     * 判断 key是否存在
     *
     * @param key 键
     * @return true 存在 false不存在
     */
    public Boolean hasKey(String key) {
        return redisTemplate.hasKey(key);
    }

    /**
     * 获得缓存的基本对象。
     *
     * @param key 缓存键值
     * @return 缓存键值对应的数据
     */
    public <T> T getCacheObject(final String key) {
        ValueOperations<String, T> operation = redisTemplate.opsForValue();
        return operation.get(key);
    }

    /**
     * 删除单个对象
     *
     * @param key
     */
    public boolean deleteObject(final String key) {
        return redisTemplate.delete(key);
    }

    /**
     * 删除集合对象
     *
     * @param collection 多个对象
     * @return
     */
    public long deleteObject(final Collection collection) {
        return redisTemplate.delete(collection);
    }

    /**
     * 缓存List数据
     *
     * @param key      缓存的键值
     * @param dataList 待缓存的List数据
     * @return 缓存的对象
     */
    public <T> long setCacheList(final String key, final List<T> dataList) {
        Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
        return count == null ? 0 : count;
    }

    /**
     * 获得缓存的list对象
     *
     * @param key 缓存的键值
     * @return 缓存键值对应的数据
     */
    public <T> List<T> getCacheList(final String key) {
        return redisTemplate.opsForList().range(key, 0, -1);
    }

    /**
     * 缓存Set
     *
     * @param key     缓存键值
     * @param dataSet 缓存的数据
     * @return 缓存数据的对象
     */
    public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet) {
        BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
        Iterator<T> it = dataSet.iterator();
        while (it.hasNext()) {
            setOperation.add(it.next());
        }
        return setOperation;
    }

    /**
     * 获得缓存的set
     *
     * @param key
     * @return
     */
    public <T> Set<T> getCacheSet(final String key) {
        return redisTemplate.opsForSet().members(key);
    }

    /**
     * 缓存Map
     *
     * @param key
     * @param dataMap
     */
    public <T> void setCacheMap(final String key, final Map<String, T> dataMap) {
        if (dataMap != null) {
            redisTemplate.opsForHash().putAll(key, dataMap);
        }
    }

    /**
     * 获得缓存的Map
     *
     * @param key
     * @return
     */
    public <T> Map<String, T> getCacheMap(final String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * 往Hash中存入数据
     *
     * @param key   Redis键
     * @param hKey  Hash键
     * @param value 值
     */
    public <T> void setCacheMapValue(final String key, final String hKey, final T value) {
        redisTemplate.opsForHash().put(key, hKey, value);
    }

    /**
     * 获取Hash中的数据
     *
     * @param key  Redis键
     * @param hKey Hash键
     * @return Hash中的对象
     */
    public <T> T getCacheMapValue(final String key, final String hKey) {
        HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
        return opsForHash.get(key, hKey);
    }

    /**
     * 获取多个Hash中的数据
     *
     * @param key   Redis键
     * @param hKeys Hash键集合
     * @return Hash对象集合
     */
    public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys) {
        return redisTemplate.opsForHash().multiGet(key, hKeys);
    }

    /**
     * 获得缓存的基本对象列表
     *
     * @param pattern 字符串前缀
     * @return 对象列表
     */
    public Collection<String> keys(final String pattern) {
        return redisTemplate.keys(pattern);
    }
}

创建自定义注解

下项目的包下创建annimates文件夹,用于存放自定义注解以及注解的实现类。

创建自定义缓存注解

annimates文件夹下创建注解,如下:


/**
 * redis 缓存注解
 */
@Target({ ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RedisCache {

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

    /**
     *  key名的后缀
     */
    String keySuffix() default "";

    /**
     *  key名的后缀使用指定第几个参数的值作为key后缀,参数从0开始。-1(默认)表示不用参数值作为key后缀值
     */
    int keySuffixIsParamIndex() default -1;

    /**
     * 缓存的key 前缀,
     */
    String keyPrefix() default "";

    /**
     * 未指定前缀时,是否在key之前插入包名+方法名作为前缀
     */
    boolean keyPrefixIsFunctionName() default true;

    /**
     * key的分隔符
     */
    String keySplitStr() default "::";

    /**
     * 缓存时长
     */
    long time() default 60L;

    /**
     * 缓存时长单位
     * @return
     */
    TimeUnit type() default TimeUnit.SECONDS;

    /**
     * 操作类型
     * select : 查询
     * update:更新
     * insert:插入
     * delete:删除
     * selectOrInsert: (默认)
     */
    String optionType() default "selectOrInsert";


}

自定义缓存注解的逻辑实现


@Aspect
@Slf4j
@Component
public class RedisCacheAopHandler {
    @Pointcut("@annotation(com.zhang.annimates.RedisCache)")
    public void annotationPoint() throws Throwable {
    }
	/**
	* 注入redis操作对象
	*/
    @Autowired
    RedisService redisService;

    @SneakyThrows
    @Around(value = "annotationPoint() && @annotation(redisCache)",argNames = "proceedingJoinPoint,redisCache")
    public Object around(ProceedingJoinPoint proceedingJoinPoint,RedisCache redisCache){
        String redisKey = createKeyName(proceedingJoinPoint, redisCache);
        Object cacheObject = redisService.getCacheObject(redisKey);

        System.out.println("key=>"+redisKey);
        if (Objects.isNull(cacheObject)){
            // 不存在缓存中
            Object methodResult = proceedingJoinPoint.proceed(); //继续执行使用注解的方法内的代码逻辑, 得到方法的返回值
            //方法的返回值存入缓存中
            redisService.setCacheObject(redisKey,methodResult,redisCache.time(),redisCache.type());
            log.info("不存在缓存中,存入缓存:"+methodResult.toString());
            return methodResult; 
        }else{
            log.info("存在缓存中,缓存取出:"+cacheObject.toString());
            // 缓存命中后,直接返回缓存中的数据。并不执行方法内的逻辑。
            return cacheObject;
        }
    }

    /***
     * 生成缓存的 key 规则
     * @param redisCache
     * @return
     */
    private String createKeyName(ProceedingJoinPoint proceedingJoinPoint,RedisCache redisCache){
        String key = redisCache.key(); //key
        String keySuffix = redisCache.keySuffix(); //key名的后缀
        int keySuffixIsParamIndex = redisCache.keySuffixIsParamIndex(); //key名的后缀使用指定第几个参数的值作为key后缀,
        String keyPrefix = redisCache.keyPrefix();//缓存的key 前缀,
        boolean keyPrefixIsFunctionName = redisCache.keyPrefixIsFunctionName(); //未指定前缀时,是否在key之前插入包名+方法名作为前缀
        String keySplitStr = redisCache.keySplitStr(); //key的分隔符

        StringJoiner stringJoiner = new StringJoiner(keySplitStr);
        if (StringUtils.hasText(key) && keyPrefixIsFunctionName != true){
            // 前缀
            if (StringUtils.hasText(keyPrefix)){
                stringJoiner.add(keyPrefix);
            }

            //后缀
            stringJoiner.add(key);
            if (StringUtils.hasText(keySuffix)){
                stringJoiner.add(keySuffix);
            }else if (keySuffixIsParamIndex >= 0){
                Object[] args = proceedingJoinPoint.getArgs();
                if (Objects.isNull(args) == false && args.length > 0 && keySuffixIsParamIndex <= args.length-1){
                    stringJoiner.add(args[keySuffixIsParamIndex].toString());
                }
            }
        }else if (keyPrefixIsFunctionName == true){
            //未指定前缀以及key,则使用当前注解所在的包名+方法名+[后缀|参数位置值]作为key
            String packageName = proceedingJoinPoint.getSignature().getDeclaringTypeName();//获取注解所在的包和类名
            String functionName = proceedingJoinPoint.getSignature().getName(); //获取注解作用的方法名称
            stringJoiner.add(packageName+"."+functionName);

            // 后缀
            if (StringUtils.hasText(keySuffix)){
                stringJoiner.add(keySuffix);
            }else if (keySuffixIsParamIndex >= 0){
                Object[] args = proceedingJoinPoint.getArgs();
                if (Objects.isNull(args) == false && args.length > 0 && keySuffixIsParamIndex <= args.length-1){
                    stringJoiner.add(args[keySuffixIsParamIndex].toString());
                }
            }
        }
        return stringJoiner.toString();
    }
}

在方法上使用自定义注解

创建一个TestController控制器,并在此创建一个test方法,在此方法上使用注解

@RestController
@RequestMapping(value = "/testController")
public class TestController {
	
    @RequestMapping(value = "/test")
     @RedisCache(keySplitStr = "::",time = 1L,type = TimeUnit.DAYS)
    public String test(@RequestParam(value = "id")Integer id)throws Exception{
    	// todo 缓存未命中时的处理逻辑,用于数据查询,缓存命中后,不执行以下的代码。
    	
        System.out.println("======分隔线,缓存未命中则会运行到此=======");
        return "这是查询出存入缓存的数据";
    }
}

运行结果

缓存中无数据

控制台输出:

key=>com.zhang.controller.TestController.test
======分隔线,缓存未命中则会运行到此=======
不存在缓存中,存入缓存:这是查询出存入缓存的数据

缓存中有数据

控制台输出:

key=>com.zhang.controller.TestController.test 
存在缓存中,缓存取出:这是查询出存入缓存的数据

缓存命中后,并不会执行test方法中的 System.out.println(“分隔线,缓存未命中则会运行到此=”); 代码。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小张帅三代

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值