zoukankan      html  css  js  c++  java
  • SpringBoot缓存管理(三) 自定义Redis缓存序列化机制

    前言

    在上一篇文章中,我们完成了SpringBoot整合Redis进行数据缓存管理的工作,但缓存管理的实体类数据使用的是JDK序列化方式(如下图所示),不便于使用可视化管理工具进行查看和管理。

    接下来分别针对基于API的Redis缓存实现和基于注解的Redis缓存实现中的数据序列化机制进行介绍,并自定义JSON格式的数据序列化方式进行数据缓存管理。

    基于API的Redis缓存实现——自定义RedisTemplate

    1、Redis API默认序列化方式源码解析

    基于API的Redis缓存实现是使用RedisTemplate模板进行数据缓存操作的,查看RedisTemplate的源码信息:

    public class RedisTemplate<K, V> extends RedisAccessor implements RedisOperations<K, V>, BeanClassLoaderAware {
    
        private boolean enableTransactionSupport = false;
        private boolean exposeConnection = false;
        private boolean initialized = false;
        private boolean enableDefaultSerializer = true;
        private @Nullable RedisSerializer<?> defaultSerializer;
        private @Nullable ClassLoader classLoader;
    
        // 声明了key、value的各种序列化方式,初始值为空
        @SuppressWarnings("rawtypes") private @Nullable RedisSerializer keySerializer = null;
        @SuppressWarnings("rawtypes") private @Nullable RedisSerializer valueSerializer = null;
        @SuppressWarnings("rawtypes") private @Nullable RedisSerializer hashKeySerializer = null;
        @SuppressWarnings("rawtypes") private @Nullable RedisSerializer hashValueSerializer = null;
    ...
    
        /*
         * 进行默认序列化方式设置,设置为JDK序列化方式
         * (non-Javadoc)
         * @see org.springframework.data.redis.core.RedisAccessor#afterPropertiesSet()
         */
        @Override
        public void afterPropertiesSet() {
    
            super.afterPropertiesSet();
    
            boolean defaultUsed = false;
    
            if (defaultSerializer == null) {
    
                defaultSerializer = new JdkSerializationRedisSerializer(
                        classLoader != null ? classLoader : this.getClass().getClassLoader());
            }
    
            if (enableDefaultSerializer) {
    
                if (keySerializer == null) {
                    keySerializer = defaultSerializer;
                    defaultUsed = true;
                }
                if (valueSerializer == null) {
                    valueSerializer = defaultSerializer;
                    defaultUsed = true;
                }
                if (hashKeySerializer == null) {
                    hashKeySerializer = defaultSerializer;
                    defaultUsed = true;
                }
                if (hashValueSerializer == null) {
                    hashValueSerializer = defaultSerializer;
                    defaultUsed = true;
                }
            }
    
            if (enableDefaultSerializer && defaultUsed) {
                Assert.notNull(defaultSerializer, "default serializer null and not all serializers initialized");
            }
    
            if (scriptExecutor == null) {
                this.scriptExecutor = new DefaultScriptExecutor<>(this);
            }
    
            initialized = true;
        }
        ...
    }

    从上述RedisTemplate核心源码可以看出,在RedisTemplate内部声明了缓存数据key、value的各种序列化方式,各种初始值都为空;在afterPropertiesSet()方法中,判断如果默认序列化参数defaultSerializer为空,则将数据的默认序列化方式设置为JdkSerializationRedisSerializer。

    根据上述源码信息可得出以下两个重要结论:

    (1)使用RedisTemplate进行Redis数据缓存操作时,内部默认使用的是JdkSerializationRedisSerializer序列化方式,所以进行数据缓存的实体类必须实现JDK自带的序列化接口(例如Serializable);

    (2)使用RedisTemplate进行Redis数据缓存操作时,如果自定义了缓存序列化方式defaultSerializer,那么将使用自定义的序列化方式。

    另外,在RedisTemplate类的源码中,看到的缓存数据key、value的各种序列化类型都是RedisSerializer。进入RedisSerializer查看RedisSerializer支持的序列化方式:

    可以看到,RedisSerializer是一个Redis序列化接口,默认有6个实现类,这6个实现类代表了6种不同的数据序列化方式。其中,JdkSerializationRedisSerializer是JDK自带的,也是RedisTemplate内部默认使用的序列化方式,开发者可以根据需要选择其他支持的序列化方式(例如JSON方式)。

    2、自定义RedisTemplate序列化机制

    在项目中引入Redis依赖后,SpringBoot提供的RedisAutoConfiguration自动配置会生效。打开RedisAutoConfiguration类,查看内部源码中关于RedisTemplate的定义方式:

    package org.springframework.boot.autoconfigure.data.redis;
    
    import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
    import org.springframework.boot.context.properties.EnableConfigurationProperties;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Import;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    import org.springframework.data.redis.core.RedisOperations;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.core.StringRedisTemplate;
    
    /**
     * {@link EnableAutoConfiguration Auto-configuration} for Spring Data's Redis support.
     *
     * @author Dave Syer
     * @author Andy Wilkinson
     * @author Christian Dupuis
     * @author Christoph Strobl
     * @author Phillip Webb
     * @author Eddú Meléndez
     * @author Stephane Nicoll
     * @author Marco Aust
     * @author Mark Paluch
     * @since 1.0.0
     */
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(RedisOperations.class)
    @EnableConfigurationProperties(RedisProperties.class)
    @Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
    public class RedisAutoConfiguration {
    
        @Bean
        @ConditionalOnMissingBean(name = "redisTemplate")
        @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
        public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
            RedisTemplate<Object, Object> template = new RedisTemplate<>();
            template.setConnectionFactory(redisConnectionFactory);
            return template;
        }
    
        ...
    
    }

    从上述RedisAutoConfiguration核心源码中可以看出,在Redis自动配置类中,通过Redis连接工厂RedisConnectionFactory初始化了一个RedisTemplate;在该方法上方添加了一个@ConditionalOnMissingBean注解(顾名思义,当某个Bean不存在时生效),用来表明如果开发者自定义了一个名为redisTemplate的Bean,那么该默认初始化的RedisTemplate就不会生效。

    如果要使用自定义序列化方式的RedisTemplate进行数据缓存操作,可以参考上述核心代码创建一个名为redisTemplate的Bean组件,并在该组件中设置对应的序列化方式即可。

    接下来,在项目中创建名为com.hardy.springbootdatacache.config的包,在该包下创建一个Redis自定义配置类RedisConfig,并按照上述思路自定义名为redisTemplate的Bean组件:

    package com.hardy.springbootdatacache.config;
    
    import com.fasterxml.jackson.annotation.JsonAutoDetect;
    import com.fasterxml.jackson.annotation.PropertyAccessor;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
    
    /**
     * @Author: HardyYao
     * @Date: 2021/6/24
     */
    @Configuration
    public class RedisConfig {
    
        @Bean
        public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
            RedisTemplate<Object, Object> template = new RedisTemplate();
            template.setConnectionFactory(redisConnectionFactory);
            // ֵ使用JSON格式序列化对象,对缓存数据key和value进行转换
            Jackson2JsonRedisSerializer jacksonSeial = new Jackson2JsonRedisSerializer(Object.class);
            // 解决查询缓存转换异常的问题
            ObjectMapper om = new ObjectMapper();
            om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
            om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
            jacksonSeial.setObjectMapper(om);
            // 设置RedisTemplate模板API的序列化方式为JSON
            template.setDefaultSerializer(jacksonSeial);
            return template;
        }
    
    }

    上述代码通过@Configuration注解定义了一个RedisConfig配置类,并使用@Bean注解注入了一个默认名称为方法名的redisTemplate的Bean组件(注意:该Bean组件名称必须是redisTemplate)。在定义的Bean组件中,自定义了一个RedisTemplate,使用自定义的Jackson2JsonRedisSerializer数据序列化方式;在定制序列化方式中,定义了一个ObjectMapper用于进行数据转换设置。

    3、效果测试

    启动项目,通过浏览器访问:http://localhost:8080/api/findCommentById?id=2(连续访问三次),查看网页返回信息及控制台消息:

    根据控制台打印消息可知,执行findById()方法正确查询出了用户评论信息Comment,重复进行同样的查询操作,数据库也不会重复执行SQL语句,这表明定制的Redis缓存生效了。

    使用Redis客户端可视化管理工具Redis Desktop Manager查看缓存数据:

    执行findById()方法查询到的用户评论信息Comment正确存储到了Redis缓存库中,且缓存到Redis服务的数据已经使用了JSON格式的数据存储展示,查看和管理也十分方便,这说明自定义的Redis API模板工具RedisTemplate生效了。

    基于注解的Redis缓存实现——自定义RedisCacheManager

    刚刚针对基于API方式的RedisTemplate进行了自定义序列化方式的改进,从而实现了JSON序列化方式缓存数据,但是这种自定义的RedisTemplate对于基于注解的Redis缓存来说,是没有作用的。

    接下来,针对基于注解的Redis缓存机制和自定义序列化方式进行讲解。

    1、Redis注解默认序列化机制

    打开SpringBoot整合Redis组件提供的缓存自动配置类RedisCacheConfiguration(org.springframework.boot.autoconfigure.cache包下的),查看该类的源码信息,其核心代码如下:

    package org.springframework.boot.autoconfigure.cache;
    
    import java.util.LinkedHashSet;
    import java.util.List;
    
    import org.springframework.beans.factory.ObjectProvider;
    import org.springframework.boot.autoconfigure.AutoConfigureAfter;
    import org.springframework.boot.autoconfigure.cache.CacheProperties.Redis;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
    import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
    import org.springframework.cache.CacheManager;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Conditional;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.io.ResourceLoader;
    import org.springframework.data.redis.cache.RedisCacheManager;
    import org.springframework.data.redis.cache.RedisCacheManager.RedisCacheManagerBuilder;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
    import org.springframework.data.redis.serializer.RedisSerializationContext.SerializationPair;
    
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(RedisConnectionFactory.class)
    @AutoConfigureAfter(RedisAutoConfiguration.class)
    @ConditionalOnBean(RedisConnectionFactory.class)
    @ConditionalOnMissingBean(CacheManager.class)
    @Conditional(CacheCondition.class)
    class RedisCacheConfiguration {
    
        @Bean
        RedisCacheManager cacheManager(CacheProperties cacheProperties, CacheManagerCustomizers cacheManagerCustomizers,
                ObjectProvider<org.springframework.data.redis.cache.RedisCacheConfiguration> redisCacheConfiguration,
                ObjectProvider<RedisCacheManagerBuilderCustomizer> redisCacheManagerBuilderCustomizers,
                RedisConnectionFactory redisConnectionFactory, ResourceLoader resourceLoader) {
            RedisCacheManagerBuilder builder = RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(
                    determineConfiguration(cacheProperties, redisCacheConfiguration, resourceLoader.getClassLoader()));
            List<String> cacheNames = cacheProperties.getCacheNames();
            if (!cacheNames.isEmpty()) {
                builder.initialCacheNames(new LinkedHashSet<>(cacheNames));
            }
            if (cacheProperties.getRedis().isEnableStatistics()) {
                builder.enableStatistics();
            }
            redisCacheManagerBuilderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
            return cacheManagerCustomizers.customize(builder.build());
        }
    
        private org.springframework.data.redis.cache.RedisCacheConfiguration determineConfiguration(
                CacheProperties cacheProperties,
                ObjectProvider<org.springframework.data.redis.cache.RedisCacheConfiguration> redisCacheConfiguration,
                ClassLoader classLoader) {
            return redisCacheConfiguration.getIfAvailable(() -> createConfiguration(cacheProperties, classLoader));
        }
    
        private org.springframework.data.redis.cache.RedisCacheConfiguration createConfiguration(
                CacheProperties cacheProperties, ClassLoader classLoader) {
            Redis redisProperties = cacheProperties.getRedis();
            org.springframework.data.redis.cache.RedisCacheConfiguration config = org.springframework.data.redis.cache.RedisCacheConfiguration
                    .defaultCacheConfig();
            // 默认也是使用JdkSerializationRedisSerializer作为序列化方式
            config = config.serializeValuesWith(
                    SerializationPair.fromSerializer(new JdkSerializationRedisSerializer(classLoader)));
            if (redisProperties.getTimeToLive() != null) {
                config = config.entryTtl(redisProperties.getTimeToLive());
            }
            if (redisProperties.getKeyPrefix() != null) {
                config = config.prefixCacheNameWith(redisProperties.getKeyPrefix());
            }
            if (!redisProperties.isCacheNullValues()) {
                config = config.disableCachingNullValues();
            }
            if (!redisProperties.isUseKeyPrefix()) {
                config = config.disableKeyPrefix();
            }
            return config;
        }
    
    }

    从上述核心源码可看出,同RedisAutoConfiguration源码(其中定义的RedisTemplate)类似,RedisCacheConfiguration内部同样通过Redis连接工厂RedisConnectionFactory定义了一个缓存管理器RedisCacheManager;同时定制RedisCacheManager时,也默认使用了JdkSerializationRedisSerializer序列化方式。

    如果想要使用自定义序列化方式的RedisCacheManager进行数据缓存操作,可以参考上述核心源码创建一个名为cacheManager的Bean组件,并在该组件中设置对应的序列化方式即可。

    注意:在SpringBoot 2.X版本中,RedisCacheManager是单独进行构建的。因此,在SpringBoot 2.X版本中,对RedisTemplate进行自定义序列化机制构建后,仍然无法对RedisCacheManager内部默认序列化机制进行覆盖(这也就解释了基于注解的Redis缓存实现仍然会使用JDK默认序列化机制的原因),想要基于注解的Redis缓存实现也是用自定义序列化机制。想要自定义RedisCacheManager。

    2、自定义RedisCacheManager

    在项目的Redis配置类RedisConfig,按照上一步分析的定制方法自定义名为cacheManager的Bean组件:

    package com.hardy.springbootdatacache.config;
    
    import com.fasterxml.jackson.annotation.JsonAutoDetect;
    import com.fasterxml.jackson.annotation.PropertyAccessor;
    import com.fasterxml.jackson.databind.ObjectMapper;
    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.connection.RedisConnectionFactory;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
    import org.springframework.data.redis.serializer.RedisSerializationContext;
    import org.springframework.data.redis.serializer.RedisSerializer;
    import org.springframework.data.redis.serializer.StringRedisSerializer;
    
    import java.time.Duration;
    
    /**
     * @Author: HardyYao
     * @Date: 2021/6/24
     */
    @Configuration
    public class RedisConfig {
    
        ...
    
        @Bean
        public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
            // 分别创建String和JSON格式序列化对象,对缓存数据key和value进行转换
            RedisSerializer<String> strSerializer = new StringRedisSerializer();
            Jackson2JsonRedisSerializer jacksonSerial = new Jackson2JsonRedisSerializer(Object.class);
            // 解决查询缓存转换异常问题
            ObjectMapper om = new ObjectMapper();
            om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
            om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
            jacksonSerial.setObjectMapper(om);
            // 定制缓存数据序列化方式及时效
            RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                    .entryTtl(Duration.ofDays(1))
                    .serializeKeysWith(RedisSerializationContext.SerializationPair
                            .fromSerializer(strSerializer))
                    .serializeValuesWith(RedisSerializationContext.SerializationPair
                            .fromSerializer(jacksonSerial))
                    .disableCachingNullValues();
            RedisCacheManager cacheManager = RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(config).build();
            return cacheManager;
        }
    
    }

    上述代码中,在RedisConfig配置类中使用@Bean注解注入了一个默认名称为方法名的cacheManager组件。在定义的Bean组件中,通过RedisCacheConfiguration对缓存数据的key和value分别进行了序列化方式的定制,其中缓存数据的key定制为StringRedisSerializer(即String格式),而value定制了Jackson2JsonRedisSerializer(即JSON格式),同时还是用entryTtl(Duration.ofDays(1))方式将缓存数据有效期设置为1天。

    完成基于注解的Redis缓存管理器RedisCacheManager定制后,可以对该缓存管理器的效果进行测试。(记得要开启SpringBoot基于注解的缓存管理支持,即在启动类上添加@EnableCaching注解。另外,使用自定义序列化机制的RedisCacheManager测试时,实体类可以不用实现序列化接口)。

    启动项目,通过浏览器访问:http://localhost:8080/findCommentById?id=2(连续访问三次),查看网页返回信息及控制台消息:

    根据控制台打印消息可知,执行findById()方法正确查询出了用户评论信息Comment,重复进行同样的查询操作,数据库也不会重复执行SQL语句,这表明定制的Redis缓存生效了。

    使用Redis客户端可视化管理工具Redis Desktop Manager查看缓存数据:

    可以看到用户评论信息Comment正确存储到了Redis缓存库中,且缓存到Redis服务的数据已经使用了JSON格式的数据存储展示,这说明自定义的基于注解的Redis缓存管理器RedisCacheManager生效了。

    作者:blayn
    出处:https://www.cnblogs.com/blayn/
    版权:本文版权归作者和博客园共有
    转载:欢迎转载,但未经作者同意,必须保留此段声明;必须在文章中给出原文连接;否则必究法律责任
  • 相关阅读:
    poj 3322 不错的搜索题,想通了就很简单的。
    spoj 10649 镜子数的统计(正过来反过去一样)
    搜索第一题(poj 1190)蛋糕
    HashMap和Hashtable的区别
    ajax简单联动查询以及遇到的问题
    PHP之面向对象
    pg_bulkload快速加载数据
    WalMiner
    postgresWAL写放大优化
    postgresql创建统计信息优化
  • 原文地址:https://www.cnblogs.com/blayn/p/14890977.html
Copyright © 2011-2022 走看看