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

引言

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=20
redis.maxIdle=10
redis.maxWaitMillis=3000
redis.minIdle=1
redis.testOnBorrow=true
redis.timeout=10000
redis1.port=26379
redis1.ip=10.7.13.152
redis2.port=26379
redis2.ip=10.7.13.160
redis3.port=26379
redis3.ip=10.7.13.209
redis.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









猜你喜欢

转载自blog.csdn.net/java1993666/article/details/78271584