緩存
對於 JSR107 規範:
Java Caching (使用需要引入JCache)定義了5個核心接口,分別是CachingProvider, CacheManager, Cache, Entry
和 Expiry。
- CachingProvider定義了創建、配置、獲取、管理和控制多個CacheManager。一個應用可
以在運行期訪問多個CachingProvider。 • CacheManager定義了創建、配置、獲取、管理和控制多個唯一命名的Cache,這些Cache
存在於CacheManager的上下文中。一個CacheManager僅被一個CachingProvider所擁有。 - Cache是一個類似Map的數據結構並臨時存儲以Key爲索引的值。一個Cache僅被一個
CacheManager所擁有。 - Entry是一個存儲在Cache中的key-value對。
- Expiry每一個存儲在Cache中的條目有一個定義的有效期。一旦超過這個時間,條目爲過期
的狀態。一旦過期,條目將不可訪問、更新和刪除。緩存有效期可以通過ExpiryPolicy設置。
關係圖如下:
Spring緩存抽象
由於 JSR107 使用過程比較複雜,所以 Spring 定義了 org.springframework.cache.Cache
和 org.springframework.cache.CacheManager 接口來統一不同的緩存技術;並支持使用JCache(JSR-107)註解簡化我們開發。
注:Cache接口下Spring提供了各種xxxCache的實現,如:RedisCache,EhCacheCache ,
ConcurrentMapCache等;
主要的概念與緩存註解:
示例
- 開啓緩存註解
@SpringBootApplication
@MapperScan("com.moke.cache.mapper")
@EnableCaching
public class SpringbootCacheApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootCacheApplication.class, args);
}
}
2.使用緩存
@Service
public class EmployeeService {
@Autowired
private EmployeeMapper employeeMapper;
/*
@Cacheable的幾個屬性
cacheNmaes/value:指定緩存組件的名字,將方法的返回結果放到指定緩存中,可以以數組方式指定多個緩存
key:緩存數據使用的key,默認是使用方法參數的值【SpEL表達式】
keyGenerator:key的生成器,也可以自己指定
cacheManager/cacheResolver:指定緩存管理器/緩存解析器
condition:指定符合條件的情況下,才進行緩存
condition = "#id>0",
unless:否定緩存,當unless指定的條件爲true,不會被緩存,可以獲取到結果進行判斷
unless = "#result==null"
sync:是由使用異步模式
*/
@Cacheable(cacheNames = "emp")
public Employee getEmp(Integer id){
return employeeMapper.getEmpById(id);
}
}
緩存原理
相關自動配置類:CacheAutoConfiguration
@Import({CacheAutoConfiguration.CacheConfigurationImportSelector.class})
由上面的註解會根據我們使用的緩存類型選擇相應的緩存配置類:
而默認使用的是:org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration
SimpleCacheConfiguration 在容器中註冊了一個緩存管理器 ConcurrentMapCacheManager ,而它可以獲取和創建 ConcurrentMapCache 類型的緩存組件,並將數據保存在一個 ConcurrentMap 中:
//SimpleCacheConfiguration
@Bean
public ConcurrentMapCacheManager cacheManager() {
ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
List<String> cacheNames = this.cacheProperties.getCacheNames();
if (!cacheNames.isEmpty()) {
cacheManager.setCacheNames(cacheNames);
}
return (ConcurrentMapCacheManager)this.customizerInvoker.customize(cacheManager);
}
//ConcurrentMapCacheManager
public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderAware {
private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap(16);
...
public Cache getCache(String name) {
Cache cache = (Cache)this.cacheMap.get(name);
if (cache == null && this.dynamic) {
synchronized(this.cacheMap) {
cache = (Cache)this.cacheMap.get(name);
if (cache == null) {
cache = this.createConcurrentMapCache(name);
this.cacheMap.put(name, cache);
}
}
}
return cache;
}
...
}
//ConcurrentMapCache
public class ConcurrentMapCache extends AbstractValueAdaptingCache {
private final String name;
private final ConcurrentMap<Object, Object> store;
...
}
運行流程
@Cacheable:
- 方法運行之前,先去查詢 ConcurrentMapCache (緩存組件),按照 cacheNames 指定的名字獲取;第一次獲取緩存如果沒有會自動創建緩存組件。
- 去 ConcurrentMap 中查找緩存的內容,使用一個key,默認就是方法的參數;
(1)key 默認是使用keyGenerator(SimpleKeyGenerator)生成的;
(2)SimpleKeyGenerator生成key的默認策略;- 如果沒有參數;key=new SimpleKey();
- 如果有一個參數:key=參數的值
- 如果有多個參數:key=new SimpleKey(params);
- 沒有查到緩存就調用目標方法;
- 將目標方法返回的結果,放進緩存中
@Cacheable 標註的方法執行之前先來檢查緩存中有沒有這個數據,默認按照參數的值作爲key去查詢緩存;如果沒有就運行方法並將結果放入緩存;以後再來調用就可以直接使用緩存中的數據;
@CachePut:
@CachePut(value = "emp",key = "#employee.id")//"#result.id"
public Employee updateEmp(Employee employee){
employeeMapper.updateEmp(employee);
return employee;
}
@CachePut,既調用方法,又更新緩存數據,用於同步更新緩存;不同於 @Cacheable ,它是先調用目標方法,將目標方法的結果緩存起來。
注:緩存使用的 key 默認是方法參數列表,所以我們需要統一緩存的 key 的格式。
@CacheEvict:
/* 相關屬性:
* allEntries = true:指定清除這個緩存中所有的數據
* beforeInvocation = false:緩存的清除是否在方法之前執行
* 默認代表緩存清除操作是在方法執行之後執行;如果出現異常緩存就不會清除
*
* beforeInvocation = true:
* 代表清除緩存操作是在方法運行之前執行,無論方法是否出現異常,緩存都清除
*/
@CacheEvict(value = "emp",key = "#id")
public void deleteEmp(Integer id){
employeeMapper.deleteById(id);
}
@CacheEvict 用於緩存清除,可以用 key 指定要清除的數據。
@Caching:
@Caching(
cacheable = {
@Cacheable(/*value="emp",*/key = "#lastName")
},
put = {
@CachePut(/*value="emp",*/key = "#result.id"),
@CachePut(/*value="emp",*/key = "#result.email")
}
)
public Employee getEmpByLastName(String lastName){
return employeeMapper.getEmpByLastName(lastName);
}
@Caching 用於自定義複雜的緩存規則。
@CacheConfig:
@CacheConfig(cacheNames="emp"/*,cacheManager = "employeeCacheManager"*/) //抽取緩存的公共配置
@Service
public class EmployeeService {...}
@CacheConfig 用於定義公共配置。
RedisCache
如果我們不使用 Sping 給我們提供的緩存,而是使用 Redis 緩存,則查閱官方文檔:
引入依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
增加配置:
而由自動配置原理,我們來看 RedisAutoConfiguration 中給我們的容器添加了哪些組件:
可以看到爲我們容器中自動添加了兩個 redistemplate 模板,使用時我們只需要自動注入即可。
使用:
@Autowired
private RedisTemplate redisTemplatel;
更多的 redis 的使用可以參考,本人之前的總結:再見 Redis
在xml中配置 redistemplate 以及序列化方式:
而在 SpringBoot 中,改變默認的序列化規則:
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<Object, Employee> empRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<Object, Employee> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer<Employee> ser = new Jackson2JsonRedisSerializer<Employee>(Employee.class);
template.setDefaultSerializer(ser);
return template;
}
}
原理:
- 我們知道 SpringBoot 默認使用的是 SimpleCacheConfiguration 創建 ConcurrentMapCacheManager,使用 ConcurrentMapCache 作爲緩存組件,且使用 map 來保存數據
- 而在配置完 Redis 後使用 RedisCacheConfiguration 來創建RedisCacheManager,使用 RedisCache 作爲緩存組件,其不會自己保存數據,而是會操作 redis 中數據。
自定義 RedisCacheManager:
上面只是修改了 redistemplate 的序列化規則,那如果也想修改註解方式的序列化規則就得修改RedisCacheManager。
@Bean
public RedisCacheManager empCacheManager(RedisConnectionFactory redisConnectionFactory) {
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration
.defaultCacheConfig()
.serializeValuesWith( RedisSerializationContext
.SerializationPair
.fromSerializer(new Jackson2JsonRedisSerializer<Employee>(Employee.class)));
return RedisCacheManager
.builder(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory))
.cacheDefaults(redisCacheConfiguration).build();
}
對於多個 CacheManager ,需要使用 @Primary 指定默認的,並可以在註解屬性中指定,例如:
@Cacheable(cacheNames = "emp",cacheManager = "empCacheManager")