Java分布式缓存之Redis

2020-04-24  本文已影响0人  在error边缘疯狂试探

JSR107(最早提出)

Spring缓存抽象

重要缓存注解及概念

1 . @Cacheable/@CachePut/@CacheEvict 主要的参数(属性值)
2 . 缓存可用的SpEL表达式(可以百度看具体的表达式详情)

root

表示根对象,不可省略

参数名

方法参数的名字. 可以直接 #参数名 ,也可以使用 #p0或#a0 的形式,0代表参数的索引;

如 #iban 、 #a0 、 #p0

返回值

方法执行后的返回值(仅当方法执行之后的判断有效,如‘unless’ , @CachePut、@CacheEvict’的表达式beforeInvocation=false )

如 #result

SpringBoot 整合 Redis

在SpringBoot中,缓存的自动配置类CacheAutoConfiguration向容器中导入了CacheConfigurationImportSelector,此类的selectImports()方法添加了许多配置类,其中SimpleCacheConfiguration默认生效。

【但是我们一般都不使用默认的,导入对应的缓存依赖就会实现对应的缓存配置类】

<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-data-redis</artifactId>
 </dependency>
#redis服务器主机地址(可在Redis服务器中查看)
spring.redis.host=192.168.31.162
k:"emp::1"
v:
\xAC\xED\x00\x05sr\x00$cn.edu.ustc.springboot.bean.Employeeuqf\x03p\x9A\xCF\xE0\x02\x00\x05L\x00\x03dIdt\x00\x13Ljava/lang/Integer;L\x00\x05emailt\x00\x12Ljava/lang/String;L\x00\x06genderq\x00~\x00\x01L\x00\x02idq\x00~\x00\x01L\x00\x08lastNameq\x00~\x00\x02xpsr\x00\x11java.lang.Integer\x12\xE2\xA0\xA4\xF7\x81\x878\x02\x00\x01I\x00\x05valuexr\x00\x10java.lang.Number\x86\xAC\x95\x1D\x0B\x94\xE0\x8B\x02\x00\x00xp\x00\x00\x00\x03t\x00\x07cch@aaasq\x00~\x00\x04\x00\x00\x00\x01q\x00~\x00\x08t\x00\x03cch

要想让对象以json形式存储在redis中,需要自定义RedisCacheManager,使用GenericJackson2JsonRedisSerializer类对value进行序列化!

@Configuration
public class MyRedisConfig {

    @Bean
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        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
public class MyRedisConfig {
    @Bean
    RedisCacheManager cacheManager(RedisConnectionFactory factory){
        //创建默认RedisCacheWriter
        RedisCacheWriter cacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(factory);
        
        //创建默认RedisCacheConfiguration并使用GenericJackson2JsonRedisSerializer构造的       SerializationPair对value进行转换
        //创建GenericJackson2JsonRedisSerializer的json序列化器
        GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
        //使用json序列化器构造出对转换Object类型的SerializationPair序列化对
        RedisSerializationContext.SerializationPair<Object> serializationPair = RedisSerializationContext.SerializationPair.fromSerializer(jsonRedisSerializer);
        //将可以把Object转换为json的SerializationPair传入RedisCacheConfiguration
        //使得RedisCacheConfiguration在转换value时使用定制序列化器
        RedisCacheConfiguration cacheConfiguration=RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(serializationPair);
        
        RedisCacheManager cacheManager = new RedisCacheManager(cacheWriter,cacheConfiguration);
        return cacheManager;
    }
}

序列化数据如下:

k:"emp::3"

v:
{
  "@class": "cn.edu.ustc.springboot.bean.Employee",
  "id": 3,
  "lastName": "aaa",
  "email": "aaaa",
  "gender": 1,
  "dId": 5
}

【注意】:这里可以用GenericJackson2JsonRedisSerializer进行value的序列化解析,如果使用Jackson2JsonRedisSerializer,序列化的json没有"@class": "com.cwx.entity.Employee",在读取缓存时会报类型转换异常。

package com.cwx.cache.service;
import com.uos.cache.domain.Comment;
import com.uos.cache.repository.CommentRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.Optional;
import java.util.concurrent.TimeUnit;

@Service
public class ApiCommentService {
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private CommentRepository commentRepository;
    public Comment findById(int comment_id){
      // 先从Redis缓存中查询数据
        Object object =  redisTemplate.opsForValue().get("comment_"+comment_id);
        if (object!=null){
            return (Comment)object;}else {
            Optional<Comment> optional = commentRepository.findById(comment_id);
            if(optional.isPresent()){Comment comment= optional.get();
                // 将查询结果进行缓存,并设置有效期为1天
                redisTemplate.opsForValue().set("comment_"+comment_id,
                        comment,1, TimeUnit.DAYS);return comment;
            }else {return null;}
        }
    }
    public Comment updateComment(Comment comment){
        commentRepository.updateComment(comment.getAuthor(), comment.getaId());
        // 更新数据后进行缓存更新
        redisTemplate.opsForValue().set("comment_"+comment.getId(),comment);
        return comment;
    }
    public void deleteComment(int comment_id){
        commentRepository.deleteById(comment_id);
        // 删除数据后进行缓存删除
        redisTemplate.delete("comment_"+comment_id);
    }

}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisConnectionFactory.class)
@AutoConfigureAfter(RedisAutoConfiguration.class)
@ConditionalOnBean(RedisConnectionFactory.class)
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class RedisCacheConfiguration {
    
    //向容器中导入RedisCacheManager
    @Bean
    RedisCacheManager cacheManager(CacheProperties cacheProperties, CacheManagerCustomizers cacheManagerCustomizers,
            ObjectProvider<org.springframework.data.redis.cache.RedisCacheConfiguration> redisCacheConfiguration,
            ObjectProvider<RedisCacheManagerBuilderCustomizer> redisCacheManagerBuilderCustomizers,
            RedisConnectionFactory redisConnectionFactory, ResourceLoader resourceLoader) {
        //使用determineConfiguration()的返回值生成RedisCacheManagerBuilder
        //调用了RedisCacheManagerBuilder的cacheDefaults()方法(见下一代码块)
        RedisCacheManagerBuilder builder = RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(
                determineConfiguration(cacheProperties, redisCacheConfiguration, resourceLoader.getClassLoader()));
        List<String> cacheNames = cacheProperties.getCacheNames();
        if (!cacheNames.isEmpty()) {
            builder.initialCacheNames(new LinkedHashSet<>(cacheNames));
        }
        redisCacheManagerBuilderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
        //使用RedisCacheManagerBuilder的build()方法创建RedisCacheManager并进行定制操作
        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) {
        //determineConfiguration()调用了createConfiguration()
        return redisCacheConfiguration.getIfAvailable(() -> createConfiguration(cacheProperties, classLoader));
    }

    
    //createConfiguration()定义了其序列化value的规则
    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();
        //使用jdk序列化器对value进行序列化
        config = config.serializeValuesWith(
                SerializationPair.fromSerializer(new JdkSerializationRedisSerializer(classLoader)));
        //设置properties文件中设置的各项属性
        if (redisProperties.getTimeToLive() != null) {
            config = config.entryTtl(redisProperties.getTimeToLive());
        }
        if (redisProperties.getKeyPrefix() != null) {
            config = config.prefixKeysWith(redisProperties.getKeyPrefix());
        }
        if (!redisProperties.isCacheNullValues()) {
            config = config.disableCachingNullValues();
        }
        if (!redisProperties.isUseKeyPrefix()) {
            config = config.disableKeyPrefix();
        }
        return config;
    }

}

RedisCacheManager的直接构造类,该类保存了配置类RedisCacheConfiguration,该配置在会传递给RedisCacheManager

public static class RedisCacheManagerBuilder {

        private final RedisCacheWriter cacheWriter;
        //默认缓存配置使用RedisCacheConfiguration的默认配置
        //该默认配置缓存时默认将k按字符串存储,v按jdk序列化数据存储(见下一代码块)
        private RedisCacheConfiguration defaultCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
        private final Map<String, RedisCacheConfiguration> initialCaches = new LinkedHashMap<>();
        private boolean enableTransactions;
        boolean allowInFlightCacheCreation = true;

        private RedisCacheManagerBuilder(RedisCacheWriter cacheWriter) {
            this.cacheWriter = cacheWriter;
        }

        
        //传入RedisCacheManagerBuilder使用的缓存配置规则RedisCacheConfiguration类
        public RedisCacheManagerBuilder cacheDefaults(RedisCacheConfiguration defaultCacheConfiguration) {

            Assert.notNull(defaultCacheConfiguration, "DefaultCacheConfiguration must not be null!");

            this.defaultCacheConfiguration = defaultCacheConfiguration;

            return this;
        }
    
    
    //使用默认defaultCacheConfiguration创建RedisCacheManager
    public RedisCacheManager build() {

            RedisCacheManager cm = new RedisCacheManager(cacheWriter, defaultCacheConfiguration, initialCaches,
                    allowInFlightCacheCreation);

            cm.setTransactionAware(enableTransactions);

            return cm;
        }

RedisCacheConfiguration保存了许多缓存规则,这些规则都保存在RedisCacheManagerBuilder的RedisCacheConfiguration defaultCacheConfiguration属性中,并且当RedisCacheManagerBuilder创建RedisCacheManager传递过去。

public class RedisCacheConfiguration {

    private final Duration ttl;
    private final boolean cacheNullValues;
    private final CacheKeyPrefix keyPrefix;
    private final boolean usePrefix;

    private final SerializationPair<String> keySerializationPair;
    private final SerializationPair<Object> valueSerializationPair;

    private final ConversionService conversionService;
    
    //默认缓存配置
    public static RedisCacheConfiguration defaultCacheConfig(@Nullable ClassLoader classLoader) {

            DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();

            registerDefaultConverters(conversionService);

            return new RedisCacheConfiguration(Duration.ZERO, true, true, CacheKeyPrefix.simple(),
                                     SerializationPair.fromSerializer(RedisSerializer.string()),//key使用字符串
                                               SerializationPair.fromSerializer(RedisSerializer.java(classLoader)), conversionService);
        //value按jdk序列化存储
    }

RedisCacheManager在创建RedisCache时将RedisCacheConfiguration传递过去,并在创建RedisCache时通过createRedisCache()起作用。

public class RedisCacheManager extends AbstractTransactionSupportingCacheManager {

    private final RedisCacheWriter cacheWriter;
    private final RedisCacheConfiguration defaultCacheConfig;
    private final Map<String, RedisCacheConfiguration> initialCacheConfiguration;
    private final boolean allowInFlightCacheCreation;
    
        protected RedisCache createRedisCache(String name, @Nullable RedisCacheConfiguration cacheConfig) {
        //如果调用该方法时RedisCacheConfiguration有值则使用定制的,否则则使用默认的RedisCacheConfiguration defaultCacheConfig,即RedisCacheManagerBuilder传递过来的配置
        return new RedisCache(name, cacheWriter, cacheConfig != null ? cacheConfig : defaultCacheConfig);
    }

RedisCache,Redis缓存,具体负责将缓存数据序列化的地方,将RedisCacheConfiguration的序列化对SerializationPair提取出来并使用其定义的序列化方式分别对k和v进行序列化操作。

public class RedisCache extends AbstractValueAdaptingCache {
   
   private static final byte[] BINARY_NULL_VALUE = RedisSerializer.java().serialize(NullValue.INSTANCE);

   private final String name;
   private final RedisCacheWriter cacheWriter;
   private final RedisCacheConfiguration cacheConfig;
   private final ConversionService conversionService;
   
   public void put(Object key, @Nullable Object value) {

       Object cacheValue = preProcessCacheValue(value);

       if (!isAllowNullValues() && cacheValue == null) {

           throw new IllegalArgumentException(String.format(
                   "Cache '%s' does not allow 'null' values. Avoid storing null via '@Cacheable(unless=\"#result == null\")' or configure RedisCache to allow 'null' via RedisCacheConfiguration.",
                   name));
       }

       //在put k-v时使用cacheConfig中的k-v序列化器分别对k-v进行序列化
       cacheWriter.put(name, createAndConvertCacheKey(key), serializeCacheValue(cacheValue), cacheConfig.getTtl());
   }
   
   //从cacheConfig(即RedisCacheConfiguration)中获取KeySerializationPair并写入key值
   protected byte[] serializeCacheKey(String cacheKey) {
       return ByteUtils.getBytes(cacheConfig.getKeySerializationPair().write(cacheKey));
   }
   
   
   //从cacheConfig(即RedisCacheConfiguration)中获取ValueSerializationPair并写入key值
   protected byte[] serializeCacheValue(Object value) {

       if (isAllowNullValues() && value instanceof NullValue) {
           return BINARY_NULL_VALUE;
       }

       return ByteUtils.getBytes(cacheConfig.getValueSerializationPair().write(value));
   }

分析到这也就不难理解,要使用json保存序列化数据时,需要自定义RedisCacheManager,在RedisCacheConfiguration中定义序列化转化规则,并向RedisCacheManager传入我们自己定制的RedisCacheConfiguration了,我定制的序列化规则会跟随RedisCacheConfiguration一直传递到RedisCache,并在序列化时发挥作用。

上一篇下一篇

猜你喜欢

热点阅读