zoukankan      html  css  js  c++  java
  • 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来完成的。

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

        

  • 相关阅读:
    性能测试的一些大实话:会linux命令以及其它工具,到底能不能做性能分析调优?
    使用Docker方式部署Gitlab,Gitlab-Runner并使用Gitlab提供的CI/CD功能自动化构建SpringBoot项目
    Docker安装Gitlab
    Docker部署ELK
    Dockerfile中ADD命令详细解读
    使用Gitlab CI/CD功能在本地部署 Spring Boot 项目
    SSH 克隆跟HTTP 克隆地址的区别
    Docker安装Gitlab-runner
    Docker方式安装Jenkins并且插件更改国内源
    使用docker-compose部署SonarQube
  • 原文地址:https://www.cnblogs.com/jkavor/p/7263306.html
Copyright © 2011-2022 走看看