本文介紹如何使用 spring-cache蜈出,以及集成 Redis 作為緩存實現(xià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ù)庫索引(默認為0)
spring.redis.database=0
# Redis服務(wù)器地址
spring.redis.host=localhost
# Redis服務(wù)器連接端口
spring.redis.port=6379
# Redis服務(wù)器連接密碼(默認為空)
#spring.redis.password=yourpwd
# 連接池最大連接數(shù)(使用負值表示沒有限制)
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
標記注解 @EnableCaching
,開啟緩存,并配置Redis緩存管理器拨拓,需要初始化一個緩存空間。在緩存的時候氓栈,也需要標記使用哪一個緩存空間
@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è)置緩存管理器管理的緩存的默認過期時間
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é)果緩存,當參數(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);
}
/**
* 移除當前 cacheName下所有緩存
*
*/
@CacheEvict(allEntries = true)
public void deleteAll() {
System.out.println("===> deleteAll()");
}
}
注解 | 作用 |
---|---|
@Cacheable |
將方法的結(jié)果緩存起來,下一次方法執(zhí)行參數(shù)相同時徒欣,將不執(zhí)行方法逐样,返回緩存中的結(jié)果 |
@CacheEvict |
移除指定緩存 |
@CachePut |
標記該注解的方法總會執(zhí)行,根據(jù)注解的配置將結(jié)果緩存 |
@Caching |
可以指定相同類型的多個緩存注解打肝,例如根據(jù)不同的條件 |
@CacheConfig |
類級別注解脂新,可以設(shè)置一些共通的配置,@CacheConfig(cacheNames="user") , 代表該類下的方法均使用這個cacheNames |
下面詳細講一下每個注解的作用和可選項粗梭。
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) {...}
默認 cache key
緩存的本質(zhì)還是以 key-value 的形式存儲的始花,默認情況下我們不指定key的時候 ,使用 SimpleKeyGenerator
作為key的生成策略
- 如果沒有給出參數(shù)孩锡,則返回SimpleKey.EMPTY酷宵。
- 如果只給出一個Param,則返回該實例躬窜。
- 如果給出了更多的Param浇垦,則返回包含所有參數(shù)的SimpleKey。
注意:當使用默認策略時荣挨,我們的參數(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 使用朴摊,下文會詳細介紹 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) {...}
當我們的項目包含多個緩存管理器時,可以指定具體的緩存管理器此虑,作為緩存解析
同步緩存
在多線程環(huán)境中甚纲,可能會出現(xiàn)相同的參數(shù)的請求并發(fā)調(diào)用方法的操作,默認情況下朦前,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 | 當前調(diào)用方法的對象 | #root.target |
targetClass | Root object | 當前調(diào)用方法的類 | #root.targetClass |
args | Root object | 當前方法的參數(shù) | #root.args[0] |
caches | Root object | 當前方法的緩存集合 | #root.caches[0].name |
Argument name | Evaluation context | 當前方法的參數(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é)果緩存积锅,但是標記 @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
類級別注解,用于配置一些共同的選項(當方法注解聲明的時候會被覆蓋)虎锚,例如 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