Spring-cache的抽象

来源:互联网 发布:淘宝c店详情页banner 编辑:程序博客网 时间:2024/05/22 01:27

背景

前面已经分析了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.Cacheorg.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里面最核心的代码:

@Beanpublic ConcurrentMapCacheManager cacheManager() {    ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();    List<String> cacheNames = this.cacheProperties.getCacheNames();    if (!cacheNames.isEmpty()) {        cacheManager.setCacheNames(cacheNames);    }    return this.customizerInvoker.customize(cacheManager);}

这段代码:

  1. 生成了ConcurrentMapCacheManager(cacheManager的实现类)的bean。
  2. 根据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时,会在调用方法之前移除数据。

原创粉丝点击