Spring Cache

什么是Spring Cache?

  Spring Cache 是Spring 提供的一整套的缓存解决方案,它不是具体的缓存实现,它只提供一整套的接口和代码规范、配置、注解等,用于整合各种缓存方案,比如Caffeine、Guava Cache、Ehcache

  Spring Cache并不是缓存的实现,而是缓存使用的一种方式,其基于注解和Spring高级特性提供缓存读写以及失效刷新等各种能力

 

 Spring Cache实现:

  在 Spring 3.1 中引入了多 Cache 的支持,在 spring-context 包中定义了org.springframework.cache.Cache 和 org.springframework.cache.CacheManager 两个接口来统一不同的缓存技术。

  Cache 接口包含缓存的常用操作:增加、删除、读取等。CacheManager 是 Spring 各种缓存的抽象接口。

  Spring 支持的常用 CacheManager 如下:

  

  
CaffeineCacheManager 使用 Caffeine 作为缓存
CompositeCacheManager 用于组合 CacheManager,可以从多个 CacheManager 中轮询得到相应的缓存
ConcurrentMapCacheManager

默认,使用 java.util.ConcurrentHashMap 来实现缓存

private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<>(16);

EhCacheCacheManager  
JCacheCacheManager  
NoOpCacheManager 仅测试用,不会实际存储缓存
RedisCacheManager 使用Redis作为缓存
SimpleCacheManager

使用简单的 Collection 来缓存

private Collection<? extends Cache> caches = Collections.emptySet();

 

  默认的缓存实现是ConcurrentMapCacheManager

  除此之外,抽象的 CacheManager 既能集成基于本地内存的单体应用,也能集成 EhCache、Redis 等缓存服务器

 

Spring Cache使用

 1、Caffeine Cache

  1、添加Spring Cache依赖

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

  2、添加缓存服务依赖,如Caffeine

        <!--Caffeine-->
        <dependency>
            <groupId>com.github.ben-manes.caffeine</groupId>
            <artifactId>caffeine</artifactId>
            <version>3.1.2</version> <!-- spring-boot-dependencies在dependencyManagement里指定了Caffeine依赖的版本,此处可以不用指定 -->
        </dependency>

  3、配置缓存管理器

import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.concurrent.TimeUnit;

/**
 * SpringCache配置
 *
 * @author yangyongjie
 */
@EnableCaching
@Configuration
public class SpringCacheConfig {
    /**
     * 配置缓存管理器
     *
     * @return 缓存管理器
     */
    @Bean("caffeineCacheManager")
    public CacheManager caffeineCacheManager() {
        CaffeineCacheManager caffeineCacheManager = new CaffeineCacheManager();
        caffeineCacheManager.setCaffeine(Caffeine.newBuilder()
                // 设置最后一次写入或访问后经过固定时间过期
                .expireAfterAccess(10, TimeUnit.MINUTES)
                // 初始的缓存空间大小
                .initialCapacity(200)
                // 缓存的最大条数
                .maximumSize(1000));
        return caffeineCacheManager;
    }

}

  4、开启Spring Cache

    启动类或者配置类上添加注解 @EnableCaching

  5、使用注解激活缓存,在业务服务类或者在mapper上使用注解:

@CacheConfig(cacheNames = "caffeineCacheManager")
public interface CcContentLevelMapper {
    /**
     * 按id查询
     *
     * @param id
     * @return
     */
    @Cacheable(key = "#id")
    CcContentLevel selectById(Long id);

    /**
     * 按cpId与channelId查询
     *
     * @param cpId
     * @param channelId
     * @return
     */
    @Cacheable(key = "#cpId+'_'+#channelId")
    CcContentLevel selectByCpIdAndChannelId(Integer cpId, Integer channelId);

}

 

  Spring Cache注解说明:

注解

功能

用法

说明

@Cacheable:

触发缓存写入

用于查询方法

触发缓存读取操作,用于查询方法上,如果缓存中找到则直接取出缓存并返回,否则执行目标方法并将结果缓存
@CachePut: 在不干扰方法执行的情况下更新缓存 用在新增/更新方法 触发缓存写入的方法上,与 Cacheable 相比,该注解的方法始终都会被执行,并且使用方法返回的结果去写入缓存
@CacheEvict 触发缓存删除 更新或者删除的方法 触发缓存失效,删除缓存项或者清空缓存
@Caching 将多个缓存操作重新分组以应用于方法    
@CacheConfig 在类级别共享一些常见的缓存相关设置 类上 当我们需要缓存的地方越来越多,你可以使用@CacheConfig(cacheNames = {"cacheName"})注解在 class 之上来统一指定value的值,这时可省略value,如果你在你的方法依旧写上了value,那么依然以方法的value值为准 

                   

 

Spring Cache源码解读:

  1、CachingConfigurationSelector

@Import(CachingConfigurationSelector.class)
public @interface EnableCaching {...}

    EnableCaching注解导入了CachingConfigurationSelector

     CachingConfigurationSelector注册了两个Bean

      AutoProxyRegistrar.class

      ProxyCachingConfiguration.class

  2、ProxyCachingConfiguration 注入了三个bean,BeanFactoryCacheOperationSourceAdvisor、CacheOperationSource、CacheInterceptor,重点关注CacheInterceptor

  3、CacheInterceptor:对于@Cacheable,@CachePut,@CacheEvict等注解做一个拦截aop切面操作

    Object target = invocation.getThis();通过代理创建并获取了这个对象

    Method method = invocation.getMethod();获取了需要进行切面的方法

    执行execute(aopAllianceInvoker, target, method, invocation.getArguments());


  4、CacheAspectSupport

    execute方法进入到了CacheAspectSupport中

     // Check if we have a cached item matching the conditions 检查是否有匹配的缓存
         Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));    

	private Cache.ValueWrapper findCachedItem(Collection<CacheOperationContext> contexts) {
		Object result = CacheOperationExpressionEvaluator.NO_RESULT;
		for (CacheOperationContext context : contexts) {
			if (isConditionPassing(context, result)) {
				Object key = generateKey(context, result);
				Cache.ValueWrapper cached = findInCaches(context, key);
				if (cached != null) {
					return cached;
				}
				else {
					if (logger.isTraceEnabled()) {
						logger.trace("No cache entry for key '" + key + "' in cache(s) " + context.getCacheNames());
					}
				}
			}
		}
		return null;
	}

  如果命中缓存,则返回缓存,否则调用查询获取结果,然后缓存起来

if (cacheHit != null && !hasCachePut(contexts)) {
   // If there are no put requests, just use the cache hit
   cacheValue = cacheHit.get();
   returnValue = wrapCacheValue(method, cacheValue);
}
else {
   // Invoke the method if we don't have a cache hit
   returnValue = invokeOperation(invoker);
   cacheValue = unwrapReturnValue(returnValue);
}

 

  总结:Spring Cache会根据 被代理的类+方法+返回类型+id寻找是否存在cache,存在则缓存,否则执行数据库查询操作 

 

 

 2、Redis Cache

<!-- Spring Cache的引入-->
        <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>

  缓存管理器

import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;

import java.time.Duration;

/**
 * SpringCache配置
 *
 * @author yangyongjie
 */
@EnableCaching
@Configuration
public class SpringCacheConfig {

    @Bean
    @Primary    // 加入这个注解表示默认使用
    public RedisCacheManager redisCacheManager30M(RedisConnectionFactory connectionFactory) {
        RedisCacheConfiguration config = initRedisCacheConfig(30L);
        return RedisCacheManager.builder(connectionFactory).cacheDefaults(config).build();
    }

    /**
     * 1小时过期的Redis缓存管理器
     *
     * @param connectionFactory Redis连接工厂
     * @return Redis缓存管理器
     */
    @Bean
    public RedisCacheManager redisCacheManager1H(RedisConnectionFactory connectionFactory) {
        RedisCacheConfiguration config = initRedisCacheConfig(60L);
        return RedisCacheManager.builder(connectionFactory).cacheDefaults(config).build();
    }

    /**
     * 初始化Redis缓存配置
     *
     * @param minute 分钟
     * @return Redis缓存配置
     */
    private RedisCacheConfiguration initRedisCacheConfig(Long minute) {
        return RedisCacheConfiguration.defaultCacheConfig()
                // 设置缓存的默认过期时间(min)
                .entryTtl(Duration.ofMinutes(minute))
                // 不缓存空值
                .disableCachingNullValues()
                // 覆盖默认的构造key,否则会多出一个冒号 原规则:cacheName::key  现规则:cacheName:key
                .computePrefixWith(name -> name + ":");
    }

}

 

  在CacheManager中可以设置usePrefix的值,如果为true,那么最后操作redis的key=value::(key或者keyGenerator生成的key);如果为false,那么就是原始key

 

    
附录:
  Spring Cache官方文档:https://docs.spring.io/spring-framework/docs/current/reference/html/integration.html#cache
 
 

END.

posted @ 2022-06-06 09:55  杨岂  阅读(418)  评论(0编辑  收藏  举报