早些前写了Cacheable的自定义缓存啥的, 这次优化了一下Cacheable注解错误时缓存

需缓存的方法

@Cacheable(key = "#cardNo + '_' + #unifTranId", cacheNames = "bill.usTrade")
public TradeSkinResDto<StmtTradeResDto> usTrade(String cardNo, String unifTranId) {
    return null;
}

AOP

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

/**
 * redis缓存调用切面
 * <p>
 * 由于服务调用时, 返回的可能是错误的信息, 因此需要将错误信息也缓存起来
 * <p>
 * 方法中使用这个 @Cacheable 注解, 就会处理返回的参数
 *
 * @author roothk
 * @date 2020/4/23 8:53
 */
@Slf4j
@Order(1)
@Aspect
@Component
public class CacheableHandlerAspect {

    @Autowired
    private CacheableUtil cacheableUtil;

    @Pointcut("@annotation(org.springframework.cache.annotation.Cacheable)")
    public void feignAspectPointCut() {
    }

    /**
     * 环绕通知 @Around  , 当然也可以使用 @Before (前置通知)  @After (后置通知)
     *
     * @param point
     * @return
     * @throws Throwable
     */
    @Around("feignAspectPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        Cacheable cacheable = cacheableUtil.getCacheable(point);
        String cacheName = cacheable.cacheNames()[0];
        String key = cacheableUtil.getValueByKey(cacheable.key(), point);

        try {
            // 错误处理
            handlerException(key, cacheName);

            Object o = point.proceed();
            // 其他正常返回, 如空对象
            if (EsbThreadLocal.get()) {
                // 也依旧缓存, 防止缓存穿透
                cacheableUtil.saveCache(o, key, cacheName);
            }
            return o;
        } catch (EsbException e) {
            cacheableUtil.saveCache(e, key, cacheName);
            throw e;
        }
    }

    /**
     * 如果缓存报错的是Exception, 则直接抛出Exception
     *
     * @param key
     * @param cacheName
     */
    private void handlerException(Object key, String cacheName) {
        Object o = cacheableUtil.getCache(key, cacheName);
        if (o == null) {
            return;
        }
        // 如果是错误就直接输出
        if (o instanceof EsbException) {
            throw (EsbException) o;
        } else if (o instanceof EsbHystrixException) {
            throw (EsbHystrixException) o;
        } else if (o instanceof EsbServiceException) {
            throw (EsbServiceException) o;
        }
    }

}

CacheableUtil工具类

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

/**
 * @author RootHK
 */
@Slf4j
@Component
public class CacheableUtil {

    private final SpelExpressionParser parserSpel = new SpelExpressionParser();
    private final DefaultParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();

    // 错误时的缓存时间
    @Value("${esb.cache.not.result.time}")
    private Long exceptionTimeout;

    @Autowired
    private CacheManager cacheManager;
    @Autowired
    private RedisCacheManager redisCacheManager;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 获取方法上的 @Cacheable
     *
     * @param point
     * @return
     */
    public Cacheable getCacheable(ProceedingJoinPoint point) {
        Signature signature = point.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method targetMethod = methodSignature.getMethod();
        return targetMethod.getAnnotation(Cacheable.class);
    }

    /**
     * 获取Spring Sl语法的值
     * @param key
     * @param pjp
     * @return
     */
    public String getValueByKey(String key, ProceedingJoinPoint pjp) {
        MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
        return getValueByKey(key, methodSignature.getMethod(), pjp.getArgs());
    }

    /**
     * 获取Spring Sl语法的值
     * @param key
     * @param m
     * @param args
     * @return
     */
    public String getValueByKey(String key, Method m, Object[] args) {
        Expression expression = parserSpel.parseExpression(key);
        EvaluationContext context = new StandardEvaluationContext();
        String[] paramNames = parameterNameDiscoverer.getParameterNames(m);
        for (int i = 0; i < args.length; i++) {
            if (paramNames == null) {
                continue;
            }
            context.setVariable(paramNames[i], args[i]);
        }
        Object o = expression.getValue(context);
        return o != null ? o.toString() : null;
    }

    public Object getCache(Object key, String cacheName) {
        // 获取指定命名空间的cache
        Cache cache = cacheManager.getCache(cacheName);
        if (cache == null) {
            if (log.isDebugEnabled()) {
                log.debug("------ 获取缓存: {} 失败", cacheName);
            }
            return null;
        }
        Cache.ValueWrapper wrapper = cache.get(key);
        if (wrapper == null) {
            // 空的
            return null;
        }
        return wrapper.get();
    }

    public Cache getCache(String cacheName) {
        return cacheManager.getCache(cacheName);
    }

    public void saveCache(Object o, Object key, String cacheName) {
        // 获取指定命名空间的cache
        Cache cache = this.getCache(cacheName);
        if (cache == null) {
            log.warn("获得cacheName失败, {}", cacheName);
            return;
        }
        // 加入缓存
        cache.put(key, o);
        try {
            // 获取redis的配置
            RedisCacheConfiguration configuration = redisCacheManager.getCacheConfigurations().get(cacheName);
            String redisKey = configuration.getKeyPrefixFor(cacheName).concat(key.toString());
            log.info("save exception|not result redis, key:{} timeout:{}", redisKey, exceptionTimeout);
            stringRedisTemplate.expire(redisKey, exceptionTimeout, TimeUnit.SECONDS);
        } catch (Exception e) {
            e.printStackTrace();
            log.info("获取RedisCacheConfiguration失败", e);
        }
    }
}

缓存配置 CacheConfig


import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.core.StringRedisTemplate;

import java.time.Duration;
import java.util.Objects;

/**
 * redis 缓存设置
 * @author RootHK
 */
@Configuration
@EnableCaching
public class CacheConfig extends CachingConfigurerSupport {

    // 正常缓存的时间
    @Value("${esb.cache.result.time}")
    private Long timeout;

    /**
     * 设置缓存策略
     *
     * @param redisTemplate
     * @return
     */
    @Bean
    public CacheManager cacheManager(StringRedisTemplate redisTemplate) {
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                .prefixCacheNameWith(SystemConstant.REDIS_PREFIX)
                .entryTtl(Duration.ofSeconds(timeout));
        RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(Objects.requireNonNull(redisTemplate.getConnectionFactory()));
        return new RedisCacheManager(redisCacheWriter, redisCacheConfiguration);
    }

}

Mga Tag: none

Pagdugang og bag-ong komentaryo