参考文献

  • SpringBoot2 实战之旅

Spring Cache

  • Spring CacheSpring3.1以后引入的新技术.

  • 其核心思想是:当调用一个缓存方法时,会把该方法参数和返回值作为一个键值对存档在缓存中,等到下次利用同样的参数来调用该方法时将不再执行该方法,而是直接从缓存中获取结果进行返回,从实现缓存的功能.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
    </dependency>

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

    <!-- redisson -->
    <dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>${redisson.version}</version>
    <exclusions>
    <exclusion>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-data-31</artifactId>
    </exclusion>
    </exclusions>
    </dependency>
    <dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-data-22</artifactId>
    <version>${redisson.version}</version>
    </dependency>
    • 配置文件指定缓存为redis
    1
    2
    3
    spring:
    cache:
    type: redis
  • 需要加上@EnableCaching来开启缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
package com.yw.ct.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import org.bson.types.ObjectId;
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.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.io.IOException;
import java.io.Serializable;
import java.net.UnknownHostException;
import java.time.Duration;


/**
* @author HoleLin
*/
@Configuration
public class RedisAutoConfiguration {


@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisSerializer<Object> serializer = redisSerializer();
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(serializer);
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(serializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}

@Bean
public RedisSerializer<Object> redisSerializer() {
//创建JSON序列化器
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,
ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.WRAPPER_ARRAY);
om.registerModule(new SimpleModule().addSerializer(ObjectId.class, ToStringSerializer.instance));
jackson2JsonRedisSerializer.setObjectMapper(om);
return jackson2JsonRedisSerializer;
}

@Bean
public RedisCacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory) {
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);
//设置Redis缓存有效期为15分钟
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer())).entryTtl(Duration.ofMinutes(15));
return new RedisCacheManager(redisCacheWriter, redisCacheConfiguration);
}

@Bean("cacheKeyGenerator")
public CacheKeyGenerator cacheKeyGenerator() {
return new CacheKeyGenerator();
}

}

注解

@EnableCaching开启缓存功能

  • 开启缓存功能,配置类中需要加上这个注解,有了这个注解之后,Spring才知道你需要使用缓存的功能,其他和缓存相关的注解才会有效,Spring中主要通过Aop实现的,通过Aop来拦截需要使用缓存的方法,实现缓存的功能.
注解 作用
@Cacheable 将方法的结果缓存起来,下一次方法执行参数相同时,将不执行方法,返回缓存中的结果
@CacheEvict 移除指定缓存
@CachePut 标记该注解的方法总会执行,根据注解的配置将结果缓存
@Caching 可以指定相同类型的多个缓存注解,例如根据不同的条件
@CacheConfig 类级别注解,可以设置一些共通的配置,@CacheConfig(cacheNames="user"), 代表该类下的方法均使用这个cacheNames

@Cacheable

  • @Cacheable注解用于标记缓存,也就是对@Cacheable注解的位置进行缓存.
  • @Cacheable可以在方法或类上进行标记,当对方法进行标记时,表示此方法支持缓存;当对类进行标记时,表明当前类中所有方法都支持缓存.
  • Spring在每次调用方法前都会根据key查询当前Cache中是否存在相同的key的缓存元素,如果存在,就不再执行该方法,而是直接从缓存中获取结果进行返回,否则执行该方法并将返回结果存入指定的缓存中.
  • @Cacheable通常会搭配3个属性进行使用
    • value: 在使用@Cacheable注解的时候,value属性是必须的,用于指定Cache的名称.表明当前缓存的返回值用于哪个缓存上.

    • key: 和名称一样,用于指定缓存对应的key,key属性不是必须指定的,如果没有指定key,Spring就会使用默认策略生成的key.

      • 其中默认策略规定:如果当前缓存方法没有参数,那么当前key为0;如果当前缓存方法有一个参数,那么以key为参数;如果当前缓存方法有多个参数,那么key为所有参数的hashcode值.
      • 也可以使用EL表达式来指定当前缓存方法的key,通常为#参数名
    • condition: 主要用于指定当前缓存的触发条件.

1
2
3
4
@Cacheable(value="users",key="#user.id",condition="#user.id%2==0")
public User findUser(User user){
return new User();
}
conditionunless对比
  • 缓存的使用过程中有2个点:
    1. 查询缓存中是否有数据
    2. 如果缓存中没有数据,则去执行目标方法,然后将方法结果丢到缓存中
  • Spring中通过conditionunless对这2点进行干预。
  • condition作用域上面2个过程,当为true的时候,会尝试从缓存中获取数据,如果没有,会执行方法, 然后将方法返回值丢到缓存中;如果为false,则直接调用目标方法,并且结果不会放在缓存中。
  • unlessconditiontrue的情况下才有效,用来判断上面第2点中,是否不要将结果丢到缓存中,如果为true,则结果不会丢到缓存中,如果为false,则结果会丢到缓存中,并且unless中可以使用spel表达式通过#result来获取方法返回值。

@CachePut将结果放入缓存

  • 从名称上来看,@CachePut只是用于将标记该注解的方法的返回值放入缓存中,无论缓存中是否包含当前缓存,只是以键值的形式将执行结果放入缓存中.
  • 有3种情况,结果不会丢到缓存
    1. 当方法向外抛出的时候
    2. condition的计算结果为false的时候
    3. unless的计算结果为true的时候

@CacheEvict清除缓存

  • @CacheEvict注解用于清除缓存数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface CacheEvict {

@AliasFor("cacheNames")
String[] value() default {};

@AliasFor("value")
String[] cacheNames() default {};

String key() default "";

String keyGenerator() default "";

String cacheManager() default "";

String cacheResolver() default "";

String condition() default "";

/**
* 是否清理 cacheNames指定的缓存中是所有缓存信息,默认为false
* 可以将cache想象为一个hashMap,当allEntries为true的时候,相当与HashMap.clear()
* 当allEntries为false的时候,只会清除key对应的数据,相当与HashMap.remove(key)

* Whether all the entries inside the cache(s) are removed.
* <p>By default, only the value under the associated key is removed.
* <p>Note that setting this parameter to {@code true} and specifying a
* {@link #key} is not allowed.
*/
boolean allEntries() default false;

/**
* 何时执行清除(方法执行之前 or 方法执行成功之后)
* true: @CacheEvict 标注的方法执行之前,执行清除操作
* false: @CacheEvict 标注的方法执行成功之后,执行清除操作,当方法抛出异常的时候,不会执行清除操作.

* Whether the eviction should occur before the method is invoked.
* <p>Setting this attribute to {@code true}, causes the eviction to
* occur irrespective of the method outcome (i.e., whether it threw an
* exception or not).
* <p>Defaults to {@code false}, meaning that the cache eviction operation
* will occur <em>after</em> the advised method is invoked successfully (i.e.
* only if the invocation did not throw an exception).
*/
boolean beforeInvocation() default false;

}

@Caching 缓存注解组

  • 当需要在类上或者同一个方法上同时使用@Cacheable,@CachePut@CacheEvict这几个注解中的多个时,此时可以使用@Caching这个注解来实现
1
2
3
4
5
6
7
8
9
10
11
12
13
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Caching {

Cacheable[] cacheable() default {};

CachePut[] put() default {};

CacheEvict[] evict() default {};

}

@CacheConfig提取公共配置

  • 这个注解标注在类上,可以将其他几个缓存注解(@Cacheable,@CachePut,@CacheEvict)的公共参数给提取出来放在@CacheConfig

Spring ELCache 的支持

Name Location Description Example
methodName Root object 被调用的方法的名称 #root.methodName
method Root object 被调用的方法 #root.method.name
target Root object 当前调用方法的对象 #root.target
targetClass Root object 当前调用方法的类 #root.targetClass
args Root object 当前方法的参数 #root.args[0]
caches Root object 当前方法的缓存集合 #root.caches[0].name
Argument name Evaluation context 当前方法的参数名称 #iban or #a0 (you can also use #p0 or #p<#arg> notation as an alias).
result Evaluation context 方法返回的结果(要缓存的值)。只有在 **unless 、@CachePut(用于计算键)或@CacheEvict(beforeInvocation=false)中才可用.对于支持的包装器(例如Optional),#result**引用的是实际对象,而不是包装器 #result

Spring @Cacheable指定失效时间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Configuration
@EnableCaching
public class RedisCacheConfig {
@Bean
public RedisCacheManagerBuilderCustomizer redisCacheManagerBuilderCustomizer() {
return (builder) -> {
for (Map.Entry<String, Duration> entry : RedisCacheName.getCacheMap().entrySet()) {
builder.withCacheConfiguration(entry.getKey(),
RedisCacheConfiguration.defaultCacheConfig().entryTtl(entry.getValue()));
}
};
}

public static class RedisCacheName {
public static final String CACHE_10MIN ="CACHE_10MIN";
@Getter
private static final Map<String, Duration> cacheMap;
static {
cacheMap = ImmutableMap.<String, Duration>builder().put(CACHE_10MIN, Duration.ofSeconds(10L)).build();
}
}
}