自定义注解设置Redis缓存有效期的正确姿势
  引言
 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.***.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> ***Expires = 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, ***Expires);
        }
        //设置有效期
        super.setExpires(***Expires);
    }
    private void addCacheExpires(final Class clazz, final Map<String, Long> ***Expires) {
        ReflectionUtils.doWithMethods(clazz, new ReflectionUtils.MethodCallback() {
            @Override
            public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
                ReflectionUtils.makeAccessible(method);
                CacheDuration ***Duration = findCacheDuration(clazz, method);
                Cacheable ***able = findAnnotation(method, Cacheable.class);
                CacheConfig ***Config = findAnnotation(clazz, CacheConfig.class);
                Set<String> ***Names = findCacheNames(***Config, ***able);
                for (String ***Name : ***Names) {
                    ***Expires.put(***Name, ***Duration.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 ***Config, Cacheable ***able) {
        return isEmpty(***able.value()) ?
                newHashSet(***Config.***Names()) : newHashSet(***able.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:***="http://www.springframework.org/schema/***" 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/*** http://www.springframework.org/schema/***/spring-***.xsd"> <context:component-scan base-package="redis.***"/> <context:annotation-config/> <***:annotation-driven ***-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.***.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秒。 
    
 查看7道真题和解析
查看7道真题和解析 字节跳动公司福利 1313人发布
字节跳动公司福利 1313人发布