spring cache浅析-结合spring-data-redis
最近在弄shiro的缓存用redis实现,同时又考虑到spring的缓存。一下子把自己搞混了,现在先记录一下对spring的缓存理解;
由于本人菜鸟,所以只能浅显的说一下,有错误请见谅并指正,谢谢!
一:基本内容介绍
spring的cache缓存使用接触到的两个基本接口:
1.cache;
2.cacheManager;
解释:
1.cache:根据底下的源码,很明显cache即相当于对缓存的实际crud操作者,这个肯定必须的;
我用的是spring-data-redis,该框架提供了一个RedisCache类,可以直接拿来使用;
Cache接口:
1 public interface Cache { 2 String getName(); 3 Object getNativeCache(); 4 ValueWrapper get(Object key); 5 <T> T get(Object key, Class<T> type); 6 <T> T get(Object key, Callable<T> valueLoader); 7 void put(Object key, Object value); 8 ValueWrapper putIfAbsent(Object key, Object value); 9 void evict(Object key); 10 void clear(); 11 interface ValueWrapper { 12 13 /** 14 * Return the actual value in the cache. 15 */ 16 Object get(); 17 } 18 ..... 19 }
2.cacheManager:cache实例,实际是保存cache的实例;spring-data-redis提供了一个RedisCacheManager类,可以直接使用;
CacheManager接口:
public interface CacheManager { /** * Return the cache associated with the given name. * @param name the cache identifier (must not be {@code null}) * @return the associated cache, or {@code null} if none found */ Cache getCache(String name); /** * Return a collection of the cache names known by this manager. * @return the names of all caches known by the cache manager */ Collection<String> getCacheNames(); }
RedisCacheManager类:
public class RedisCacheManager extends AbstractTransactionSupportingCacheManager { @SuppressWarnings("rawtypes") public RedisCacheManager(RedisOperations redisOperations) { this(redisOperations, Collections.<String> emptyList()); } ... }
二、缓存相关的常用的三个注解
1.@Cacheable,@CacheEvict,@CachePut:
暂时只说一些简单的,①,这三个注解都有个属性-value,这个value对应的就是cache的一个实例(本文即RedisCache)的名称。当第一次使用时,系统会自动生成这个实例,并注册给缓存管理器(本文即RedisCacheManeger);②,还有个属性-key,表示要查询/删除的缓存键;③,方法的的返回值即为缓存的值,与②中的键对应。具体是如何实现的,接下来会有简单分析。
三、spring配置redis缓存
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:task="http://www.springframework.org/schema/task" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:cache="http://www.springframework.org/schema/cache" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tool http://www.springframework.org/schema/tool/spring-tool.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <cache:annotation-driven /> <context:component-scan base-package="redis"/> <context:component-scan base-package="aop"/> <context:property-placeholder location="classpath:redis.properties"/> <!--jedis连接池 --> <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig"> <property name="maxTotal" value="${redis.maxTotal}"/> <property name="maxIdle" value="${redis.maxIdle}"/> <property name="maxWaitMillis" value="${redis.maxWaitMillis}"/> <property name="testOnBorrow" value="${redis.testOnBorrow}"/> </bean> <bean id="jedisFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"> <property name="hostName" value="${redis.host}"/> <property name="port" value="${redis.port}"/> <property name="password" value="${redis.password}"/> <property name="database" value="${redis.database}"/> <property name="usePool" value="true"/> <property name="poolConfig" ref="jedisPoolConfig"/> </bean> <!--redis的实际操作者 --> <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"> <property name="connectionFactory" ref="jedisFactory"/> <property name="keySerializer"> <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/> </property> <property name="valueSerializer"> <bean class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer"/> </property> </bean> <!--redis的实际操作者 --> <bean id="jdkSerializeRedisTemplate" class="org.springframework.data.redis.core.RedisTemplate"> <property name="connectionFactory" ref="jedisFactory"/> <property name="keySerializer"> <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/> </property> </bean> <!-- 注册缓存处理器 --> <bean name="cacheManager" class="org.springframework.data.redis.cache.RedisCacheManager"> <constructor-arg ref="redisTemplate"/> </bean> <!-- <aop:aspectj-autoproxy/> --> </beans>
根据上面的RedisCacheManager源码可知,他需要通过构造方法注入一个RedisOperations<K, V>,而RedisTemplate<K, V>实现了RedisOperations<K, V>接口,所以我们需要注入该bean(这个是数据缓存的实际操作者,肯定要传入);然后在项目代码中既可以使用上面的三个注解进行测试了;即spring整合spring-data-redis注解方式就完成了。非注解的方式,我的理解就是每次自己操作RedisTemplate,在方法前后执行缓存查询,缓存删除,缓存更新等操作,那个代码耦合太严重了;
四、缓存工作原理
1.我是从@Cacheable入手,找到了CacheAspectSupport这个类;这是个抽象类,里面有个方法是在cache过程中被调用的,即execute;
public abstract class CacheAspectSupport implements InitializingBean { public interface Invoker { Object invoke(); } ... protected Object execute(Invoker invoker, Object target, Method method, Object[] args) { // check whether aspect is enabled // to cope with cases where the AJ is pulled in automatically if (!this.initialized) { return invoker.invoke(); } // get backing class Class<?> targetClass = AopProxyUtils.ultimateTargetClass(target); if (targetClass == null && target != null) { targetClass = target.getClass(); } final Collection<CacheOperation> cacheOp = getCacheOperationSource().getCacheOperations(method, targetClass); // analyze caching information if (!CollectionUtils.isEmpty(cacheOp)) { Map<String, Collection<CacheOperationContext>> ops = createOperationContext(cacheOp, method, args, target, targetClass); // start with evictions inspectBeforeCacheEvicts(ops.get(EVICT)); // follow up with cacheable CacheStatus status = inspectCacheables(ops.get(CACHEABLE)); Object retVal = null; Map<CacheOperationContext, Object> updates = inspectCacheUpdates(ops.get(UPDATE)); if (status != null) { if (status.updateRequired) { updates.putAll(status.cUpdates); } // return cached object else { return status.retVal; } } retVal = invoker.invoke(); inspectAfterCacheEvicts(ops.get(EVICT)); if (!updates.isEmpty()) { update(updates, retVal); } return retVal; } return invoker.invoke(); } ... }
上面这个类是个抽象类。从execute方法中可以看出来,@CacheEvict会@Cacheable注解先执行;这个类的继承者CacheInterceptor即是实际执行者:
@SuppressWarnings("serial") public class CacheInterceptor extends CacheAspectSupport implements MethodInterceptor, Serializable { private static class ThrowableWrapper extends RuntimeException { private final Throwable original; ThrowableWrapper(Throwable original) { this.original = original; } } public Object invoke(final MethodInvocation invocation) throws Throwable { Method method = invocation.getMethod(); Invoker aopAllianceInvoker = new Invoker() { public Object invoke() { try { return invocation.proceed(); } catch (Throwable ex) { throw new ThrowableWrapper(ex); } } }; try { return execute(aopAllianceInvoker, invocation.getThis(), method, invocation.getArguments()); } catch (ThrowableWrapper th) { throw th.original; } } }
从MethodInterceptor这个借口看得出来,代理确实是使用了aop来完成的。
到这里,目前就了解到这里,后续应该还会有更新...