本文介紹如何使用 spring-cache,以及集成 Redis 作為緩存實現(xiàn)。
表格過長,推薦讀者使用電腦閱讀
準(zhǔn)備工作
如何配置
1. maven
完整依賴詳見 ==> Gitee
<!-- 使用spring cache -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 為了解決 ClassNotFoundException: org.apache.commons.pool2.impl.GenericObjectPoolConfig -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.0</version>
</dependency>
2. application.properties
# Redis數(shù)據(jù)庫索引(默認(rèn)為0)
spring.redis.database=0
# Redis服務(wù)器地址
spring.redis.host=localhost
# Redis服務(wù)器連接端口
spring.redis.port=6379
# Redis服務(wù)器連接密碼(默認(rèn)為空)
#spring.redis.password=yourpwd
# 連接池最大連接數(shù)(使用負(fù)值表示沒有限制)
spring.redis.lettuce.pool.max-active=8
# 連接池最大阻塞等待時間
spring.redis.lettuce.pool.max-wait=-1ms
# 連接池中的最大空閑連接
spring.redis.lettuce.pool.max-idle=8
# 連接池中的最小空閑連接
spring.redis.lettuce.pool.min-idle=0
# 連接超時時間(毫秒)
spring.redis.timeout=5000ms
#配置緩存相關(guān)
cache.default.expire-time=200
cache.user.expire-time=180
cache.user.name=test
3. @EnableCaching
標(biāo)記注解 @EnableCaching,開啟緩存,并配置Redis緩存管理器,需要初始化一個緩存空間。在緩存的時候,也需要標(biāo)記使用哪一個緩存空間
@Configuration
@EnableCaching
public class RedisConfig {
@Value("${cache.default.expire-time}")
private int defaultExpireTime;
@Value("${cache.user.expire-time}")
private int userCacheExpireTime;
@Value("${cache.user.name}")
private String userCacheName;
/**
* 緩存管理器
*
* @param lettuceConnectionFactory
* @return
*/
@Bean
public CacheManager cacheManager(RedisConnectionFactory lettuceConnectionFactory) {
RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig();
// 設(shè)置緩存管理器管理的緩存的默認(rèn)過期時間
defaultCacheConfig = defaultCacheConfig.entryTtl(Duration.ofSeconds(defaultExpireTime))
// 設(shè)置 key為string序列化
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
// 設(shè)置value為json序列化
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()))
// 不緩存空值
.disableCachingNullValues();
Set<String> cacheNames = new HashSet<>();
cacheNames.add(userCacheName);
// 對每個緩存空間應(yīng)用不同的配置
Map<String, RedisCacheConfiguration> configMap = new HashMap<>();
configMap.put(userCacheName, defaultCacheConfig.entryTtl(Duration.ofSeconds(userCacheExpireTime)));
RedisCacheManager cacheManager = RedisCacheManager.builder(lettuceConnectionFactory)
.cacheDefaults(defaultCacheConfig)
.initialCacheNames(cacheNames)
.withInitialCacheConfigurations(configMap)
.build();
return cacheManager;
}
}
到此配置工作已經(jīng)結(jié)束了
Spring Cache 使用
@Service
@CacheConfig(cacheNames="user")// cacheName 是一定要指定的屬性,可以通過 @CacheConfig 聲明該類的通用配置
public class UserService {
/**
* 將結(jié)果緩存,當(dāng)參數(shù)相同時,不會執(zhí)行方法,從緩存中取
*
* @param id
* @return
*/
@Cacheable(key = "#id")
public User findUserById(Integer id) {
System.out.println("===> findUserById(id), id = " + id);
return new User(id, "taven");
}
/**
* 將結(jié)果緩存,并且該方法不管緩存是否存在,每次都會執(zhí)行
*
* @param user
* @return
*/
@CachePut(key = "#user.id")
public User update(User user) {
System.out.println("===> update(user), user = " + user);
return user;
}
/**
* 移除緩存,根據(jù)指定key
*
* @param user
*/
@CacheEvict(key = "#user.id")
public void deleteById(User user) {
System.out.println("===> deleteById(), user = " + user);
}
/**
* 移除當(dāng)前 cacheName下所有緩存
*
*/
@CacheEvict(allEntries = true)
public void deleteAll() {
System.out.println("===> deleteAll()");
}
}
| 注解 | 作用 |
|---|---|
@Cacheable |
將方法的結(jié)果緩存起來,下一次方法執(zhí)行參數(shù)相同時,將不執(zhí)行方法,返回緩存中的結(jié)果 |
@CacheEvict |
移除指定緩存 |
@CachePut |
標(biāo)記該注解的方法總會執(zhí)行,根據(jù)注解的配置將結(jié)果緩存 |
@Caching |
可以指定相同類型的多個緩存注解,例如根據(jù)不同的條件 |
@CacheConfig |
類級別注解,可以設(shè)置一些共通的配置,@CacheConfig(cacheNames="user"), 代表該類下的方法均使用這個cacheNames |
下面詳細(xì)講一下每個注解的作用和可選項。
Spring Cache 注解
1. @EnableCaching 做了什么
@EnableCaching注釋觸發(fā)后置處理器, 檢查每一個Spring bean 的 public 方法是否存在緩存注解。如果找到這樣的一個注釋, 自動創(chuàng)建一個代理攔截方法調(diào)用和處理相應(yīng)的緩存行為。
2. 常用緩存注解簡述
2.1 @Cacheable
將方法的結(jié)果緩存,必須要指定一個 cacheName(緩存空間)
@Cacheable("books")
public Book findBook(ISBN isbn) {...}
默認(rèn) cache key
緩存的本質(zhì)還是以 key-value 的形式存儲的,默認(rèn)情況下我們不指定key的時候 ,使用 SimpleKeyGenerator 作為key的生成策略
- 如果沒有給出參數(shù),則返回SimpleKey.EMPTY。
- 如果只給出一個Param,則返回該實例。
- 如果給出了更多的Param,則返回包含所有參數(shù)的SimpleKey。
注意:當(dāng)使用默認(rèn)策略時,我們的參數(shù)需要有 有效的hashCode()和equals()方法
自定義 cache key
@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)
如上,配合Spring EL 使用,下文會詳細(xì)介紹 Spring EL 對 Cache 的支持
- 指定對象
- 指定對象中的屬性
- 某個類的某個靜態(tài)方法
自定義 keyGenerator
@Cacheable(cacheNames="books", keyGenerator="myKeyGenerator")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
實現(xiàn) KeyGenerator接口可以自定義 cache key 的生成策略
自定義 cacheManager
@Cacheable(cacheNames="books", cacheManager="anotherCacheManager")
public Book findBook(ISBN isbn) {...}
當(dāng)我們的項目包含多個緩存管理器時,可以指定具體的緩存管理器,作為緩存解析
同步緩存
在多線程環(huán)境中,可能會出現(xiàn)相同的參數(shù)的請求并發(fā)調(diào)用方法的操作,默認(rèn)情況下,spring cache 不會鎖定任何東西,相同的值可能會被計算幾次,這就違背了緩存的目的
對于這些特殊情況,可以使用sync屬性。此時只有一個線程在處于計算,而其他線程則被阻塞,直到在緩存中更新條目為止。
@Cacheable(cacheNames="foos", sync=true)
public Foo executeExpensiveOperation(String id) {...}
條件緩存
- condition: 什么情況緩存,condition = true 時緩存,反之不緩存
- unless: 什么情況不緩存,unless = true 時不緩存,反之緩存
@Cacheable(cacheNames="book", condition="#name.length() < 32")
public Book findBook(String name)
@Cacheable(cacheNames="book", condition="#name.length() < 32", unless="#result?.hardback")
public Optional<Book> findBook(String name)
Spring EL 對 Cache 的支持
| Name | Location | Description | Example |
|---|---|---|---|
| methodName | Root object | 被調(diào)用的方法的名稱 | #root.methodName |
| method | Root object | 被調(diào)用的方法 | #root.method.name |
| target | Root object | 當(dāng)前調(diào)用方法的對象 | #root.target |
| targetClass | Root object | 當(dāng)前調(diào)用方法的類 | #root.targetClass |
| args | Root object | 當(dāng)前方法的參數(shù) | #root.args[0] |
| caches | Root object | 當(dāng)前方法的緩存集合 | #root.caches[0].name |
| Argument name | Evaluation context | 當(dāng)前方法的參數(shù)名稱 | #iban or #a0 (you can also use #p0 or #p<#arg> notation as an alias). |
| result | Evaluation context | 方法返回的結(jié)果(要緩存的值)。只有在 unless 、@CachePut(用于計算鍵)或@CacheEvict(beforeInvocation=false)中才可用.對于支持的包裝器(例如Optional),#result引用的是實際對象,而不是包裝器 |
#result |
2.2 @CachePut
這個注解和 @Cacheable 有點類似,都會將結(jié)果緩存,但是標(biāo)記 @CachePut 的方法每次都會執(zhí)行,目的在于更新緩存,所以兩個注解的使用場景完全不同。@Cacheable 支持的所有配置選項,同樣適用于@CachePut
@CachePut(cacheNames="book", key="#isbn")
public Book updateBook(ISBN isbn, BookDescriptor descriptor)
- 需要注意的是,不要在一個方法上同時使用
@Cacheable和@CachePut
2.3 @CacheEvict
用于移除緩存
- 可以移除指定key
- 聲明
allEntries=true移除該CacheName下所有緩存 - 聲明
beforeInvocation=true在方法執(zhí)行之前清除緩存,無論方法執(zhí)行是否成功
@CacheEvict(cacheNames="book", key="#isbn")
public Book updateBook(ISBN isbn, BookDescriptor descriptor)
@CacheEvict(cacheNames="books", allEntries=true)
public void loadBooks(InputStream batch)
2.4 @Caching
可以讓你在一個方法上嵌套多個相同的Cache 注解(@Cacheable, @CachePut, @CacheEvict),分別指定不同的條件
@Caching(evict = { @CacheEvict("primary"), @CacheEvict(cacheNames="secondary", key="#p0") })
public Book importBooks(String deposit, Date date)
2.5 @CacheConfig
類級別注解,用于配置一些共同的選項(當(dāng)方法注解聲明的時候會被覆蓋),例如 CacheName。
支持的選項如下:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CacheConfig {
String[] cacheNames() default {};
String keyGenerator() default "";
String cacheManager() default "";
String cacheResolver() default "";
}
參考:
本文demo:
https://gitee.com/yintianwen7/taven-springboot-learning/tree/master/springboot-redis