自定义注解设置缓存有效期的正确姿势

来源:互联网 发布:趋势科技杀毒软件 知乎 编辑:程序博客网 时间:2024/05/21 04:21
引言

redis缓存的有效期可以通过xml配置文件设置(默认有效期),也可以通过编码的方式手动去设置,但是这两种方式都存在缺陷。xml方式设置的是全局的默认有效期,虽然灵活,但不能给某个缓存设置单独的有效期;硬编码方式虽然可以给不同的缓存设置单独的有效期,但是管理上不够灵活。Spring提供的Cache相关注解中并没有提供有效期的配置参数,so,自定义注解实现缓存有效期的灵活设置诞生了,具体源码前往github下载。

redis-queue.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"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd    "><!-- 配置一个高可用环境的哨兵模式的redis环境 -->     <bean id="redisSentinelConfiguration"        class="org.springframework.data.redis.connection.RedisSentinelConfiguration">        <property name="master">            <bean class="org.springframework.data.redis.connection.RedisNode">                <property name="name" value="mymaster"/>            </bean>        </property>        <property name="sentinels">            <set>                <bean class="org.springframework.data.redis.connection.RedisNode">                    <constructor-arg name="host" value="${redis1.ip}"/>                    <constructor-arg name="port" value="${redis1.port}"/>                                       </bean>                <bean class="org.springframework.data.redis.connection.RedisNode">                    <constructor-arg name="host" value="${redis2.ip}"/>                    <constructor-arg name="port" value="${redis2.port}"/>                               </bean>                <bean class="org.springframework.data.redis.connection.RedisNode">                                       <constructor-arg name="host" value="${redis3.ip}"/>                    <constructor-arg name="port" value="${redis3.port}"/>                                   </bean>            </set>        </property>   </bean>              <!-- redis 连接池相关配置--><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="minIdle" value="${redis.minIdle}"/><property name="testOnBorrow" value="${redis.testOnBorrow}" /></bean><!-- JedisConnectionFactory 相关配置--><bean id="jedisFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"><property name="poolConfig" ><ref bean="jedisPoolConfig"/></property><constructor-arg name="sentinelConfig" ref="redisSentinelConfiguration"/> <property name="timeout" value="${redis.timeout}"/></bean><bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"><property name="connectionFactory"><ref bean="jedisFactory"/></property></bean><!-- 使用自定义的SpringRedisCacheManager类  --><bean id="redisCacheManager" class="tf56.skynet.annotation.SpringRedisCacheManager"><!--为默认的构造方法设置值--><constructor-arg name="redisTemplate" ref="redisTemplate"/><!--设置redi缓存默认的过期时间--><property name="defaultExpiration" value="${redis.expiration}"/></bean>        </beans>

这里介绍RedisCacheManager中一个重要的方法,void setExpires(Map<String, Long> expires),该方法的传入参数是一个Map,Map的key值是@Cacheable(或@CacheEvict或@CachePut)注解的value值,Map的value值是缓存的有效期(单位秒),用于批量设置缓存的有效期。

自定义注解
直接贴代码了,如下。

@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.METHOD, ElementType.TYPE})public @interface CacheDuration {    //Sets the expire time (in seconds).    public long duration() default 60;}
使用@CacheDuration

@Service("userService")@CacheDuration(duration = 6)public class UserService {    @Cacheable(value = "User", key = "'UserId_' + #id", condition = "#id<=110")    @CacheDuration(duration = 16)    public String queryFullNameById(long id) {        System.out.println("execute queryFullNameById method");        return "ZhangSanFeng";    }}
新RedisCacheManager
新写了一个SpringRedisCacheManager,继承自RedisCacheManager,用于对@CacheDuration解析及有效期的设置,代码如下。
package tf56.skynet.annotation;import org.springframework.beans.BeansException;import org.springframework.beans.factory.InitializingBean;import org.springframework.cache.annotation.CacheConfig;import org.springframework.cache.annotation.Cacheable;import org.springframework.context.ApplicationContext;import org.springframework.context.ApplicationContextAware;import org.springframework.data.redis.cache.RedisCacheManager;import org.springframework.data.redis.core.RedisOperations;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.stereotype.Service;import org.springframework.util.ReflectionUtils;import java.lang.reflect.Method;import java.util.HashMap;import java.util.Map;import java.util.Set;import static com.google.common.collect.Sets.newHashSet;import static org.springframework.core.annotation.AnnotationUtils.findAnnotation;import static org.springframework.util.StringUtils.isEmpty;/** * Created by Administrator on 2017/10/18. * 新写了一个SpringRedisCacheManager,继承自RedisCacheManager, * 用于对@CacheDuration解析及有效期的设置 */public class SpringRedisCacheManager extends RedisCacheManager implements ApplicationContextAware, InitializingBean {    private ApplicationContext applicationContext;    public SpringRedisCacheManager(RedisTemplate redisTemplate) {        super(redisTemplate);    }    @Override    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {        this.applicationContext = applicationContext;    }    @Override    public void afterPropertiesSet() {        parseCacheDuration(applicationContext);    }    private void parseCacheDuration(ApplicationContext applicationContext) {        final Map<String, Long> cacheExpires = new HashMap<>();        String[] beanNames = applicationContext.getBeanNamesForType(Object.class);        for (String beanName : beanNames) {            final Class clazz = applicationContext.getType(beanName);            Service service = findAnnotation(clazz, Service.class);            if (null == service) {                continue;            }            addCacheExpires(clazz, cacheExpires);        }        //设置有效期        super.setExpires(cacheExpires);    }    private void addCacheExpires(final Class clazz, final Map<String, Long> cacheExpires) {        ReflectionUtils.doWithMethods(clazz, new ReflectionUtils.MethodCallback() {            @Override            public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {                ReflectionUtils.makeAccessible(method);                CacheDuration cacheDuration = findCacheDuration(clazz, method);                Cacheable cacheable = findAnnotation(method, Cacheable.class);                CacheConfig cacheConfig = findAnnotation(clazz, CacheConfig.class);                Set<String> cacheNames = findCacheNames(cacheConfig, cacheable);                for (String cacheName : cacheNames) {                    cacheExpires.put(cacheName, cacheDuration.duration());                }            }        }, new ReflectionUtils.MethodFilter() {            @Override            public boolean matches(Method method) {                return null != findAnnotation(method, Cacheable.class);            }        });    }    /**     * CacheDuration标注的有效期,优先使用方法上标注的有效期     * @param clazz     * @param method     * @return     */    private CacheDuration findCacheDuration(Class clazz, Method method) {        CacheDuration methodCacheDuration = findAnnotation(method, CacheDuration.class);        if (null != methodCacheDuration) {            return methodCacheDuration;        }        CacheDuration classCacheDuration = findAnnotation(clazz, CacheDuration.class);        if (null != classCacheDuration) {            return classCacheDuration;        }        throw new IllegalStateException("No CacheDuration config on Class " + clazz.getName() + " and method " + method.toString());    }    private Set<String> findCacheNames(CacheConfig cacheConfig, Cacheable cacheable) {        return isEmpty(cacheable.value()) ?                newHashSet(cacheConfig.cacheNames()) : newHashSet(cacheable.value());    }}
redis.properties配置如下:

redis.maxTotal=20redis.maxIdle=10redis.maxWaitMillis=3000redis.minIdle=1redis.testOnBorrow=trueredis.timeout=10000redis1.port=26379redis1.ip=10.7.13.152redis2.port=26379redis2.ip=10.7.13.160redis3.port=26379redis3.ip=10.7.13.209redis.expiration=3000
最后将redis.properties 、redis-queue.xml 配置文件配置加载到项目主配置文件xxx-servlet.xml中  部分配置如下所示,对应着配置即可:

<?xml version="1.0" encoding="UTF-8"?><!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure --><beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"       xmlns="http://www.springframework.org/schema/beans" xmlns:mvc="http://www.springframework.org/schema/mvc"       xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context"       xmlns:cache="http://www.springframework.org/schema/cache"       xmlns:util="http://www.springframework.org/schema/util" xmlns:aop="http://www.springframework.org/schema/aop"       xsi:schemaLocation="    http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd        http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd         http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd        http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.1.xsd">    <bean id="handlerMapping"          class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">        <property name="order" value="1"/>    </bean>    <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>    <!-- 解决@responseBody返回乱码问题 -->    <bean            class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">        <property name="messageConverters">            <list>                <bean                        class="org.springframework.http.converter.ByteArrayHttpMessageConverter"/>                <bean                        class="org.springframework.http.converter.StringHttpMessageConverter">                    <property name="supportedMediaTypes">                        <list>                            <value>text/plain;charset=UTF-8</value>                        </list>                    </property>                </bean>                <!-- <bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">                    <property name="supportedMediaTypes" value="application/json" /> </bean> -->                <bean                        class="org.springframework.http.converter.ResourceHttpMessageConverter"/>                <bean                        class="org.springframework.http.converter.xml.SourceHttpMessageConverter"/>                <bean                        class="org.springframework.http.converter.xml.XmlAwareFormHttpMessageConverter"/>                <bean                        class="org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter"/>            </list>        </property>        <property name="webBindingInitializer">            <bean                    class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer">                <property name="validator" ref="validator"/>                <!-- <property name="conversionService">                    <bean                        class="org.springframework.format.support.FormattingConversionServiceFactoryBean"></bean>                </property> -->            </bean>        </property>    </bean>    <mvc:default-servlet-handler/>    <!-- 指定redisCacheManager-->    <cache:annotation-driven cache-manager="redisCacheManager"/>    <bean id="configBean"          class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">        <property name="locations">            <list>                <value>classpath:config/redis.properties</value>            </list>        </property>    </bean>    <context:component-scan base-package="tf56.skynet"/>    <import resource="classpath:/config/redis-queue.xml"/>    <context:annotation-config/>    <mvc:annotation-driven/></beans>

编写service实现类:

@Service("userService")@CacheDuration(duration = 6)public class UserService {    @Cacheable(value = "User", key = "'UserId_' + #id", condition = "#id<=110")    @CacheDuration(duration = 16)    public String queryFullNameById(long id) {        System.out.println("execute queryFullNameById method");        return "ZhangSanFeng";    }}
测试代码:

@Test    public void testRedisCacheManager() {        ApplicationContext context = new ClassPathXmlApplicationContext("redisCacheContext.xml");        UserService userService = (UserService) context.getBean("userService");        RedisTemplate redisTemplate = (RedisTemplate) context.getBean("redisTemplate");        System.out.println("第一次执行查询:" + userService.queryFullNameById(100L));        System.out.println("----------------------------------");        System.out.println("第二次执行查询:" + userService.queryFullNameById(100L));        System.out.println("----------------------------------");        System.out.println("UserId_100有效期(单位秒):" + redisTemplate.getExpire("UserId_100", TimeUnit.SECONDS));        System.out.println("----------------------------------");        try {            Thread.sleep(3000);            System.out.println("主线程休眠3秒后");            System.out.println("----------------------------------");        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("UserId_100有效期(单位秒):" + redisTemplate.getExpire("UserId_100", TimeUnit.SECONDS));        System.out.println("----------------------------------");        System.out.println("第三次执行查询:" + userService.queryFullNameById(100l));    }
测试结果:

execute queryFullNameById method第一次执行查询:ZhangSanFeng----------------------------------第二次执行查询:ZhangSanFeng----------------------------------UserId_100有效期(单位秒):15----------------------------------主线程休眠3秒后----------------------------------UserId_100有效期(单位秒):12----------------------------------第三次执行查询:ZhangSanFeng

结果分析
UserService类上标注的CacheDuration设置有效期是6秒,而方法queryFullNameById上CacheDuration设置的有效期是16秒,最后生效的是16秒。


参考原文:

http://www.jianshu.com/p/2633fb37862c









原创粉丝点击