背景
前面已经分析了SpringBoot的一套starter和autoconfigure 的机制,具体参考:http://blog.csdn.net/u010853261/article/details/77961716 这篇博客。
其实对于SpringBoot的starter机制,其实就是依据SpringBoot的约定大于配置的思想,启动SpringBoot时候,根据依赖的starter,就已经自动装载了很多bean,也就是开箱即用,不需要用户自己配置。
缓存在我们的应用系统中也是应用的很多的地方,而且现在也有各种各样的缓存,有单机的ConcurrentHashMap、guava的cache、ehcache、以及分布式的redis、tair等等,Spring通过一个Spring-boot-cache-starter的模块,帮我们抽象的封装了底层具体是哪种cache的实现。 也就是我们只需要使用Spring cache就行了,而不用关心底层是哪一种cache.(当然,底层肯定是需要配置具体缓存的starter的)
那么这种抽象的封装是怎么实现的呢?
1. Spring cache的抽象实现
在Spring中定义了两个接口org.springframework.cache.Cache
和org.springframework.cache.CacheManager
,来实现对cache和cache管理的封装。Cache就是具体的缓存读取对象,提供最基本的put、get、evict等操作。CacheManager则是具体的Cache的一个manager对象,用来管理具体的每个Cache对象, 其实也就是类似于一个Cache的factory。
我们先来看看这两个接口的能力:
Cache.java
public interface Cache {
/**Return the cache name.*/
String getName();
/**Return the underlying native cache provider.*/
Object getNativeCache();
/**Return the value to which this cache maps the specified key.*/
ValueWrapper get(Object key);
<T> T get(Object key, Class<T> type);
/**
* Return the value to which this cache maps the specified key, obtaining
* that value from {@code valueLoader} if necessary. This method provides
* a simple substitute for the conventional "if cached, return; otherwise
* create, cache and return" pattern.
*/
<T> T get(Object key, Callable<T> valueLoader);
/**
* Associate the specified value with the specified key in this cache.
*/
void put(Object key, Object value);
/**
* Atomically associate the specified value with the specified key in this cache
* if it is not set already.
*/
ValueWrapper putIfAbsent(Object key, Object value);
/**
* Evict the mapping for this key from this cache if it is present.
*/
void evict(Object key);
/**
* Remove all mappings from the cache.
*/
void clear();
interface ValueWrapper {
Object get();
}
@SuppressWarnings("serial")
class ValueRetrievalException extends RuntimeException {
private final Object key;
public ValueRetrievalException(Object key, Callable<?> loader, Throwable ex) {
super(String.format("Value for key '%s' could not be loaded using '%s'", key, loader), ex);
this.key = key;
}
public Object getKey() {
return this.key;
}
}
}
CacheManager.java
public interface CacheManager {
/**
* Return the cache associated with the given name.
*/
Cache getCache(String name);
/**
* Return a collection of the cache names known by this manager.
*/
Collection<String> getCacheNames();
}
我们看看这两个接口在spring-context的实现:
cache的实现
CacheManager的实现
在没有SpringBoot的时候,我们需要自己配置注入CacheManager,然后才能够使用cache。但是SpringBoot已经帮我们解决了这个问题,通过自动装配,默认帮我们注入这个cache。
我们看看SpringBoot的autoconfigure包下面关于cache的自动装配的实现:
可以知道,对于一些常见的缓存,已经帮我们做了实现:比如guava, redis, concurrentHashMap等等。
当我们引入了相应的依赖包时,这个cacheManager就会被自动装配。在不适用任何额外配置的情况下,默认使用SimpleCacheConfiguration,也就是基于ConcurrentMap.
下面我们以一个最简单的实例来说明:SimpleCacheConfiguration
SimpleCacheConfiguration的实现
我们先看看SimpleCacheConfiguration里面最核心的代码:
@Bean
public ConcurrentMapCacheManager cacheManager() {
ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
List<String> cacheNames = this.cacheProperties.getCacheNames();
if (!cacheNames.isEmpty()) {
cacheManager.setCacheNames(cacheNames);
}
return this.customizerInvoker.customize(cacheManager);
}
这段代码:
- 生成了ConcurrentMapCacheManager(cacheManager的实现类)的bean。
- 根据application.properties里面配置的信息,生成其CacheManager的bean
在ConcurrentMapCacheManager里面维护这一个CacheName到cache的Map:
private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<String, Cache>(16);
然后对于以ConcurrentMap作为底层存储的Cache实现是:ConcurrentMapCache,底层维护着具体的存储载体:
private final ConcurrentMap<Object, Object> store;
这就是大概的实现过程。对于其余的存储方式也是类似的。
Tair的实现
在tair的Spring-boot-tair-starter里面的autoconfigure里面,TairManager的实现并没有依赖于Spring的CacheManager, 而Spring也没有提供正式的autoConfig给我们,所以对于tair的集成使用Spring cache需要我们自己实现。 集团的pandora-boot-tair-spring-boot-autoconfigurate也只是注入了tair的TairManager, 这个与Spring的cache并没有什么关系。所以如果我们如果要通过SpringCache集成tair, 就必须自己手动写插件。
这里我们必须实现的其实就是类似于基于tair的TairManager(已注入), 然后实现一个Spring Cache的插件。
Spring cache 基于注解的使用
使用说明
1. 引入依赖
(1)maven依赖:包括spring-boot-starter-cache(必须)、第三方缓存技术依赖(可选),比如Redis等等
(2)Springboot的配置类上加上@EnableCaching注解开启缓存功能
(3)在数据访问接口中,增加缓存配置注解。比如下例子:
@CacheConfig(cacheNames = "users")
public interface UserRepository extends JpaRepository<User, Long> {
@Cacheable
User findByName(String name);
}
常用注解如下:
(1)@CacheConfig:主要用于配置该类中会用到的一些共用的缓存配置。在这里比如@CacheConfig(cacheNames = “users”):配置了该数据访问对象中返回的内容将存储于名为users的缓存对象中,我们也可以不使用该注解,直接通过@Cacheable自己配置缓存集的名字来定义。
(2)@Cacheable:配置了findByName函数的返回值将被加入缓存。同时在查询时,会先从缓存中获取,若不存在才再发起对数据库的访问。该注解主要有下面几个参数:
value、cacheNames:两个等同的参数(cacheNames为Spring 4新增,作为value的别名),用于指定缓存存储的集合名。由于Spring 4中新增了@CacheConfig,因此在Spring 3中原本必须有的value属性,也成为非必需项了
key:缓存对象存储在Map集合中的key值,非必需,缺省按照函数的所有参数组合作为key值,若自己配置需使用SpEL表达式,比如:
@Cacheable(key = "#p0")
:使用函数第一个参数作为缓存的key值,更多关于SpEL表达式的详细内容可参考官方文档condition:缓存对象的条件,非必需,也需使用SpEL表达式,只有满足表达式条件的内容才会被缓存,比如:
@Cacheable(key = "#p0", condition = "#p0.length() < 3")
,表示只有当第一个参数的长度小于3的时候才会被缓存,若做此配置上面的AAA用户就不会被缓存,读者可自行实验尝试。unless:另外一个缓存条件参数,非必需,需使用SpEL表达式。它不同于condition参数的地方在于它的判断时机,该条件是在函数被调用之后才做判断的,所以它可以通过对result进行判断。
keyGenerator:用于指定key生成器,非必需。若需要指定一个自定义的key生成器,我们需要去实现org.springframework.cache.interceptor.KeyGenerator接口,并使用该参数来指定。需要注意的是:该参数与key是互斥的
cacheManager:用于指定使用哪个缓存管理器,非必需。只有当有多个时才需要使用
cacheResolver:用于指定使用那个缓存解析器,非必需。需通过org.springframework.cache.interceptor.CacheResolver接口来实现自己的缓存解析器,并用该参数指定。
除了这里用到的两个注解之外,还有下面几个核心注解:
(1)@CachePut:配置于函数上,能够根据参数定义条件来进行缓存,它与@Cacheable不同的是,它每次都会真是调用函数,所以主要用于数据新增和修改操作上。它的参数与@Cacheable类似,具体功能可参考上面对@Cacheable参数的解析
(2)@CacheEvict:配置于函数上,通常用在删除方法上,用来从缓存中移除相应数据。除了同@Cacheable一样的参数之外,它还有下面两个参数:
* allEntries:非必需,默认为false。当为true时,会移除所有数据
* beforeInvocation:非必需,默认为false,会在调用方法之后移除数据。当为true时,会在调用方法之前移除数据。