1、缓存抽象 {#1缓存抽象}
本文将带你了解如何使用 Spring Cache 来提高系统性能。
2、开始使用 {#2开始使用}
Spring 提供的核心缓存抽象位于 spring-context 模块中。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.3</version>
</dependency>
另外,还有一个名为 spring-context-support 的模块,它位于 spring-context 模块之上,并提供了更多由 EhCache 或 Caffeine 支持的 CacheManager
。如果想使用这些模块作为缓存存储,则需要使用 spring-context-support 模块:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>5.3.3</version>
</dependency>
由于 spring-context-support 模块临时依赖于 spring-context 模块,因此 spring-context 不需要单独的依赖声明。
2.1、Spring Boot {#21spring-boot}
如果是 Spring Boot 项目,可以利用 spring-boot-starter-cache Starter 来轻松添加缓存依赖项:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
<version>2.4.0</version>
</dependency>
该 Starter 包含了 spring-context-support 模块。
3、启用缓存 {#3启用缓存}
只需在任何配置类中添加 @EnableCaching
注解,即可启用缓存功能:
@Configuration
@EnableCaching
public class CachingConfig {
@Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager("addresses");
}
}
当然,也可以通过 XML 配置来启用缓存:
<beans>
<!-- 缓存,注解驱动 -->
<cache:annotation-driven />
<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
<property name="caches">
<set>
<bean
class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean"
name="addresses"/>
</set>
</property>
</bean>
</beans>
注意 :启用缓存后,必须注册一个 cacheManager
,这是最基本的设置。
3.1、使用 Spring Boot {#31使用-spring-boot}
在使用 Spring Boot 时,只需在 classpath 上存在 Starter 依赖,并且与 @EnableCaching
注解一起使用,就会注册相同的 ConcurrentMapCacheManager
,因此不需要单独的 Bean 声明。
还可以使用一个或多个 CacheManagerCustomizer<T>
Bean 来自定义 自动配置 的 CacheManager
:
@Component
public class SimpleCacheCustomizer
implements CacheManagerCustomizer<ConcurrentMapCacheManager> {
@Override
public void customize(ConcurrentMapCacheManager cacheManager) {
cacheManager.setCacheNames(asList("users", "transactions"));
}
}
CacheAutoConfiguration
自动配置会获取到这些 Customizer ,并在完成初始化之前将其应用到当前的 CacheManager
中。
4、通过注解使用缓存 {#4通过注解使用缓存}
启用缓存后,下一步就是通过声明性注解将缓存行为绑定到方法上。
4.1、@Cacheable {#41cacheable}
为方法启用缓存行为的最简单方法是用 @Cacheable
对其进行注解,它会将方法返回结果存储在注解指定的缓存中:
@Cacheable("addresses")
public String getAddress(Customer customer) {...}
getAddress()
调用将首先检查缓存 addresses,然后才实际调用该方法并缓存结果。
虽然在大多数情况下一个缓存就足够了,但 Spring 也支持传递多个缓存:
@Cacheable({"addresses", "directory"})
public String getAddress(Customer customer) {...}
在这种情况下,如果任何缓存中包含所需的结果,就会返回该结果,而不会调用该方法。
4.2、@CacheEvict {#42cacheevict}
缓存的数据如果不进行清理,会保留大量陈旧或未使用甚至是过期的数据。
可以使用 @CacheEvict
注解来表示删除一个、多个或所有的值,以刷新缓存:
@CacheEvict(value="addresses", allEntries=true)
public String getAddress(Customer customer) {...}
如上,使用参数 allEntries
与要清空的缓存结合使用;这将清除缓存 addresses 中的所有条目。
4.3、@CachePut {#43cacheput}
使用 @CachePut
注解,可以更新缓存的内容,而不会影响方法的执行。也就是说,方法始终会被执行并将结果缓存起来:
@CachePut(value="addresses")
public String getAddress(Customer customer) {...}
@Cacheable
和 @CachePut
的区别在于,@Cacheable
会跳过运行方法,而 @CachePut
会实际运行方法,然后将结果放入缓存。
4.4、@Caching {#44caching}
如果想使用同一类型的多个注解来缓存一个方法,该怎么办?
来看一个错误的例子:
@CacheEvict("addresses")
@CacheEvict(value="directory", key=customer.name)
public String getAddress(Customer customer) {...}
上述代码无法编译,因为 Java 不允许为一个给定的方法声明多个相同类型的注解。
解决上述问题的办法是:
@Caching(evict = {
@CacheEvict("addresses"),
@CacheEvict(value="directory", key="#customer.name") })
public String getAddress(Customer customer) {...}
如上面的代码片段所示,可以用 @Caching
对多个缓存注解进行分组,并用它来实现自己定制的缓存逻辑。
4.5、@CacheConfig {#45cacheconfig}
通过 @CacheConfig
注解,可以将部分缓存配置精简到类级的一个地方,这样就不必多次声明了:
@CacheConfig(cacheNames={"addresses"})
public class CustomerDataService {
@Cacheable
public String getAddress(Customer customer) {...}
5、条件式缓存 {#5条件式缓存}
有时,缓存并不是在所有情况下都适用于某种方法。
重用 @CachePut
注解中的示例,这会既执行方法,又缓存结果。
@CachePut(value="addresses")
public String getAddress(Customer customer) {...}
5.1、condition 参数 {#51condition-参数}
如果想对注解激活的条件进行更多控制,可以用 condition
参数定义一个 SpEL 表达式,框架会根据该表达式的计算结果进行缓存:
@CachePut(value="addresses", condition="#customer.name=='Tom'")
public String getAddress(Customer customer) {...}
在 SpEL
表达式中可以通过 #
访问方法参数。
5.2、unless 参数 {#52unless-参数}
还可以通过 unless
参数,根据方法的返回值而不是参数值来控制缓存:
@CachePut(value="addresses", unless="#result.length()<64")
public String getAddress(Customer customer) {...}
除非返回的地址短于 64 个字符,否则上述注解将缓存地址。
condition
和 unless
参数可与所有缓存注解结合使用。
事实证明,这种条件缓存对管理大型结果非常有效。它还有助于根据方法参数自定义行为,而不是对所有操作强制执行通用行为。
6、基于 XML 的声明式缓存 {#6基于-xml-的声明式缓存}
如果无法访问应用的源码,或者想从外部注入缓存行为,也可以使用基于 XML 的声明式缓存。
XML 配置如下:
<!-- 希望缓存的服务 -->
<bean id="customerDataService"
class="com.your.app.namespace.service.CustomerDataService"/>
<bean id="cacheManager"
class="org.springframework.cache.support.SimpleCacheManager">
<property name="caches">
<set>
<bean
class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean"
name="directory"/>
<bean
class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean"
name="addresses"/>
</set>
</property>
</bean>
<!-- 定义缓存行为 -->
<cache:advice id="cachingBehavior" cache-manager="cacheManager">
<cache:caching cache="addresses">
<cache:cacheable method="getAddress" key="#customer.name"/>
</cache:caching>
</cache:advice>
<!-- 将该行为应用于 CustomerDataService 接口的所有实现 -->
<aop:config>
<aop:advisor advice-ref="cachingBehavior"
pointcut="execution(* com.your.app.namespace.service.CustomerDataService.*(..))"/>
</aop:config>
7、基于 Java 配置的缓存 {#7基于-java-配置的缓存}
下面是相应的 Java 配置:
@Configuration
@EnableCaching
public class CachingConfig {
@Bean
public CacheManager cacheManager() {
SimpleCacheManager cacheManager = new SimpleCacheManager();
cacheManager.setCaches(Arrays.asList(
new ConcurrentMapCache("directory"),
new ConcurrentMapCache("addresses")));
return cacheManager;
}
}
CustomerDataService
如下:
@Component
public class CustomerDataService {
@Cacheable(value = "addresses", key = "#customer.name")
public String getAddress(Customer customer) {
return customer.getAddress();
}
}
8、总结 {#8总结}
本文介绍了如何使用 Spring Cache 抽象来实现声明式缓存以及如何通过缓存注解的 condition
和 unless
属性来灵活地控制缓存条件。
Ref:https://www.baeldung.com/spring-cache-tutorial