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請求即可攻擊到系統內部
            }
        }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM