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来完成的。

到这里,目前就了解到这里,后续应该还会有更新...

    

原文地址:https://www.cnblogs.com/jkavor/p/7263306.html