美文网首页
0x02 SpringBoot使用注解方式同时集成Redis、E

0x02 SpringBoot使用注解方式同时集成Redis、E

作者: 夏大王2019 | 来源:发表于2018-09-18 16:21 被阅读551次

    目标是

    1. 同时使用redis 和encache
    2. 部分缓存使用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

    参考链接

    1. 在SpringBoot中配置多个cache,实现多个cacheManager灵活切换

    我的博客即将同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=1rys0lnd117vx

    相关文章

      网友评论

          本文标题:0x02 SpringBoot使用注解方式同时集成Redis、E

          本文链接:https://www.haomeiwen.com/subject/pzppnftx.html