Spring缓存框架使用及原理
使用
maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
开启缓存
@SpringBootApplication
@EnableCaching
public class CacheApplication {
public static void main(String[] args) {
SpringApplication.run(CacheApplication.class, args);
}
}
使用@EnableCaching注解
添加配置
server:
port: 8080
spring:
redis:
host: ip
port: 6379
password: xxx
cache:
type: REDIS
表示使用Redis来实现缓存
代码中使用
import com.imooc.cache.model.User;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
@Service
public class UserService {
@Cacheable(value = "users")
public List<User> getAllUsers() throws InterruptedException {
TimeUnit.SECONDS.sleep(5);
List<User> users = new ArrayList<>();
users.add(new User().setUsername("lisi1").setPassword("123").setAge(21));
users.add(new User().setUsername("lisi2").setPassword("456").setAge(22));
return users;
}
@Cacheable(value = "user", key = "#p0")
public User getUser(String userName) throws InterruptedException {
TimeUnit.SECONDS.sleep(2);
return new User().setUsername(userName).setPassword("123").setAge(21);
}
}
主要是@Cacheable注解,users()方法在redis中的key为users::SimpleKey []
,getUser()方法的key为user::${userName}
。
常用注解
- @Cacheable: 方法的返回值会被缓存下来,下一次调用该方法前,会去检查缓存中是否已经有值,如果有就直接返回。如果没有,就执行方法,然后将结果缓存起来。这个注解一般用在查询方法上。
- @CacheEvict: 清空指定缓存。一般用在更新或者删除的方法上。
- @CachePut: 会将方法的返回值put到缓存里面缓存起来,供其它地方使用。通常用在新增方法上。
- @Caching: 一个方法会操作多个缓存(这个在删除缓存操作中比较常见,在添加操作中不太常见)。
- @CacheConfig: 它是一个类级别的注解,可以在类级别上配置cacheNames、keyGenerator、cacheManager、cacheResolver等。
原理分析
因为我们添加了cache的maven依赖,SpringBoot自动装配了CacheAutoConfiguration,会根据我们配置的spring.cache.type
来选择不同的缓存实现,这里使用了RedisCacheConfiguration,配置了RedisCacheManager这个CacheManager,当然如果我们自己定义了,就会使用我们自己的。
因为我们使用@EnableCaching注解开启了缓存,就自动导入了CachingConfigurationSelector配置类,它又会导入ProxyCachingConfiguration,会配置BeanFactoryCacheOperationSourceAdvisor,使用的拦截器为CacheInterceptor,它就是查询、添加、删除缓存的核心。
总结起来,就是通过AOP对添加了常用的那些注解的类创建动态代理类,在方法执行前后做一些操作。
- 进入CacheInterceptor的invoke()方法
- 进入父类CacheAspectSupport的execute(CacheOperationInvoker,Method,CacheOperationContexts)方法,在此方法中处理@CacheEvict,@Cacheable等注解
- 先处理@CacheEvict注解,如果配置的beforeInvocation为true,就先删除缓存值
- 再处理@Cacheable注解,查询出缓存值,如果没查询到,就执行方法,并将结果添加到缓存中
- 再处理@CachePut注解,将方法返回值添加到缓存中
- 再处理@CacheEvict注解,如果配置的beforeInvocation为false,就后删除缓存值
关于@Cacheable注解的sync属性
设置为true的情况下,会走CacheAspectSupport的execute(CacheOperationInvoker,Method,CacheOperationContexts)方法的上面一部分,最终调用的是Cache的get(Object, Callcable)方法,Spring Cache是期望Cache的实现类在这个方法内部实现"同步"的功能,RedisCache就是通过给方法加上 synchronized 来实现同步的。
自定义CacheManager
点击查看代码
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
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.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class CacheBeanConfig extends CachingConfigurerSupport {
@Value("${jwt.expiration:50}")
private int jwtExpiration;
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
return RedisCacheManager.builder(factory)
.cacheDefaults(getDefaultCacheConfiguration(jwtExpiration))
.withInitialCacheConfigurations(getCacheConfigurations())
.build();
}
/**
* 提前创建Cache对象,可以加快一点速度
*/
private Map<String, RedisCacheConfiguration> getCacheConfigurations() {
Map<String, RedisCacheConfiguration> configurationMap = new HashMap<>();
configurationMap.put("user", this.getDefaultCacheConfiguration(jwtExpiration));
return configurationMap;
}
private RedisCacheConfiguration getDefaultCacheConfiguration(long seconds) {
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
GenericJackson2JsonRedisSerializer serializer = new GenericJackson2JsonRedisSerializer();
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(serializer))
//默认为 name -> name + "::"
.computePrefixWith(name -> name + ":");
if (seconds > 0) {
//缓存有效期配置
redisCacheConfiguration = redisCacheConfiguration.entryTtl(Duration.ofSeconds(seconds));
}
return redisCacheConfiguration;
}
}
相比默认的CacheManager,修改了key和value的序列化器,增加了过期时间