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

引言

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

Redis缓存

如何使用Redis实现数据缓存,请参考上篇《使用Spring-Data-Redis实现数据缓存》。

工具类介绍

1.JedisPoolConfig

jedis连接池配置类,位于jedis包中,用于配置连接池中jedis连接数的个数、是否阻塞、逐出策略等。示例配置如下所示。

<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig"> <!-- maxIdle最大空闲连接数 --> <property name="maxIdle" value="${redis.maxIdle}"/> <!-- maxTotal最大连接数 --> <property name="maxTotal" value="${redis.maxActive}"/> <!-- maxWaitMillis获取连接时的最大等待毫秒数,小于零表示阻塞不确定的时间,默认为-1 --> <property name="maxWaitMillis" value="${redis.maxWait}"/> <!-- testOnBorrow在获取连接的时是否检查有效性 --> <property name="testOnBorrow" value="${redis.testOnBorrow}"/> </bean>

2.JedisConnectionFactory

jedis实例的创建工厂,基于连接池创建jedis实例,位于spring-data-redis包中。示例配置如下所示。

<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"> <!-- hostName Redis主机名,默认是localhost --> <property name="hostName" value="${redis.host}"/> <!-- port Redis提供服务的端口--> <property name="port" value="${redis.port}"/> <!-- password Redis认证密码 --> <property name="password" value="${redis.pass}"/> <!-- database 连接工厂使用到的数据库索引,默认是0 --> <property name="database" value="${redis.dbIndex}"/> <!-- poolConfig 连接池配置 --> <property name="poolConfig" ref="poolConfig"/> </bean>

3.RedisTemplate

RedisTemplate可以从JedisConnectionFactory中获取jedis实例,封装了jedis的操作,位于spring-data-redis包中,让使用者无需关心连接的获取及释放,集中关注业务处理。示例配置如下所示。

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

4.RedisCacheManager

使用RedisTemplate对Redis缓存进行管理,位于spring-data-redis包中。示例配置如下所示。

<bean id="redisCacheManager" class="org.springframework.data.redis.cache.RedisCacheManager"> <constructor-arg name="redisOperations" ref="redisTemplate"/> <property name="defaultExpiration" value="${redis.expiration}"/> </bean>

这里介绍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解析及有效期的设置,代码如下。

public class SpringRedisCacheManager extends RedisCacheManager implements ApplicationContextAware, InitializingBean { private ApplicationContext applicationContext; public SpringRedisCacheManager(RedisOperations redisOperations) { super(redisOperations); } @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()); } }

Spring的xml配置

完整配置redisCacheContext.xml如下所示。

<?xml version="1.0" encoding="UTF-8" standalone="no"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" 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.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd"> <context:component-scan base-package="redis.cache"/> <context:annotation-config/> <cache:annotation-driven cache-manager="redisCacheManager"/> <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations"> <list> <value>classpath:redis.properties</value> </list> </property> </bean> <!-- 配置JedisPoolConfig实例 --> <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig"> <!-- maxIdle最大空闲连接数 --> <property name="maxIdle" value="${redis.maxIdle}"/> <!-- maxTotal最大连接数 --> <property name="maxTotal" value="${redis.maxActive}"/> <!-- maxWaitMillis获取连接时的最大等待毫秒数,小于零表示阻塞不确定的时间,默认为-1 --> <property name="maxWaitMillis" value="${redis.maxWait}"/> <!-- testOnBorrow在获取连接的时是否检查有效性 --> <property name="testOnBorrow" value="${redis.testOnBorrow}"/> </bean> <!-- 配置JedisConnectionFactory --> <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"> <!-- hostName Redis主机名,默认是localhost --> <property name="hostName" value="${redis.host}"/> <!-- port Redis提供服务的端口--> <property name="port" value="${redis.port}"/> <!-- password Redis认证密码 --> <property name="password" value="${redis.pass}"/> <!-- database 连接工厂使用到的数据库索引,默认是0 --> <property name="database" value="${redis.dbIndex}"/> <!-- poolConfig 连接池配置 --> <property name="poolConfig" ref="poolConfig"/> </bean> <!-- 配置RedisTemplate --> <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"> <property name="connectionFactory" ref="jedisConnectionFactory"/> </bean> <!-- 配置RedisCacheManager --> <bean id="redisCacheManager" class="redis.cache.SpringRedisCacheManager"> <constructor-arg name="redisOperations" ref="redisTemplate"/> <property name="defaultExpiration" value="${redis.expiration}"/> </bean> </beans>

Redis连接配置

完整配置如下。

redis.host=127.0.0.1 redis.port=6379 redis.pass= redis.dbIndex=0 redis.expiration=3000 redis.maxIdle=300 redis.maxActive=600 redis.maxWait=1000 redis.testOnBorrow=true

测试代码

@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秒。

原文发布于微信公众号 - JavaQ(Java-Q)

原文发表时间:2016-09-15

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏JavaEdge

Memcached的扩容源码分析

2165
来自专栏java 成神之路

RocketMQ 底层通信机制 源码分析

RocketMQ 底层通讯是使用Netty来实现的。 下面我们通过源码分析下RocketMQ是怎么利用Netty进行通讯的。

1682
来自专栏Hadoop实操

如何开发HBase Endpoint类型的Coprocessor以及部署使用

3532
来自专栏Java编程技术

使用数据库悲观锁实现不可重入的分布式锁

在同一个jvm进程中时,可以使用JUC提供的一些锁来解决多个线程竞争同一个共享资源时候的线程安全问题,但是当多个不同机器上的不同jvm进程共同竞争同一个共享资源...

771
来自专栏智能大石头

实体处理模块IEntityModule

在2015年7月16日,XCode新增了实体处理模块IEntityModule,用于拦截实体对象添删改操作。 该接口参考IHttpModule设计理念,横切在实...

19910
来自专栏大内老A

了解ASP.NET MVC几种ActionResult的本质:EmptyResult & ContentResult

在之前的两篇文章(《EmptyResult & ContentResult》和《FileResult》)我们剖析了EmptyResult、ContentResu...

2695
来自专栏分布式系统进阶

Influxdb的Meta data分析

Influxdb定义了一个Service:Precreator Serivec(services/precreator/service.go),实现比较简单,周...

1302
来自专栏游戏开发那些事

【游戏开发】Excel表格批量转换成lua的转表工具

  在上篇博客《【游戏开发】Excel表格批量转换成CSV的小工具》 中,我们介绍了如何将策划提供的Excel表格转换为轻便的CSV文件供开发人员使用。实际在U...

2593
来自专栏分布式系统和大数据处理

.Net自定义应用程序配置

几乎所有的应用程序都离不开配置,有时候我们会将配置信息存在数据库中(例如大家可能常会见到名为Config这样的表);更多时候,我们会将配置写在Web.confi...

943
来自专栏大内老A

ASP.NET Core应用针对静态文件请求的处理[3]: StaticFileMiddleware中间件如何处理针对文件请求

我们通过《以Web的形式发布静态文件》和《条件请求与区间请求》中的实例演示,以及上面针对条件请求和区间请求的介绍,从提供的功能和特性的角度对这个名为Static...

3295

扫码关注云+社区

领取腾讯云代金券