spring cache 學(xué)習(xí)文檔
1, spring cache 的使用
1.1 依賴導(dǎo)入
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
1.2 代碼示例
@RestController
@RequestMapping("/books")
public class BookController {
@Cacheable(value = "books", key = "#role+':'+#id")
@GetMapping("/{id}")
public Book getBook(@PathVariable("id") String id, @RequestHeader("role") String role) {
sleep(5000);
Book book = MockUtils.selectById(id);
return book;
}
@PostMapping
@Caching(put = {@CachePut(value = "books", key = "#role+':'+#book.id")}, evict = {@CacheEvict(cacheNames = {"book2"}, allEntries = true)})
public Book updateById(@RequestBody Book book, @RequestHeader("role") String role) {
MockUtils.updateById(book);
return book;
}
@Cacheable(cacheNames = {"book2", "book3"}, key = "#arg")
@GetMapping("/all")
public Object getAll(String arg) {
sleep(5000);
return MockUtils.books();
}
@CacheEvict(cacheNames = {"book3"},allEntries = true)
@GetMapping("/clear")
public void clear() {
}
@SneakyThrows
public void sleep(long time) {
Thread.sleep(time);
}
}
1.3 cache 注解介紹
Spring Cache 注解是 Spring 框架提供的一種方便的方式,用于簡化應(yīng)用程序中的緩存操作。通過使用這些注解,您可以輕松地將方法結(jié)果緩存起來,以提高應(yīng)用程序的性能。以下是 Spring Cache 注解的使用介紹:
@EnableCaching: 這個注解通常用于配置類上,用于啟用 Spring Cache 支持。它告訴 Spring 框架要啟用緩存功能。配置類上使用@EnableCaching后,您可以在其他組件中使用緩存注解。-
@Cacheable: 這個注解用于標(biāo)記方法,表示該方法的結(jié)果應(yīng)該被緩存。您可以指定一個或多個緩存名稱,以及一個緩存鍵(可使用 SpEL 表達(dá)式動態(tài)生成)。當(dāng)方法被調(diào)用時,Spring 框架會首先檢查緩存,如果找到緩存值,則直接返回緩存值,而不執(zhí)行方法體。如果緩存中沒有找到值,方法會執(zhí)行,結(jié)果將被緩存。示例:
@Cacheable(value = "books", key = "#id") public Book getBook(String id) { // ... } -
@CacheEvict: 這個注解用于標(biāo)記方法,表示該方法會清除一個或多個緩存中的值。您可以指定一個或多個緩存名稱,以及一個緩存鍵(可使用 SpEL 表達(dá)式動態(tài)生成)。當(dāng)方法執(zhí)行后,它會清除指定緩存中的值。示例:
@CacheEvict(value = "books", key = "#id") public void deleteBook(String id) { // ... } -
@CachePut: 這個注解用于標(biāo)記方法,表示該方法會將結(jié)果放入緩存。與@Cacheable不同,@CachePut無論緩存中是否已存在相同的鍵,都會執(zhí)行方法體并將結(jié)果放入緩存。示例:
@CachePut(value = "books", key = "#book.id") public Book updateBook(Book book) { // ... } -
@Caching: 這個注解允許您同時應(yīng)用多個緩存注解在同一個方法上。您可以在@Caching注解中包含多個@Cacheable,@CacheEvict, 或@CachePut注解,以定義多個緩存操作。示例:
javaCopy code @Caching(put = {@CachePut(value = "books", key = "#book.id")}, evict = {@CacheEvict(value = "books", allEntries = true)}) public void saveOrUpdateBook(Book book) { // ... } -
@CacheConfig: 這個注解通常用于配置類上,用于指定緩存的默認(rèn)配置。您可以在該類中使用@Cacheable、@CacheEvict等注解時,省略緩存名稱等配置,因為它們會繼承自@CacheConfig。示例:
@CacheConfig(cacheNames = "books") public class BookService { // ... }
這些注解使緩存配置更加簡單,讓您可以通過注解方式輕松地控制緩存操作。通過在適當(dāng)?shù)姆椒ㄉ咸砑舆@些注解,您可以提高應(yīng)用程序的性能,尤其是對于需要頻繁訪問的數(shù)據(jù)
1.4 緩存 key 的生成
@FunctionalInterface
public interface KeyGenerator {
/**
* Generate a key for the given method and its parameters.
* @param target the target instance
* @param method the method being called
* @param params the method parameters (with any var-args expanded)
* @return a generated key
*/
Object generate(Object target, Method method, Object... params);
}
2 spring cache 源碼解析
2.1 開啟注解
2.2 配置類引入
2.2.1 CachingConfigurationSelector
public class CachingConfigurationSelector extends AdviceModeImportSelector<EnableCaching> {
private static final String PROXY_JCACHE_CONFIGURATION_CLASS =
"org.springframework.cache.jcache.config.ProxyJCacheConfiguration";
private static final String CACHE_ASPECT_CONFIGURATION_CLASS_NAME =
"org.springframework.cache.aspectj.AspectJCachingConfiguration";
private static final String JCACHE_ASPECT_CONFIGURATION_CLASS_NAME =
"org.springframework.cache.aspectj.AspectJJCacheConfiguration";
private static final boolean jsr107Present;
private static final boolean jcacheImplPresent;
static {
ClassLoader classLoader = CachingConfigurationSelector.class.getClassLoader();
jsr107Present = ClassUtils.isPresent("javax.cache.Cache", classLoader);
jcacheImplPresent = ClassUtils.isPresent(PROXY_JCACHE_CONFIGURATION_CLASS, classLoader);
}
/**
* Returns {@link ProxyCachingConfiguration} or {@code AspectJCachingConfiguration}
* for {@code PROXY} and {@code ASPECTJ} values of {@link EnableCaching#mode()},
* respectively. Potentially includes corresponding JCache configuration as well.
*/
@Override
public String[] selectImports(AdviceMode adviceMode) {
switch (adviceMode) {
case PROXY:
return getProxyImports();
case ASPECTJ:
return getAspectJImports();
default:
return null;
}
}
/**
* Return the imports to use if the {@link AdviceMode} is set to {@link AdviceMode#PROXY}.
* <p>Take care of adding the necessary JSR-107 import if it is available.
*/
private String[] getProxyImports() {
List<String> result = new ArrayList<>(3);
result.add(AutoProxyRegistrar.class.getName());
result.add(ProxyCachingConfiguration.class.getName());
if (jsr107Present && jcacheImplPresent) {
result.add(PROXY_JCACHE_CONFIGURATION_CLASS);
}
return StringUtils.toStringArray(result);
}
/**
* Return the imports to use if the {@link AdviceMode} is set to {@link AdviceMode#ASPECTJ}.
* <p>Take care of adding the necessary JSR-107 import if it is available.
*/
private String[] getAspectJImports() {
List<String> result = new ArrayList<>(2);
result.add(CACHE_ASPECT_CONFIGURATION_CLASS_NAME);
if (jsr107Present && jcacheImplPresent) {
result.add(JCACHE_ASPECT_CONFIGURATION_CLASS_NAME);
}
return StringUtils.toStringArray(result);
}
}
2.2.2 ProxyCachingConfiguration 默認(rèn)
@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyCachingConfiguration extends AbstractCachingConfiguration {
@Bean(name = CacheManagementConfigUtils.CACHE_ADVISOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public BeanFactoryCacheOperationSourceAdvisor cacheAdvisor() {
//advistor 顧問 ,負(fù)責(zé)把
切點(diǎn) pointcut(用來指定需要將通知使用到哪些地方,比如需要用在哪些類的哪些方法上,切入點(diǎn)就是做這個配置的。
) 和
advice(需要增強(qiáng)的功能)
BeanFactoryCacheOperationSourceAdvisor advisor = new BeanFactoryCacheOperationSourceAdvisor();
advisor.setCacheOperationSource(cacheOperationSource());
advisor.setAdvice(cacheInterceptor());
if (this.enableCaching != null) {
advisor.setOrder(this.enableCaching.<Integer>getNumber("order"));
}
return advisor;
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public CacheOperationSource cacheOperationSource() {
return new AnnotationCacheOperationSource();
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public CacheInterceptor cacheInterceptor() {
CacheInterceptor interceptor = new CacheInterceptor();
interceptor.configure(this.errorHandler, this.keyGenerator, this.cacheResolver, this.cacheManager);
interceptor.setCacheOperationSource(cacheOperationSource());
return interceptor;
}
}
- AbstractCachingConfiguration : ProxyCachingConfiguration 的父類
@Configuration
public abstract class AbstractCachingConfiguration implements ImportAware {
@Nullable
protected AnnotationAttributes enableCaching;
@Nullable
protected Supplier<CacheManager> cacheManager;
@Nullable
protected Supplier<CacheResolver> cacheResolver;
@Nullable
protected Supplier<KeyGenerator> keyGenerator;
@Nullable
protected Supplier<CacheErrorHandler> errorHandler;
@Override
public void setImportMetadata(AnnotationMetadata importMetadata) {
this.enableCaching = AnnotationAttributes.fromMap(
importMetadata.getAnnotationAttributes(EnableCaching.class.getName(), false));
if (this.enableCaching == null) {
throw new IllegalArgumentException(
"@EnableCaching is not present on importing class " + importMetadata.getClassName());
}
}
@Autowired(required = false)
void setConfigurers(Collection<CachingConfigurer> configurers) {
if (CollectionUtils.isEmpty(configurers)) {
return;
}
if (configurers.size() > 1) {
throw new IllegalStateException(configurers.size() + " implementations of " +
"CachingConfigurer were found when only 1 was expected. " +
"Refactor the configuration such that CachingConfigurer is " +
"implemented only once or not at all.");
}
CachingConfigurer configurer = configurers.iterator().next();
useCachingConfigurer(configurer);
}
/**
* Extract the configuration from the nominated {@link CachingConfigurer}.
*/
protected void useCachingConfigurer(CachingConfigurer config) {
this.cacheManager = config::cacheManager;
this.cacheResolver = config::cacheResolver;
this.keyGenerator = config::keyGenerator;
this.errorHandler = config::errorHandler;
}
}
- CacheOperationSourcePointcut: 配置切入點(diǎn)
- CacheOperationSource: 解析方法緩存配置用的
-
BeanFactoryCacheOperationSourceAdvisor :
CacheOperationSourcePointcut :
-
matches 最終調(diào)用的是CacheOperationSource 的方法 即系帶有
public class SpringCacheAnnotationParser implements CacheAnnotationParser, Serializable { private static final Set<Class<? extends Annotation>> CACHE_OPERATION_ANNOTATIONS = new LinkedHashSet<>(8); static { CACHE_OPERATION_ANNOTATIONS.add(Cacheable.class); CACHE_OPERATION_ANNOTATIONS.add(CacheEvict.class); CACHE_OPERATION_ANNOTATIONS.add(CachePut.class); CACHE_OPERATION_ANNOTATIONS.add(Caching.class); } @Override public boolean isCandidateClass(Class<?> targetClass) { return AnnotationUtils.isCandidateClass(targetClass, CACHE_OPERATION_ANNOTATIONS); }
```
abstract class CacheOperationSourcePointcut extends StaticMethodMatcherPointcut implements Serializable {
//過濾類
protected CacheOperationSourcePointcut() {
setClassFilter(new CacheOperationSourceClassFilter());
}
@Override
//過濾方法
public boolean matches(Method method, Class<?> targetClass) {
CacheOperationSource cas = getCacheOperationSource();
return (cas != null && !CollectionUtils.isEmpty(cas.getCacheOperations(method, targetClass)));
}
protected abstract CacheOperationSource getCacheOperationSource();
private class CacheOperationSourceClassFilter implements ClassFilter {
@Override
public boolean matches(Class<?> clazz) {
if (CacheManager.class.isAssignableFrom(clazz)) {
return false;
}
CacheOperationSource cas = getCacheOperationSource();
return (cas == null || cas.isCandidateClass(clazz));
}
}
}
```
2.3 aop 相關(guān)
2.3.1 cache 對應(yīng)的 advistor
public class BeanFactoryCacheOperationSourceAdvisor extends AbstractBeanFactoryPointcutAdvisor {
@Nullable
private CacheOperationSource cacheOperationSource;
private final CacheOperationSourcePointcut pointcut = new CacheOperationSourcePointcut() {
@Override
@Nullable
protected CacheOperationSource getCacheOperationSource() {
return cacheOperationSource;
}
};
public void setCacheOperationSource(CacheOperationSource cacheOperationSource) {
this.cacheOperationSource = cacheOperationSource;
}
public void setClassFilter(ClassFilter classFilter) {
this.pointcut.setClassFilter(classFilter);
}
@Override
public Pointcut getPointcut() {
return this.pointcut;
}
}
2.3 .2 aop 的 MethodInterceptor 方法攔截器
public class CacheInterceptor extends CacheAspectSupport implements MethodInterceptor, Serializable {
@Override
@Nullable
public Object invoke(final MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();
CacheOperationInvoker aopAllianceInvoker = () -> {
try {
return invocation.proceed();
}
catch (Throwable ex) {
throw new CacheOperationInvoker.ThrowableWrapper(ex);
}
};
try {
// CacheAspectSupport 提供通用邏輯
return execute(aopAllianceInvoker, invocation.getThis(), method, invocation.getArguments());
}
catch (CacheOperationInvoker.ThrowableWrapper th) {
throw th.getOriginal();
}
}
}
2.3.3 , CacheOperationInvoker
實際上只是個函數(shù)式接口 用來開執(zhí)行 代理對象的MethodInvocation.proceed 方法,來獲取返回值
@FunctionalInterface
public interface CacheOperationInvoker {
*/
Object invoke() throws ThrowableWrapper;
/**
* Wrap any exception thrown while invoking {@link #invoke()}.
*/
@SuppressWarnings("serial")
class ThrowableWrapper extends RuntimeException {
private final Throwable original;
public ThrowableWrapper(Throwable original) {
super(original.getMessage(), original);
this.original = original;
}
public Throwable getOriginal() {
return this.original;
}
}
}
2 .3.4 CacheAspectSupport : 用來處理緩存的操作
-
CacheOperationSource 用于獲取緩存配置信息
[圖片上傳失敗...(image-204870-1697073547857)]
BasicOperation:
[圖片上傳失敗...(image-8ac017-1697073547857)]
- CacheOperation: 對應(yīng)于緩存注解配置信息
[圖片上傳失敗...(image-be5fe2-1697073547857)]
- CachePutRequest: 實際上就是獲取cache ,然后根據(jù)key 來更新緩存
private class CachePutRequest {
private final CacheOperationContext context;
private final Object key;
public CachePutRequest(CacheOperationContext context, Object key) {
this.context = context;
this.key = key;
}
public void apply(@Nullable Object result) {
if (this.context.canPutToCache(result)) {
for (Cache cache : this.context.getCaches()) {
//更新緩存
doPut(cache, this.key, result);
}
}
}
}
@Nullable
protected Object execute(CacheOperationInvoker invoker, Object target, Method method, Object[] args) {
// Check whether aspect is enabled (to cope with cases where the AJ is pulled in automatically)
if (this.initialized) {
Class<?> targetClass = getTargetClass(target);
CacheOperationSource cacheOperationSource = getCacheOperationSource();
if (cacheOperationSource != null) {
Collection<CacheOperation> operations = cacheOperationSource.getCacheOperations(method, targetClass);
if (!CollectionUtils.isEmpty(operations)) {
return execute(invoker, method,
new CacheOperationContexts(operations, method, args, target, targetClass));
}
}
}
return invoker.invoke();
}
@Nullable
private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {
// 方法調(diào)用前的緩存過期處理,設(shè)置了CacheEvict.isBeforeInvocation屬性時
processCacheEvicts(contexts.get(CacheEvictOperation.class), true,
CacheOperationExpressionEvaluator.NO_RESULT);
// Cacheable注解時,去查找緩存
Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));
// 緩存未命中時,把Cacheable加入到CachePut的集合,后面用于寫入緩存
List<CachePutRequest> cachePutRequests = new LinkedList<>();
if (cacheHit == null) {
collectPutRequests(contexts.get(CacheableOperation.class),
CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
}
Object cacheValue;
Object returnValue;
if (cacheHit != null && !hasCachePut(contexts)) {
// If there are no put requests, just use the cache hit
cacheValue = cacheHit.get();
returnValue = wrapCacheValue(method, cacheValue);
}
else {
// 未找到緩存時,調(diào)用目標(biāo)方法
returnValue = invokeOperation(invoker);
cacheValue = unwrapReturnValue(returnValue);
}
// 收集CachePut注解,后面寫入緩存
collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);
// 寫入緩存
for (CachePutRequest cachePutRequest : cachePutRequests) {
cachePutRequest.apply(cacheValue);
}
// 處理緩存過期邏輯
processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);
// 返回結(jié)果給代理類
return returnValue;
}
3 spring cache 落地
3.1 spring cache 集成 caffeine
3.1.1 caffeine 介紹
Caffeine 是一個高性能的 Java 緩存庫,用于在應(yīng)用程序中管理內(nèi)存緩存。它提供了許多功能和優(yōu)化,以幫助應(yīng)用程序提高數(shù)據(jù)的訪問速度和降低資源消耗。以下是 Caffeine 的一些主要特點(diǎn)和優(yōu)勢:
- 高性能: Caffeine 被設(shè)計成一個高性能的緩存庫,具有低延遲和高吞吐量。它通過各種算法和數(shù)據(jù)結(jié)構(gòu)的使用來優(yōu)化緩存操作,以確??焖俚臄?shù)據(jù)訪問。
- 內(nèi)存管理: Caffeine 具有靈活的內(nèi)存管理功能,可以控制緩存的容量和淘汰策略。您可以指定緩存的最大容量、最大條目數(shù)、過期時間等,以滿足您的應(yīng)用程序需求。
- 并發(fā)支持: Caffeine 是線程安全的,支持高并發(fā)訪問。它使用先進(jìn)的并發(fā)數(shù)據(jù)結(jié)構(gòu)來確保多線程環(huán)境下的數(shù)據(jù)一致性和性能。
- 加載和刷新策略: 您可以定義自定義的加載策略,以便在緩存中沒有找到數(shù)據(jù)時加載數(shù)據(jù)。還可以定義自動刷新策略,以確保緩存中的數(shù)據(jù)保持新鮮。
- 監(jiān)聽器支持: Caffeine 允許您注冊監(jiān)聽器以監(jiān)聽緩存事件,如數(shù)據(jù)加載、數(shù)據(jù)移除等。
- 統(tǒng)計信息: Caffeine 提供了豐富的緩存統(tǒng)計信息,幫助您了解緩存的使用情況和性能。
- 自定義策略: 您可以自定義各種策略,包括淘汰策略、加載策略和刷新策略,以滿足不同的使用場景。
- 輕量級: Caffeine 是一個輕量級的庫,沒有過多的依賴,易于集成到各種 Java 項目中。
- 廣泛使用: Caffeine 被廣泛用于各種應(yīng)用程序中,包括大型企業(yè)級應(yīng)用、微服務(wù)架構(gòu)、桌面應(yīng)用程序等。
總的來說,Caffeine 是一個強(qiáng)大的緩存庫,適用于需要高性能和可配置性的應(yīng)用程序,它提供了豐富的功能和優(yōu)化,可以顯著提高應(yīng)用程序的性能。
3.1.2 caffeine 依賴引入
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>2.9.1</version> <!-- 按需替換為最新版本 -->
</dependency>
3.1.3 配置 CaffeineCacheManager
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager caffeineCacheManager = new CaffeineCacheManager();
caffeineCacheManager.setCaffeine(Caffeine.newBuilder()
.expireAfterWrite(30, TimeUnit.SECONDS)
.initialCapacity(100)
.maximumSize(10_000));
caffeineCacheManager.setCacheLoader(new CacheLoader<Object, Object>() {
@Override
public @Nullable Object load(@NonNull Object key) throws Exception {
log.info("數(shù)據(jù)過期了");
return null;
}
});
return caffeineCacheManager;
}
3.1.4 給不同的cacheName 配置不同的過期時間
3.1.4.1 ,自定義 CacheResolver
public class CustomCacheResolver extends SimpleCacheResolver {
private Map<String, Caffeine<Object, Object>> caffeineConfigMap;
public CustomCacheResolver(CacheManager cacheManager, Map<String, Caffeine<Object, Object>> caffeineConfigMap) {
super(cacheManager);
this.caffeineConfigMap = caffeineConfigMap;
}
@Override
protected Caffeine<Object, Object> createCacheConfiguration(String cacheName) {
return caffeineConfigMap.get(cacheName);
}
}
@Bean
public CustomCacheResolver customCacheResolver(CacheManager cacheManager) {
Map<String, Caffeine<Object, Object>> caffeineConfigMap = new HashMap<>();
caffeineConfigMap.put("cache1", caffeineConfig1());
caffeineConfigMap.put("cache2", caffeineConfig2());
return new CustomCacheResolver(cacheManager, caffeineConfigMap);
}
3.1.5 配置多個 cachemanger
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CaffeineCacheManager cacheManager1() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeine(caffeineConfig1());
return cacheManager;
}
@Bean
public CaffeineCacheManager cacheManager2() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeine(caffeineConfig2());
return cacheManager;
}
@Bean
public Caffeine<Object, Object> caffeineConfig1() {
return Caffeine.newBuilder()
.maximumSize(500)
.expireAfterWrite(10, TimeUnit.MINUTES);
}
@Bean
public Caffeine<Object, Object> caffeineConfig2() {
return Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(30, TimeUnit.MINUTES);
}
}
@Cacheable(cacheNames = "cache1", cacheManager = "cacheManager1")
public Object cache1Method() {
// ...
}
@Cacheable(cacheNames = "cache2", cacheManager = "cacheManager2")
public Object cache2Method() {
// ...
}
3.2 spring cache 集成redis
3.2.1 依賴
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(10)); // 設(shè)置緩存過期時間
return RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(cacheConfiguration)
.build();
}
3.2.2 配置類
@Configuration
public class CacheConfig {
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(10)); // 設(shè)置緩存過期時間
return RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(cacheConfiguration)
.build();
}
}