SpringBoot2+Jackson+RedisTemplate+RedisCache配置

2,337 阅读2分钟

SpringBoot2+Jackson+RedisTemplate+RedisCache配置

版本

依赖版本
spring boot2.7.18

配置用途

  1. 拓展SpringBoot的Jackson配置
  2. 封装JacksonUtil方便使用,且使用Spring配置的Jackson单例
  3. Redis序列化使用基于Jackson基础上修改的配置,并用于Spring Cache

代码

需要依赖

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

Jackson配置

/**
 * Jackson ObjectMapper配置
 * <p>使用此配置后别再定义其他 ObjectMapper 的 bean, 否则会导致该配置bean失效
 */
@Configuration
public class JacksonConfig {
    @Bean
    public Jackson2ObjectMapperBuilderCustomizer jacksonCustomizer() {
        return builder -> builder
                .serializationInclusion(JsonInclude.Include.NON_NULL)
                .featuresToEnable(
                        JsonParser.Feature.ALLOW_SINGLE_QUOTES
                )
                .featuresToDisable(
                        SerializationFeature.FAIL_ON_EMPTY_BEANS,
                        SerializationFeature.WRITE_DATES_AS_TIMESTAMPS,
                        DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES
                )
                .simpleDateFormat(BaseConsts.DATE_TIME_PT)
                .serializerByType(Long.class, ToStringSerializer.instance)
                .serializerByType(BigInteger.class, ToStringSerializer.instance)
                .serializerByType(LocalDateTime.class, new LocalDateTimeSerializer(BaseConsts.DATE_TIME_FMT))
                .serializerByType(LocalDate.class, new LocalDateSerializer(BaseConsts.DATE_FMT))
                .serializerByType(LocalTime.class, new LocalTimeSerializer(BaseConsts.TIME_FMT))
                .deserializerByType(LocalDateTime.class, new LocalDateTimeDeserializer(BaseConsts.DATE_TIME_FMT))
                .deserializerByType(LocalDate.class, new LocalDateDeserializer(BaseConsts.DATE_FMT))
                .deserializerByType(LocalTime.class, new LocalTimeDeserializer(BaseConsts.TIME_FMT))
                ;
    }
}

public class BaseConsts {
    public static final String DATE_TIME_PT = "yyyy-MM-dd HH:mm:ss";

    public static final String            DATE_PT          = "yyyy-MM-dd";
    public static final String            TIME_PT          = "HH:mm:ss";
    public static final String            DATE_COMPACT_PT  = "yyyyMMdd";
    public static final DateTimeFormatter DATE_TIME_FMT    = DateTimeFormatter.ofPattern(DATE_TIME_PT);
    public static final DateTimeFormatter DATE_FMT         = DateTimeFormatter.ofPattern(DATE_PT);
    public static final DateTimeFormatter TIME_FMT         = DateTimeFormatter.ofPattern(TIME_PT);
    public static final DateTimeFormatter DATE_COMPACT_FMT = DateTimeFormatter.ofPattern(DATE_COMPACT_PT);

    private BaseConsts() {
    }
}

JacksonUtil封装

import cn.hutool.extra.spring.SpringUtil;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;

import java.util.List;
import java.util.Optional;

/**
 * 封装Jackson方便使用
 */
@Slf4j
public class JacksonUtil {

    public static <T> Optional<T> executeWithExHandle(ThrowsExFunction<ObjectMapper, T> function, String exMsg) {
        try {
            return Optional.of(function.apply(getObjectMapper()));
        } catch (Exception e) {
            log.error(exMsg, e);
            return Optional.empty();
        }
    }

    public static String toJsonStr(Object object) {
        return executeWithExHandle(om -> om.writeValueAsString(object), "toJsonStr Exception")
                .orElse(null);
    }

    public static JsonNode parseJsonNode(String jsonStr) {
        return executeWithExHandle(om -> om.readTree(jsonStr), "parseJsonNode Exception")
                .orElse(null);
    }

    public static <T> T parseObject(String jsonStr, Class<T> clazz) {
        return executeWithExHandle(om -> om.readValue(jsonStr, clazz), "parseObject Exception")
                .orElse(null);
    }

    public static <T> List<T> parseList(String jsonStr, Class<T> clazz) {
        return executeWithExHandle((ThrowsExFunction<ObjectMapper, List<T>>) om ->
                om.readValue(jsonStr, om.getTypeFactory().constructCollectionType(List.class, clazz)), "parseList Exception")
                .orElse(null);
    }

    public static <T> T parseByType(String jsonStr, TypeReference<T> typeReference) {
        return executeWithExHandle(om -> om.readValue(jsonStr, typeReference), "parseByType Exception")
                .orElse(null);
    }

    @FunctionalInterface
    private interface ThrowsExFunction<T, R> {
        R apply(T t) throws Exception;
    }

    public static ObjectMapper copyObjectMapper() {
        return getObjectMapper().copy();
    }

    private volatile static ObjectMapper OBJECT_MAPPER;

    private JacksonUtil() {
    }

    private static ObjectMapper getObjectMapper() {
        if (OBJECT_MAPPER == null) {
            synchronized (JacksonUtil.class) {
                if (OBJECT_MAPPER == null) {
                    try {
                        OBJECT_MAPPER = SpringUtil.getBean(ObjectMapper.class);
                    } catch (Exception e) {
                        log.warn("Get Spring ObjectMapper Fail");
                        OBJECT_MAPPER = new ObjectMapper();
                    }
                }
            }
        }
        return OBJECT_MAPPER;
    }
}

Redis配置

@EnableCaching
@Configuration
@AutoConfigureAfter(JacksonAutoConfiguration.class) // Jackson配置后再配置Redis
public class RedisConfig {

    @Bean(name = "redisTemplate")
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        GenericJackson2JsonRedisSerializer jsonSerializer = new GenericJackson2JsonRedisSerializer(getRedisConfigObjectMapper());
        redisTemplate.setValueSerializer(jsonSerializer);
        redisTemplate.setHashValueSerializer(jsonSerializer);
        StringRedisSerializer stringSerializer = new StringRedisSerializer();
        redisTemplate.setKeySerializer(stringSerializer);
        redisTemplate.setHashKeySerializer(stringSerializer);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

    @Bean
    public RedisCacheManager cacheManager(RedisTemplate<Object, Object> redisTemplate) {
        RedisSerializationContext.SerializationPair<String> keyPair = RedisSerializationContext.SerializationPair
                .fromSerializer((StringRedisSerializer) redisTemplate.getKeySerializer());
        RedisSerializationContext.SerializationPair<?> valuePair = RedisSerializationContext.SerializationPair
                .fromSerializer(redisTemplate.getValueSerializer());
        return RedisCacheManager
                .builder(Objects.requireNonNull(redisTemplate.getConnectionFactory()))
                .cacheDefaults(RedisCacheConfiguration
                        // spring.cache.redis.xxx
                        .defaultCacheConfig()
                        .entryTtl(Duration.ZERO)
                        .disableCachingNullValues()
                        .serializeKeysWith(keyPair)
                        .serializeValuesWith(valuePair))
                .transactionAware()
                .build();
    }

    private static ObjectMapper getRedisConfigObjectMapper() {
        ObjectMapper om = JacksonUtil.copyObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        // NON_FINAL配置,让RedisTemplate反序列化时得到正确的对象类型,而不影响如@RequestBody之类的功能
        om.activateDefaultTyping(om.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
        return om;
    }
}