当前位置 : 首页 » 文章分类 :  开发  »  Spring-Cache

Spring-Cache

Spring 缓存使用笔记


CacheManager

要启用缓存支持,我们需要创建一个新的 CacheManager bean。
CacheManager 接口有很多实现,比如 RedisCacheManagerEhCacheCacheManager 等。

假如系统中只有一个 CacheManager 实例的话,Spring 默认使用这个缓存管理器,不需要在 @Cacheable 等缓存操作注解上额外指定 cacheManager 参数。
如果系统中有多个 CacheManager 实例的话,可以通过 cacheManager 参数选择使用的缓存管理器。

Spring缓存集成redis

以构造 RedisCacheManager 为例, 先构造连接工厂,再构造 RedisTemplate,再构造 CacheManager。
配置 CacheManager 时可以指定默认的过期时间,不设置的话默认为永久有效。

package com.masikkk.cache.redis;

import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;

@Configuration
@EnableCaching
public class RedisCacheConfig extends CachingConfigurerSupport {

    @Bean
    public JedisConnectionFactory redisConnectionFactory() {
        JedisConnectionFactory redisConnectionFactory = new JedisConnectionFactory();
        redisConnectionFactory.setHostName("192.168.1.166");
        redisConnectionFactory.setPort(6379);
        return redisConnectionFactory;
    }

    @Bean
    public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory cf) {
        RedisTemplate<String, String> redisTemplate = new RedisTemplate<String, String>();
        redisTemplate.setConnectionFactory(cf);
        return redisTemplate;
    }

    @Bean
    public CacheManager cacheManager(RedisTemplate redisTemplate) {
        RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);

        // Number of seconds before expiration. Defaults to unlimited (0)
        cacheManager.setDefaultExpiration(3000); // Sets the default expire time (in seconds)
        return cacheManager;
    }

}

Redis 缓存 + Spring 的集成示例
https://blog.csdn.net/defonds/article/details/48716161

要缓存的 Java 对象必须实现 Serializable 接口

要缓存的 Java 对象必须实现 Serializable 接口,因为 Spring 会将对象先序列化再存入 Redis。
比如缓存方法返回一个 UserBean ,则此 UserBean 必须实现 Serializable 接口,否则会报如下序列化错误:

org.springframework.data.redis.serializer.SerializationException: Cannot serialize; nested exception is org.springframework.core.serializer.support.SerializationFailedException: Failed to serialize object using DefaultSerializer; nested exception is java.lang.IllegalArgumentException: DefaultSerializer requires a Serializable payload but received an object of type [com.masikkk.bean.UserBean]

缓存Java对象时报错Could not read JSON: Unrecognized field “xx”

从 reids 中反序列化 json 对象时报错
spring cache class Could not read JSON: Unrecognized field “new”

直接原因:
序列化后的 json 中有多余字段,反序列化时 Jackson 找不到对于的字段,又没配置可忽略未知字段,所以就报错了。

深层原因:
在 json 序列化时,不仅是根据 get 方法来序列化的,而是实体类中所有的有返回值的方法都会将返回的值序列化,但是反序列化时是根据 set 方法来实现的,所以当实体类中有非get,set方法的方法有返回值时,反序列化时就会出错。

解决方法:
1 去掉多余方法,实体类中只放get,set方法或返回值为空的方法。

2 关闭 Jackson 的 FAIL_ON_UNKNOWN_PROPERTIES
RedisTemplate 的 HashValue 序列化器 Jackson2JsonRedisSerializer 的 ObjectMapper 配置关闭 FAIL_ON_UNKNOWN_PROPERTIES 选项

@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
    RedisTemplate<String, Object> template = new RedisTemplate<>();
    template.setConnectionFactory(connectionFactory);
    template.setValueSerializer(jackson2JsonRedisSerializer());
    template.setKeySerializer(new StringRedisSerializer());
    template.setHashKeySerializer(new StringRedisSerializer());
    template.setHashValueSerializer(jackson2JsonRedisSerializer());
    template.afterPropertiesSet();
    return template;
}

@Bean
public RedisSerializer<Object> jackson2JsonRedisSerializer() {
    //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
    Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<Object>(Object.class);

    ObjectMapper mapper = new ObjectMapper();
    mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
    mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
    mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    serializer.setObjectMapper(mapper);
    return serializer;
}

Spring 为我们提供了几个注解来支持 Spring Cache。其核心主要是 @Cacheable 和 @CacheEvict。使用 @Cacheable 标记的方法在执行后 Spring Cache 将缓存其返回结果,而使用 @CacheEvict 标记的方法会在方法执行前或者执行后移除 Spring Cache 中的某些元素。

@Cacheable

@Cacheable 可以标记在一个方法上,也可以标记在一个类上。当标记在一个方法上时表示该方法是支持缓存的,当标记在一个类上时则表示该类所有的方法都是支持缓存的。

对于一个支持缓存的方法,Spring会在其被调用后将其返回值缓存起来,以保证下次利用同样的参数来执行该方法时可以直接从缓存中获取结果,而不需要再次执行该方法。Spring在缓存方法的返回值时是以键值对进行缓存的,值就是方法的返回结果,至于键的话,Spring又支持两种策略,默认策略和自定义策略,这个稍后会进行说明。

需要注意的是当一个支持缓存的方法在对象内部被调用时是不会触发缓存功能的

@Cacheable 可以指定三个属性,value、key和condition。

value指定cache名称

value 属性是必须指定的,其表示当前方法的返回值是会被缓存在哪个Cache上的,对应Cache的名称。其可以是一个Cache也可以是多个Cache,当需要指定多个Cache时其是一个数组。

//Cache是发生在cache1上的
@Cacheable("cache1")
public User find(Integer id) {
  returnnull;
}

//Cache是发生在cache1和cache2上的
@Cacheable({"cache1", "cache2"})
public User find(Integer id) {
  returnnull;
}

key指定缓存key

key 属性是用来指定 Spring 缓存方法的返回结果时对应的 key 的。该属性支持 SpringEL 表达式。没有指定该属性时,Spring 将使用默认策略生成 key。
自定义策略是指我们可以通过 Spring 的 EL 表达式来指定key。这里的EL表达式可以使用方法参数及它们对应的属性。使用方法参数时我们可以直接使用 #参数名 或者 #p参数index。下面是几个使用参数作为key的示例。

@Cacheable(value="users", key="#id")
public User find(Integer id) {
  return null;
}

@Cacheable(value="users", key="#p0")
public User find(Integer id) {
  return null;
}

@Cacheable(value="users", key="#user.id")
public User find(User user) {
  return null;
}

@Cacheable(value="users", key="#p0.id")
public User find(User user) {
  return null;
}

SpEL

用 缓存名 + 字符串入参的小写 作为key,入参的小写可用spel写作:args[0]?.toLowerCase(),使用?符号代表若左边的值为null,将不执行右边方法,避免空指针产生

@Cacheable(value = "user-name-", key = "caches[0].name + args[0]?.toLowerCase()")
public UserSourceTypeBean getUserByName(String name) {
    checkState(StringUtils.isNotBlank(name), "Required string param 'name' can not be blank.");
    UserExample example = new UserExample();
    example.createCriteria().andNameEqualTo(name.toLowerCase());
    List<User> userList = userMapper.selectByExample(example);
    return CollectionUtils.isNotEmpty(userList) ? convertToUserBean(userList.get(0)) : null;
}

root对象

除了上述使用方法参数作为key之外,Spring还为我们提供了一个root对象可以用来生成key。通过该root对象我们可以获取到以下信息。

属性名称 描述 示例
methodName 当前方法名 #root.methodName
method 当前方法 #root.method.name 等于 #root.methodName
target 当前被调用的对象 #root.target
targetClass 当前被调用的对象的class #root.targetClass
args 当前方法参数组成的数组 #root.args[0]
caches 当前被调用的方法使用的Cache #root.caches[0].name

当我们要使用root对象的属性作为key时我们也可以将“#root”省略,因为Spring默认使用的就是root对象的属性。如:

@Cacheable(value={"users", "xxx"}, key="caches[1].name")
public User find(User user) {
  return null;
}

比如用cache名称和第一个参数做key:

@Cacheable(value = "cache-user", key = "caches[0].name + args[0]")
public User findUser(Long userId) {
 ...
}

keyGenerator 指定key生成器

自定义 key 生成器的 bean 名称,实现 org.springframework.cache.interceptor.KeyGenerator 接口

condition指定缓存的条件

有的时候我们可能并不希望缓存一个方法所有的返回结果。通过condition属性可以实现这一功能。
condition属性默认为空,表示将缓存所有的调用情形。其值是通过SpringEL表达式来指定的,当为true时表示进行缓存处理;当为false时表示不进行缓存处理,即每次调用该方法时该方法都会执行一次。

如下示例表示只有当user的id为偶数时才会进行缓存。

@Cacheable(value={"users"}, key="#user.id", condition="#user.id%2==0")
public User find(User user) {
  System.out.println("find user by user " + user);
  return user;
}

unless 指定不缓存的条件

使用 Spring Expression Language (SpEL) 指定不缓存哪些情况

使@Cacheable不缓存null值

@Cacheable(key = "#id", unless="#result == null")
public XXXPO get(int id) {
   //get from db
}

@CachePut

在支持Spring Cache的环境下,对于使用 @Cacheable 标注的方法,Spring在每次执行前都会检查Cache中是否存在相同key的缓存元素,如果存在就不再执行该方法,而是直接从缓存中获取结果进行返回,否则才会执行并将返回结果存入指定的缓存中。
@CachePut 也可以声明一个方法支持缓存功能。与@Cacheable不同的是使用@CachePut标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中。

@CachePut 也可以标注在类上和方法上。使用@CachePut时我们可以指定的属性跟@Cacheable是一样的。

//每次都会执行方法,并将结果存入指定的缓存中
@CachePut("users")
public User find(Integer id) {
  returnnull;
}

@CacheEvict

@CacheEvict 是用来标注在需要清除缓存元素的方法或类上的。当标记在一个类上时表示其中所有的方法的执行都会触发缓存的清除操作。
@CacheEvict 可以指定的属性有value、key、condition、allEntries和beforeInvocation。其中value、key和condition的语义与@Cacheable对应的属性类似。即value表示清除操作是发生在哪些Cache上的(对应Cache的名称);key表示需要清除的是哪个key,如未指定则会使用默认策略生成的key;condition表示清除操作发生的条件。

下面我们来介绍一下新出现的两个属性allEntries和beforeInvocation。

allEntries属性

allEntries 是boolean类型,表示是否需要清除缓存中的所有元素。默认为false,表示不需要。当指定了allEntries为true时,Spring Cache将忽略指定的key。有的时候我们需要Cache一下清除所有的元素,这比一个一个清除元素更有效率。

@CacheEvict(value="users", allEntries=true)
public void delete(Integer id) {
  System.out.println("delete user by id: " + id);
}

beforeInvocation属性

清除操作默认是在对应方法成功执行之后触发的,即方法如果因为抛出异常而未能成功返回时也不会触发清除操作。使用beforeInvocation可以改变触发清除操作的时间,当我们指定该属性值为true时,Spring会在调用该方法之前清除缓存中的指定元素。

@CacheEvict(value="users", beforeInvocation=true)
public void delete(Integer id) {
  System.out.println("delete user by id: " + id);
}

其实除了使用@CacheEvict清除缓存元素外,当我们使用Ehcache作为实现时,我们也可以配置Ehcache自身的驱除策略,其是通过Ehcache的配置文件来指定的。

Spring缓存注解@Cacheable、@CacheEvict、@CachePut使用
https://blog.csdn.net/wjacketcn/article/details/50945887

注释驱动的 Spring cache 缓存介绍
https://www.ibm.com/developerworks/cn/opensource/os-cn-spring-cache/


上一篇 Node.js

下一篇 VIM

阅读
评论
2,663
阅读预计11分钟
创建日期 2019-01-30
修改日期 2020-08-17
类别

页面信息

location:
protocol:
host:
hostname:
origin:
pathname:
href:
document:
referrer:
navigator:
platform:
userAgent:

评论