目標是
- 同時使用redis 和encache
- 部分緩存使用redis,部分緩存使用encache,可代碼自動選擇
在pom.xml中增加支持
<!-- 本地緩存依賴 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <!--<dependency> <groupId>org.ehcache</groupId> <artifactId>ehcache</artifactId> </dependency>--> <dependency> <groupId>javax.cache</groupId> <artifactId>cache-api</artifactId> </dependency> <!-- redis緩存 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache</artifactId> <version>2.10.5</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> <version>2.0</version> </dependency> <!-- https://mvnrepository.com/artifact/com.google.collections/google-collections --> <dependency> <groupId>com.google.collections</groupId> <artifactId>google-collections</artifactId> <version>1.0-rc5</version> </dependency>
配置Application類
//通過exclude不注入數據源 @SpringBootApplication(exclude = DataSourceAutoConfiguration.class) public class DemocacheApplication { public static void main(String[] args) { SpringApplication.run(DemocacheApplication.class, args); } }
如果項目中沒有數據源可以使用如下註解
排除不進行數據源配置
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
也可以在有yml配置中增加數據源 : 如數據庫等
CacheManagerConfig
package com.demo.cache; import com.alibaba.fastjson.support.spring.GenericFastJsonRedisSerializer; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.ImmutableMap; import org.springframework.boot.autoconfigure.cache.CacheManagerCustomizer; import org.springframework.boot.autoconfigure.cache.CacheProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.EnableCaching; import org.springframework.cache.ehcache.EhCacheCacheManager; import org.springframework.cache.ehcache.EhCacheManagerUtils; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.core.io.Resource; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; import java.util.Map; import java.util.concurrent.TimeUnit; @Configuration @EnableCaching @EnableConfigurationProperties(CacheProperties.class) public class CacheManagerConfig { private final CacheProperties cacheProperties; CacheManagerConfig(CacheProperties cacheProperties) { this.cacheProperties = cacheProperties; } /** * cacheManager名字 */ public interface CacheManagerNames { /** * redis */ String REDIS_CACHE_MANAGER = "redisCacheManager"; /** * ehCache */ String EHCACHE_CACHE_MAANGER = "ehCacheCacheManager"; } /** * 緩存名,名稱暗示了緩存時長 注意: 如果添加了新的緩存名,需要同時在下面的RedisCacheCustomizer#RedisCacheCustomizer裏配置名稱對應的緩存時長 * ,時長爲0代表永不過期;緩存名最好公司內部唯一,因爲可能多個項目共用一個redis。 * * @see RedisCacheCustomizer#customize(RedisCacheManager) */ public interface CacheNames { /** 15分鐘緩存組 */ String CACHE_15MINS = "cp_salary:cache:15m"; /** 30分鐘緩存組 */ String CACHE_30MINS = "cp_salary:cache:30m"; /** 60分鐘緩存組 */ String CACHE_60MINS = "cp_salary:cache:60m"; /** 180分鐘緩存組 */ String CACHE_180MINS = "cp_salary:cache:180m"; } /** * ehcache緩存名 */ public interface EhCacheNames { String CACHE_10MINS = "cp_salary:cache:10m"; String CACHE_20MINS = "cp_salary:cache:20m"; String CACHE_30MINS = "cp_salary:cache:30m"; } /** * 默認的redisCacheManager * @param redisTemplate 通過參數注入,這裏沒有手動給它做配置。在引入了redis的jar包,並且往 * application.yml裏添加了spring.redis的配置項,springboot的autoconfig會自動生成一個 * redisTemplate的bean * @return */ @Primary @Bean public RedisCacheManager redisCacheManager(RedisConnectionFactory factory) { RedisCacheManager cacheManager = RedisCacheManager.create(factory); return cacheManager; } @Bean public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory){ StringRedisTemplate template = new StringRedisTemplate(factory); setSerializer(template);//設置序列化工具 template.afterPropertiesSet(); return template; } /** cache的一些自定義配置 */ @Bean public RedisCacheCustomizer redisCacheManagerCustomizer() { return new RedisCacheCustomizer(); } private static class RedisCacheCustomizer implements CacheManagerCustomizer<RedisCacheManager> { /** CacheManager緩存自定義初始化比較早,儘量不要@autowired 其他spring 組件 */ @Override public void customize(RedisCacheManager cacheManager) { // 自定義緩存名對應的過期時間 Map<String, Long> expires = ImmutableMap.<String, Long>builder() .put(CacheNames.CACHE_15MINS, TimeUnit.MINUTES.toSeconds(15)) .put(CacheNames.CACHE_30MINS, TimeUnit.MINUTES.toSeconds(30)) .put(CacheNames.CACHE_60MINS, TimeUnit.MINUTES.toSeconds(60)) .put(CacheNames.CACHE_180MINS, TimeUnit.MINUTES.toSeconds(180)).build(); // spring cache是根據cache name查找緩存過期時長的,如果找不到,則使用默認值 /*cacheManager.setDefaultExpiration(TimeUnit.MINUTES.toSeconds(30)); cacheManager.setExpires(expires);*/ } } /** * 創建ehCacheCacheManager */ @Bean public EhCacheCacheManager ehCacheCacheManager() { Resource p = this.cacheProperties.getEhcache().getConfig(); Resource location = this.cacheProperties .resolveConfigLocation(p); return new EhCacheCacheManager(EhCacheManagerUtils.buildCacheManager(location)); } private void setSerializer(StringRedisTemplate template){ @SuppressWarnings({ "rawtypes", "unchecked" }) Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); template.setValueSerializer(jackson2JsonRedisSerializer); } }
CacheConfig.java
將出錯信息 寫入日誌
package com.demo.cache; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CachingConfigurerSupport; import org.springframework.cache.annotation.EnableCaching; import org.springframework.cache.interceptor.CacheErrorHandler; import org.springframework.cache.interceptor.SimpleCacheErrorHandler; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.cache.RedisCacheManager; /** * Spring cache的一些配置,建議組件相關配置都放在相應的configuration類中 * * @author */ @Configuration @ConditionalOnBean(RedisCacheManager.class) public class CacheConfig extends CachingConfigurerSupport { @Autowired private RedisCacheManager redisCacheManager; /** * 重寫這個方法,目的是用以提供默認的cacheManager * @return */ @Override public CacheManager cacheManager() { return redisCacheManager; } /** 如果cache出錯, 我們會記錄在日誌裏,方便排查,比如反序列化異常 */ @Override public CacheErrorHandler errorHandler() { return new LoggingCacheErrorHandler(); } /* non-public */ static class LoggingCacheErrorHandler extends SimpleCacheErrorHandler { private final Logger logger = LoggerFactory.getLogger(this.getClass()); @Override public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) { logger.error(String.format("cacheName:%s,cacheKey:%s", cache == null ? "unknown" : cache.getName(), key), exception); super.handleCacheGetError(exception, cache, key); } @Override public void handleCachePutError(RuntimeException exception, Cache cache, Object key, Object value) { logger.error(String.format("cacheName:%s,cacheKey:%s", cache == null ? "unknown" : cache.getName(), key), exception); super.handleCachePutError(exception, cache, key, value); } @Override public void handleCacheEvictError(RuntimeException exception, Cache cache, Object key) { logger.error(String.format("cacheName:%s,cacheKey:%s", cache == null ? "unknown" : cache.getName(), key), exception); super.handleCacheEvictError(exception, cache, key); } @Override public void handleCacheClearError(RuntimeException exception, Cache cache) { logger.error(String.format("cacheName:%s", cache == null ? "unknown" : cache.getName()), exception); super.handleCacheClearError(exception, cache); } } }
CacheDemoService.java
測試代碼
@Service public class CacheDemoService { @Cacheable(key = "'key'", cacheManager = CacheManagerConfig.CacheManagerNames.EHCACHE_CACHE_MAANGER, cacheNames = CacheManagerConfig.EhCacheNames.CACHE_10MINS) public String demo(String key) { return "abc" + key; } //@Cacheable(key = "'key'", cacheNames = CacheManagerConfig.CacheNames.CACHE_15MINS) @Cacheable(key = "'key'",cacheManager = CacheManagerConfig.CacheManagerNames.REDIS_CACHE_MANAGER, cacheNames = CacheManagerConfig.CacheNames.CACHE_15MINS) public String demo2(String key) { return "abcdemo2" + key; } }
出錯提示
1.'org.springframework.cache.interceptor.CacheExpressionRootObject' -maybe not public
org.springframework.expression.spel.SpelEvaluationException: EL1008E: Property or field 'test' cannot be found on object of type 'org.springframework.cache.interceptor.CacheExpressionRootObject' - maybe not public?
將
@Cacheable(value = Fields.SYS_CACHE,key = "key")
@Cacheable(value = Fields.SYS_CACHE,key = "'key'")
2.java.lang.NoClassDefFoundError: org/apache/commons/pool2/impl/GenericObjectPoolConfig
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'redisCacheManager' defined in class path resource [com/mx/config/CacheManagerConfig.class]: Unsatisfied dependency expressed through method 'redisCacheManager' parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'redisConnectionFactory' defined in class path resource [org/springframework/boot/autoconfigure/data/redis/LettuceConnectionConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory]: Factory method 'redisConnectionFactory' threw exception; nested exception is java.lang.NoClassDefFoundError: org/apache/commons/pool2/impl/GenericObjectPoolConfig
解決方法:
缺少依賴包
pom.xml 中加入
<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> <version>2.0</version> </dependency>
代碼demo下
democache-ehcache-redis-springboot.rar
鏈接:https://pan.baidu.com/s/1xZqpQQOcVgkr0W_FrItHRw 密碼:x2jo
參考鏈接
我的博客即將同步至騰訊雲+社區,邀請大家一同入駐:https://cloud.tencent.com/developer/support-plan?invite_code=1rys0lnd117vx