searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

基于spring-cache实现多级缓存

2024-06-04 09:06:51
7
0

一、基于spring-cache实现多级缓存

  • 实现效果,完全兼用原有SpringCache缓存使用方式和相关注解
  • 通过自定义注解,驱逐的相关缓存层的数据
  • 无须后续再处理多级缓存逻辑

二、具体实现代码

1、引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
    <version>2.8.8</version>
</dependency>

2、自定义缓存控制器

package top.xy23.cache;
​
import lombok.Getter;
import lombok.NonNull;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
​
/**
 * 两级缓存控制器
 * 一级缓存:本地缓存
 * 二级缓存:Redis缓存
 * author: XiaoYang
 * date: 2024/5/31 下午5:16
 */
@Getter
public class TwoLevelCacheManager implements CacheManager {
    private static final Logger log = LoggerFactory.getLogger(TwoLevelCacheManager.class);
    private final CacheManager primaryCacheManager;
    private final CacheManager secondaryCacheManager;
​
    public TwoLevelCacheManager(CacheManager primary, CacheManager secondary) {
        this.primaryCacheManager = primary;
        this.secondaryCacheManager = secondary;
    }
​
    @Override
    public Cache getCache(@NonNull String name) {
        Cache primary = primaryCacheManager.getCache(name);
        Cache secondary = secondaryCacheManager.getCache(name);
        if (primary == null || secondary == null) {
            log.warn("未找到名为 {} 的缓存", name);
            return null;
        }
        return new TwoLevelCache(primary, secondary);
    }
​
    @Override
    public @NonNull Collection<String> getCacheNames() {
        List<String> cacheNames = new ArrayList<>();
        cacheNames.addAll(primaryCacheManager.getCacheNames());
        cacheNames.addAll(secondaryCacheManager.getCacheNames());
        return cacheNames;
    }
}

3、自定义缓存实现

package top.xy23.cache;
​
import lombok.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.Cache;
​
import java.util.concurrent.Callable;
​
/**
 * 缓存层
 * author: XiaoYang
 * date: 2024/6/4 下午2:01
 */
public class TwoLevelCache implements Cache {
    private static final Logger log = LoggerFactory.getLogger(TwoLevelCache.class);
​
    private final Cache primary;
    private final Cache secondary;
​
    public TwoLevelCache(Cache primary, Cache secondary) {
        this.primary = primary;
        this.secondary = secondary;
    }
​
    @Override
    public @NonNull String getName() {
        return primary.getName();
    }
​
    @Override
    public @NonNull Object getNativeCache() {
        return primary.getNativeCache();
    }
​
    @Override
    public ValueWrapper get(@NonNull Object key) {
        ValueWrapper value = primary.get(key);
        if (value == null) {
            value = secondary.get(key);
            if (value != null) {
                primary.put(key, value.get());
            }
        }
        return value;
    }
​
    @Override
    public <T> T get(@NonNull Object key, Class<T> type) {
        T value = primary.get(key, type);
        if (value == null) {
            value = secondary.get(key, type);
            if (value != null) {
                primary.put(key, value);
            }
        }
        return value;
    }
​
    @Override
    public @NonNull <T> T get(@NonNull Object key,@NonNull Callable<T> valueLoader) {
        T value = primary.get(key, valueLoader);
        if (value == null) {
            try {
                value = valueLoader.call();
                secondary.put(key, value);
                primary.put(key, value);
            } catch (Exception e) {
                log.error("加载缓存值失败,键为 {}", key, e);
                throw new ValueRetrievalException(key, valueLoader, e);
            }
        }
        return value;
    }
​
    @Override
    public void put(@NonNull Object key, Object value) {
        primary.put(key, value);
        secondary.put(key, value);
    }
​
    @Override
    public void evict(@NonNull Object key) {
        primary.evict(key);
        secondary.evict(key);
    }
​
    @Override
    public void clear() {
        primary.clear();
        secondary.clear();
    }
}

4、将自定义的缓存管理器注入Spring容器

package top.xy23.config;
​
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import top.xy23.cache.TwoLevelCacheManager;
​
import java.time.Duration;
​
@Configuration
@EnableCaching
public class CacheConfig {
​
    @Bean
    public Caffeine<Object, Object> caffeineConfig() {
        return Caffeine.newBuilder()
                .expireAfterWrite(Duration.ofSeconds(30))
                .maximumSize(100);
    }
​
    @Bean
    @Primary
    public TwoLevelCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory, Caffeine<Object, Object> caffeine) {
        // 本地缓存使用 Caffeine
        CaffeineCacheManager caffeineCacheManager = new CaffeineCacheManager();
        caffeineCacheManager.setCaffeine(caffeine);
​
        // redis缓存
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofHours(1));  // 设置默认的expire时间为1小时
        RedisCacheManager redisCacheManager = RedisCacheManager.builder(redisConnectionFactory)
                .cacheDefaults(config)
                .transactionAware()
                .build();
​
        // 自定义缓存管理器
        return new TwoLevelCacheManager(caffeineCacheManager, redisCacheManager);
    }
}

5、可选-自定义驱逐缓存注解-用于精细控制缓存层

5.1、自定义注解-TwoLevelCacheEvict

package top.xy23.cache.aop;
​
import org.springframework.core.annotation.AliasFor;
import top.xy23.cache.CacheType;
​
import java.lang.annotation.*;
​
/**
 * 自定义缓存驱逐器用于设置要驱逐的缓存
 * author: XiaoYang
 * date: 2024/6/4 上午8:42
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface TwoLevelCacheEvict {
    /**
     * 要驱逐的缓存类型,默认为全部
     */
    CacheType cacheType() default CacheType.ALL;
​
    /**
     * Alias for {@link #cacheNames}.
     */
    @AliasFor("cacheNames")
    String[] value() default {};
​
    /**
     * 用于缓存逐出操作的缓存的名称。
     * 名称可用于确定目标高速缓存(或高速缓存),与特定 Bean 定义的限定符值或 Bean 名称匹配
     */
    @AliasFor("value")
    String[] cacheNames() default {};
​
    /**
     Spring 表达式语言 (SpEL) 表达式,用于动态计算密钥。
     默认值为 "",表示除非设置了自定义 keyGenerator 量,否则所有方法参数都被视为键。
     SpEL 表达式根据提供以下元数据的专用上下文进行计算:
     #result作为对方法调用结果的引用,只有在 is false时才能使用beforeInvocation()。对于支持的包装器,例如 Optional,#result指的是实际对象,而不是包装器
     #root. method、 #root. target和 #root. caches 分别用于对 method、目标对象和受影响缓存的引用。
     方法名称 (#root. methodName) 和目标类 (#root. targetClass) 的快捷方式也可用。
     方法参数可以通过索引访问。例如,可以通过 或 #p1 #a1访问#root. args[1]第二个参数。如果该信息可用,也可以按名称访问参数。
     */
    String key() default "";
​
    /**
     * 是否删除缓存中的所有条目。
     * <p>默认情况下,仅删除关联键下的值。
     * <p>请注意,将此参数设置为 {@code true} 并指定
     * {@link #key} 是不允许的。
     */
    boolean allEntries() default false;
}

5.2、自定义切面-处理TwoLevelCacheEvict注解

package top.xy23.cache.aop;
​
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Component;
import top.xy23.cache.CacheType;
import top.xy23.cache.TwoLevelCacheManager;
​
/**
 * 自定义缓存清除切面
 * author: XiaoYang
 * date: 2024/6/4 上午8:58
 */
@Aspect
@Component
@Slf4j
public class TwoLeveCacheEvictAspect {
    @Autowired
    private TwoLevelCacheManager twoLevelCacheManager;
​
    @Around("@annotation(twoLevelCacheEvict)")
    public Object evictCache(ProceedingJoinPoint pjp, TwoLevelCacheEvict twoLevelCacheEvict) throws Throwable {
        evictCaches(twoLevelCacheEvict);
        return pjp.proceed();
    }
​
    private void evictCaches(TwoLevelCacheEvict twoLevelCacheEvict) {
        CacheManager cacheManager;
        CacheType cacheType = twoLevelCacheEvict.cacheType();
        switch (cacheType) {
            case PRIMARY:
                log.info("Evicting primary caches");
                cacheManager = twoLevelCacheManager.getPrimaryCacheManager();
                break;
            case SECONDARY:
                log.info("Evicting secondary caches");
                cacheManager = twoLevelCacheManager.getSecondaryCacheManager();
                break;
            case ALL:
                log.info("Evicting all caches");
                cacheManager = twoLevelCacheManager;
                break;
            default:
                throw new IllegalArgumentException();
        }
​
        for (String cacheName : twoLevelCacheEvict.cacheNames()) {
            Cache cache = cacheManager.getCache(cacheName);
            if (cache == null) {
                continue;
            }
            try {
                if (twoLevelCacheEvict.allEntries()) {
                    cache.clear();
                } else {
                    cache.evict(twoLevelCacheEvict.key());
                }
            } catch (Exception ignored) {
            }
        }
    }
}

5.3、缓存层级枚举类

package top.xy23.cache;
​
/**
 * author: XiaoYang
 * date: 2024/6/4 上午8:54
 */
public enum CacheType {
    /**
     * 全部缓存
     */
    ALL,
    /**
     * 主缓存/一级缓存
     */
    PRIMARY,
    /**
     * 二级缓存
     */
    SECONDARY;
}

6、未完-继续完善自定义put相关注解-用于精细控制缓存层

0条评论
0 / 1000
晓阳
1文章数
0粉丝数
晓阳
1 文章 | 0 粉丝
晓阳
1文章数
0粉丝数
晓阳
1 文章 | 0 粉丝
原创

基于spring-cache实现多级缓存

2024-06-04 09:06:51
7
0

一、基于spring-cache实现多级缓存

  • 实现效果,完全兼用原有SpringCache缓存使用方式和相关注解
  • 通过自定义注解,驱逐的相关缓存层的数据
  • 无须后续再处理多级缓存逻辑

二、具体实现代码

1、引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
    <version>2.8.8</version>
</dependency>

2、自定义缓存控制器

package top.xy23.cache;
​
import lombok.Getter;
import lombok.NonNull;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
​
/**
 * 两级缓存控制器
 * 一级缓存:本地缓存
 * 二级缓存:Redis缓存
 * author: XiaoYang
 * date: 2024/5/31 下午5:16
 */
@Getter
public class TwoLevelCacheManager implements CacheManager {
    private static final Logger log = LoggerFactory.getLogger(TwoLevelCacheManager.class);
    private final CacheManager primaryCacheManager;
    private final CacheManager secondaryCacheManager;
​
    public TwoLevelCacheManager(CacheManager primary, CacheManager secondary) {
        this.primaryCacheManager = primary;
        this.secondaryCacheManager = secondary;
    }
​
    @Override
    public Cache getCache(@NonNull String name) {
        Cache primary = primaryCacheManager.getCache(name);
        Cache secondary = secondaryCacheManager.getCache(name);
        if (primary == null || secondary == null) {
            log.warn("未找到名为 {} 的缓存", name);
            return null;
        }
        return new TwoLevelCache(primary, secondary);
    }
​
    @Override
    public @NonNull Collection<String> getCacheNames() {
        List<String> cacheNames = new ArrayList<>();
        cacheNames.addAll(primaryCacheManager.getCacheNames());
        cacheNames.addAll(secondaryCacheManager.getCacheNames());
        return cacheNames;
    }
}

3、自定义缓存实现

package top.xy23.cache;
​
import lombok.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.Cache;
​
import java.util.concurrent.Callable;
​
/**
 * 缓存层
 * author: XiaoYang
 * date: 2024/6/4 下午2:01
 */
public class TwoLevelCache implements Cache {
    private static final Logger log = LoggerFactory.getLogger(TwoLevelCache.class);
​
    private final Cache primary;
    private final Cache secondary;
​
    public TwoLevelCache(Cache primary, Cache secondary) {
        this.primary = primary;
        this.secondary = secondary;
    }
​
    @Override
    public @NonNull String getName() {
        return primary.getName();
    }
​
    @Override
    public @NonNull Object getNativeCache() {
        return primary.getNativeCache();
    }
​
    @Override
    public ValueWrapper get(@NonNull Object key) {
        ValueWrapper value = primary.get(key);
        if (value == null) {
            value = secondary.get(key);
            if (value != null) {
                primary.put(key, value.get());
            }
        }
        return value;
    }
​
    @Override
    public <T> T get(@NonNull Object key, Class<T> type) {
        T value = primary.get(key, type);
        if (value == null) {
            value = secondary.get(key, type);
            if (value != null) {
                primary.put(key, value);
            }
        }
        return value;
    }
​
    @Override
    public @NonNull <T> T get(@NonNull Object key,@NonNull Callable<T> valueLoader) {
        T value = primary.get(key, valueLoader);
        if (value == null) {
            try {
                value = valueLoader.call();
                secondary.put(key, value);
                primary.put(key, value);
            } catch (Exception e) {
                log.error("加载缓存值失败,键为 {}", key, e);
                throw new ValueRetrievalException(key, valueLoader, e);
            }
        }
        return value;
    }
​
    @Override
    public void put(@NonNull Object key, Object value) {
        primary.put(key, value);
        secondary.put(key, value);
    }
​
    @Override
    public void evict(@NonNull Object key) {
        primary.evict(key);
        secondary.evict(key);
    }
​
    @Override
    public void clear() {
        primary.clear();
        secondary.clear();
    }
}

4、将自定义的缓存管理器注入Spring容器

package top.xy23.config;
​
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import top.xy23.cache.TwoLevelCacheManager;
​
import java.time.Duration;
​
@Configuration
@EnableCaching
public class CacheConfig {
​
    @Bean
    public Caffeine<Object, Object> caffeineConfig() {
        return Caffeine.newBuilder()
                .expireAfterWrite(Duration.ofSeconds(30))
                .maximumSize(100);
    }
​
    @Bean
    @Primary
    public TwoLevelCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory, Caffeine<Object, Object> caffeine) {
        // 本地缓存使用 Caffeine
        CaffeineCacheManager caffeineCacheManager = new CaffeineCacheManager();
        caffeineCacheManager.setCaffeine(caffeine);
​
        // redis缓存
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofHours(1));  // 设置默认的expire时间为1小时
        RedisCacheManager redisCacheManager = RedisCacheManager.builder(redisConnectionFactory)
                .cacheDefaults(config)
                .transactionAware()
                .build();
​
        // 自定义缓存管理器
        return new TwoLevelCacheManager(caffeineCacheManager, redisCacheManager);
    }
}

5、可选-自定义驱逐缓存注解-用于精细控制缓存层

5.1、自定义注解-TwoLevelCacheEvict

package top.xy23.cache.aop;
​
import org.springframework.core.annotation.AliasFor;
import top.xy23.cache.CacheType;
​
import java.lang.annotation.*;
​
/**
 * 自定义缓存驱逐器用于设置要驱逐的缓存
 * author: XiaoYang
 * date: 2024/6/4 上午8:42
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface TwoLevelCacheEvict {
    /**
     * 要驱逐的缓存类型,默认为全部
     */
    CacheType cacheType() default CacheType.ALL;
​
    /**
     * Alias for {@link #cacheNames}.
     */
    @AliasFor("cacheNames")
    String[] value() default {};
​
    /**
     * 用于缓存逐出操作的缓存的名称。
     * 名称可用于确定目标高速缓存(或高速缓存),与特定 Bean 定义的限定符值或 Bean 名称匹配
     */
    @AliasFor("value")
    String[] cacheNames() default {};
​
    /**
     Spring 表达式语言 (SpEL) 表达式,用于动态计算密钥。
     默认值为 "",表示除非设置了自定义 keyGenerator 量,否则所有方法参数都被视为键。
     SpEL 表达式根据提供以下元数据的专用上下文进行计算:
     #result作为对方法调用结果的引用,只有在 is false时才能使用beforeInvocation()。对于支持的包装器,例如 Optional,#result指的是实际对象,而不是包装器
     #root. method、 #root. target和 #root. caches 分别用于对 method、目标对象和受影响缓存的引用。
     方法名称 (#root. methodName) 和目标类 (#root. targetClass) 的快捷方式也可用。
     方法参数可以通过索引访问。例如,可以通过 或 #p1 #a1访问#root. args[1]第二个参数。如果该信息可用,也可以按名称访问参数。
     */
    String key() default "";
​
    /**
     * 是否删除缓存中的所有条目。
     * <p>默认情况下,仅删除关联键下的值。
     * <p>请注意,将此参数设置为 {@code true} 并指定
     * {@link #key} 是不允许的。
     */
    boolean allEntries() default false;
}

5.2、自定义切面-处理TwoLevelCacheEvict注解

package top.xy23.cache.aop;
​
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Component;
import top.xy23.cache.CacheType;
import top.xy23.cache.TwoLevelCacheManager;
​
/**
 * 自定义缓存清除切面
 * author: XiaoYang
 * date: 2024/6/4 上午8:58
 */
@Aspect
@Component
@Slf4j
public class TwoLeveCacheEvictAspect {
    @Autowired
    private TwoLevelCacheManager twoLevelCacheManager;
​
    @Around("@annotation(twoLevelCacheEvict)")
    public Object evictCache(ProceedingJoinPoint pjp, TwoLevelCacheEvict twoLevelCacheEvict) throws Throwable {
        evictCaches(twoLevelCacheEvict);
        return pjp.proceed();
    }
​
    private void evictCaches(TwoLevelCacheEvict twoLevelCacheEvict) {
        CacheManager cacheManager;
        CacheType cacheType = twoLevelCacheEvict.cacheType();
        switch (cacheType) {
            case PRIMARY:
                log.info("Evicting primary caches");
                cacheManager = twoLevelCacheManager.getPrimaryCacheManager();
                break;
            case SECONDARY:
                log.info("Evicting secondary caches");
                cacheManager = twoLevelCacheManager.getSecondaryCacheManager();
                break;
            case ALL:
                log.info("Evicting all caches");
                cacheManager = twoLevelCacheManager;
                break;
            default:
                throw new IllegalArgumentException();
        }
​
        for (String cacheName : twoLevelCacheEvict.cacheNames()) {
            Cache cache = cacheManager.getCache(cacheName);
            if (cache == null) {
                continue;
            }
            try {
                if (twoLevelCacheEvict.allEntries()) {
                    cache.clear();
                } else {
                    cache.evict(twoLevelCacheEvict.key());
                }
            } catch (Exception ignored) {
            }
        }
    }
}

5.3、缓存层级枚举类

package top.xy23.cache;
​
/**
 * author: XiaoYang
 * date: 2024/6/4 上午8:54
 */
public enum CacheType {
    /**
     * 全部缓存
     */
    ALL,
    /**
     * 主缓存/一级缓存
     */
    PRIMARY,
    /**
     * 二级缓存
     */
    SECONDARY;
}

6、未完-继续完善自定义put相关注解-用于精细控制缓存层

文章来自个人专栏
java大世界
1 文章 | 1 订阅
0条评论
0 / 1000
请输入你的评论
0
0