簡易入門
一、作用
當我們在調用一個緩存方法時會根據相關信息和返回結果作為一個鍵值對存放在緩存中,等到下次利用同樣的參數來調用該方法時將不再執行該方法,而是直接從緩存中獲取結果進行返回。
二、啟用方式
1.POM.xml 文件中添加spring cache依賴(Spring Boot)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
2.添加一種cacheManager的bean
若注解了@EnableCaching,則spring可自動發現並配置cacheManager,只要有一種可用於緩存提供的即可,詳情見文獻[1]。常用的有Ehcache、redis等實現
為方便起見,這里使用最簡單的內存map實現。(可以使用Redis來提供CacheManager。詳見附錄)
@Bean
public CacheManager cacheManager() {
SimpleCacheManager cacheManager = new SimpleCacheManager();
cacheManager.setCaches(Collections.singletonList(new ConcurrentMapCache("models")));
return cacheManager;
}
3.啟用注解@EnableCaching
@Configuration
@EnableCaching
public class CacheConfiguration {
//...
}
三、使用方式
三個主要的注解 Cacheable (最常用的注解,用於標注需要緩存方法)、CacheEvict(用於僅清除緩存)、CachePut(用於僅存放緩存)
先定義一個測試POJO: TestModel。 含有name和address兩個字符串變量。
class TestModel {
String name;
String address;
// 省略getter和setter
}
1.Cacheable
例子:
@Cacheable(value = "models", key = "#testModel.name", condition = "#testModel.address != '' ")
public TestModel getFromMem(TestModel testModel) throws InterruptedException {
TimeUnit.SECONDS.sleep(1);
testModel.setName(testModel.getName().toUpperCase());
return testModel;
}
例子里的注解@Cacheable中存在有以下幾個元素
- value (也可使用 cacheNames) : 可看做命名空間,表示存到哪個緩存里了。
- key : 表示命名空間下緩存唯一key,使用Spring Expression Language(簡稱SpEL,詳見參考文獻[5])生成。
- condition : 表示在哪種情況下才緩存結果(對應的還有unless,哪種情況不緩存),同樣使用SpEL
當第一次使用
{name: 'XiaoMing', address: 'ChengDu'}
調用getFromMem時,會等待一秒鍾,然后返回
{name: 'XIAOMING', address: 'ChengDu'}
當再次使用name為’XiaoMing’的對象作為參數調用getFromMem時,會立即返回上一個結果,無論參數中的address是什么。
但是如果第一次調用時,address為空字符串,第二次調用仍然需要等待一秒鍾,這就是condition的作用。
2.CacheEvict
例子:
@CacheEvict(value = "models", allEntries = true)
@Scheduled(fixedDelay = 10000)
public void deleteFromRedis() {
}
@CacheEvict(value = "models", key = "#name")
public void deleteFromRedis(String name) {
}
例子里的注解 @CacheEvict 中存在有以下幾個元素
- value (也可使用 cacheNames) : 同Cacheable注解,可看做命名空間。表示刪除哪個命名空間中的緩存
- allEntries: 標記是否刪除命名空間下所有緩存,默認為false
- key: 同Cacheable注解,代表需要刪除的命名空間下唯一的緩存key。
例子中第一段,與 @Scheduled 注解同時使用,每十秒刪除命名空間name下所有的緩存。
第二段,調用此方法后刪除命名空間models下, key == 參數 的緩存
同樣含有unless與condition
3.CachePut
例子
@CachePut(value = "models", key = "#name")
public TestModel saveModel(String name, String address) {
return new TestModel(name, address);
}
例子里的注解 @CachePut 中存在有以下幾個元素
- value: 同上
- key: 同上
- condition(unless): 同上
比如可用於后台保存配置時及時刷新緩存。
以上三個注解中還有少量未提到的元素,於附錄中敘述。
文獻與深入
1.文獻
[1] Spring Boot中的緩存支持(一)注解配置與EhCache使用
[2] Spring Boot中的緩存支持(二)使用Redis做集中式緩存
[3] A Guide To Caching in Spring
[4] spring cache官方文檔
[5] Spring Expression Language官方文檔
2.創建RedisCacheManager
這里我們依賴spring redis
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
然后在配置中添加RedisConnectionFactory用於獲取redis鏈接
@Bean
public RedisConnectionFactory redisConnectionFactory() {
JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory();
jedisConnectionFactory.setHostName("127.0.0.1");
jedisConnectionFactory.setPort(6379);
return jedisConnectionFactory;
}
還需要一個RedisTemplate。RedisTemplate提供了對Jedis API的高級封裝,使用serializers序列化key和value,用於將java與字符串相互轉換。這里使用Jackson的serializers(詳見附錄),來將java對象序列化為json字符串。
jackson類似gson
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
Jackson2JsonRedisSerializer<Object> serializer = jackson2JsonRedisSerializer();
redisTemplate.setConnectionFactory(redisConnectionFactory());
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(serializer);
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(serializer);
return redisTemplate;
}
然后就可以得到一個RedisCacheManager
@Bean
public CacheManager redisCacheManager() {
RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate());
cacheManager.setDefaultExpiration(300);
cacheManager.setLoadRemoteCachesOnStartup(true); // 啟動時加載遠程緩存
cacheManager.setUsePrefix(true); //是否使用前綴生成器
// 這里可進行一些配置 包括默認過期時間 每個cacheName的過期時間等 前綴生成等等
return cacheManager;
}
運行文中”三”示例程序,並且可以觀察到redis中多了一個models:XiaoMing的key
其中的值為 [\"com.xxx.TestModel\",{\"name\":\"XIAOMING\",\"address\":\"ChengDu\"}]
注意這里的序列化的實現方式,保存了對象類的信息,保證方法返回與緩存返回一致(當然如果你的setter getter實現不一致,還是會造成不一致的)。 我曾經因為手動序列化造成了一個BUG,一個有序Map存進去,反序列化出來失去了有序性 ,當然方法返回值定義成了Map而不是SortedMap也是一個重要原因。
3. 上一條中使用的Jackson2JsonRedisSerializer
@Bean
public Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer() {
final Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
final ObjectMapper objectMapper = Jackson2ObjectMapperBuilder
.json().build();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
return jackson2JsonRedisSerializer;
}
4. Cache* 注解中未提到的元素
共同的:
- keyGenerator: 實現
org.springframework.cache.interceptor.KeyGenerator接口的類bean,用於統一自定義生成key - cacheManager: 用於選擇使用哪個cacheManager
- cacheResolver: 實現
org.springframework.cache.interceptor.CacheResolver接口的類bean,用於自定義如何處理緩存
CacheEvict:
- beforeInvocation: bool值,標志是否在調用前就清除緩存。防止方法因為異常意外退出。
Cacheable:
- sync: 是否同步 從相同key加載值 的方法,原文為:
Synchronize the invocation of the underlying method if several threads are
如何同步加載緩存,具體需要看CacheManager的實現。(2019年10月11日 此部分在https://www.cnblogs.com/imyijie/p/11651679.html SpringCache - 請求級別緩存的簡易實現 有進一步的討論)
attempting to load a value for the same key
5. 關於緩存TTL等設置
請查看各 CacheManager實現 的配置。
