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

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 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容器会在运行时识别并且知道它是用来干嘛的。

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

相关文章
|
2月前
|
缓存 NoSQL Java
什么是缓存?如何在 Spring Boot 中使用缓存框架
什么是缓存?如何在 Spring Boot 中使用缓存框架
81 0
|
22天前
|
Java Spring
【Spring】方法注解@Bean,配置类扫描路径
@Bean方法注解,如何在同一个类下面定义多个Bean对象,配置扫描路径
159 73
|
17天前
|
Java Spring 容器
【SpringFramework】Spring IoC-基于注解的实现
本文主要记录基于Spring注解实现IoC容器和DI相关知识。
48 21
|
22天前
|
存储 Java Spring
【Spring】获取Bean对象需要哪些注解
@Conntroller,@Service,@Repository,@Component,@Configuration,关于Bean对象的五个常用注解
|
22天前
|
Java Spring
【Spring配置】idea编码格式导致注解汉字无法保存
问题一:对于同一个项目,我们在使用idea的过程中,使用汉字注解完后,再打开该项目,汉字变成乱码问题二:本来a项目中,汉字注解调试好了,没有乱码了,但是创建出来的新的项目,写的注解又成乱码了。
|
1月前
|
缓存 NoSQL Java
Spring Boot中的分布式缓存方案
Spring Boot提供了简便的方式来集成和使用分布式缓存。通过Redis和Memcached等缓存方案,可以显著提升应用的性能和扩展性。合理配置和优化缓存策略,可以有效避免常见的缓存问题,保证系统的稳定性和高效运行。
56 3
|
1月前
|
缓存 Java 数据库连接
深入探讨:Spring与MyBatis中的连接池与缓存机制
Spring 与 MyBatis 提供了强大的连接池和缓存机制,通过合理配置和使用这些机制,可以显著提升应用的性能和可扩展性。连接池通过复用数据库连接减少了连接创建和销毁的开销,而 MyBatis 的一级缓存和二级缓存则通过缓存查询结果减少了数据库访问次数。在实际应用中,结合具体的业务需求和系统架构,优化连接池和缓存的配置,是提升系统性能的重要手段。
89 4
|
2月前
|
前端开发 Java 开发者
Spring MVC中的请求映射:@RequestMapping注解深度解析
在Spring MVC框架中,`@RequestMapping`注解是实现请求映射的关键,它将HTTP请求映射到相应的处理器方法上。本文将深入探讨`@RequestMapping`注解的工作原理、使用方法以及最佳实践,为开发者提供一份详尽的技术干货。
173 2
|
2月前
|
前端开发 Java Spring
探索Spring MVC:@Controller注解的全面解析
在Spring MVC框架中,`@Controller`注解是构建Web应用程序的基石之一。它不仅简化了控制器的定义,还提供了一种优雅的方式来处理HTTP请求。本文将全面解析`@Controller`注解,包括其定义、用法、以及在Spring MVC中的作用。
68 2
|
27天前
|
存储 缓存 NoSQL
解决Redis缓存数据类型丢失问题
解决Redis缓存数据类型丢失问题
171 85