四时宝库

程序员的知识宝库

springboot 2.x入门(四)—springboot 注解缓存实现并增加过期时间


晚上去参加了一个本田的车友会,一水的思域,虽然我不是本田车主啊,但是因为我非常喜欢车,所以有这种聚会都会去凑凑热闹,两厢思域红色是真的帅,哈哈哈,晚上快十点才到家,这篇文章写完都十二点了,磨磨蹭蹭的。好了,废话不多说了进入正题。


今天是springboot2.x整合redis的第二节,基于注解实现缓存,其实spring的CacheManager支持多种缓存组件,像ehcache,jcache,redis,但是redis最火嘛,所以我们就拿它来举例了,另外正是因为支持多种缓存数据库,各数据库的ttl机制又不一样,所以spring 提供的注解并没有ttl的设置参数,在实际应用当中很不方便,所以今天我们就通过复写CacheManager增加注解的ttl配置。


spring 从3.1开始就基于JSR107规范实现了自己的缓存注解,其中包含Cacheable、CachePut、CacheEvict三个常用注解。


首先要想开启缓存管理器,需要在启动类上增加@EnableCaching注解


@EnableCaching
@SpringBootApplication
public class SpringbootRedisApplication {

	public static void main(String[] args) {
		SpringApplication.run(SpringbootRedisApplication.class, args);
	}

}



@Cacheable
该注解可以作用在类或方法上, 在类上表示所有方法返回对象都加入缓存, 如果在方法上则指定该方法返回对象加入缓存。


@CachePut
该注解与 @Cacheable 类似,区别在于它会每次都执行,并且替换缓存中相同名称,一般用来更新缓存。


@CacheEvict
该注解用来清除缓存,它的属性与 @Cacheable 相比多了两个,allEntries 默认 false,根据 key 来清除指定缓存,如果为 true 则表示忽略 key 属性,对所有缓清除。beforeInvocation 属性为 true 时表示,在执行该方法之前执行清除缓存操作。


解释完了,我们进入实操阶段,今天我们只展开讲@Cacheable,会用这个了,另外两个也就会用了。


@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Cacheable {
    @AliasFor("cacheNames")
    String[] value() default {};

    @AliasFor("value")
    String[] cacheNames() default {};

    String key() default "";

    String keyGenerator() default "";

    String cacheManager() default "";

    String cacheResolver() default "";

    String condition() default "";

    String unless() default "";

    boolean sync() default false;
}



以上是@Cacheable注解的参数,常用的是key、value、condition、unless这四个key就是redis键值,value是缓存的命名空间,condition是前置条件,unless是后置条件,过滤数据。在注解的参数中可以使用SpEl表达式进行动态拼接,下面我提供一些测试代码,给大家演示一下。


@Service
public class TestServiceImpl implements TestService{
    
    //常规姿势
    @Override
    @Cacheable(value = {"test"}, key = "#id")
    public String test(Integer id) {
        return "test";
    }

    //带有字符串拼接
    @Override
    @Cacheable(value = {"test1"}, key = "'id-' + #id")
    public String test1(Integer id) {
        return "test";
    }

    //只缓存id>1的
    @Override
    @Cacheable(value = {"test2"}, key = "'hash' + #id",condition = "#id > 1")
    public String test2(Integer id) {
        return "test";
    }

    //结果为null的不缓存
    @Override
    @Cacheable(value = {"test3"}, key = "#id", unless = "#result != null")
    public String test3(Integer id) {
        return null;
    }
}



以上就是Cacheable的使用方法,是不是很方便,但是就是不能设置缓存的失效时间,接下来我们就来实现CacheManager来实现。接下来我们要对上一节的RedisConfig类进行改造,上代码!!!!!!


import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.cache.CacheManager;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.lang.reflect.Method;
import java.time.Duration;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;

@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
        template.setConnectionFactory(redisConnectionFactory);
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        // key采用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        // hash的key也采用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        // value序列化方式采用jackson
        template.setValueSerializer(jackson2JsonRedisSerializer);
        // hash的value序列化方式采用jackson
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        return new RedisCacheManager(
                RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory),
                this.getRedisCacheConfigurationWithTtl(30 * 60), // 默认策略,未配置的 key 会使用这个
                this.getRedisCacheConfigurationMap() // 指定 key 策略
        );
    }

    private Map<String, RedisCacheConfiguration> getRedisCacheConfigurationMap() {
        Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>();
        //SsoCache和BasicDataCache进行过期时间配置
        redisCacheConfigurationMap.put("test", this.getRedisCacheConfigurationWithTtl(24 * 60 * 60));
        return redisCacheConfigurationMap;
    }

    private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(Integer seconds) {
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);

        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
        redisCacheConfiguration = redisCacheConfiguration.serializeValuesWith(
                RedisSerializationContext
                        .SerializationPair
                        .fromSerializer(jackson2JsonRedisSerializer)
        ).entryTtl(Duration.ofSeconds(seconds));

        return redisCacheConfiguration;
    }

    @Bean
    public KeyGenerator wiselyKeyGenerator() {
        return new KeyGenerator() {
            @Override
            public Object generate(Object target, Method method, Object... params) {
                StringBuilder sb = new StringBuilder();
                sb.append(target.getClass().getName());
                sb.append("." + method.getName());
                if (params == null || params.length == 0 || params[0] == null) {
                    return null;
                }
                String join = String.join("&", Arrays.stream(params).map(Object::toString).collect(Collectors.toList()));
                String format = String.format("%s{%s}", sb.toString(), join);
                return format;
            }
        };
    }

}



上面代码中,我们在CacheManager中进行了缓存时间的配置,对统一命名空间的缓存进行了统一的失效时间处理。我们在使用中,只需要将注解中的value配置在RedisConfig类中,就完成了失效时间的配置。

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言
    友情链接