Spring Cache源码分析(1) - CacheManager和Cache

Spring Cache源码分析(1) - CacheManager和Cache

这篇作为Spring Cache源码分析的起始篇,重要在于分析CacheManager和Cache。但是,在分析CacheManager和Cache之前,还是先看一个简单的例子,这样有助于理解Spring Cache的概念,知道Spring Cache在干什么,有什么作用,只有知道了Spring Cache的用处,在分析源码的时候才能知道Spring Cache的功能是怎么实现的! 这里不讲使用的细节,如果想要了解细节请看其他质料或后面篇章中的详细分析。


Hello示例 下载

下面直接上示例代码,一个简单的Hello程序,Hello、HelloService、HelloTest。在下面代码中我们用到了注解:@CachePut,@Cacheable,@CacheEvict。从单词意思我们就应该知道这些的作用是什么,@CachePut用于把数据存放到缓存中;@Cacheable用于从缓存中获取数据,如果缓存中不存在就执行代码得到并存放在缓存中去,以便下次从缓存中获取;@CacheEvict用于驱除缓存中的数据。在后面的章节中会详细的讲解这些注解中的每个属性。

Hello对象:

public class Hello {
	String name;
	public Hello(String name){
		this.name = name;
	}
	public String getName() {
		return name;
	}
	@Override
	public String toString() {
		return "Hello," + name;
	}
}

HelloService对象:

@Service
public class HelloService {
	/**
	 * 以Hello#name属性域作为缓存key(不管缓存是否存在,都会去执行)
	 * @param hello
	 * @return
	 */
	@CachePut(value="hello", key="#hello.name")
	public Hello put(Hello hello){
		System.out.println("put Hello:" + hello.getName());
		return hello;
	}

	/**
	 * 以name参数为key
	 * 如果缓存中没有,则执行代码并缓存
	 * 如果缓存中已经存在,则直接从缓存中获取
	 * @param name
	 * @return
	 */
	@Cacheable(value="hello", key="#name")
	public Hello get(String name){
		System.out.println("new Hello:" + name);
		return new Hello(name);
	}

	/**
	 * 从命名为"hello"的缓存中,删除掉name参数的key
	 * @param name
	 */
	@CacheEvict(value="hello", key="#name")
	public void remove(String name){
		System.out.println("remove Hello:" + name);
	}

	/**
	 * 从命名为"hello"的缓存中,删除所有缓存
	 */
	@CacheEvict(value="hello", allEntries=true)
	public void removeAll(){
		System.out.println("remove all!");
	}
}

HelloTest测试:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:applicationContext.xml")
public class HelloTest extends AbstractJUnit4SpringContextTests{

	@Test
	public void hello(){
		HelloService service = applicationContext.getBean(HelloService.class);
		service.get("linya");
		Hello hello = service.get("linya");
		System.out.println(hello.toString());
	}
}

我们可以试着运行上面的测试文件,但是程序是不能运行的,为什么呢,因为需要配置文件applicationContext.xml。上面我们说过通过注解可以缓存、获取、删除数据,那么数据被缓存到了哪里呢?很显然这样需要applicationContext.xml配置文件来处理,指明数据需要缓存的地方,这个缓存的地方在Spring Cache被定义为Cache和CacheManager,下面我们来看看如何简单的配置Spring Cache。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:cache="http://www.springframework.org/schema/cache"
	xsi:schemaLocation="
        http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context-4.1.xsd
        http://www.springframework.org/schema/cache
        http://www.springframework.org/schema/cache/spring-cache-4.1.xsd"
	default-lazy-init="true">
	<context:component-scan base-package="seven.xiaoqiyiye.spring.cache"/>
    <context:annotation-config/>
    
	<!-- SpringCache驱动一定要配置,后面会详细讲解这个配置的作用 -->
    <cache:annotation-driven/>

	<!-- 配置CacheManager,CacheManager中管理着Cache集合,在这里配置了一个名称为"hello"的Cache-->
	<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
        <property name="caches">
            <set>
                <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean">
                    <property name="name" value="hello"/>
                </bean>
            </set>
        </property>
    </bean>
</beans>

CacheManager 和 Cache

CacheManager定义很简单,用于管理Cache集合,并提供通过Cache名称获取对应Cache对象的方法。下面是CacheManager接口定义:

/**
 * CacheManager可以通过名称来获取一个Cache对象
 * @author Costin Leau
 * @since 3.1
 */
public interface CacheManager {

	/**
	 * 通过name来获取Cache对象
	 */
	Cache getCache(String name);
	
	/**
     * 返回这个CacheManager管理的Cache集合的所有Cache名称
	 */
	Collection<String> getCacheNames();
}

从上面的CacheManager可以知道,每个Cache必须要指定一个name,这个name需要在CacheManager中是唯一的。另外Cache对象还需要支持一些数据操作,存放数据、获取数据、驱除数据等等。下面,我们看看Cache接口的定义:

/**
 * Interface that defines common cache operations.
 */
public interface Cache {

	/**
     * 获取该Cache的名称
	 */
	String getName();

	/**
     * 返回底层真是的缓存对象,接口中并不需要关系具体是怎么实现的,
     * 使用Map、Reids、Guava,Ecache等
	 */
	Object getNativeCache();

	/**
     * 返回被包装的值,主要是为了null的处理
	 */
	ValueWrapper get(Object key);

	/**
     * 返回缓存中指定key的值,并获得这个值的特定类型
	 */
	<T> T get(Object key, Class<T> type);

	/**
     * 存放key-value数据到缓存中
	 */
	void put(Object key, Object value);

	ValueWrapper putIfAbsent(Object key, Object value);

	/**
     * 从缓存中删除指定key的数据
	 */
	void evict(Object key);

	/**
     * 删除缓存中所有数据
	 * Remove all mappings from the cache.
	 */
	void clear();

	interface ValueWrapper {
		Object get();
	}

}

CacheManager和Cache接口定义就是这么简单,下面再看看CacheManager和Cache的实现类。它们的实现类很多,我们这里选基于ConcurrentMap的实现:ConcurrentMapCacheManager和ConcurrentMapCache。

/**
 * ConcurrentMapCacheManager负责管理ConcurrentMapCache对象,支持懒加载获取,也支持预先实例化对象,
 * 通过调用#setCacheNames方法可以预定义一些ConcurrentMapCache到ConcurrentMapCacheManager中,
 * 但是一旦设置过后,dynamic就会设置为false,这时就不再支持动态创建Cache功能。
 *
 * 通常是否懒加载可以通过不同的构造器来控制,new ConcurrentMapCacheManager()创建懒加载的CacheManager,
 * new ConcurrentMapCacheManager(String... cacheNames)创建预先定义Cache的CacheManager。
 */
public class ConcurrentMapCacheManager implements CacheManager {
	
	//使用ConcurrentMap来管理Cache集合对象
	private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<String, Cache>(16);
	//表示是否可以动态创建Cache缓存,如果指定过name那么就不能动态创建
	private boolean dynamic = true;
	
	public ConcurrentMapCacheManager() {
	}

	public ConcurrentMapCacheManager(String... cacheNames) {
		setCacheNames(Arrays.asList(cacheNames));
	}
	
	/**
     * 这里会初始化ConcurrentMapCache,并存放到ConcurrentMap中进行管理
     * 一旦初始化过,这dynamic=false,在调用getCache(name)是就不会动态创建了
	 */
	public void setCacheNames(Collection<String> cacheNames) {
		if (cacheNames != null) {
			for (String name : cacheNames) {
				this.cacheMap.put(name, createConcurrentMapCache(name));
			}
			this.dynamic = false;
		}
		else {
			this.dynamic = true;
		}
	}

	@Override
	public Collection<String> getCacheNames() {
		return Collections.unmodifiableSet(this.cacheMap.keySet());
	}

	/**
	 * 根据缓存name来获取关联的ConcurrentMapCache实例
	 * 如果ConcurrentMapCacheManager中没有获取到,则动态获取。(能否动态获取需要看dynamic是否为true)
	 */
	@Override
	public Cache getCache(String name) {
		Cache cache = this.cacheMap.get(name);
		if (cache == null && this.dynamic) {
			synchronized (this.cacheMap) {
				cache = this.cacheMap.get(name);
				if (cache == null) {
					cache = createConcurrentMapCache(name);
					this.cacheMap.put(name, cache);
				}
			}
		}
		return cache;
	}

	/**
	 * 创建Cache对象
	 */
	protected Cache createConcurrentMapCache(String name) {
		return new ConcurrentMapCache(name, isAllowNullValues());
	}

}

CacheManager和Cache是不是很简单?是的,非常简单! 可是在上面的applicationContext.xml并没有配置ConcurrentMapCacheManager和ConcurrentMapCache呀,在回顾一下applicationContext.xml中是怎么配置的吧,配置如下:

<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
    <property name="caches">
        <set>
            <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean">
                <property name="name" value="hello"/>
            </bean>
        </set>
    </property>
</bean>

在applicationContext.xml中配置了SimpleCacheManager和ConcurrentMapCacheFactoryBean,SimpleCacheManager也是一个CacheManager的实现类,一个比ConcurrentMapCacheManager更简单的实现类,它需要外部指定Cache集合对象,而这个Cache对象正是使用ConcurrentMapCacheFactoryBean来注入到Spring的。虽然这两个类很简单,但是还是看一下部分源码吧(去掉了一些方法)。

public class ConcurrentMapCacheFactoryBean implements FactoryBean<ConcurrentMapCache>, BeanNameAware, InitializingBean {

	//定义Cache的名称
	private String name = "";

	//注入到Spring时缓存存储的数据
	private ConcurrentMap<Object, Object> store;

	//是否允许null值
	private boolean allowNullValues = true;

	//真实的Cache实现类

	private ConcurrentMapCache cache;


	/**
	 * Specify the name of the cache.
	 * <p>Default is "" (empty String).
	 */
	public void setName(String name) {
		this.name = name;
	}

	public void setStore(ConcurrentMap<Object, Object> store) {
		this.store = store;
	}

	public void setAllowNullValues(boolean allowNullValues) {
		this.allowNullValues = allowNullValues;
	}

	/**
	 * 以Spring注入的beanName作为Cache的名称
	 */
	@Override
	public void setBeanName(String beanName) {
		if (!StringUtils.hasLength(this.name)) {
			setName(beanName);
		}
	}

	/**
	 * 对象注入到Spring的时候就初始化好了Cache对象(ConcurrentMapCache)
	 */
	@Override
	public void afterPropertiesSet() {
		this.cache = (this.store != null ? new ConcurrentMapCache(this.name, this.store, this.allowNullValues) :
				new ConcurrentMapCache(this.name, this.allowNullValues));
	}

	@Override
	public ConcurrentMapCache getObject() {
		return this.cache;
	}
}

其实,在applicationContext.xml中就负责配置了CacheManager,告诉Spring Cache使用什么要的CacheManager实现,接下来我们使用ConcurrentMapCacheManager来配置,可以达到同样的效果。

<bean id="cacheManager" class="org.springframework.cache.concurrent.ConcurrentMapCacheManager">
    <!-- 可以不设置cacheNames哦,设置之后就不能动态创建Cache了。前面代码已经分析过,明白了吗! -->
    <property name="cacheNames">
    	<set>
    		<value>hello</value>
    		<value>world</value>
    	</set>
    </property>
</bean>

通过上面的分析,我们已经清楚地了解了CacheManager和Cache接口的作用,以及基于ConcurrentMap的实现。但是,分析了这么久,那Spring到底是怎么缓存数据的呢? @CachePut、@Cacheable、@CacheEvict是怎么产生作用的呢? 莫急,莫急,这个在后续章节中详细说明。至少我们现在知道,数据被存储到哪里去了。 对,数据被存储在Cache是实现类里。

CacheManager和Cache还有基于Redis、Guava、EhCache、JCache的实现,这里就不分析了,其实原理都一样。

转载于:https://my.oschina.net/xiaoqiyiye/blog/1624302

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值