Spring集成Spring-data-redis RedisCacheManager缓存源码分析
2017-12-28 14:31
513 查看
在项目中,一般我们会把服务层(service)的一些查询结果和一些数据进行缓存。缓存的种类有很多。这里进行redis作为缓存框架,进行一个缓存的配置。配置前需要先了解一些基本的知识
在Spring中缓存主要有一个缓存接口(Cache)与缓存管理接口(CacheManager)。可以通过扩展这两个接口实现对应的缓存管理。redis就是这样。当然还有很多比如Guava等都对其进行了扩展。这里只看redis的缓存基本架构:
首先一个抽象的缓存管理类AbstractCacheManager,对CacheManager进行了基本功能的实现,这个类还实现了InitializingBean接口,这个我们知道在spring中实现这个接口就可以在类初始化完成之后,执行接口的afterPropertiesSet方法,AbstractCacheManager在这个方法中执行了initializeCaches方法,也就是初始化缓存。其代码如下:
//首先加载缓存。loadCaches是一个模板方法,具体怎么加载子类决定。 Collection<? extends Cache> caches = loadCaches(); synchronized (this.cacheMap) { //每次初始化都创建一个新的缓存名称Set。代码最后一行,把这个set集合变成只读 this.cacheNames = Collections.emptySet(); //清空缓存Map ConcurrentHashMap this.cacheMap.clear(); //初始化 cacheNames //1. 循环遍历子类加载的caches Set<String> cacheNames = new LinkedHashSet<String>(caches.size()); for (Cache cache : caches) { String name = cache.getName(); //加入到缓存map集合中,在加入前要进行对缓存进行装配 decorateCache(cache) //decorateCache 这个方法本身就是返回cache。但是子类 //AbstractTransactionSupportingCacheManager 重写它。这个类从名字可以看出 //是支持事务。它有一个属性transactionAware默认为false. 如果配置了支持事务, //就会把当前cache装配成支持事物的cache 所以后面会有支持事务的配置,配置的就是 //transactionAware 这个属性为true //TransactionAwareCacheDecorator this.cacheMap.put(name, decorateCache(cache)); //2. 把cache的name加入到name集合中 cacheNames.add(name); } //set变成只读 this.cacheNames = Collections.unmodifiableSet(cacheNames); } }
初始化缓存之后,也实现了基本的获取缓存方法:
public Cache getCache(String name) { //根据缓存name直接从自己的cache map中获取 Cache cache = this.cacheMap.get(name); if (cache != null) { return cache; } else { //如果没有,那么就同步创建 synchronized (this.cacheMap) { //这里再一次获取,避免在同步前,有线程已经添加进去了。 cache = this.cacheMap.get(name); if (cache == null) { //如果还没有,就调用getMissingCache方法获取。 //这个方法可以看作一个模板方法,只不过自己实现了返回null //也就是说默认不创建。子类可以通过重写这个方法进行创建。 cache = getMissingCache(name); if (cache != null) { //对创建的cache进行装配 cache = decorateCache(cache); //放入缓存 map中 this.cacheMap.put(name, cache); //更新缓存名称集合。 上面我们知道缓存名称集合被修改成只读, //所以更新方法里面是创建一个新的,然后size进行加1,再把前面的 //添加进去,然后加入新建的这个,再修改成只读 updateCacheNames(name); } } return cache; } } }
当然你也可以直接调用lookupCache方法,该方
4000
法就是直接获取。没有后续的步骤
//final类型。子类不可重写 protected final Cache lookupCache(String name) { return this.cacheMap.get(name); }
通过上面对缓存的一个基本加载获取有了认知之后,就看看redis具体怎么对其自己想要的功能进行扩展,首先我们应该知道,redis是一个键值对的缓存框架,当然值由多种类型。在spring-data-redis中,我们操作redis又是通过RedisOperations(RedisTemplate间接的实现了它)对其进行操作,并且在读取和写入的时候,都会对键值进行序列化。所以,如果要扩展,RedisOperations比不可少,redis支持事务,那么我们就可以利用上面的AbstractTransactionSupportingCacheManager属性transactionAware进行配置。其它诸如:过期时间,缓存是否为null等等进行扩展,具体看看源码RedisCacheManager:
//RedisCacheManager的基本属性 @SuppressWarnings("rawtypes") // //配置redisTemplate 通过构造函数 private final RedisOperations redisOperations; //是否使用前缀 private boolean usePrefix = false; //默认前缀 为":"。使用前缀可以对缓存进行分组,避免缓存的冲突 private RedisCachePrefix cachePrefix = new DefaultRedisCachePrefix(); //远程加载缓存 private boolean loadRemoteCachesOnStartup = false; //是否动态生成缓存。默认为true。这个就是上面如果缓存不存在,则创建 //是通过这个属性进行配置。配置为false则不会去创建缓存 private boolean dynamic = true; // 过期时间 0为永不过期 private long defaultExpiration = 0; //可以配置指定key的过期时间 可以通过定制化配置过期时间 private Map<String, Long> expires = null; //配置缓存名称集合 private Set<String> configuredCacheNames; //缓存是否可以为null private final boolean cacheNullValues;
了解这些属性的含义,对后续的配置就更加容易理解。在spring的可扩展性非常高,一部分就是spring大量运用模板模式,所以看子类扩展,重点就看其扩展的模板方法。先看一下上面提到的第一个模板方法loadCaches:
@Override protected Collection<? extends Cache> loadCaches() { //首先我们必须配置了redisOperations。因为没有redisTemplate我们就无法操作redis Assert.notNull(this.redisOperations, "A redis template is required in order to interact with data store"); //首先看是否需要远程加载缓存 //如果配置是,那么久执行 loadAndInitRemoteCaches()方法进行加载 //这个远程加载比较容易。就是用redisTemplate去加载后缀~keys的key. //后缀为~keys是因为在创建的时候加上了这个后缀。具体可以查看redis的静态内部类 //RedisCacheMetadata 的构造方法。 Set<Cache> caches = new LinkedHashSet<Cache>( loadRemoteCachesOnStartup ? loadAndInitRemoteCaches() : new ArrayList<Cache>()); //加载我们配置的缓存名称集合 上面属性有介绍 Set<String> cachesToLoad = new LinkedHashSet<String>(this.configuredCacheNames); //加载当前已经缓存名称集合 volatile类型。所以对于当前线程是可见的 cachesToLoad.addAll(this.getCacheNames()); if (!CollectionUtils.isEmpty(cachesToLoad)) { //如果不为空则循环创建缓存 并加入缓存集合中 //这个缓存为redisCache.创建就是根据我们配置的如过期时间,是否允许为null,序列化(默认为 //jdk序列化)等属性创建一个redisCache for (String cacheName : cachesToLoad) { caches.add(createCache(cacheName)); } } return caches; }
到这里基本redis cache的一个基本加载创建原理里有比较深的认识。其中另外的模板方法getMissingCache就比较容易理解了:
protected Cache getMissingCache(String name) { 根据我们配置的dynamic属性决定是否创建新的缓存 return this.dynamic ? createCache(name) : null; }
看看支持事务的重写方法:
@Override protected Cache decorateCache(Cache cache) { if (isCacheAlreadyDecorated(cache)) { return cache; } return super.decorateCache(cache); } protected boolean isCacheAlreadyDecorated(Cache cache) { return isTransactionAware() && cache instanceof TransactionAwareCacheDecorator; }
如果配置了transactionAware,则把cache装饰成支持事务的cache
另外一个重点就是看一下createCache方法:
@SuppressWarnings("unchecked") protected RedisCache createCache(String cacheName) { long expiration = computeExpiration(cacheName); return new RedisCache(cacheName, (usePrefix ? cachePrefix.prefix(cacheName) : null), redisOperations, expiration, cacheNullValues); }
我们发现它就是new了一个RedisCache缓存,然后返回。并没有保存到redis服务端去。事实上应该要保存到服务端才符合原理。Spring通过拦截器CacheInterceptor来实现缓存拦截,然后调用CacheAspectSupport中的execute方法,在execute中调用内部类的一个apply方法,这个方法中调用了doPut方法,然后调用创建的缓存的RedisCache的put方法保存到服务端。这里提供一个调用链介绍,具体源码可以自己跟着链去看看
到这里spring-data-redis集成spring的一个基本原理与源码分析差不多了,了解一些基本属性,与执行原理与顺序。然后就可以进行基本配置了。具体配置:
<?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:mvc="http://www.springframework.org/schema/mvc" xmlns:cache="http://www.springframework.org/schema/cache" xsi:schemaLocation=" http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd" default-lazy-init="false"> <!-- 不使用就需要注解掉,如果配置了启用注解注释,就必须要配置cacheManager 否则就会启动报错。因为配置了这个缓存拦截器就会去注入cacheManager --> <cache:annotation-driven /> <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations"> <list> <value>classpath:dbconfig.properties</value> <value>classpath:redis.properties</value> </list> </property> </bean> <!-- 连接池配置. --> <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig"> <!-- 连接池中最大连接数。高版本:maxTotal,低版本:maxActive --> <property name="maxTotal" value="8" /> <!-- 连接池中最大空闲的连接数. --> <property name="maxIdle" value="8" /> <!-- 连接池中最少空闲的连接数. --> <property name="minIdle" value="${redis.minIdle}" /> <!-- 当连接池资源耗尽时,调用者最大阻塞的时间,超时将跑出异常。单位,毫秒数;默认为-1.表示永不超时。高版本:maxWaitMillis,低版本:maxWait --> <property name="maxWaitMillis" value="${redis.maxWaitMillis}" /> <!-- 连接空闲的最小时间,达到此值后空闲连接将可能会被移除。负值(-1)表示不移除. --> <property name="minEvictableIdleTimeMillis" value="${redis.minEvictableIdleTimeMillis}" /> <!-- 对于“空闲链接”检测线程而言,每次检测的链接资源的个数。默认为3 --> e094 <property name="numTestsPerEvictionRun" value="${redis.numTestsPerEvictionRun}" /> <!-- “空闲链接”检测线程,检测的周期,毫秒数。如果为负值,表示不运行“检测线程”。默认为-1. --> <property name="timeBetweenEvictionRunsMillis" value="${redis.timeBetweenEvictionRunsMillis}" /> <!-- testOnBorrow:向调用者输出“链接”资源时,是否检测是有有效,如果无效则从连接池中移除,并尝试获取继续获取。默认为false。建议保持默认值. --> <!-- testOnReturn:向连接池“归还”链接时,是否检测“链接”对象的有效性。默认为false。建议保持默认值. --> <!-- testWhileIdle:向调用者输出“链接”对象时,是否检测它的空闲超时;默认为false。如果“链接”空闲超时,将会被移除。建议保持默认值. --> <!-- whenExhaustedAction:当“连接池”中active数量达到阀值时,即“链接”资源耗尽时,连接池需要采取的手段, 默认为1(0:抛出异常。1:阻塞,直到有可用链接资源。2:强制创建新的链接资源) --> </bean> <!-- Spring提供的Redis连接工厂 --> <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" destroy-method="destroy"> <!-- 连接池配置. --> <property name="poolConfig" ref="jedisPoolConfig" /> <!-- Redis服务主机. --> <property name="hostName" value="${redis.hostName}" /> <!-- Redis服务端口号. --> <property name="port" value="${redis.port}" /> <!-- Redis服务连接密码. --> <!-- <property name="password" value="${redis.password}" /> --> <!-- 连超时设置. --> <property name="timeout" value="${redis.timeout}" /> <!-- 是否使用连接池. --> <property name="usePool" value="${redis.usePool}" /> </bean> <!-- Spring提供的访问Redis类. --> <bean id="jedisTemplate" class="org.springframework.data.redis.core.RedisTemplate"> <property name="connectionFactory" ref="jedisConnectionFactory" /> <!-- 开启事务 --> <property name="enableTransactionSupport" value="true"></property> <property name="keySerializer"> <bean class="org.springframework.data.redis.serializer.StringRedisSerializer" /> </property> <property name="valueSerializer"> <bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" /> <!-- <bean class="com.pdz.util.ApiRedisSerializa"/> <bean class="com.alibaba.fastjson.support.spring.GenericFastJsonRedisSerializer"/> <bean class="org.springframework.data.redis.serializer.StringRedisSerializer" /> --> </property> <property name="hashKeySerializer"> <bean class="org.springframework.data.redis.serializer.StringRedisSerializer" /> </property> <!-- <property name="hashValueSerializer"> <bean class="org.springframework.data.redis.serializer.StringRedisSerializer" /> </property> --> <property name="hashValueSerializer"> <bean class="com.pdz.util.ApiRedisSerializa" /> </property> </bean> <bean id="stringRedisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate"> <property name="connectionFactory" ref="jedisConnectionFactory" /> <!-- 开启事务 --> <property name="enableTransactionSupport" value="true"></property> </bean> <!-- <bean id="cacheManager" 这里面可以配置上面分析的各种字段属性-- > class="org.springframework.data.redis.cache.RedisCacheManager"> <!--配置 redisTemplate--> <constructor-arg index = "0" type="RedisOperations"> <ref bean="jedisTemplate" /> </constructor-arg> <!-- 过期时间 --> <property name="defaultExpiration" value="300000"/> <!-- 支持事务 --> <property name="transactionAware" value = "true"/> </bean>
具体使用采用注解可以参照这篇文章:http://jinnianshilongnian.iteye.com/blog/2001040
相关文章推荐
- Spring集成Spring-data-redis RedisCacheManager缓存源码分析
- Spring集成Spring-data-redis RedisCacheManager缓存源码分析
- Spring集成Spring-data-redis RedisCacheManager缓存源码分析
- Spring集成Spring-data-redis RedisCacheManager缓存源码分析
- Spring集成Spring-data-redis RedisCacheManager缓存源码分析
- Spring集成Spring-data-redis RedisCacheManager缓存源码分析
- Spring集成Spring-data-redis RedisCacheManager缓存源码分析
- Spring集成Spring-data-redis RedisCacheManager缓存源码分析
- Spring集成Spring-data-redis RedisCacheManager缓存源码分析
- Spring集成Spring-data-redis RedisCacheManager缓存源码分析
- Spring集成Spring-data-redis RedisCacheManager缓存源码分析
- 分布式缓存技术redis学习系列(五)——spring-data-redis与JedisPool的区别、使用ShardedJedisPool与spring集成的实现及一致性哈希分析
- 分布式缓存技术redis学习系列(五)——spring-data-redis与JedisPool的区别、使用ShardedJedisPool与spring集成的实现及一致性哈希分析
- 分布式缓存技术redis学习系列(五)——spring-data-redis与JedisPool的区别、使用ShardedJedisPool与spring集成的实现及一致性哈希分析
- spring-data-redis与JedisPool的区别、使用ShardedJedisPool与spring集成的实现及一致性哈希分析
- MySpace DataRelay 分布式数据缓存源码分析[转]
- Spring集成Mybatis配置与源码分析
- spring-data-mongo 关于_id 字段解析源码分析
- MySpace DataRelay 分布式数据缓存源码分析[转]
- Dubbo源码分析之二:spring集成之注解