Spring缓存框架使用及原理

发布时间 2023-09-29 23:53:32作者: strongmore

使用

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 [],user()方法的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对添加了常用的那些注解的类创建动态代理类,在方法执行前后做一些操作。

  1. 进入CacheInterceptor的invoke()方法
  2. 进入父类CacheAspectSupport的execute(CacheOperationInvoker,Method,CacheOperationContexts)方法,在此方法中处理@CacheEvict,@Cacheable等注解
  3. 先处理@CacheEvict注解,如果配置的beforeInvocation为true,就先删除缓存值
  4. 再处理@Cacheable注解,查询出缓存值,如果没查询到,就执行方法,并将结果添加到缓存中
  5. 再处理@CachePut注解,将方法返回值添加到缓存中
  6. 再处理@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的序列化器,增加了过期时间

参考

Spring Cache,从入门到真香