• SpringBoot2(十一)集成RedisCache


    应用层的东西,找到接口实现它即可。

    如果想要自己选择序列化工具,难点还是在自动转型上,在序列化字符串转成对象的过程中,Spring并未提供有效的、带Class参数的接口,类型自动转换问题,需要第三方框架自行处理。
    最好选用带自动转型的序列化框架,错误的写法,很容易导致类型强转失败,本文采用的是FastJSON。

    简单的工具类,按自己需求封装,可以指定各种数据类型序列化格式。

    /**
     * @author ChenSS
     * @date    2018年7月13日  v1
     *          2019年10月16日 v2  优化日期
     */
    public class FastJsonUtils {
        public static final SerializeConfig serializeConfig;
    
        static {
            serializeConfig = new SerializeConfig();
            FastJsonDateSerializer dateTimeSerializer = new FastJsonDateSerializer("yyyy-MM-dd HH:mm:ss");
            serializeConfig.put(Date.class, dateTimeSerializer);
            serializeConfig.put(java.sql.Timestamp.class, dateTimeSerializer);
            serializeConfig.put(java.sql.Date.class, new FastJsonDateSerializer("yyyy-MM-dd"));
            serializeConfig.put(java.sql.Time.class, new FastJsonDateSerializer("HH:mm:ss"));
    
    //        // 使用和json-lib兼容的日期输出格式
    //        config.put(java.util.Date.class, new JSONLibDataFormatSerializer());
    //        config.put(java.sql.Date.class, new JSONLibDataFormatSerializer());
        }
    }

    FastJson2JsonRedisSerializer

    很多人写这个类,完全模仿Jackson的写法,其实没必要,明确自己的需求,保留最少的代码即可。

    我泛型直接写Object,因为代码已经能够处理全部类型的数据了。

    import cn.seaboot.common.core.FastJsonUtils;
    import com.alibaba.fastjson.JSON;
    import com.alibaba.fastjson.serializer.SerializerFeature;
    import org.springframework.data.redis.serializer.RedisSerializer;
    import org.springframework.data.redis.serializer.SerializationException;
    
    import java.nio.charset.Charset;
    
    /**
     * @author Mr.css
     * @date 2020/1/2 11:24
     */
    public class FastJson2JsonRedisSerializer implements RedisSerializer<Object> {
      @Override
      public byte[] serialize(Object o) throws SerializationException {
        if (o == null) {
          return new byte[0];
        } else {
          return JSON.toJSONString(o, FastJsonUtils.serializeConfig, SerializerFeature.WriteClassName).getBytes(Charset.defaultCharset());
        }
      }
    
      @Override
      public Object deserialize(byte[] bytes) throws SerializationException {
        if (bytes == null || bytes.length <= 0) {
          return null;
        } else {
          return JSON.parse(new String(bytes, Charset.defaultCharset()));
        }
      }
    }

    RedisConfig

    如果在使用Cache注解的时候有写key的习惯,KeyGenerator 可以不需要配置,我这里把函数名和所有的参数拼在一起,做成默认的Key值。

    import cn.seaboot.common.core.Converter;
    import com.alibaba.fastjson.parser.ParserConfig;
    import org.springframework.cache.CacheManager;
    import org.springframework.cache.annotation.CachingConfigurerSupport;
    import org.springframework.cache.annotation.EnableCaching;
    import org.springframework.cache.interceptor.KeyGenerator;
    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.cache.RedisCacheWriter;
    import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
    import org.springframework.data.redis.serializer.RedisSerializationContext;
    
    import javax.annotation.Resource;
    import java.time.Duration;
    
    /**
     * @author Mr.css on 2019/12/26
     * @date 2019/12/31
     */
    @Configuration
    @EnableCaching
    public class RedisConfig extends CachingConfigurerSupport {
    
      @Resource
      private LettuceConnectionFactory lettuceConnectionFactory;
    
      /**
       * Cache注解可以不指定key,需要有默认策略,按需调整
       */
      @Bean
      @Override
      public KeyGenerator keyGenerator() {
        return (target, method, params) -> {
          StringBuilder sb = new StringBuilder();
          sb.append(method.getName());
          if(params.length > 0){
            for (int i = 1; i < params.length; i++) {
              sb.append(Converter.toString(params[i]));
            }
          }
          return sb.toString();
        };
      }
    
      @Bean
      @Override
      public CacheManager cacheManager() {
        RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(lettuceConnectionFactory);
    
        FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer();
        RedisSerializationContext.SerializationPair<Object> pair =
            RedisSerializationContext.SerializationPair.fromSerializer(serializer);
        RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(pair);
        //设置过期时间 30天
        defaultCacheConfig = defaultCacheConfig.entryTtl(Duration.ofDays(30));
        //初始化RedisCacheManager
        RedisCacheManager cacheManager = new RedisCacheManager(redisCacheWriter, defaultCacheConfig);
        //反序列化白名单
        ParserConfig.getGlobalInstance().addAccept("cn.seaboot.admin.bean.");
        return cacheManager;
      }
    }

    yml

      redis:
        host: 127.0.0.1
        port: 6379
        timeout: 1000
        jedis:
          pool:
            min-idle: 1
            max-idle: 8
            max-wait: 5000

    Maven

            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-cache</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-redis</artifactId>
            </dependency>

    补充

    集成RedisCache,上面代码已经足够,这里介绍FastJSON的一些问题。

    FastJSON自动转型的写法

      public static void main(String[] args) {
        ParserConfig.getGlobalInstance().addAccept("cn.swsk.xbry.entity");
        TUserInfoEntity entity = new TUserInfoEntity();
        entity.setId("13123");
        //使用 SerializerFeature.WriteClassName 可以让JSON自动转型
        //不依赖 clazz 参数也达到 JSON.parseObject(String json, Class<T> clazz) 相同效果
        String str = JSON.toJSONString(entity, SerializerFeature.WriteClassName);
        System.out.println(JSON.parse(str).getClass());
      }

    ParserConfig.getGlobalInstance()的必要性

    FastJSON最初是没有白名单这个要求的,addAccept接口的设计源自于系统漏洞。

    假设去除掉白名单的设计,在知道全类名的情况下,通过Http接口即可创建出系统的任何对象。

                import com.alibaba.fastjson.JSON;
                import com.alibaba.fastjson.parser.ParserConfig;
    
                /**
                 * @author Mr.css
                 * @date 2020/1/6
                 */
                public class Test {
                    public static void main(String[] args) {
                    //在没有 ParserConfig.getGlobalInstance() 的情况下,只要知道全类名,即可 new 出程序中任何一个对象
                    ParserConfig.getGlobalInstance().addAccept("cn.swsk.xbry.entity");
                    //下列这行代码,等效于Class.forName().newInstance()
                    System.out.println(JSON.parse("{"@type":"cn.swsk.xbry.entity.TUserInfoEntity"}").getClass());
                    //因为设计原因,或者生产需求,或许你曾经改造过@RequestBody,如果是采用JSON.parse()方式实现的,
                    //那么,要是没有白名单的设计,通过http请求即可攻击到系统内部
                }
            }
  • 相关阅读:
    为什么包含多句代码的宏要用do while包括起来?
    Android之JUnit深入浅出
    android unit test
    dlopen,dlsym的问题,实在搞不明白了。
    pthread多线程学习笔记五条件变量2使用
    posix多线程程序使用条件变量的一个常见bug
    Android Bitmap和Canvas学习笔记
    c++filt
    pthread_cond_signal只能唤醒已经处于pthread_cond_wait的线程
    百度知道推广技巧大全
  • 原文地址:https://www.cnblogs.com/chenss15060100790/p/12168581.html
Copyright © 2020-2023  润新知