SpringBoot2.x—SpringCache(1)集成

JAVA && Spring && SpringBoot2.x — 学习目录

SpringBoot2.x—SpringCache(1)集成
SpringBoot2.x—SpringCache(2)使用
SpringBoot2.x—SpringCache(3) CacheManager源码

声明式与编程式

说起SpringCache您可能不清楚。但您绝对清楚事务。

一般使用事务分为编程式和声明式。

编程式:事务操作与业务代码耦合,一般我们不会使用这种方式;
声明式:AOP的运用,通过注解使得事务代码与业务代码解耦,目前项目中一般都是使用事务注解。

而我们平时使用缓存,正是编程式,即对缓存的操作与业务代码耦合。那么是否存在一种类似于事务的技术,完成声明式的缓存操作呢?

而SpringCahe便可以提供透明化的缓存操作,即用户可以使用注解的方式。灵活的操纵缓存。

1. 引入依赖

本篇是SpringCache+Redis的整合。SpringCache只是缓存抽象,即具体缓存的操作需要子类实现。

spring-boot-starter-data-redis中实现了SpringCache的抽象接口,即我们整合SpringCache+Redis无需自己实现具体缓存。

        
        
            org.springframework.boot
            spring-boot-starter-cache
        
       
        
            org.springframework.boot
            spring-boot-starter-data-redis
            
                
                    io.lettuce
                    lettuce-core
                
            
        
        
        
            redis.clients
            jedis
        

SpringBoot2.X整合Redis缓存可以看这篇文章,因为有个项目在生产环境中,使用lettuce客户端每隔一段时间连接断开(初步估计是Redis机房和应用服务器机房网络问题)。切换成了jedis客户端。

2. SpringCache配置

两种配置,一种可以在yml中配置,一种是在代码中配置,此处推荐在@Configuration中进行配置。

原因一是更加灵活,在配置CacheManager的Bean时,可以初始化Cache对象,在项目启动的时候注册到CacheManager中。

import com.galax.Config.serialize.RedisObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
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.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;

import java.time.Duration;
import java.util.HashMap;
import java.util.Map;

@Configuration
@EnableCaching  //开启缓存,可以放在启动类上。
public class RedisSpringCache {

    /**
     * 自定义KeyGenerator。
     * @return
     */
    @Bean
    public KeyGenerator keyGenerator() {
        return (target, method, params) -> {
            //获取代理对象的最终目标对象
            Class targetClass = AopProxyUtils.ultimateTargetClass(target);
            StringBuilder sb = new StringBuilder();
            sb.append(targetClass.getSimpleName()).append(":");
            sb.append(method.getName()).append(":");
            //调用SimpleKey的逻辑
            Object key = SimpleKeyGenerator.generateKey(params);
            return sb.append(key);
        };
    }

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
        //设置特有的Redis配置
        Map cacheConfigurations = new HashMap<>();
        //定制化的Cache为300s
        cacheConfigurations.put("as",customRedisCacheConfiguration(Duration.ofSeconds(300)));
        cacheConfigurations.put("books",customRedisCacheConfiguration(Duration.ofSeconds(300)));
        cacheConfigurations.put("cs",customRedisCacheConfiguration(Duration.ofSeconds(300)));
        //默认超时时间60s
        return RedisCacheManager.builder(connectionFactory).
                transactionAware().   //Cache的事务支持
                cacheDefaults(customRedisCacheConfiguration(Duration.ofSeconds(60))).
                withInitialCacheConfigurations(cacheConfigurations).   //设置个性化的Cache配置
                build();
    }


    /**
     * 设置RedisConfiguration配置
     *
     * @param ttl
     * @return
     */
    public RedisCacheConfiguration customRedisCacheConfiguration(Duration ttl) {
        //设置序列化格式
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer
                = new Jackson2JsonRedisSerializer<>(Object.class);
        jackson2JsonRedisSerializer.setObjectMapper(RedisObjectMapper.redisConfigurationObjectMapper());
        return RedisCacheConfiguration.
                defaultCacheConfig().serializeValuesWith(
                RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)).
                computePrefixWith(cacheName -> cacheName + ":").   //设置Cache的前缀,默认::
                disableCachingNullValues().   //若返回值为null,则不允许存储到Cache中
                entryTtl(ttl);  //设置缓存缺省超时时间
    }
}
 
 

注意不要将ObjectMapper加入到Spring容器中。因为Spring容器中存在一个ObjectMapper,以用于@RequestBodyResponseBodyRestTemplate等地的序列化和反序列化。

为什么不采用Spring容器的ObjectMapper对象,而要自己设置是因为Redis配置了objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);属性,在序列化时记录类/属性的类型,以便在反序列化时得到POJO对象。此属性详见——Jackson的ObjectMapper.DefaultTyping.NON_FINAL属性。

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;

import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class RedisObjectMapper {

    public static ObjectMapper redisConfigurationObjectMapper() {
        ObjectMapper objectMapper = new ObjectMapper();
        //JDK1.8新版时间格式化Model
        JavaTimeModule javaTimeModule = new JavaTimeModule();
        javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        objectMapper.registerModule(javaTimeModule);
        //Date类型禁止转换为时间戳
        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        //序列化时格式化时间戳
        objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
        //字段名字开启驼峰命名法
        objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
        //序列化无public的属性或方法时,不会抛出异常
        objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
        //序列化时保存对象类型,以便反序列化时直接得到具体POJO
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        //非空数据才进行格式化
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        //针对BigDecimal,序列化时,不采取科学计数法
        objectMapper.enable(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN);
        //反序列化时,POJO中不含有JSON串的属性,不解析该字段,并且不会抛出异常
        objectMapper.enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
        //反序列化{}时,不抛出异常,而是得到null值
        objectMapper.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
        return objectMapper;
    }
}

此处可以使用protostuff替换Jackson进行序列化和反序列化,详细内容请点击...

3. key的设置

需要注意的是,SpringCache作为应用层的声明式缓存。其数据结构为Key-Value。那么设计一个安全优雅的Key,是一个重要的任务。

  1. 在SpringCache官网中,这样描述SpringCache默认的KeyGenerator的:
  • 若没有参数值被得到,返回SimpleKey.EMPTY(空数组)。
  • 若只有一个参数值被得到,返回该参数值的实例。
  • 若多个参数值被得到,返回一个包含所有参数值SimpleKey对象。
  1. 默认的KeyGenerator如何获取参数值?
  • 若注解上只是指定cacheName属性,SimpleKeyGenerator将获取所有的参数值。组成SimpleKey对象。
  • 指定cacheNamekey属性,并且key的属性支持SpEL表达式:
  1. 基本形式
@Cacheable(value="cacheName", key="#id")
public ResultDTO method(int id);
  1. 组合形式
@Cacheable(value="cacheName", key="T(String).valueOf(#name).concat('-').concat(#password))
public ResultDTO method(int name, String password);
  1. 对象形式
@Cacheable(value="cacheName", key="#user.id)
public ResultDTO method(User user);
  1. 默认的SimpleKeyGenerator的缺陷

SimpleGenerator只会将参数值封装为SimpleKey对象。然后作为Key,可能会导致不同方法Key冲突。
我们虽然可以使用SpEL表达式获取类名、方法名,在进行拼接。但是需要为每一个注解指定,太过于繁杂。

  1. 自定义KeyGenerator

注解上keyGenerator属性与key属性是不共存的,即我们若通过keyGenerator来自定义我们的Key生成器,那么就需要将所有的参数值均进行处理,而不能指定特定的参数值
处理。

    @Bean
    public KeyGenerator keyGenerator() {
        return (target, method, params) -> {
            //获取代理对象的最终目标对象
            Class targetClass = AopProxyUtils.ultimateTargetClass(target);
            StringBuilder sb = new StringBuilder();
            sb.append(targetClass.getSimpleName()).append(":");
            sb.append(method.getName()).append(":");
            //调用SimpleKey的逻辑
            Object key = SimpleKeyGenerator.generateKey(params);
            return sb.append(key);
        };
    }

使用:

    @Cacheable(value = "book2",keyGenerator = "keyGenerator")
    public Account getAccInfo(String customerId, String accType) {
      //业务逻辑
    }

4. 使用

springCache和事务类型,均采用AOP原理。故它们的注意事项也是相同。

  1. 若一个service中,注解方法被调用,则注解不会生效;
  2. 只有访问修饰符为public的方法,注解才会生效;

SpringCache的使用请参考——SpringBoot2.x—SpringCache(2)使用

文章参考

SpringBoot2.X整合Redis缓存

https://www.cnblogs.com/zhangjianbin/p/6439206.html

https://www.cnblogs.com/wzdnwyyu/p/11180461.html

你可能感兴趣的:(SpringBoot2.x—SpringCache(1)集成)