Spring声明式基于注解的缓存(1-理论篇)

简介: 目录一、概述二、声明式基于注解的缓存1、@Cacheable注解(1) 默认缓存key的生成(2) 声明式自定义key生成(3) 默认缓存解析(3) 自定义缓存解析(4) 条件式缓存2、@CachePut注解3、@CacheEvict注解4、@Caching注解5、@CacheConfig注解三、开启声明式缓存注解四、使用自定义注解

目录

一、概述

二、声明式基于注解的缓存

1、@Cacheable注解

(1) 默认缓存key的生成

(2) 声明式自定义key生成

(3) 默认缓存解析

(3) 自定义缓存解析

(4) 条件式缓存

2、@CachePut注解

3、@CacheEvict注解

4、@Caching注解

5、@CacheConfig注解

三、开启声明式缓存注解

四、使用自定义注解

一、概述


从3.1版本起,Spring框架就已经支持将缓存添加到现有的Spring应用中,和事务支持一样,缓存抽象允许在对代码影响最小的情况下一致性地使用各种缓存解决方案。

Spring 4.1版本起,有了JSR-107注解和更多定制化的选项支持后,缓存抽象有了重大的改进。


二、声明式基于注解的缓存



@Cacheable:触发缓存构建。

@CacheEvict:触发缓存销毁。

@CachePut:更新缓存。

@Caching:重组应用到方法上的多个缓存操作。

@CacheConfig:类级别共享缓存相关的通用设置。


1、@Cacheable注解

正如其名,@Cacheable注解用来区分方法执行结果是否应该被缓存,如果后续该方法再次被调用,方法的执行结果直接从缓存中获取,而不会调用实际的方法逻辑。示例如下:

@Cacheable("books")
public Book findBook(ISBN isbn) {...}


当然我们也可以指定多个缓存名称,如果至少一个缓存被命中,那么关联的缓存结果就会返回,示例如下:

@Cacheable({"books", "isbns"})
public Book findBook(ISBN isbn) {...}

因为缓存都是key-value存储,每次缓存方法的调用都会被转义为缓存key的访问。Spring缓存抽象对于key的生成会采用KeyGenerator来生成,算法如下:


如果没有方法参数,返回SimpleKey.EMPTY。

如果该方法只有一个参数,返回参数实例。

如果方法不止一个参数,返回包含所有参数的SimpleKey实例。

这种key的生成策略适用于大部分场景,只要方法参数合理实现了hashCode()和equals()方法。


SimpleKeyGenerator源码如下:

public class SimpleKeyGenerator implements KeyGenerator {
  @Override
  public Object generate(Object target, Method method, Object... params) {
    return generateKey(params);
  }
  /**
   * Generate a key based on the specified parameters.
   */
  public static Object generateKey(Object... params) {
    if (params.length == 0) {
      return SimpleKey.EMPTY;
    }
    if (params.length == 1) {
      Object param = params[0];
      if (param != null && !param.getClass().isArray()) {
        return param;
      }
    }
    return new SimpleKey(params);
  }
}


备注:如果要实现自定义key生成策略,需要实现org.springframework.cache.interceptor.KeyGenerator接口。

(2) 声明式自定义key生成

目标方法可能会有多个参数,有些参数可能只应用于方法逻辑,而不适合用作key的生成,例如:

@Cacheable("books")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)


对于这种情况,@Cacheable注解有一个key属性,通过该属性可以自定义Key生成。我们也可以使用SPEL(Spring表达式语言)去指定参数或者参数的嵌套属性,示例如下:

@Cacheable(cacheNames="books", key="#isbn")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
@Cacheable(cacheNames="books", key="#isbn.rawNumber")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
@Cacheable(cacheNames="books", key="T(someType).hash(#isbn)")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

(3) 默认缓存解析

Spring缓存抽象通过CacheResolver去解析操作级别的缓存,而CacheResolver会用CacheManager去获取缓存,接口定义如下:

@FunctionalInterface
public interface CacheResolver {
  /**
   * Return the cache(s) to use for the specified invocation.
   * @param context the context of the particular invocation
   * @return the cache(s) to use (never {@code null})
   * @throws IllegalStateException if cache resolution failed
   */
  Collection<? extends Cache> resolveCaches(CacheOperationInvocationContext<?> context);
}


public interface CacheManager {
  /**
   * Get the cache associated with the given name.
   * <p>Note that the cache may be lazily created at runtime if the
   * native provider supports it.
   * @param name the cache identifier (must not be {@code null})
   * @return the associated cache, or {@code null} if such a cache
   * does not exist or could be not created
   */
  @Nullable
  Cache getCache(String name);
  /**
   * Get a collection of the cache names known by this manager.
   * @return the names of all caches known by the cache manager
   */
  Collection<String> getCacheNames();
}

(3) 自定义缓存解析

默认缓存解析对于单CacheManager应用适应很好,对于有多个缓存管理器的应用,我们可以对每个操作设置缓存管理器,如下:

@Cacheable(cacheNames="books", cacheManager="anotherCacheManager")
public Book findBook(ISBN isbn) {...}


(4) 条件式缓存

有时方法缓存结果可能要取决于指定的参数,缓存注解通过支持SPELcondition属性实现该功能,示例如下:

@Cacheable(cacheNames="book", condition="#name.length() < 32")
public Book findBook(String name

备注:如果Book对象的hardback属性为true则不缓存,为false才缓存。

当然,缓存抽象同时也支持java.util.Optional,只有当Optional中的值存在时,方法返回值才会被缓存。#result代表方法的执行结果,上面的我们可以改写:

@Cacheable(cacheNames="book", condition="#name.length() < 32", unless="#result?.hardback")
public Optional<Book> findBook(String name)


备注;#result引用的始终是Book对象,而不是Optional对象,因为返回值可能为空,所以我们应该使用安全导航操作符 => ?.

关于其它可以用的缓存SpEL表达式上下文,可以参考:Available Caching SpEL Evaluation Context。



2、@CachePut注解

这个注解主要用于更新缓存,也就说带有该注解的方法总是会执行,并且方法的返回值会刷新缓存。该注解和@Cacheable的参数相同,示例如下:

@CachePut(cacheNames="book", key="#isbn")
public Book updateBook(ISBN isbn, BookDescriptor descriptor)


备注:@CachePut和@Cacheable的主要区别在于后者会通过缓存跳过方法的执行,而前者为了更新缓存会迫使方法执行。


3、@CacheEvict注解

这个注解主要用来清除缓存,与@Cacheable注解相反,方法的执行会触发从缓存中删除数据。@CacheEvit注解要求指定一个或多个缓存名。除此之外,该注解还有一个额外的属性allEntries,指定该属性值为true后会清除某个缓存名下的所有缓存key。示例如下:

@CacheEvict(cacheNames="books", allEntries=true)
public void loadBooks(InputStream batch)


4、@Caching注解

有些情况下,相同类型多个注解,如@CacheEvict或者@CachePut需要被指定。@Caching注解允许多个嵌套@Cacheable、@CachePut、@CacheEvict注解用在同一个方法上。示例如下:

@Caching(evict = { @CacheEvict("primary"), @CacheEvict(cacheNames="secondary", key="#p0") })
public Book importBooks(String deposit, Date date)


5、@CacheConfig注解

目前我们已经了解到缓存操作提供了很多定制化的选项,然而有些定制化选项如果应用到类中的所有操作可能会有些冗余,示例如下:

@CacheConfig("books")
public class BookRepositoryImpl implements BookRepository {
  @Cacheable
  public Book findBook(ISBN isbn) {...}
}

@CacheConfig是一个类级别的注解,这个注解可以共享缓存名称,自定义的KeyGenerator,自定义的CacheManager和自定义的CacheResolver。


方法操作级别的自定义选项总是会重写@CacheConfig中的自定义选项。下面是每个缓存操作自定义选项对应的3个级别,优先级从上至下越来越高。

全局配置的CacheManager,KeyGenerator等。

类级别,通过@CacheConfig指定。

方法操作级别。

三、开启声明式缓存注解



直接在配置类上加上#EnableCaching即可,如下:

@Configuration
@EnableCaching
public class AppConfig {
}

四、使用自定义注解



Spring缓存抽象允许我们用自定义注解去标识什么方法可以触发缓存构建或者消除。@Cacheable, @CachePut, @CacheEvict and @CacheConfig这些注解都可以作为元注解,其实即使可以修饰其它注解,示例如下:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Cacheable(cacheNames="books", key="#isbn")
public @interface SlowService {
}


@Cacheable(cacheNames="books", key="#isbn")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
@SlowService
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

尽管@SlowService注解并不是Spring原生注解,但Spring容器会在运行时识别并且知道它是用来干嘛的。

备注:后面我们会利用自定义注解实现自定义过期时间的缓存方案。

相关文章
|
5月前
|
缓存 监控 Java
SpringBoot @Scheduled 注解详解
使用`@Scheduled`注解实现方法周期性执行,支持固定间隔、延迟或Cron表达式触发,基于Spring Task,适用于日志清理、数据同步等定时任务场景。需启用`@EnableScheduling`,注意线程阻塞与分布式重复问题,推荐结合`@Async`异步处理,提升任务调度效率。
858 128
|
4月前
|
XML Java 应用服务中间件
【SpringBoot(一)】Spring的认知、容器功能讲解与自动装配原理的入门,带你熟悉Springboot中基本的注解使用
SpringBoot专栏开篇第一章,讲述认识SpringBoot、Bean容器功能的讲解、自动装配原理的入门,还有其他常用的Springboot注解!如果想要了解SpringBoot,那么就进来看看吧!
544 2
|
5月前
|
Java 测试技术 数据库
使用Spring的@Retryable注解进行自动重试
在现代软件开发中,容错性和弹性至关重要。Spring框架提供的`@Retryable`注解为处理瞬时故障提供了一种声明式、可配置的重试机制,使开发者能够以简洁的方式增强应用的自我恢复能力。本文深入解析了`@Retryable`的使用方法及其参数配置,并结合`@Recover`实现失败回退策略,帮助构建更健壮、可靠的应用程序。
666 1
使用Spring的@Retryable注解进行自动重试
|
5月前
|
XML Java 数据格式
常用SpringBoot注解汇总与用法说明
这些注解的使用和组合是Spring Boot快速开发和微服务实现的基础,通过它们,可以有效地指导Spring容器进行类发现、自动装配、配置、代理和管理等核心功能。开发者应当根据项目实际需求,运用这些注解来优化代码结构和服务逻辑。
420 12
|
5月前
|
缓存 Java 应用服务中间件
Spring Boot配置优化:Tomcat+数据库+缓存+日志,全场景教程
本文详解Spring Boot十大核心配置优化技巧,涵盖Tomcat连接池、数据库连接池、Jackson时区、日志管理、缓存策略、异步线程池等关键配置,结合代码示例与通俗解释,助你轻松掌握高并发场景下的性能调优方法,适用于实际项目落地。
859 5
|
5月前
|
传感器 Java 数据库
探索Spring Boot的@Conditional注解的上下文配置
Spring Boot 的 `@Conditional` 注解可根据不同条件动态控制 Bean 的加载,提升应用的灵活性与可配置性。本文深入解析其用法与优势,并结合实例展示如何通过自定义条件类实现环境适配的智能配置。
291 0
探索Spring Boot的@Conditional注解的上下文配置
|
5月前
|
智能设计 Java 测试技术
Spring中最大化@Lazy注解,实现资源高效利用
本文深入探讨了 Spring 框架中的 `@Lazy` 注解,介绍了其在资源管理和性能优化中的作用。通过延迟初始化 Bean,`@Lazy` 可显著提升应用启动速度,合理利用系统资源,并增强对 Bean 生命周期的控制。文章还分析了 `@Lazy` 的工作机制、使用场景、最佳实践以及常见陷阱与解决方案,帮助开发者更高效地构建可扩展、高性能的 Spring 应用程序。
224 0
Spring中最大化@Lazy注解,实现资源高效利用
|
5月前
|
Java 测试技术 编译器
@GrpcService使用注解在 Spring Boot 中开始使用 gRPC
本文介绍了如何在Spring Boot应用中集成gRPC框架,使用`@GrpcService`注解实现高效、可扩展的服务间通信。内容涵盖gRPC与Protocol Buffers的原理、环境配置、服务定义与实现、测试方法等,帮助开发者快速构建高性能的微服务系统。
1066 0
|
9月前
|
缓存 NoSQL 关系型数据库
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
|
4月前
|
缓存 负载均衡 监控
135_负载均衡:Redis缓存 - 提高缓存命中率的配置与最佳实践
在现代大型语言模型(LLM)部署架构中,缓存系统扮演着至关重要的角色。随着LLM应用规模的不断扩大和用户需求的持续增长,如何构建高效、可靠的缓存架构成为系统性能优化的核心挑战。Redis作为业界领先的内存数据库,因其高性能、丰富的数据结构和灵活的配置选项,已成为LLM部署中首选的缓存解决方案。

热门文章

最新文章