【深入浅出Spring原理及实战】「缓存Cache开发系列」带你深入分析Spring所提供的缓存Cache管理器的实战开发指南(修正篇)

简介: 【深入浅出Spring原理及实战】「缓存Cache开发系列」带你深入分析Spring所提供的缓存Cache管理器的实战开发指南(修正篇)

前提介绍

Spring Cache是基于方法级别的,其核心思想是将调用带有缓存的方法时的参数和返回结果作为键值对存储在缓存中。当下次调用相同参数的方法时,直接从缓存中获取结果,而不再执行该方法。因此,在使用Spring Cache时,需要确保被缓存的方法对于相同的参数具有相同的返回结果。

使用Spring Cache需要进行两个步骤:

  1. 声明需要使用缓存的方法。
  2. 配置Spring对Cache的支持。

与Spring对事务管理的支持类似,Spring对Cache的支持有两种方式:基于注解和基于XML配置。下面我们先来看看基于注解的方式。

基于注解的支持

Spring提供了几个注解来支持Spring Cache,其中核心的注解包括@Cacheable和@CacheEvict。使用@Cacheable标记的方法在执行后,Spring Cache会缓存其返回结果,而使用@CacheEvict标记的方法可以在方法执行前或执行后移除Spring Cache中的某些元素。接下来,我们将详细介绍Spring基于注解对Cache的支持所提供的几个注解。

@Cacheable

@Cacheable注解可以应用于单个方法或整个类。当应用于方法时,该注解表示该方法支持缓存,当应用于类时表示该类的所有方法都支持缓存。被@Cacheable注解标记的方法会在调用后将返回值缓存起来,这样下次使用相同参数调用该方法时,可以直接从缓存中获取结果,无需再次执行该方法,从而提高方法的执行效率。

cacheable的属性介绍

Spring使用键值对来缓存方法的返回值。键用于检索缓存中的结果,而值则是方法的返回结果。Spring支持两种缓存键的策略:默认策略和自定义策略,接下来将对这两种策略进行详细说明。请注意,当一个支持缓存的方法在对象内部被调用时,不会触发缓存功能。

@Cacheable注解还提供了三个属性:value、key和condition。value属性用于指定缓存的名称或命名空间,可以用来区分不同缓存的作用域。key属性用于指定生成缓存键的条件,可以根据方法参数或其他条件生成不同的键,以实现更灵活的缓存策略。condition属性用于指定缓存的条件,只有在符合条件的情况下才会进行缓存。

value属性指定Cache名称

value属性是必须指定的,它表示当前方法的返回值会被缓存在哪个Cache上,也就是对应的Cache的名称。可以指定一个Cache,也可以指定多个Cache。当需要指定多个Cache时,可以使用数组形式。

java

复制代码

//Cache是发生在cache1上的
@Cacheable("cache1")
public User find(Integer id) {
   Return null;
}
 
 //Cache是发生在cache1和cache2上的
@Cacheable({"cache1", "cache2"})
public User find(Integer id) {
  Return null;
}
使用key属性自定义key

key属性用于指定Spring缓存方法返回结果时对应的key。该属性支持使用SpringEL表达式。如果未指定该属性,Spring将使用默认策略生成key。

下面是一个自定义策略的示例,我们可以使用SpringEL表达式来指定key。在EL表达式中,我们可以使用方法参数及其属性。使用方法参数作为key时,可以直接使用“#参数名”或者“#p参数索引”。

案例分析

以下是几个使用参数作为key的示例:

java

复制代码

@Cacheable(value="users", key="#id")
   public User find(Integer id) {
      return null;
   }
 
   @Cacheable(value="users", key="#p0")
   public User find(Integer id) {
      return null;
   }
 
   @Cacheable(value="users", key="#user.id")
   public User find(User user) {
      return null;
   }
 
   @Cacheable(value="users", key="#p0.id")
   public User find(User user) {
      return null;
   }

除了上述使用方法参数作为key的方法外,Spring还提供了一个root对象,可以用来生成key。通过该root对象,我们可以获取以下信息:

  1. 方法名称(#root.methodName):可以获取当前缓存方法的名称。
  2. 目标类名称(#root.targetClass):可以获取当前缓存方法所在的目标类的名称。
  3. 目标对象(#root.target):可以获取当前缓存方法所在的目标对象。
  4. 方法参数(#root.args):可以获取当前缓存方法的参数列表。

使用这些信息作为key的一部分,可以更精确地指定缓存方法的返回结果所对应的key。下面是一个示例:

typescript

复制代码

@Cacheable(value = "myCache", key = "#root.methodName + ':' + #root.targetClass + ':' + #root.args[0]")
public String getSomeData(String key) {
    // ...
}

在这个示例中,使用了方法名称、目标类名称和第一个参数作为key的一部分,以确保每个不同的参数组合都能生成不同的缓存key。

注意:当我们要使用root对象的属性作为key时,我们也可以省略“#root”

caches

caches:当前被调用的方法使用的Cache,#root.caches[0].name,因为Spring默认使用的就是root对象的属性。下面是一个示例:

java

复制代码

@Cacheable(value = {"users", "xxx"}, key = "caches[1].name")
public User find(User user) {
    return null;
}

在这个示例中,我们使用了root对象的属性caches[1].name作为缓存的key。这意味着根据缓存的设置,当调用这个方法时,Spring将会使用usersxxx这两个缓存的名称,以及caches[1].name作为缓存的key。

condition属性指定发生的条件

有时候,我们可能不希望缓存一个方法的所有返回结果。通过condition属性,我们可以实现这一功能。condition属性的默认值为空,表示缓存所有的调用情况。该属性的值通过SpringEL表达式来指定,当为true时表示进行缓存处理,当为false时表示不进行缓存处理,即每次调用该方法时都会执行一次。下面的示例展示了只有当userid为偶数时才会进行缓存:

java

复制代码

@Cacheable(value = "users", key = "#user.id", condition = "#user.id % 2 == 0")
public User find(User user) {
    // 方法逻辑
}

在这个示例中,我们使用了SpringEL表达式#user.id % 2 == 0作为condition属性的值。这个表达式的含义是只有当userid是偶数时才会进行缓存。如果userid是奇数,则每次调用find()方法时,方法都会执行一次,而不会使用缓存的结果。

@CachePut

@CachePut注解也可以用来声明一个支持缓存功能的方法。与@Cacheable不同之处在于,@CachePut`注解用来标识一个支持缓存功能的方法。它不会在执行之前检查缓存条目,而是每次都执行该方法,并将执行结果以键值对的形式存入指定的缓存中。

使用案例

@CachePut可以用于类和方法级别的标注。使用@CachePut时,我们可以像@Cacheable一样指定属性。

java

复制代码

@CachePut(value = "users") // 每次都会执行方法,并将结果存入指定的缓存中
public User find(Integer id) {
    return null;
}

@CacheEvict

allEntries属性

allEntries是一个布尔类型的属性,表示是否需要清除缓存中的所有元素。默认值为false,表示不需要清除所有元素。当将allEntries设置为true时,Spring Cache将忽略指定的key。有时,我们可以通过清除所有元素来提高效率,而不是逐个清除各个元素。

java

复制代码

@CacheEvict(value = "users", allEntries = true)
public void delete(Integer id) {
    System.out.println("delete user by id: " + id);
}

beforeInvocation属性

清除操作默认是在对应方法成功执行之后触发的,即方法如果因为抛出异常而未能成功返回时也不会触发清除操作。使用beforeInvocation属性可以改变触发清除操作的时间,当将该属性值设置为true时,Spring会在调用该方法之前清除缓存中的指定元素。

java

复制代码

@CacheEvict(value = "users", beforeInvocation = true)
public void delete(Integer id) {
    System.out.println("delete user by id: " + id);
}

实际上,在使用Ehcache作为实现时,除了使用@CacheEvict来清除缓存元素之外,我们也可以通过配置Ehcache自身的驱除策略来实现。这可以通过Ehcache的配置文件来指定。

@Caching

@Caching注解可以让我们在一个方法或者类上同时指定多个Spring Cache相关的注解。它拥有三个属性:cacheable、put和evict,分别用于指定@Cacheable、@CachePut和@CacheEvict。

@Caching定义如下

java

复制代码

public @interface Caching {  
    Cacheable[] cacheable() default {}; //声明多个@Cacheable  
    CachePut[] put() default {};        //声明多个@CachePut  
    CacheEvict[] evict() default {};    //声明多个@CacheEvict  
}

使用案例

java

复制代码

@Caching(cacheable = @Cacheable("users"), evict = {
    @CacheEvict("cache2"),
    @CacheEvict(value = "cache3", allEntries = true)
})
public User find(Integer id) {
    return null;
}

自定义缓存注解

上面介绍的@Caching组合,会让方法上的注解显得整个代码比较乱,此时可以使用自定义注解把这些注解组合到一个注解中。

java

复制代码

@Caching(  
        put = {  
                @CachePut(value = "user", key = "#user.id"),  
                @CachePut(value = "user", key = "#user.username"),  
                @CachePut(value = "user", key = "#user.email")  
        }  
)  
@Target({ElementType.METHOD, ElementType.TYPE})  
@Retention(RetentionPolicy.RUNTIME)  
@Inherited  
public @interface WrapperCache {  
}  
@WrapperCache 
public User save(User user)

Spring允许我们在配置可缓存的方法时使用自定义的注解,前提是自定义的注解上必须使用对应的注解进行标注。例如,我们可以创建一个使用@Cacheable进行标注的自定义注解,如下所示:

java

复制代码

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Cacheable(value = "users")
public @interface MyCacheable {
}

然后,我们可以在需要缓存的方法上使用@MyCacheable进行标注,以实现相同的效果。以下是优化后的示例代码:

java

复制代码

@MyCacheable
public User findById(Integer id) {
    System.out.println("find user by id: " + id);
    User user = new User();
    user.setId(id);
    user.setName("Name" + id);
    return user;
}

通过这样的设置,方法findById将会被缓存起来,以便在后续调用中直接返回缓存的结果。如果您还有其他问题或需要进一步帮助,请随时告诉我。

相关文章
|
6天前
|
安全 Java 测试技术
Spring Boot集成支付宝支付:概念与实战
【4月更文挑战第29天】在电子商务和在线业务应用中,集成有效且安全的支付解决方案是至关重要的。支付宝作为中国领先的支付服务提供商,其支付功能的集成可以显著提升用户体验。本篇博客将详细介绍如何在Spring Boot应用中集成支付宝支付功能,并提供一个实战示例。
24 2
|
1天前
|
Java 开发者 微服务
Spring Cloud原理详解
【5月更文挑战第4天】Spring Cloud是Spring生态系统中的微服务框架,包含配置管理、服务发现、断路器、API网关等工具,简化分布式系统开发。核心组件如Eureka(服务发现)、Config Server(配置中心)、Ribbon(负载均衡)、Hystrix(断路器)、Zuul(API网关)等。本文讨论了Spring Cloud的基本概念、核心组件、常见问题及解决策略,并提供代码示例,帮助开发者更好地理解和实践微服务架构。此外,还涵盖了服务通信方式、安全性、性能优化、自动化部署、服务网格和无服务器架构的融合等话题,揭示了微服务架构的未来趋势。
13 6
|
3天前
|
缓存 NoSQL Java
17:缓存机制-Java Spring
17:缓存机制-Java Spring
17 5
|
4天前
|
XML 存储 缓存
Spring缓存是如何实现的?如何扩展使其支持过期删除功能?
总之,Spring的缓存抽象提供了一种方便的方式来实现缓存功能,并且可以与各种缓存提供商集成以支持不同的过期策略。您可以根据项目的具体需求选择适合的方式来配置和扩展Spring缓存功能。
9 0
|
5天前
|
XML Java API
Spring Boot 整合 LiteFlow 规则引擎:概念与实战
【4月更文挑战第30天】在现代软件开发中,规则引擎允许我们以声明式的方式定义业务逻辑和决策路径。LiteFlow 是一个轻量级、易于使用的组件式规则引擎,它可以与 Spring Boot 应用无缝整合。本文将介绍如何在 Spring Boot 项目中引入 LiteFlow,实现灵活的业务流程管理。
17 0
|
5天前
|
负载均衡 Java 开发者
Spring Cloud:一文读懂其原理与架构
Spring Cloud 是一套微服务解决方案,它整合了Netflix公司的多个开源框架,简化了分布式系统开发。Spring Cloud 提供了服务注册与发现、配置中心、消息总线、负载均衡、熔断机制等工具,让开发者可以快速地构建一些常见的微服务架构。
|
6天前
|
存储 缓存 Java
【Spring系列笔记】依赖注入,循环依赖以及三级缓存
依赖注入: 是指通过外部配置,将依赖关系注入到对象中。依赖注入有四种主要方式:构造器注入、setter方法注入、接口注入以及注解注入。其中注解注入在开发中最为常见,因为其使用便捷以及可维护性强;构造器注入为官方推荐,可注入不可变对象以及解决循环依赖问题。本文基于依赖注入方式引出循环依赖以及三层缓存的底层原理,以及代码的实现方式。
17 0
|
6天前
|
安全 Java 测试技术
利用Java反射机制提高Spring Boot的代码质量:概念与实战
【4月更文挑战第29天】Java反射机制提供了一种强大的方法来在运行时检查或修改类和对象的行为。在Spring Boot应用中,合理利用反射可以提高代码的灵活性和可维护性。本篇博客将探讨Java反射的核心概念,并展示如何通过反射提高Spring Boot项目的代码质量。
22 0
|
6天前
|
监控 Java 测试技术
Spring Boot与事务钩子函数:概念与实战
【4月更文挑战第29天】在复杂的业务逻辑中,事务管理是确保数据一致性和完整性的关键。Spring Boot提供了强大的事务管理机制,其中事务钩子函数(Transaction Hooks)允许开发者在事务的不同阶段插入自定义逻辑。本篇博客将详细探讨事务钩子函数的概念及其在Spring Boot中的应用。
17 1
|
6天前
|
安全 Java 数据安全/隐私保护
Spring Boot优雅实现多租户架构:概念与实战
【4月更文挑战第29天】在多租户系统中,一个应用实例服务于多个租户,每个租户享有独立的数据视图,而应用的基础设施被共享。这样的架构不仅优化了资源使用,还能降低维护和运营成本。本文将详细介绍如何在Spring Boot中实现多租户架构,并提供具体的实战案例。
28 2