0
点赞
收藏
分享

微信扫一扫

自定义注解缓存feign结果

落拓尘嚣 2022-02-08 阅读 118

一、背景

在springcloud微服务架构中,可能会经常通过 feign 组件调用其它的微服务,feign的底层其实是模拟一个http请求,通过访问接口的方式调用远程服务,要经历三次握手建立TCP连接,在项目中是一个比较“耗时”的操作。
如果经常请求一些很少变动的数据,或者在一定时间段内可容忍已过期的数据,那么则需要在调用feign之前能不能从缓存中获取,可以自定义注解,将feign返回的结果缓存到redis中一段时间。

二、自定义注解

1 - 定义一个注解标记需要缓存的方法

package cn.wework.backend.aspect;

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

/**
 * 此注解用于缓存feign rpc调用的结果,由于远程调用比较耗时,并且大多数情况下返回的结果都不会改变
 * 在可容忍的时间内缓存结果提升前端响应速度
 * @see FeignClientAspect
 * @author Lianghao Teng
 * @date 2021/9/23 10:00 AM
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FeignCache {

    /**
     * 指定缓存失效时间
     * 注意:所有的缓存都应该指定一个合适的缓存时间,项目启动时会自动删除feign-client的缓存
     * 如果不指定,则缓存将一直存在,直到下次项目重启
     * @return 缓存失效时间,单位:秒
     */
    long expiresIn() default -1;
}

2 - 定义切面

package cn.wework.backend.aspect;

import cn.hutool.extra.spring.SpringUtil;
import cn.wework.backend.common.constant.GlobalConstant;
import cn.wework.backend.util.RedisUtil;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
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.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.interceptor.SimpleKeyGenerator;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.Set;

/**
 * @author lianghaoteng
 */
@Aspect
@Component
@Slf4j
public class FeignClientAspect implements InitializingBean {

    @Autowired
    private RedisUtil redisUtil;

    private static final String FEIGN_CLIENT_CACHE_PREFIX = "FEIGN-CLIENT-CACHE:";

    /**
     * 缓存rpc调用的环绕通知
     * @param joinPoint
     * @return 目标方法调用的结果
     * @throws Throwable
     */
    @Around("@annotation(cn.wework.backend.aspect.FeignCache)")
    public Object cacheAround(ProceedingJoinPoint joinPoint) throws Throwable {
        String key = FEIGN_CLIENT_CACHE_PREFIX + joinPoint.getSignature().getDeclaringTypeName() + "#" + joinPoint.getSignature().getName() + "#" + SimpleKeyGenerator.generateKey(joinPoint.getArgs());
        Object cache = redisUtil.get(key);
        if (cache != null) {
            return cache;
        }

        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        FeignCache annotation = signature.getMethod().getAnnotation(FeignCache.class);
		// 这里可以对结果进行过滤,缓存正确的结果,视具体的业务逻辑来定
        cache = joinPoint.proceed(joinPoint.getArgs());
        redisUtil.set(key, cache, annotation.expiresIn());
        return cache;
    }

	/**
	这个方法的作用是:启动项目之前先把feign的缓存都清除掉,视具体情况而定
	*/
    @Override
    public void afterPropertiesSet() {
        RedisTemplate<String, Object> redisTemplate = redisUtil.getRedisTemplate();
        Set<String> keys = redisTemplate.keys(FEIGN_CLIENT_CACHE_PREFIX + "*");
        if (keys != null) {
            Long count = redisTemplate.delete(keys);
            log.info("feign cache has bean deleted, count = {}", count);
        }

    }
}

3 - redis操作工具类

package cn.wework.backend.util;


import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * @author lianghaoteng
 */
public class RedisUtil {
    private RedisTemplate<String, Object> redisTemplate;

    public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    public RedisTemplate<String, Object> getRedisTemplate() {
        return redisTemplate;
    }

    /**
     * 判断key是否存在
     *
     * @param key 键
     * @return true 存在 false不存在
     */
    public boolean hasKey(String key) {
        try {
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 删除缓存
     * @param key 可以传一个值 或多个
     */
    @SuppressWarnings("unchecked")
    public void del(String... key) {
        if (key != null && key.length > 0) {
            if (key.length == 1) {
                redisTemplate.delete(key[0]);
            } else {
                redisTemplate.delete(CollectionUtils.arrayToList(key));
            }
        }
    }

    // ============================String=============================
    /**
     * 普通缓存获取
     *
     * @param key  键
     * @return 值
     */
    public Object get(String key) {
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }

    /**
     * 普通缓存放入
     *
     * @param key   键
     * @param value 值
     * @return true成功 false失败
     */
    public boolean set(String key, Object value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }

    }

    /**
     * 普通缓存放入并设置时间
     *
     * @param key 键
     * @param value  值
     * @param time  时间(秒) time要大于0 如果time小于等于0 将设置无限期
     * @return true成功 false 失败
     */
    public boolean set(String key, Object value, long time) {
        try {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            } else {
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 获取hashKey对应的所有键值
     *
     * @param key 键
     * @return 对应的多个键值
     */
    public Map<Object, Object> hmget(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * HashSet
     *
     * @param key  键
     * @param map  对应多个键值
     * @return true 成功 false 失败
     */
    public boolean hmset(String key, Map<String, Object> map) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 根据key 获取过期时间
     *
     * @param key 键 不能为null
     * @return 时间(秒) 返回0代表为永久有效
     */
    public long getExpire(String key) {
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }

}

三、使用

以上工作做完之后,使用起来就非常方便了,直接在需要缓存的方法上使用注解即可

	/**
     * 查询city
     * @param identity
     * @return
     */
    @GetMapping("/api/v1/city/{identity}")
    @FeignCache(expiresIn = 2 * 60 * 60)
    Response<MulanCityDTO> getCity(@PathVariable(value = "identity") String identity);
举报

相关推荐

0 条评论