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
注意不要将ObjectMapper加入到Spring容器中。因为Spring容器中存在一个ObjectMapper,以用于@RequestBody
、ResponseBody
、RestTemplate
等地的序列化和反序列化。
为什么不采用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,是一个重要的任务。
- 在SpringCache官网中,这样描述SpringCache默认的KeyGenerator的:
- 若没有参数值被得到,返回SimpleKey.EMPTY(空数组)。
- 若只有一个参数值被得到,返回该参数值的实例。
- 若多个参数值被得到,返回一个包含所有参数值SimpleKey对象。
- 默认的KeyGenerator如何获取参数值?
- 若注解上只是指定
cacheName
属性,SimpleKeyGenerator将获取所有的参数值。组成SimpleKey对象。 - 指定
cacheName
和key
属性,并且key
的属性支持SpEL
表达式:
- 基本形式
@Cacheable(value="cacheName", key="#id")
public ResultDTO method(int id);
- 组合形式
@Cacheable(value="cacheName", key="T(String).valueOf(#name).concat('-').concat(#password))
public ResultDTO method(int name, String password);
- 对象形式
@Cacheable(value="cacheName", key="#user.id)
public ResultDTO method(User user);
- 默认的SimpleKeyGenerator的缺陷
SimpleGenerator只会将参数值封装为SimpleKey对象。然后作为Key,可能会导致不同方法Key冲突。
我们虽然可以使用SpEL
表达式获取类名、方法名,在进行拼接。但是需要为每一个注解指定,太过于繁杂。
- 自定义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原理。故它们的注意事项也是相同。
- 若一个service中,注解方法被调用,则注解不会生效;
- 只有访问修饰符为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