spring-cache

本文深入解析Spring Cache特性,介绍其配置步骤,包括pom.xml依赖添加、Redis安装配置、spring配置等,以及如何通过@Cacheable、@CachePut、@CacheEvict注解实现缓存管理。探讨缓存机制、多参数key生成器CacheKeyGenerator和注解配置细节。

pringCache是SpringFramework3.1引入的新特性,提供了基于注解的缓存配置方法。

作用

当我们在调用一个缓存方法时会根据相关信息和返回结果作为一个键值对存放在缓存中,等到下次利用同样的参数来调用该方法时将不再执行该方法,而是直接从缓存中获取结果进行返回

 

配置

1、pom.xml文件中添加依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

2、redis

安装和配置redis

redis.key.prefix=prefix
redis.host=host
redis.port=6379
redis.timeout=10000
redis.password=123456
redis.database=1

3、spring配置中配置redis

<cache:annotation-driven cache-manager="redisManager"/>

<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
    <property name="maxIdle" value="${redis.maxIdle}" />
    <property name="maxTotal" value="${redis.maxActive}" />
    <property name="maxWaitMillis" value="${redis.maxWait}" />
    <property name="testOnBorrow" value="${redis.testOnBorrow}" />
</bean>

<bean id="connectionFactory"
    class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
    <property name="poolConfig" ref="poolConfig" />
    <property name="port" value="${redis.port}" />
    <property name="hostName" value="${redis.host}" />
    <property name="password" value="${redis.pass}" />
    <property name="timeout" value="${redis.timeout}" />
</bean>

<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
    <property name="connectionFactory" ref="connectionFactory" />
</bean>

4、配置spring-cache

<bean id="redisManager" class="org.springframework.cache.support.SimpleCacheManager">
        <property name="caches">
            <set>
                <!--用户信息缓存-->
                <bean class="com.core.utils.RedisCache">
                    <property name="redisTemplate" ref="redisTemplate"/>
                    <property name="name" value="UserCache"/>
                    <property name="timeout" value="${redis.expireTime.seconds}"/>
                </bean>
            </set>
        </property>
    </bean>

5、RedisCache

public class RedisCache implements Cache {

    /**
     * 日志对象
     */
    protected Logger logger = LoggerFactory.getLogger(getClass());

    /**
     * redis模版
     */
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 缓存名称
     */
    private String name;

    /**
     * 超时时间(秒)
     */
    private long timeout;

    /**
     * 偏移量(秒)
     */
    private int offset = 0;

    /**
     * 清除系统缓存
     */
    @Override
    public void clear() {

        redisTemplate.execute(new RedisCallback<String>() {
            @Override
            public String doInRedis(RedisConnection connection) throws DataAccessException {
                connection.flushDb();
                return "ok";
            }
        });
    }

    /**
     * 根据key删除缓存
     */
    @Override
    public void evict(Object key) {
        logger.debug("删除" + key);
        if (null != key) {
            byte[][] keys = null;
            if (key.getClass().isArray()) {
                int len = Array.getLength(key);
                if (len <= 0) {
                    logger.info("删除len={}", len);
                    return;
                }

                keys = new byte[len][];
                for (int i = 0; i < len; ++i) {
                    keys[i] = Array.get(key, i).toString().getBytes();
                }
            } else {
                keys = new byte[1][];
                keys[0] = key.toString().getBytes();
            }

            if (logger.isDebugEnabled()) {
                String[] strKeys = new String[keys.length];
                for (int i = 0; i < strKeys.length; ++i) {
                    strKeys[i] = new String(keys[i]);
                }
                logger.debug("删除keys={}", JsonMapper.toJsonString(strKeys));
            }

            final byte[][] redisKeys = keys;
            redisTemplate.execute(new RedisCallback<Long>() {
                @Override
                public Long doInRedis(RedisConnection connection) throws DataAccessException {
                    return connection.del(redisKeys);
                }
            });
        }
    }

    /**
     * 根据key获取缓存
     */
    @Override
    public ValueWrapper get(Object key) {
        logger.debug("获取:" + key);
        final String keyf = key.toString();
        Object object = null;
        object = redisTemplate.execute(new RedisCallback<Object>() {
            @Override
            public Object doInRedis(RedisConnection connection) throws DataAccessException {
                byte[] key = keyf.getBytes();
                byte[] value = connection.get(key);
                if (value == null) {
                    return null;
                }
                return SerializationUtils.deserialize(value);
            }
        });
        ValueWrapper obj = (object != null ? new SimpleValueWrapper(object) : null);
        return obj;
    }

    /**
     * 加入缓存
     */
    @Override
    public void put(Object key, Object value) {
        logger.debug("put key:" + key + " value:" + value);
        final String keyString = key.toString();
        final Object valuef = value;
        final long liveTime = getTimeout();
        redisTemplate.execute(new RedisCallback<Long>() {
            @Override
            public Long doInRedis(RedisConnection connection) throws DataAccessException {
                byte[] keyb = keyString.getBytes();
                byte[] valueb = SerializationUtils.serialize((Serializable) valuef);
                connection.set(keyb, valueb);
                if (liveTime > 0) {
                    connection.expire(keyb, liveTime);
                }
                return 1L;
            }
        });

    }

    @Override
    public <T> T get(Object arg0, Class<T> arg1) {
        return null;
    }

    @Override
    public String getName() {
        return this.name;
    }

    @Override
    public Object getNativeCache() {
        return this.redisTemplate;
    }

    @Override
    public ValueWrapper putIfAbsent(Object arg0, Object arg1) {
        return null;
    }

    public RedisTemplate<String, Object> getRedisTemplate() {
        return redisTemplate;
    }

    public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    public void setName(String name) {
        this.name = name;
    }

    public long getTimeout() {
        if (getOffset() > 0) {
            // 设置偏移量,防止缓存雪崩
            int addOffset = new Random().nextInt(getOffset());

            return (timeout + addOffset);
        }

        return timeout;
    }

    public void setTimeout(long timeout) {
        this.timeout = timeout;
    }

    public int getOffset() {
        return offset;
    }

    public void setOffset(int offset) {
        this.offset = offset;
    }

6、多参数key生成器 CacheKeyGenerator

配置项:

<bean id="defaultKeyGenerator" class="com.stnts.core.utils.CacheKeyGenerator"/>

代码 

public class CacheKeyGenerator implements KeyGenerator {

    /**
     * 日志对象
     */
    public Logger logger = LoggerFactory.getLogger(getClass());

    // custom cache key
    public static final int NO_PARAM_KEY = 0;
    public static final int NULL_PARAM_KEY = 53;

    @Override
    public Object generate(Object target, Method method, Object... params) {
        StringBuilder key = new StringBuilder();               
        key.append(target.getClass().getSimpleName()).append(".")
            .append(method.getName()).append(":");
        if (params.length == 0) {
            return key.append(NO_PARAM_KEY).toString();
        }
        for (Object param : params) {
            if (param == null) {
                logger.warn("input null param for Spring cache, use default key={}",             
                     NULL_PARAM_KEY);
                key.append(NULL_PARAM_KEY);
            } else if (ClassUtils.isPrimitiveArray(param.getClass())) {
                int length = Array.getLength(param);
                for (int i = 0; i < length; i++) {
                    key.append(Array.get(param, i));
                    key.append(',');
                }
            } else if (ClassUtils.isPrimitiveOrWrapper(param.getClass()) 
                    || param instanceof String) {
                key.append(param);
            } else {
                logger.warn("Using an object as a cache key may lead to unexpected results. " +"Either use @Cacheable(key=..) or implement CacheKey. Method is " + target.getClass() + "#" + method.getName());
                key.append(param.hashCode());
            }
            key.append('-');
        }
        String finalKey = key.toString();
        long cacheKeyHash = Hashing.murmur3_128().hashString(finalKey, Charset.defaultCharset()).asLong();
        logger.debug("using cache key={} hashCode={}", finalKey, cacheKeyHash);
        return key.toString();
    }

 

 

7、@Cacheable、@CachePut、@CacheEvict 注释介绍

@Cacheable 作用和配置方法

@Cacheable 的作用主要针对方法配置,能够根据方法的请求参数对其结果进行缓存
@Cacheable 主要的参数
value缓存的名称,在 spring 配置文件中定义,必须指定至少一个例如:
@Cacheable(value=”mycache”) 或者 
@Cacheable(value={”cache1”,”cache2”}
key缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合例如:
@Cacheable(value=”testcache”,key=”#userName”)
condition缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存例如:
@Cacheable(value=”testcache”,condition=”#userName.length()>2”)

@CachePut 作用和配置方法

@CachePut 的作用主要针对方法配置,能够根据方法的请求参数对其结果进行缓存,和 @Cacheable 不同的是,它每次都会触发真实方法的调用
@CachePut 主要的参数
value缓存的名称,在 spring 配置文件中定义,必须指定至少一个例如:
@Cacheable(value=”mycache”) 或者 
@Cacheable(value={”cache1”,”cache2”}
key缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合例如:
@Cacheable(value=”testcache”,key=”#userName”)
condition缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存例如:
@Cacheable(value=”testcache”,condition=”#userName.length()>2”)

@CacheEvict 作用和配置方法

@CachEvict 的作用主要针对方法配置,能够根据一定的条件对缓存进行清空
@CacheEvict 主要的参数
value缓存的名称,在 spring 配置文件中定义,必须指定至少一个例如:
@CachEvict(value=”mycache”) 或者 
@CachEvict(value={”cache1”,”cache2”}
key缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合例如:
@CachEvict(value=”testcache”,key=”#userName”)
condition缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才清空缓存例如:
@CachEvict(value=”testcache”,
condition=”#userName.length()>2”)
allEntries是否清空所有缓存内容,缺省为 false,如果指定为 true,则方法调用后将立即清空所有缓存例如:
@CachEvict(value=”testcache”,allEntries=true)
beforeInvocation是否在方法执行前就清空,缺省为 false,如果指定为 true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存例如:
@CachEvict(value=”testcache”,beforeInvocation=true)

 

spring cache 是基于动态生成的 proxy 代理机制来对方法的调用进行切面,这里关键点是对象的引用问题,如果对象的方法是内部调用(即 this 引用)而不是外部引用,则会导致 proxy 失效,那么我们的切面就失效,也就是说上面定义的各种注释包括 @Cacheable、@CachePut 和 @CacheEvict 都会失效。

 

 

@CacheEvict 的可靠性问题

我们看到,@CacheEvict 注释有一个属性 beforeInvocation,缺省为 false,即缺省情况下,都是在实际的方法执行完成后,才对缓存进行清空操作。期间如果执行方法出现异常,则会导致缓存清空不被执行。用 @CacheEvict 注释提供的 beforeInvocation 属性,将其设置为 true,这样,在方法执行前我们的缓存就被清空了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值