SpringBoot+SpringCache+Redis整合,自定义KeyGenerator生成器,@Cacheable设置单个key的缓存时间

目录

一、SpringBoot集成SpringCache

  1.集成SpringCache,自需要在pom中加入以下依赖:

  2.需要在application.properties中配置属性:

  3.使用@EnableCaching注解开启缓存

  4.简单介绍常用注解

二、自定义KeyGenerator

1.实现KeyGenerator接口generate方法

2.使用MyKeyGenerator自定义的key生成策略 

三、设置单个key的缓存时间

1.创建MyRedisCacheWriter实现RedisCacheWriter接口

2.配置实现类到CacheManager

3.使用


一、SpringBoot集成SpringCache

    SpringBoot本身提供了一个基于 ConcurrentHashMap缓存机制,也集成了 EhCache2.x、JCache CJSR-107、Couchbase、Redis等。 SpringBoot应用通过注解的方式使用统一的缓存,只需在方法上使用缓存注解即可,其缓存的具体实现依赖于选择的目标缓存管理器。因为SpringCache的注解是采用Spring Aop来动态代理的,同个类中的调用无法生效本文采用redis缓存机制。

  1.集成SpringCache,自需要在pom中加入以下依赖:


    org.springframework.boot
    spring-boot-starter-cache

  2.需要在application.properties中配置属性:

spring.cache.type=redis

其他值含义:

     Simple:基于ConcurrentHashMap 实现的缓存,适合单体应用或者开发环境使用;

     none:关闭缓存;

     Generic:用户自定义缓存实现,用户需要实现org.sringframework.cache.CacheManager 的实现
 

  3.使用@EnableCaching注解开启缓存

@SpringBootApplication
@EnableCaching
public class WebApplication {

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

  4.简单介绍常用注解

      
@Cacheable ,作用在方法上,触发缓存读取操作
@CacheEvict ,作用在方法上,触发缓存失效操作
@CachePut ,作用在方法上,触发缓存更新操作。
@Cache ,作用在方法上,综合上面的各种操作,在有些场景下 ,调用业务会触发多种缓存操作。
@CacheConfig ,在类上设置当前缓存 一些公共设置。

二、自定义KeyGenerator

     背景:在做缓存时,有的特定的情况下SpringBoot自带的SimpleKeyGenerator和key生成器不能满足需求,例如在参数为map时,要求map里的所有key-value组合作为一个key来做缓存;

     这里是以map为入参时为例,具体按照自己的实际情况来定

1.实现KeyGenerator接口generate方法

package com.ztxy.module.log.controller.conf;

import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.cache.interceptor.SimpleKey;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Map;

/**
 * @author jie
 * @Description:
 * @date 2020/1/6 17:04
 */
@Component
public class MyKeyGenerator implements KeyGenerator {

    @Override
    public Object generate(Object target, Method method, Object... params) {
        if (params.length == 0) {
            return SimpleKey.EMPTY;
        }
        Object param = params[0];
        // 参数为map自定义key=类名+方法名+map的key-value值
        if (param instanceof Map) {
            StringBuilder builder = new StringBuilder();
            // 分隔符
            String sp = ".";
            builder.append(target.getClass().getSimpleName()).append(sp);
            builder.append(method.getName()).append(sp);
            Map map = (Map) param;
            if (map.isEmpty()) {
                return builder.toString();
            }
            for (String key : map.keySet()) {
                builder.append(key).append("-").append(map.get(key)).append(sp);
            }
            return builder.toString();
        }
        return new SimpleKey(params);
    }
}

2.使用MyKeyGenerator自定义的key生成策略 


    @Cacheable(cacheNames = "ids_cache",keyGenerator = "myKeyGenerator")
    public List
    selectList(Map map) {
        List list = sysLoginRegisterLogMapper.selectListByMap(map);
        return list;
    }

三、设置单个key的缓存时间

         SpringCache对redis进行处理,用的DefaultRedisCacheWriter,但是此类是没有被修饰符public所修饰的,所以只能包类使用,不能自己继承,也不能被重写方法,所以只能自己构造CacheManager,然后配置RedisCacheWriter。

1.创建MyRedisCacheWriter实现RedisCacheWriter接口

       put方法就是存入redis,get方法从redis中获取。我们只需要修改put方法设置过期时间即可,其他的copy的DefaultRedisCacheWriter的实现。如果需实现其他功能,修改对应其他方法即可。

package com.ztxy.module.log.controller.conf;

import org.springframework.dao.PessimisticLockingFailureException;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;

import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Collections;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;

/**
 * @author jie
 * @Description:
 * @date 2020/1/6 17:44
 */
public class MyRedisCacheWriter implements RedisCacheWriter {
    private final RedisConnectionFactory connectionFactory;
    private final Duration sleepTime;
    final static String REDIS_EXPIRE_TIME_KEY = "#key_expire_time";

    MyRedisCacheWriter(RedisConnectionFactory connectionFactory) {
        this(connectionFactory, Duration.ZERO);
    }

    MyRedisCacheWriter(RedisConnectionFactory connectionFactory, Duration sleepTime) {
        Assert.notNull(connectionFactory, "ConnectionFactory must not be null!");
        Assert.notNull(sleepTime, "SleepTime must not be null!");
        this.connectionFactory = connectionFactory;
        this.sleepTime = sleepTime;
    }

    public void put(String name, byte[] key, byte[] value, @Nullable Duration ttl) {
        Assert.notNull(name, "Name must not be null!");
        Assert.notNull(key, "Key must not be null!");
        Assert.notNull(value, "Value must not be null!");
        this.execute(name, (connection) -> {
            //当设置了过期时间,则修改取出
            //@Cacheable(cacheNames = "ids_cache#key_expire_time=120",keyGenerator = "myKeyGenerator")
            //name 对应 cacheNames 
            //key 对应 MyKeyGenerator 自定义生成的key
            int index = name.lastIndexOf(REDIS_EXPIRE_TIME_KEY);
            if(index > 0){
                String expireTime = name.substring(index + 1 + REDIS_EXPIRE_TIME_KEY.length());
                connection.set(key, value, Expiration.from(Long.valueOf(expireTime), TimeUnit.SECONDS), RedisStringCommands.SetOption.upsert());
            }else if (shouldExpireWithin(ttl)) {
                connection.set(key, value, Expiration.from(ttl.toMillis(), TimeUnit.MILLISECONDS), RedisStringCommands.SetOption.upsert());
            } else {
                connection.set(key, value);
            }

            return "OK";
        });
    }

    public byte[] get(String name, byte[] key) {
        Assert.notNull(name, "Name must not be null!");
        Assert.notNull(key, "Key must not be null!");
        String k = new String(key);
        return (byte[])this.execute(name, (connection) -> {
            return connection.get(key);
        });
    }

    public byte[] putIfAbsent(String name, byte[] key, byte[] value, @Nullable Duration ttl) {
        Assert.notNull(name, "Name must not be null!");
        Assert.notNull(key, "Key must not be null!");
        Assert.notNull(value, "Value must not be null!");
        return (byte[])this.execute(name, (connection) -> {
            if (this.isLockingCacheWriter()) {
                this.doLock(name, connection);
            }

            Object var6;
            try {
                if (!connection.setNX(key, value)) {
                    byte[] var10 = connection.get(key);
                    return var10;
                }

                if (shouldExpireWithin(ttl)) {
                    connection.pExpire(key, ttl.toMillis());
                }

                var6 = null;
            } finally {
                if (this.isLockingCacheWriter()) {
                    this.doUnlock(name, connection);
                }

            }

            return (byte[])var6;
        });
    }

    public void remove(String name, byte[] key) {
        Assert.notNull(name, "Name must not be null!");
        Assert.notNull(key, "Key must not be null!");
        this.execute(name, (connection) -> {
            return connection.del(new byte[][]{key});
        });
    }

    public void clean(String name, byte[] pattern) {
        Assert.notNull(name, "Name must not be null!");
        Assert.notNull(pattern, "Pattern must not be null!");
        this.execute(name, (connection) -> {
            boolean wasLocked = false;

            try {
                if (this.isLockingCacheWriter()) {
                    this.doLock(name, connection);
                    wasLocked = true;
                }

                byte[][] keys = (byte[][])((Set) Optional.ofNullable(connection.keys(pattern)).orElse(Collections.emptySet())).toArray(new byte[0][]);
                if (keys.length > 0) {
                    connection.del(keys);
                }
            } finally {
                if (wasLocked && this.isLockingCacheWriter()) {
                    this.doUnlock(name, connection);
                }

            }

            return "OK";
        });
    }

    void lock(String name) {
        this.execute(name, (connection) -> {
            return this.doLock(name, connection);
        });
    }

    void unlock(String name) {
        this.executeLockFree((connection) -> {
            this.doUnlock(name, connection);
        });
    }

    private Boolean doLock(String name, RedisConnection connection) {
        return connection.setNX(createCacheLockKey(name), new byte[0]);
    }

    private Long doUnlock(String name, RedisConnection connection) {
        return connection.del(new byte[][]{createCacheLockKey(name)});
    }

    boolean doCheckLock(String name, RedisConnection connection) {
        return connection.exists(createCacheLockKey(name));
    }

    private boolean isLockingCacheWriter() {
        return !this.sleepTime.isZero() && !this.sleepTime.isNegative();
    }

    private  T execute(String name, Function callback) {
        RedisConnection connection = this.connectionFactory.getConnection();

        T var4;
        try {
            this.checkAndPotentiallyWaitUntilUnlocked(name, connection);
            var4 = callback.apply(connection);
        } finally {
            connection.close();
        }

        return var4;
    }

    private void executeLockFree(Consumer callback) {
        RedisConnection connection = this.connectionFactory.getConnection();

        try {
            callback.accept(connection);
        } finally {
            connection.close();
        }

    }

    private void checkAndPotentiallyWaitUntilUnlocked(String name, RedisConnection connection) {
        if (this.isLockingCacheWriter()) {
            try {
                while(this.doCheckLock(name, connection)) {
                    Thread.sleep(this.sleepTime.toMillis());
                }

            } catch (InterruptedException var4) {
                Thread.currentThread().interrupt();
                throw new PessimisticLockingFailureException(String.format("Interrupted while waiting to unlock cache %s", name), var4);
            }
        }
    }

    private static boolean shouldExpireWithin(@Nullable Duration ttl) {
        return ttl != null && !ttl.isZero() && !ttl.isNegative();
    }

    private static byte[] createCacheLockKey(String name) {
        return (name + "~lock").getBytes(StandardCharsets.UTF_8);
    }
}

 2.配置实现类到CacheManager

      配置springCache的CacheManager需继承CachingConfigurerSupport类,重写cacheManager方法,在此之前,确保spring容器里有redisTemplate,需使用redisTemplate获取RedisConnectionFactory

package com.ztxy.module.log.controller.conf;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * redis配置类
 * @author xxx
 * @date   2019年8月6日
 *
 */
@Configuration
public class RedisAutoConfiguration {

    @Bean
    @SuppressWarnings("all")
    public RedisTemplate redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate template = new RedisTemplate();
        template.setConnectionFactory(factory);
        Jackson2JsonRedisSerializer 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);
        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;
    }
}

继承CachingConfigurerSupport类,重写cacheManager方法

package com.ztxy.module.log.controller.conf;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
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.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;

import java.time.Duration;

/**
 * @author jie
 * @Description:
 * @date 2020/1/6 17:56
 */
@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
public class RedisCacheableConfiguration extends CachingConfigurerSupport {

    @Autowired
    private RedisTemplate redisTemplate;

    @Bean
    public RedisCacheConfiguration redisCacheConfiguration(){
        RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig();
        configuration = configuration.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())).entryTtl(Duration.ofSeconds(30));
        return configuration;
    }

    @Override
    public CacheManager cacheManager() {
        RedisConnectionFactory connectionFactory = redisTemplate.getConnectionFactory();
        MyRedisCacheWriter cacheWriter = new MyRedisCacheWriter(connectionFactory);
        CacheManager redisCacheManager = new RedisCacheManager(cacheWriter, redisCacheConfiguration());
        return redisCacheManager;
    }
}

 3.使用

     实现原理就是,从cacheNames = "ids_cache#key_expire_time=120"中取出我自定义的过期时间,keyGenerator = "myKeyGenerator"是上文中自定义的key生成器。


    @Cacheable(cacheNames = "ids_cache#key_expire_time=120",keyGenerator = "myKeyGenerator")
    public List selectList(Map map) {
        List list = sysLoginRegisterLogMapper.selectListByMap(map);
        return list;
    }

如有不足,欢迎批评指正

参考文章:https://blog.csdn.net/u013685413/article/details/88556880

你可能感兴趣的:(java,缓存)