Spring基于注解的缓存声明深入探究

 更新时间:2022年08月10日 10:23:23   作者:凌波漫步&  
spring boot对缓存支持非常灵活,我们可以使用默认的EhCache,也可以整合第三方的框架,只需配置即可,下面这篇文章主要给大家介绍了关于SpringBoot学习之基于注解缓存的相关资料,需要的朋友可以参考下

一、概述

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

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

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

对于缓存声明,该抽象提供了一套Java注解:

  • @Cacheable:触发缓存构建。
  • @CacheEvict:触发缓存销毁。
  • @CachePut:更新缓存。
  • @Caching:重组应用到方法上的多个缓存操作。
  • @CacheConfig:类级别共享缓存相关的通用设置。

1、@Cacheable注解

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

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

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

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

(1) 默认缓存key的生成

因为缓存都是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)

当然,我们也可以通过@Cacheable注解指定自定义的KeyGenerator实例,示例如下:

@Cacheable(cacheNames="books", keyGenerator="myKeyGenerator")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

备注:keykeyGenerator参数是互斥的,如果两者都指定会触发异常。

(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();
}

(4) 自定义缓存解析

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

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

(5) 条件式缓存

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

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

备注:只有当参数name的长度小于32时,方法结果才会被缓存。

除了condition属性,unless属性可以用来决定方法返回值不缓存。与condition不同,unless表达式在方法被调用后才会执行,示例如下:

@Cacheable(cacheNames="book", condition="#name.length() < 32", unless="#result.hardback")
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)

备注:缓存名为books下的所有缓存key都会被清除。

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个级别,优先级从上至下越来越高。

  • 全局配置的CacheManagerKeyGenerator等。
  • 类级别,通过@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 {
}

上面我们自定义了SlowService注解,该注解被@Cacheable所修饰,现在我们可以用自定义注解代替如下代码:

@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容器会在运行时识别并且知道它是用来干嘛的。

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

到此这篇关于Spring基于注解的缓存声明深入探究的文章就介绍到这了,更多相关Spring注解的缓存内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • IntelliJ IDEA 2019.1.1 for MAC 下载和注册码激活教程图解

    IntelliJ IDEA 2019.1.1 for MAC 下载和注

    这篇文章主要介绍了IntelliJ IDEA 2019.1.1 for MAC 下载和注册码激活,教程,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-04-04
  • sqlserver和java将resultSet中的记录转换为学生对象

    sqlserver和java将resultSet中的记录转换为学生对象

    这篇文章主要介绍了如何利用sqlserver和java将resultSet中的记录转换为学生对象,附有超详细的代码,需要的朋友可以参考一下,希望对你有所帮助
    2021-12-12
  • Java基于正则实现的日期校验功能示例

    Java基于正则实现的日期校验功能示例

    这篇文章主要介绍了Java基于正则实现的日期校验功能,涉及java文件读取、日期转换及字符串正则匹配相关操作技巧,需要的朋友可以参考下
    2017-03-03
  • Java中transient关键字的详细总结

    Java中transient关键字的详细总结

    本文要介绍的是Java中的transient关键字,transient是短暂的意思。对于transient 修饰的成员变量,在类的实例对象的序列化处理过程中会被忽略,感兴趣的朋友可以参考阅读
    2023-04-04
  • java实现文本文件删除空行的示例分享

    java实现文本文件删除空行的示例分享

    这篇文章主要介绍了java实现文本文件删除空行的示例,需要的朋友可以参考下
    2014-04-04
  • 详谈Java中的二进制及基本的位运算

    详谈Java中的二进制及基本的位运算

    下面小编就为大家带来一篇详谈Java中的二进制及基本的位运算。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-07-07
  • Spring Security基本配置方法解析

    Spring Security基本配置方法解析

    这篇文章主要介绍了Spring Security基本配置方法解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-09-09
  • Spring配置文件使用占位符配置方式

    Spring配置文件使用占位符配置方式

    这篇文章主要介绍了Spring配置文件使用占位符配置方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-11-11
  • mybatis-plus的sql加载顺序源码解析

    mybatis-plus的sql加载顺序源码解析

    这篇文章主要为大家介绍了mybatis-plus的sql加载顺序源码解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-08-08
  • java实现文件分片上传并且断点续传的示例代码

    java实现文件分片上传并且断点续传的示例代码

    本文主要介绍了java实现文件分片上传并且断点续传的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-05-05

最新评论