引言
这里介绍RedisCacheManager中一个重要的方法,void setExpires(Map<String, Long> expires),该方法的传入参数是一个Map,Map的key值是@Cacheable(或@CacheEvict或@CachePut)注解的value值,Map的value值是缓存的有效期(单位秒),用于批量设置缓存的有效期。
新写了一个SpringRedisCacheManager,继承自RedisCacheManager,用于对@CacheDuration解析及有效期的设置,代码如下。
结果分析
UserService类上标注的CacheDuration设置有效期是6秒,而方法queryFullNameById上CacheDuration设置的有效期是16秒,最后生效的是16秒。
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