zoukankan      html  css  js  c++  java
  • SpringCache整合Redis实现自定义缓存时间

    Spring Cache简介

    Spring3.1开始引入了的基于注释(annotation)的缓存(cache)技术,它本质上不是一个具体的缓存实现方案,而是一个对缓存使用的抽象,通过在既有代码中添加注解,即能够达到缓存方法的返回对象的效果。

    Spring 的缓存技术还具备相当的灵活性,不仅能够使用 SpEL 来定义缓存的 key 和各种 condition,还提供开箱即用的缓存临时存储方案,也支持和主流的专业缓存例如 Redis 集成。

    @Cacheable

    这个用的比较多,用在查询方法上,先从缓存中读取,如果缓存不存在再调用该方法获取数据,然后把返回的数据添加到缓存中去。

    @Cacheable(value = "userCache", key = "targetClass + '.' + methodName + '.' + "#userid")
    public User getEntity(long userid) {
      // 业务代码省略
    }
    

    @CacheEvict

    清空缓存

    @CacheEvict(value = "userCache", key = "targetClass + '.' + methodName + '.' + "#userid")
    public boolean delete(long userid) {
      // 业务代码省略
    }
    

    @CachePut

    这个注释可以确保方法被执行,同时方法的返回值也被记录到缓存中,实现缓存与数据库的同步更新。

    @CachePut(value = "userCache", key = "targetClass + '.' + methodName + '.' + "#user.getUserid")
    public User save(User user) {
      // 业务代码省略
    }
    

    注:每个注解都有多个参数,这里不一一列出,建议进入源码查看注释。

    缺点

    虽然Spring Cache用起来很方便的, 但不支持设置动态过期时间,这里需要重写RedisCacheManager的一些方法。

    示例

    这里用的spring对redis的封装spring-data-redis,主要是对RedisCacheManager做一个二次封装。

    导包

    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>2.9.0</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring-data-redis</artifactId>
        <version>1.8.4.RELEASE</version>
    </dependency>
    

    重写 RedisCacheManager

    package com.demo.cache;
    
    import java.util.Objects;
    import java.util.regex.Pattern;
    
    import javax.script.ScriptEngine;
    import javax.script.ScriptEngineManager;
    import javax.script.ScriptException;
    
    import org.apache.commons.lang3.math.NumberUtils;
    import org.springframework.data.redis.cache.RedisCache;
    import org.springframework.data.redis.cache.RedisCacheManager;
    import org.springframework.data.redis.core.RedisOperations;
    
    import lombok.extern.log4j.Log4j2;
    
    /**
     * 重写redis缓存管理器
     * <p>
     * 重写 RedisCacheManager createCache 方法
     * <p>
     * 在缓存名字上添加过期时间表达式 如:cachename#60*60
     * @author czk
     */
    @Log4j2
    public class ExtendedRedisCacheManager extends RedisCacheManager {
    
    	private static final ScriptEngine scriptEngine = new ScriptEngineManager().getEngineByName("JavaScript");
    
    	private static final Pattern pattern = Pattern.compile("[+\-*/%]");
    	
    	/**
    	 * 分隔符
    	 */
    	private char separator = '#';
    
    	public ExtendedRedisCacheManager(@SuppressWarnings("rawtypes") RedisOperations redisOperations) {
    	    super(redisOperations);
    	}
    
    	@Override
    	@SuppressWarnings("unchecked")
    	protected RedisCache createCache(String cacheName) {
    	    // 获取默认时间
    	    long expiration = computeExpiration(cacheName);		
    	    int index = cacheName.indexOf(this.getSeparator());
    	    if (index > 0) {
    	        expiration = getExpiration(cacheName, index, expiration);
    	    }
    	    return new RedisCache(cacheName, (isUsePrefix() ? getCachePrefix().prefix(cacheName) : null),
    		        getRedisOperations(), expiration);
    	}
    
    	/**
    	 * 计算缓存时间
    	 * @param name 缓存名字 cache#60*60
    	 * @param separatorIndex 分隔符位置
    	 * @param defalutExp 默认缓存时间
    	 * @return
    	 */
    	protected long getExpiration(final String name, final int separatorIndex, final long defalutExp) {
    	    Long expiration = null;
    	    String expirationAsString = name.substring(separatorIndex + 1);
    	    try {
    	        if (pattern.matcher(expirationAsString).find()) {
    	            expiration = NumberUtils.toLong(scriptEngine.eval(expirationAsString).toString(), defalutExp);
    	        } else {
    	            expiration = NumberUtils.toLong(expirationAsString, defalutExp);
    	        }
    	    } catch (ScriptException e) {
    	        log.error("缓存时间转换错误:{},异常:{}", name, e.getMessage());
    	    }
    	    return Objects.nonNull(expiration) ? expiration.longValue() : defalutExp;
    	}
    
    	public char getSeparator() {
    	    return separator;
    	}
    
    	public void setSeparator(char separator) {
    	    this.separator = separator;
    	}
    }
    

    spring-redis.xml配置文件

    <?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:p="http://www.springframework.org/schema/p"
    	xmlns:context="http://www.springframework.org/schema/context"
    	xmlns:cache="http://www.springframework.org/schema/cache"
    	xsi:schemaLocation="http://www.springframework.org/schema/beans      
                            http://www.springframework.org/schema/beans/spring-beans-4.3.xsd      
                            http://www.springframework.org/schema/context      
                            http://www.springframework.org/schema/context/spring-context-4.3.xsd      
                            http://www.springframework.org/schema/cache   
                            http://www.springframework.org/schema/cache/spring-cache-4.3.xsd">
    	
    	<context:property-placeholder location="classpath:redis.properties" />    
    	
    	<!-- 启用缓存注解功能,否则注解不会生效 -->
    	<cache:annotation-driven cache-manager="cacheManager" />
    
    	<!-- redis 相关配置 -->
    	<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
    	    <property name="maxIdle" value="${redis.maxIdle}" />
    	    <property name="maxWaitMillis" value="${redis.maxWait}" />
    	    <property name="testOnBorrow" value="${redis.testOnBorrow}" />
    	</bean>
    
    	<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
    	    p:host-name="${redis.host}" p:port="${redis.port}" p:password="${redis.password}"
    	    p:database="${redis.database}" p:timeout="${redis.timeout}"
    	    p:pool-config-ref="poolConfig" />
    
    	<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
    	    <property name="connectionFactory" ref="jedisConnectionFactory" />
    	    <!--对key的序列化器 -->
    	    <property name="keySerializer">
    	        <bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />
    	    </property>
    	    <!--是对value的列化器 默认:JdkSerializationRedisSerializer -->
    	    <property name="valueSerializer">
    	        <bean class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer" />
    	    </property>	
    	</bean>
    
    	<!-- 扩展RedisCacheManager -->
    	<bean id="cacheManager" class="com.demo.cache.ExtendedRedisCacheManager">
    	    <constructor-arg ref="redisTemplate" />
    	    <!-- 是否使用前缀 默认: -->
    	    <property name="usePrefix" value="true" />
    	    <!-- 默认有效期1h (60 * 60 = 3600秒) -->
    	    <property name="defaultExpiration" value="3600" />
    	</bean>
    
    </beans>
    

    redis.properties

    #redis 缓存配置
    redis.host=127.0.0.1
    redis.port=6379
    redis.password=
    redis.database=0
    # 控制一个pool最多有多少个状态为idle(空闲的)的jedis实例  
    redis.maxIdle=300
    redis.maxctive=6000
    # 表示当borrow(引入)一个jedis实例时,最大的等待时间,如果超过等待时间(毫秒),则直接抛出JedisConnectionException; 
    redis.maxWait=10000
    #在borrow一个jedis实例时,是否提前进行validate操作;如果为true,则得到的jedis实例均是可用的    
    redis.testOnBorrow=true
    #读超时时间
    redis.timeout=30000
    

    注: Spring Cache是采用AOP来管理缓存,所有通过this调用的方法多不会触发缓存,key采用的是StringRedisSerializer序列化,所有key必须为String类型。

    @Cacheable指定缓存5分钟

    @Cacheable(value = "userCache#60*5", key = "targetClass + '.' + methodName + '.' + "#userid")
    public User getEntity(long userid) {
      // 业务代码省略
    }
    
    所有的进步都是不稳定, 一个问题解决了又不得不面对一个新的问题。
  • 相关阅读:
    【雕爷学编程】Arduino动手做(77)---模拟灰度传感器
    偶然发现Arduino Uno的 D0-D13与A0-A5端均可以正常使用舵机
    【雕爷学编程】Arduino动手做(76)---2路光耦继电器模块
    【雕爷学编程】Arduino动手做(75)---TTL转RS-485模块
    QT QStringList的用法
    C++类的应用、查看点和圆的关系、1、在圆上 2、在圆内 3、在圆外
    uipath当前是一年的多少周
    uipath 把excel转成pdf
    python下面的yield(生成器、迭代器)的使用
    echarts 报错 should not be called during main process
  • 原文地址:https://www.cnblogs.com/nyvi/p/8613065.html
Copyright © 2011-2022 走看看