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

0?wx_fmt=gif

引言

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

  


0?wx_fmt=gif
0?wx_fmt=jpeg

0

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值