CacheInterceptor.java

本文详细介绍了一种基于Spring框架的自定义缓存解决方案,通过创建自定义的Cacheable注解和实现相应的缓存管理器,实现了更灵活的缓存控制。文章深入探讨了如何利用Redis作为缓存存储,并通过Spring AOP实现缓存的读取和写入操作。

项目中基本上都需要使用到Cache的功能, 但是Spring提供的Cacheable并不能很好的满足我们的需求, 所以这里自己借助Spring思想完成自己的业务逻辑. 

  • 定义Cacheable注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Cacheable {

    RedisKey value();

    String key();
}
  • 定义Rediskey.java
public enum RedisKeyEnum {

    TEST_CACHE("test:", 24, TimeUnit.HOURS, "Test");

    /**
     * 缓存Key的前缀
     */
    private String keyPrefix;

    /**
     * 过期时间
     */
    private long timeout;

    /**
     * 过期时间单位
     */
    private TimeUnit timeUnit;

    /**
     * 描述
     */
    private String desc;

    private static final String REDIS_KEY_DEFUALT_SEPARATOR = ":";

    RedisKey(String keyPrefix, long timeout, TimeUnit timeUnit, String desc){
        this.keyPrefix = keyPrefix;
        this.timeout = timeout;
        this.timeUnit = timeUnit;
        this.desc = desc;
    }

    public long getTimeout() {
        return timeout;
    }

    public TimeUnit getTimeUnit() {
        return timeUnit;
    }

    public String getDesc() {
        return desc;
    }

    /**
     * 获取完整的缓存Key
     * @param keys
     * @return
     */
    public String getKey(String... keys) {
        if(keys == null || keys.length <= 0){
            return this.keyPrefix;
        }
        String redisKey = keyPrefix;
        for (int i = 0, length = keys.length; i < length; i++) {
            String key = keys[i];
            redisKey += key;
            if (i < length - 1) {
                redisKey += REDIS_KEY_DEFUALT_SEPARATOR;
            }
        }
        return redisKey;
    }
}
  • Cache.java
public interface Cache<K, V> {

    /**
     * 返回缓存名称
     * @return
     */
    String getName();

    /**
     * 添加一个缓存实例
     *
     * @param key
     * @param value
     */
    V put(K key, V value);

    /**
     * 添加一个可过期的缓存实例
     * @param key
     * @param value
     * @param expire
     * @param timeUnit
     * @return
     */
    V put(K key, V value, long expire, TimeUnit timeUnit);

    /**
     * 返回缓存数据
     *
     * @param key
     * @return
     */
    V get(K key);

    /**
     * 删除一个缓存实例, 并返回缓存数据
     *
     * @param key
     * @return
     */
    void remove(K key);

    /**
     * 获取所有的缓存key
     * @return
     */
    Set<K> keys();

    /**
     * 获取所有的缓存key
     * @return
     */
    Set<K> keys(K pattern);

    /**
     * 获取所有的缓存数据
     * @return
     */
    Collection<V> values();

    /**
     * 清空所有缓存
     */
    void clear();
}
  • RedisCache.java
public class RedisCache<K, V> implements Cache<K, V> {

    public static final String DEFAULT_CACHE_NAME =  RedisCache.class.getName() + "_CACHE_NAME";

    private RedisTemplate<K, V> redisTemplate;

    private ValueOperations<K, V> valueOperations;

    public RedisCache(RedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
        this.valueOperations = redisTemplate.opsForValue();
        DataType dataType = redisTemplate.type("a");
    }

    @Override
    public String getName() {
        return DEFAULT_CACHE_NAME;
    }

    @Override
    public V put(K key, V value) {
        valueOperations.set(key, value);
        return value;
    }

    @Override
    public V put(K key, V value, long expire, TimeUnit timeUnit) {
        valueOperations.set(key, value, expire, timeUnit);
        return value;
    }

    @Override
    public V get(K key) {
        return valueOperations.get(key);
    }

    @Override
    public void remove(K key) {
//        V value = valueOperations.get(key);
        redisTemplate.delete(key);
    }

    @Override
    public Set<K> keys() {
        return null;
    }

    @Override
    public Set<K> keys(K pattern) {
        return redisTemplate.keys(pattern);
    }

    @Override
    public Collection<V> values() {
        return null;
    }

    @Override
    public void clear() {

    }
}
  • CacheManager.java
public interface CacheManager {

    /**
     * 获取缓存
     * @return
     */
    Cache getCache(String name);

    /**
     * 获取所有的缓存名称
     */
    Collection<String> getCacheNames();

}
  • AbstractCacheManager.java
public abstract class AbstractCacheManager implements CacheManager, InitializingBean, DisposableBean {

    private static final Logger logger = LoggerFactory.getLogger(AbstractCacheManager.class);

    private final Map<String, Cache> cacheMap = new ConcurrentHashMap<>(16);

    private volatile Set<String> cacheNames = Collections.emptySet();

    private static final String DEFAULT_CACHE_NAME_SUFFIX = "_CACHE_NAME";

    @Override
    public void afterPropertiesSet() throws Exception {
        initlalizingCache();
    }

    private void initlalizingCache(){
        Collection<? extends Cache> caches = loadCaches();
        synchronized (this.cacheMap) {
            this.cacheNames = Collections.emptySet();
            this.cacheMap.clear();
            Set<String> cacheNames = new LinkedHashSet<String>(caches.size());
            for (Cache cache : caches) {
                String name = cache.getName();
                if(StringUtils.isEmpty(name)){
                    name = cache.getClass().getName() + DEFAULT_CACHE_NAME_SUFFIX;
                }
                this.cacheMap.put(name, cache);
                cacheNames.add(name);
            }
            this.cacheNames = Collections.unmodifiableSet(cacheNames);
        }
    }

    @Override
    public Cache getCache(String name) {
        Cache cache = cacheMap.get(name);
        if(cache != null){
            return cache;
        }
        return null;
    }

    protected abstract Collection<? extends Cache> loadCaches();

    @Override
    public Collection<String> getCacheNames() {
        return this.cacheNames;
    }

    @Override
    public void destroy() throws Exception {
        cacheMap.clear();
    }
}
  • RedisCacheManager.java
public class RedisCacheManager extends AbstractCacheManager {

    private RedisTemplate redisTemplate;

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


    @Override
    protected Collection<? extends Cache> loadCaches() {
        Collection<Cache<String, Object>> caches = new ArrayList<>();
        RedisCache<String, Object> redisCache = new RedisCache<>(redisTemplate);
        caches.add(redisCache);
        return caches;
    }
}
  • 实现CacheInterceptor.java
/**
 * 缓存数据过滤器, 缓存到redis数据中的数据是ServiceResult.getDateMap()数据
 * 使用: 在service方法上添加com.chinaredstar.urms.annotations.Cacheable注解, 并指定RedisKeyEunm和cache key, cache key支持Spel表达式
 * 以下情况不缓存数据:
 *  1: 返回状态为fasle时, 不缓存数据
 *  2: 返回dataMap为空时, 不缓存数据
 *  3: 返回数据结构不是ServiceReslut实例时, 不缓存数据
 *
 * 当缓存问题时, 不影响正常业务, 但所有的请求都会打到DB上, 对DB有很大的冲击
 */
public class CacheInterceptor implements MethodInterceptor {

    private static final Logger logger = LoggerFactory.getLogger(CacheInterceptor.class);

    private static final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();

    private CacheManager cacheManager;

    public void setCacheManager(CacheManager cacheManager) {
        this.cacheManager = cacheManager;
    }

    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        Method method = methodInvocation.getMethod();
        Object[] args = methodInvocation.getArguments();
        Cacheable cacheable = method.getAnnotation(Cacheable.class);
        if (cacheable == null) {
            return methodInvocation.proceed();
        }
        String key = parseCacheKey(method, args, cacheable.key());
        logger.info(">>>>>>>> -- 获取缓存key : {}", key);
        if(StringUtils.isEmpty(key)){
            return methodInvocation.proceed();
        }
        RedisKey redisKey = cacheable.value();
        Cache cache = cacheManager.getCache(RedisCache.DEFAULT_CACHE_NAME);
        Object value = null;
        try{
            value = cache.get(redisKey.getKey(key));
        } catch (Exception e){
            logger.info(">>>>>>>> -- 从缓存中获取数据异常 : {}", ExceptionUtil.exceptionStackTrace(e));
        }
        if (value != null) {
            logger.info(">>>>>>>> -- 从缓存中获取数据 : {}", JsonUtil.toJson(value));
            return ServiceResult.newInstance(true, value);
        }
        value = methodInvocation.proceed();
        logger.info(">>>>>>>> -- 从接口中获取数据 : {}", JsonUtil.toJson(value));
        if ( value != null && value instanceof ServiceResult ) {
            ServiceResult result = (ServiceResult) value;
            if(!result.isSuccess() || result.getDataMap() == null){
                return value;
            }
            try{
                cache.put(redisKey.getKey(key), result.getDataMap(), redisKey.getTimeout(), redisKey.getTimeUnit());
            } catch (Exception e){
                logger.info(">>>>>>>> -- 将数据放入缓存异常 : {}", ExceptionUtil.exceptionStackTrace(e));
            }
        }
        return value;
    }

    /**
     * 使用SpeL解析缓存key
     * @param method
     * @param args
     * @param expressionString
     * @return
     */
    private String parseCacheKey(Method method, Object[] args, String expressionString) {
        String[] parameterNames = parameterNameDiscoverer.getParameterNames(method);
        EvaluationContext context = new StandardEvaluationContext();
        if (parameterNames != null && parameterNames.length > 0
                && args != null && args.length > 0
                && args.length == parameterNames.length ) {
            for (int i = 0, length = parameterNames.length; i < length; i++) {
                context.setVariable(parameterNames[i], args[i]);
            }
        }
        ExpressionParser parser = new SpelExpressionParser();
        Expression expression = parser.parseExpression(expressionString);
        return (String) expression.getValue(context);
    }
}
  • 配置Spring.xml
    <bean id="redisCacheManager" class="com.package.cache.RedisCacheManager">
        <constructor-arg ref="cacheRedisTemplate" />
    </bean> 

    <bean id="cacheInterceptor" class="com.package.interceptor.CacheInterceptor" p:cacheManager-ref="redisCacheManager"/>

    <!-- 方法拦截器 MethodInterceptor -->
    <aop:config proxy-target-class="true">
        <aop:pointcut id="cacheInterceptorPointcut" expression="execution(* com.package..*(..))
                                                                and @annotation(com.package.annotations.Cacheable)"/>
        <aop:advisor advice-ref="cacheInterceptor" pointcut-ref="cacheInterceptorPointcut" order="2" />
    </aop:config>
  • 测试使用
@Cacheable(value = RedisKey.TEST_CACHE, key = "#code + ':' + #user.id")
public ServiceResult<String> test(String code, User user){

    return new ServiceResult("success");
}
  • 说明

       Cacheable其中的参数key拼接的规则支持Spring SpeL表达式。其规则和Spring Cacheable使用方法一致。

--------- JobThread Exception:java.lang.reflect.InvocationTargetException at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:568) at com.xxl.job.core.handler.impl.MethodJobHandler.execute(MethodJobHandler.java:31) at com.xxl.job.core.thread.JobThread.run(JobThread.java:166) Caused by: feign.RetryableException: zhny-agg executing GET at feign.FeignException.errorExecuting(FeignException.java:300) at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:105) at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:53) at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:104) at org.springframework.cloud.openfeign.FeignCachingInvocationHandlerFactory$1.proceed(FeignCachingInvocationHandlerFactory.java:66) at org.springframework.cache.interceptor.CacheInterceptor.lambda$invoke$0(CacheInterceptor.java:55) at org.springframework.cache.interceptor.CacheAspectSupport.invokeOperation(CacheAspectSupport.java:431) at org.springframework.cache.interceptor.CacheAspectSupport.execute(CacheAspectSupport.java:416) at org.springframework.cache.interceptor.CacheInterceptor.invoke(CacheInterceptor.java:65) at org.springframework.cloud.openfeign.FeignCachingInvocationHandlerFactory.lambda$create$1(FeignCachingInvocationHandlerFactory.java:53) at jdk.proxy2/jdk.proxy2.$Proxy205.powerToDlBySubtype(Unknown Source) at com.qctc.eboss.service.rec.job.subtype.PowerToDlBySubtypeHandler.execute(PowerToDlBySubtypeHandler.java:63) ... 6 more Caused by: java.net.UnknownHostException: zhny-agg at java.base/sun.nio.ch.NioSocketImpl.connect(NioSocketImpl.java:572) at java.base/java.net.Socket.connect(Socket.java:633) at java.base/sun.net.NetworkClient.doConnect(NetworkClient.java:178) at java.base/sun.net.www.http.HttpClient.openServer(HttpClient.java:534)服务发现配置的正确方式是什么,怎么配能解决这个问题
08-27
feign.RetryableException: connect timed out executing POST http://ruoyi-system/logininfor at feign.FeignException.errorExecuting(FeignException.java:268) at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:131) at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:91) at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:100) at org.springframework.cloud.openfeign.FeignCachingInvocationHandlerFactory$1.proceed(FeignCachingInvocationHandlerFactory.java:66) at org.springframework.cache.interceptor.CacheInterceptor.lambda$invoke$0(CacheInterceptor.java:54) at org.springframework.cache.interceptor.CacheAspectSupport.execute(CacheAspectSupport.java:351) at org.springframework.cache.interceptor.CacheInterceptor.invoke(CacheInterceptor.java:64) at org.springframework.cloud.openfeign.FeignCachingInvocationHandlerFactory.lambda$create$1(FeignCachingInvocationHandlerFactory.java:53) at com.sun.proxy.$Proxy124.saveLogininfor(Unknown Source) at com.ruoyi.auth.service.SysRecordLogService.recordLogininfor(SysRecordLogService.java:47) at com.ruoyi.auth.service.SysLoginService.login(SysLoginService.java:122) at com.ruoyi.auth.controller.TokenController.login(TokenController.java:62) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205) at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150) at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHand
最新发布
09-06
2025-08-26 17:16:39 [com.xxl.job.core.thread.JobThread#run]-[204]-[xxl-job, JobThread-19603-1756199760005] ----------- JobThread Exception:java.lang.reflect.InvocationTargetException at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:568) at com.xxl.job.core.handler.impl.MethodJobHandler.execute(MethodJobHandler.java:31) at com.xxl.job.core.thread.JobThread.run(JobThread.java:166) Caused by: feign.RetryableException: zhny-agg executing GET http://zhny-agg/daqarccompanyday/v1/powerToDlBySubtype?subtype=313&dataday=20250826 at feign.FeignException.errorExecuting(FeignException.java:300) at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:105) at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:53) at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:104) at org.springframework.cloud.openfeign.FeignCachingInvocationHandlerFactory$1.proceed(FeignCachingInvocationHandlerFactory.java:66) at org.springframework.cache.interceptor.CacheInterceptor.lambda$invoke$0(CacheInterceptor.java:55) at org.springframework.cache.interceptor.CacheAspectSupport.invokeOperation(CacheAspectSupport.java:431) at org.springframework.cache.interceptor.CacheAspectSupport.execute(CacheAspectSupport.java:416) at org.springframework.cache.interceptor.CacheInterceptor.invoke(CacheInterceptor.java:65) at org.springframework.cloud.openfeign.FeignCachingInvocationHandlerFactory.lambda$create$1(FeignCachingInvocationHandlerFactory.java:53) at jdk.proxy2/jdk.proxy2.$Proxy205.powerToDlBySubtype(Unknown Source) at com.qctc.eboss.service.rec.job.subtype.PowerToDlBySubtypeHandler.execute(PowerToDlBySubtypeHandler.java:63) ... 6 more Caused by: java.net.UnknownHostException: zhny-agg at java.base/sun.nio.ch.NioSocketImpl.connect(NioSocketImpl.java:572) at java.base/java.net.Socket.connect(Socket.java:633) at java.base/sun.net.NetworkClient.doConnect(NetworkClient.java:178) spring boot2,spring cloud2022时,在yml中添加zhny-agg: zhny-publish-test可使请求目标服务由zhny-agg变为zhny-publish-test,现系统升级spring boot3,spring cloud2024后这个配置不生效导致请求不通,应该怎么修改以实现原配置效果
08-27
E FATAL EXCEPTION: OkHttp Dispatcher Process: com.example.kucun2, PID: 498 java.lang.SecurityException: Permission denied (missing INTERNET permission?) at java.net.Inet6AddressImpl.lookupHostByName(Inet6AddressImpl.java:150) at java.net.Inet6AddressImpl.lookupAllHostAddr(Inet6AddressImpl.java:103) at java.net.InetAddress.getAllByName(InetAddress.java:1152) at okhttp3.Dns.lambda$static$0(Dns.java:39) at okhttp3.Dns$$ExternalSyntheticLambda0.lookup(D8$$SyntheticClass:0) at okhttp3.internal.connection.RouteSelector.resetNextInetSocketAddress(RouteSelector.java:171) at okhttp3.internal.connection.RouteSelector.nextProxy(RouteSelector.java:135) at okhttp3.internal.connection.RouteSelector.next(RouteSelector.java:84) at okhttp3.internal.connection.ExchangeFinder.findConnection(ExchangeFinder.java:187) at okhttp3.internal.connection.ExchangeFinder.findHealthyConnection(ExchangeFinder.java:108) at okhttp3.internal.connection.ExchangeFinder.find(ExchangeFinder.java:88) at okhttp3.internal.connection.Transmitter.newExchange(Transmitter.java:169) at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.java:41) at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142) at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:117) at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.java:94) at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142) at okhttp3.internal.http.RealInterceptorChain.proceed(RealInt
06-05
"EL1008E: Property or field 'creNo_' cannot be found on object of type 'com.joyintech.entity.cust.CustInfo' - maybe not public or not valid?[org.springframework.expression.spel.ast.PropertyOrFieldReference.readProperty(PropertyOrFieldReference.java:223), org.springframework.expression.spel.ast.PropertyOrFieldReference.getValueInternal(PropertyOrFieldReference.java:106), org.springframework.expression.spel.ast.PropertyOrFieldReference.access$000(PropertyOrFieldReference.java:53), org.springframework.expression.spel.ast.PropertyOrFieldReference$AccessorLValue.getValue(PropertyOrFieldReference.java:412), org.springframework.expression.spel.ast.CompoundExpression.getValueInternal(CompoundExpression.java:93), org.springframework.expression.spel.ast.OpPlus.getValueInternal(OpPlus.java:95), org.springframework.expression.spel.ast.SpelNodeImpl.getValue(SpelNodeImpl.java:114), org.springframework.expression.spel.standard.SpelExpression.getValue(SpelExpression.java:273), org.springframework.cache.interceptor.CacheOperationExpressionEvaluator.key(CacheOperationExpressionEvaluator.java:104), org.springframework.cache.interceptor.CacheAspectSupport$CacheOperationContext.generateKey(CacheAspectSupport.java:795), org.springframework.cache.interceptor.CacheAspectSupport.generateKey(CacheAspectSupport.java:591), org.springframework.cache.interceptor.CacheAspectSupport.performCacheEvict(CacheAspectSupport.java:508), org.springframework.cache.interceptor.CacheAspectSupport.processCacheEvicts(CacheAspectSupport.java:492), org.springframework.cache.interceptor.CacheAspectSupport.execute(CacheAspectSupport.java:433), org.springframework.cache.interceptor.CacheAspectSupport.execute(CacheAspectSupport.java:345), org.springframework.cache.interceptor.CacheInterceptor.invoke(CacheInterceptor.java:64), org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186), org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAo
08-13
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值