springboot的一些新特性及用法(springboot2.x入门四)
springboot的一些新特性及用法(springboot2.x入门四)@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 "
晚上去参加了一个本田的车友会,一水的思域,虽然我不是本田车主啊,但是因为我非常喜欢车,所以有这种聚会都会去凑凑热闹,两厢思域红色是真的帅,哈哈哈,晚上快十点才到家,这篇文章写完都十二点了,磨磨蹭蹭的。好了,废话不多说了进入正题。
今天是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类中,就完成了失效时间的配置。




