阿里FastJson2JsonRedisSerializer.java作为内部类强化RedisConfig的序列化实现

Posted 阿啄debugIT

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了阿里FastJson2JsonRedisSerializer.java作为内部类强化RedisConfig的序列化实现相关的知识,希望对你有一定的参考价值。

任何存储都需要序列化

任何存储都需要序列化。只不过常规你在用DB一类存储的时候,这个事情DB帮你在内部已经实现了(直接把SQL带有类型的数据转换成内部序列化的格式存储读取时再解析出来)。

而Redis并不会帮你做序列化这个事情。当你用Redis的key和value时,value对于redis来讲就是个byte array,需要把需要的数据结构转换成byte array,存储,等读取时再读出来

  • 若是字符串,因为字符串本来几乎就是byte array了,所以不需要自己处理;
  • 若是boolean类型的true/false;你要自己定义redis里怎么表示true和false。比如你可以用1代表true,0代表false;也可以用“true”这个字符串代表true,“false”这个字符串代表false;
  • 若是数字,可以直接存储数字的字符串表示(5 --> '5'),然后读取时再把数字字符串转回来(parseInt/parseDouble/...);
  • 若是时间/日期,可以自己定义一种字符串表达,比如epoc timestamp这个数的字符串表示,又或者是ISO8601的格式。
  • 若是复杂的数据结构,你需要自己用某种序列化格式来存,可以是json, protobuf, avro, java serialization, python pickle……

回到Spring这边,Spring的redisTemplate默认会使用java serialization做序列化。若用StringRedisTemplate,那么你set的所有数据都会被toString一下再存到redis里。但这个toString不一定能反解析的回来……

特别记住的序列化KryoRedisSerializer,速度很快!

import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;

import java.io.ByteArrayOutputStream;

/**
 * @param <T>
 * @author 阿啄debugIT
 */
public class KryoRedisSerializer<T> implements RedisSerializer<T> 
    Logger logger = LoggerFactory.getLogger(KryoRedisSerializer.class);

    public static final byte[] EMPTY_BYTE_ARRAY = new byte[0];

    private static final ThreadLocal<Kryo> kryos = ThreadLocal.withInitial(Kryo::new);

    private Class<T> clazz;

    public KryoRedisSerializer(Class<T> clazz) 
        super();
        this.clazz = clazz;
    

    @Override
    public byte[] serialize(T t) throws SerializationException 
        if (t == null) 
            return EMPTY_BYTE_ARRAY;
        

        Kryo kryo = kryos.get();
        kryo.setReferences(false);
        kryo.register(clazz);

        try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
             Output output = new Output(baos)) 
            kryo.writeClassAndObject(output, t);
            output.flush();
            return baos.toByteArray();
         catch (Exception e) 
            logger.error(e.getMessage(), e);
        

        return EMPTY_BYTE_ARRAY;
    

    @Override
    public T deserialize(byte[] bytes) throws SerializationException 
        if (bytes == null || bytes.length <= 0) 
            return null;
        

        Kryo kryo = kryos.get();
        kryo.setReferences(false);
        kryo.register(clazz);

        try (Input input = new Input(bytes)) 
            return (T) kryo.readClassAndObject(input);
         catch (Exception e) 
            logger.error(e.getMessage(), e);
        

        return null;
    

总之简单一句话,你要形成一个序列化的约定,确保存进去的东西解析回来不出错

优秀讲解链接:

阿里FastJson2JsonRedisSerializer.java作为内部类

用SpringBoot+Redis+SpringCache做个缓存,采用阿里FastJson2JsonRedisSerializer.java作为内部类,作为RedisConfig的强化序列化实现

package AAAAAAAAAAA.common.config;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration;
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.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

import java.io.Serializable;
import java.nio.charset.Charset;
import java.time.Duration;

@Configuration

public class RedisConfig extends CachingConfigurerSupport 

    @Value("$spring.redis.host")
    private String host;

    @Value("$spring.redis.port")
    private int port;

    @Value("$spring.redis.password")
    private String password;

    @Value("$spring.redis.timeout")
    private int timeout;

    @Value("$spring.redis.jedis.pool.max-idle")
    private int maxIdle;

    @Value("$spring.redis.jedis.pool.max-wait")
    private long maxWaitMillis;

    @Bean
    public JedisPool redisPoolFactory() 
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxIdle(maxIdle);
        jedisPoolConfig.setMaxWaitMillis(maxWaitMillis);
        if (StringUtils.isNotBlank(password)) 
            return new JedisPool(jedisPoolConfig, host, port, timeout, password);
         else 
            return new JedisPool(jedisPoolConfig, host, port, timeout);
        
    

    @Bean
    JedisConnectionFactory jedisConnectionFactory() 
        RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
        redisStandaloneConfiguration.setHostName(host);
        redisStandaloneConfiguration.setPort(port);
        redisStandaloneConfiguration.setPassword(RedisPassword.of(password));

        JedisClientConfiguration.JedisClientConfigurationBuilder jedisClientConfiguration = JedisClientConfiguration.builder();
        jedisClientConfiguration.connectTimeout(Duration.ofMillis(timeout));
        jedisClientConfiguration.usePooling();
        return new JedisConnectionFactory(redisStandaloneConfiguration, jedisClientConfiguration.build());
    

    @Bean(name = "redisTemplate")
    @SuppressWarnings("unchecked", "rawtypes")
    @ConditionalOnMissingBean(name = "redisTemplate")
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) 
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        // 全局开启AutoType,不建议使用
        // ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        // 建议使用这种方式,小范围指定白名单
        ParserConfig.getGlobalInstance().addAccept("com.xiaolyuh.");


        //使用 fastjson 序列化
        FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer(Object.class);
        // value 值的序列化采用 fastJsonRedisSerializer
        template.setValueSerializer(fastJsonRedisSerializer);
        template.setHashValueSerializer(fastJsonRedisSerializer);
        // key 的序列化采用 StringRedisSerializer
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());

        template.setConnectionFactory(redisConnectionFactory);
        return template;
    

    //缓存管理器
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) 
        RedisCacheManager.RedisCacheManagerBuilder builder = RedisCacheManager
                .RedisCacheManagerBuilder
                .fromConnectionFactory(redisConnectionFactory);
        return builder.build();
    

    @Bean
    @ConditionalOnMissingBean(StringRedisTemplate.class)
    public StringRedisTemplate stringRedisTemplate(
            RedisConnectionFactory redisConnectionFactory) 
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    

    @Bean
    public KeyGenerator wiselyKeyGenerator() 
        return (target, method, params) -> 
            StringBuilder sb = new StringBuilder();
            sb.append(target.getClass().getName());
            sb.append(method.getName());
            for (Object obj : params) 
                sb.append(obj.toString());
            
            return sb.toString();
        ;
    

    @Bean
    public RedisTemplate<String, Serializable> limitRedisTemplate(RedisConnectionFactory redisConnectionFactory) 
        RedisTemplate<String, Serializable> template = new RedisTemplate<>();
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    


class FastJsonRedisSerializer<T> implements RedisSerializer<T> 
    private static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
    private Class<T> clazz;

    FastJsonRedisSerializer(Class<T> clazz) 
        super();
        this.clazz = clazz;
    

    @Override
    public byte[] serialize(T t) throws SerializationException 
        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
    

    @Override
    public T deserialize(byte[] bytes) throws SerializationException 
        if (bytes==null||bytes.length <= 0) 
            return null;
        
        String str = new String(bytes, DEFAULT_CHARSET);
        return JSON.parseObject(str, clazz);
    

代码解析

  • 使用StringRedisSerializerkey的序列化时,StringRedisSerializer的泛型指定的是String,传其他对象就会报类型转换错误,在使用@Cacheable注解是key属性就只能传String进来。把这个序列化方式重写了,将泛型改成Object。
  • 使用FastJson来做value的序列化,重写一些序列化器,并实现RedisSerializer接口

建议采用阿里FastJson的最新的依赖,避免一些以前版本存在远程代码执行高危安全漏洞

<!-- 1.2.41是最新的版本 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.41</version>
</dependency>

友情链接:

开发者涨薪指南 48位大咖的思考法则、工作方式、逻辑体系

以上是关于阿里FastJson2JsonRedisSerializer.java作为内部类强化RedisConfig的序列化实现的主要内容,如果未能解决你的问题,请参考以下文章

阿里云怎么获取设备几天的数据

阿里云优惠券怎么使用,阿里云优惠券使用方法

阿里云代理_阿里云服务器代理商_阿里云全国最大代理_阿里云分销商

阿里云盘怎么提取别人的文件

阿里云代金券领取

阿里钉钉是免费的吗