Java——redis通过改写部分源码实现动态db-操作数据库的时候可以指定db操作

写在开头:本文的代码实现思路参考了: wen-pan大佬的项目链接: enhance-boot-data-redis,大佬的代码考虑周全,学到很多,感谢大佬指点。

起因

本文的的目的是实现程序运行中,可以动态切换redis的db。
本项目完整代码git链接:redis-dynamic-switch-db

​ 在redis的源码中,程序启动时从配置文件中读取redis配置的db,如果没有配置则默认初始化0号db。初始化好数据源的bean对象交给spring管理,需要时直接注入初始化好的数据源对象,来操作redis。

​ 但是如果想要在操作redis的时候指定要操作的db,原生代码中并没有提供直接切换的功能,我们只能在程序启动时,多初始化几个数据源,例如我想要操作1、2、3号db,就在一开始初始化3个数据源对象,需要时分别调用。

​ 显然这样并不优雅。

​ 一是不灵活,如果我中途又想操作4号db呢?只能修改代码然后重启我的程序再调用,难道每次想要操作不同的db,都要更改一次代码?

​ 二是不简洁,如果我想要操作100个db,那我就需要初始化100个数据源对象?1000个呢?10000个呢?

于是就有了下面的代码,把部分redis的代码抽出来重写,使redis的database不是在程序启动时就已经固定,而是可以在程序运行中根据我们的需要,去动态的创建指向不同db的连接,任意操作我们想操作的database。

data-redis-gps

(1)改写的源码类

redis连接配置 - RedisConnectionConfiguration.java

原:从redisProperties 中读取database

修改后:手动指定database

package com.yebuxiu.config;

import com.yebuxiu.config.properties.MyRedisProperties;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.util.StringUtils;

import java.net.URI;
import java.net.URISyntaxException;

/**
 * Base Redis connection configuration.
 * 参考:spring-boot-autoconfigure 下的 RedisConnectionConfiguration ,参考版本2.2.7
 * 原:从redisProperties 中读取database
 * 修改后:手动指定database
 *
 * @author Mark Paluch
 * @author Stephane Nicoll
 * @author Alen Turkovic
 */
public abstract class RedisConnectionConfiguration {

    /**
     * redis配置properties
     */
    private final MyRedisProperties myRedisProperties;


    /**
     * redis所使用的库,为指定的db动态的创建RedisTemplate
     */
    private final int database;

    protected RedisConnectionConfiguration(MyRedisProperties myRedisProperties,
                                           int database) {
        this.myRedisProperties = myRedisProperties;
        this.database = database;
    }

    /**
     * redis 单机模式配置信息
     */
    protected final RedisStandaloneConfiguration getStandaloneConfig() {
        RedisProperties gps = myRedisProperties.getRedisProperties();
        RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
        if (StringUtils.hasText(gps.getUrl())) {
            ConnectionInfo connectionInfo = parseUrl(gps.getUrl());
            config.setHostName(connectionInfo.getHostName());
            config.setPort(connectionInfo.getPort());
            config.setPassword(RedisPassword.of(connectionInfo.getPassword()));
        } else {
            config.setHostName(gps.getHost());
            config.setPort(gps.getPort());
            config.setPassword(RedisPassword.of(gps.getPassword()));
        }
        // 使用自定义db
        config.setDatabase(database);
        return config;
    }


    protected final RedisProperties getProperties() {
        return myRedisProperties.getRedisProperties();
    }


    /**
     * 解析Redis url连接,创建连接信息
     */
    protected static ConnectionInfo parseUrl(String url) {
        try {
            URI uri = new URI(url);
            String scheme = uri.getScheme();
            if (!"redis".equals(scheme) && !"rediss".equals(scheme)) {
                // 无效的或畸形的url
                throw new RedisUrlSyntaxException(url);
            }
            boolean useSsl = ("rediss".equals(scheme));
            String password = null;
            if (uri.getUserInfo() != null) {
                String candidate = uri.getUserInfo();
                int index = candidate.indexOf(':');
                if (index >= 0) {
                    password = candidate.substring(index + 1);
                } else {
                    password = candidate;
                }
            }
            return new ConnectionInfo(uri, useSsl, password);
        } catch (URISyntaxException ex) {
            // 无效的或畸形的url
            throw new RedisUrlSyntaxException("Malformed url '" + url + "'", ex);
        }
    }

    /**
     * Redis连接信息
     */
    static class ConnectionInfo {

        private final URI uri;
        private final boolean useSsl;
        private final String password;

        ConnectionInfo(URI uri, boolean useSsl, String password) {
            this.uri = uri;
            this.useSsl = useSsl;
            this.password = password;
        }

        boolean isUseSsl() {
            return useSsl;
        }

        String getHostName() {
            return uri.getHost();
        }

        int getPort() {
            return uri.getPort();
        }

        String getPassword() {
            return password;
        }

    }

}

Lettuce客户端配置 - LettuceConnectionConfigure.java

原:从配置文件读取redisProperties,由spring管理这个bean对象

修改后:每次new对象的时候传入database参数

package com.yebuxiu.config;

import com.yebuxiu.config.properties.MyRedisProperties;
import io.lettuce.core.ClientOptions;
import io.lettuce.core.TimeoutOptions;
import io.lettuce.core.resource.ClientResources;
import io.lettuce.core.resource.DefaultClientResources;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.boot.autoconfigure.data.redis.LettuceClientConfigurationBuilderCustomizer;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties.Pool;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration.LettuceClientConfigurationBuilder;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

/**
 * Redis connection configuration using Lettuce.
 * Author:Mark Paluch, Andy Wilkinson
 * 自定义改造lettuce连接配置:
 *  原:从配置文件读取redisProperties,由spring管理这个bean对象
 *  修改后:每次new对象的时候传入database参数
 */
class LettuceConnectionConfigure extends RedisConnectionConfiguration {

    /**
     * redis配置properties
     */
    private final MyRedisProperties myRedisProperties;

    /**
     * Lettuce客户端定制
     */
    private final List<LettuceClientConfigurationBuilderCustomizer> builderCustomizers;

    private final ClientResources clientResources;

    LettuceConnectionConfigure(MyRedisProperties myRedisProperties,
                               List<LettuceClientConfigurationBuilderCustomizer> builderCustomizers,
                               int database) {
        super(myRedisProperties, database);
        this.myRedisProperties = myRedisProperties;
        this.builderCustomizers = Optional.ofNullable(builderCustomizers).orElse(new ArrayList<>());
        // 每次new LettuceConnectionConfiguration 都新建一个clientResources,也可以使用容器中默认注入的
        // 参考 LettuceConnectionConfiguration
        clientResources = DefaultClientResources.create();
    }

    /**
     * 创建lettuce连接工厂
     */
    LettuceConnectionFactory redisConnectionFactory() {
        // 获取lettuce连接工厂配置信息
        LettuceClientConfiguration clientConfig = getLettuceClientConfiguration(clientResources, myRedisProperties.getRedisProperties().getLettuce().getPool());
        // 创建lettuce连接工厂
        return createLettuceConnectionFactory(clientConfig);
    }

    /**
     * 创建lettuce连接工厂
     */
    private LettuceConnectionFactory createLettuceConnectionFactory(LettuceClientConfiguration clientConfiguration) {
        LettuceConnectionFactory lettuceConnectionFactory = new LettuceConnectionFactory(getStandaloneConfig(), clientConfiguration);
        // 由于我们手动创建lettuceConnectionFactory连接工厂,所以afterPropertiesSet并不会像spring一样自动被吊起
        // 必须手动调用afterPropertiesSet(),初始化connectionProvider,不然创建连接会报错空指针
        lettuceConnectionFactory.afterPropertiesSet();
        return lettuceConnectionFactory;
    }

    /**
     * 获取lettuce客户端配置
     */
    private LettuceClientConfiguration getLettuceClientConfiguration(ClientResources clientResources, Pool pool) {
        LettuceClientConfigurationBuilder builder = createBuilder(pool);
        applyProperties(builder);
        if (StringUtils.hasText(getProperties().getUrl())) {
            customizeConfigurationFromUrl(builder);
        }
        builder.clientOptions(createClientOptions());
        builder.clientResources(clientResources);
        customize(builder);
        return builder.build();
    }

    /**
     * 变更源码排序
     */
    private void customize(LettuceClientConfigurationBuilder builder) {
        for (LettuceClientConfigurationBuilderCustomizer customizer : builderCustomizers) {
            customizer.customize(builder);
        }
    }

    /**
     * 创建客户端配置构建器,用于构建客户端配置
     */
    private static LettuceClientConfigurationBuilder createBuilder(Pool pool) {
        if (pool == null) {
            return LettuceClientConfiguration.builder();
        }
        return PoolBuilderFactory.createBuilder(pool);
    }

    private LettuceClientConfigurationBuilder applyProperties(
            LettuceClientConfigurationBuilder builder) {
        if (getProperties().isSsl()) {
            builder.useSsl();
        }
        if (getProperties().getTimeout() != null) {
            builder.commandTimeout(getProperties().getTimeout());
        }
        if (getProperties().getLettuce() != null) {
            RedisProperties.Lettuce lettuce = getProperties().getLettuce();
            if (lettuce.getShutdownTimeout() != null && !lettuce.getShutdownTimeout().isZero()) {
                builder.shutdownTimeout(getProperties().getLettuce().getShutdownTimeout());
            }
        }
        if (StringUtils.hasText(getProperties().getClientName())) {
            builder.clientName(getProperties().getClientName());
        }
        return builder;
    }

    private ClientOptions createClientOptions() {
        ClientOptions.Builder builder = initializeClientOptionsBuilder();
        return builder.timeoutOptions(TimeoutOptions.enabled()).build();
    }

    private ClientOptions.Builder initializeClientOptionsBuilder() {
        return ClientOptions.builder();
    }

    private void customizeConfigurationFromUrl(LettuceClientConfigurationBuilder builder) {
        ConnectionInfo connectionInfo = parseUrl(getProperties().getUrl());
        if (connectionInfo.isUseSsl()) {
            builder.useSsl();
        }
    }

    /**
     * Inner class to allow optional commons-pool2 dependency.
     */
    private static class PoolBuilderFactory {

        static LettuceClientConfigurationBuilder createBuilder(Pool properties) {
            return LettucePoolingClientConfiguration.builder().poolConfig(getPoolConfig(properties));
        }

        private static GenericObjectPoolConfig<?> getPoolConfig(Pool properties) {
            GenericObjectPoolConfig<?> config = new GenericObjectPoolConfig<>();
            config.setMaxTotal(properties.getMaxActive());
            config.setMaxIdle(properties.getMaxIdle());
            config.setMinIdle(properties.getMinIdle());
            if (properties.getTimeBetweenEvictionRuns() != null) {
                config.setTimeBetweenEvictionRunsMillis(properties.getTimeBetweenEvictionRuns().toMillis());
            }
            if (properties.getMaxWait() != null) {
                config.setMaxWaitMillis(properties.getMaxWait().toMillis());
            }
            return config;
        }

    }

}

(2)衍生的相关类

动态 RedisTemplate 工厂 - DynamicRedisTemplateFactory.java

用于为指定的db创建RedisTemplate。

package com.yebuxiu.config;

import com.yebuxiu.config.properties.MyRedisProperties;
import org.springframework.boot.autoconfigure.data.redis.LettuceClientConfigurationBuilderCustomizer;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.util.Assert;

import java.util.List;

/**
 * 动态 RedisTemplate 工厂类,用于创建和管理RedisTemplate
 */
public class DynamicRedisTemplateFactory<K, V> {

    // ==============================================================================================================
    // 从data-redis源码得知,构建lettuce客户端配置(LettuceConnectionConfigure)
    // 需要如下参数,并且从spring自动配置模块的源码可以看到,这些属性会由springboot自动配置帮我们注入到容器中,所以这里可以通过构造器
    // 将这些属性传递进来,并保存到属性上以便后面使用。
    // ==============================================================================================================

    /**
     * Redis配置信息
     */
    private final MyRedisProperties myRedisProperties;
    /**
     * lettuce配置定制
     */
    private final List<LettuceClientConfigurationBuilderCustomizer> lettuceBuilderCustomizers;


    /**
     * 这些参数由springboot自动配置帮我们自动配置并注入到容器
     * ObjectProvider更加宽松的依赖注入
     */
    public DynamicRedisTemplateFactory(MyRedisProperties myRedisProperties,
                                       List<LettuceClientConfigurationBuilderCustomizer> lettuceBuilderCustomizers) {
        this.myRedisProperties = myRedisProperties;
        this.lettuceBuilderCustomizers = lettuceBuilderCustomizers;
    }

    /**
     * 为指定的db创建RedisTemplate,用于操作Redis
     *
     * @param database redis db
     * @return org.springframework.data.redis.core.RedisTemplate<K, V>
     * @author Mr_wenpan@163.com 2021/8/7 1:47 下午
     */
    public RedisTemplate<K, V> createRedisTemplate(int database) {
        RedisConnectionFactory redisConnectionFactory = null;
        // 根据Redis客户端类型创建Redis连接工厂(用于创建RedisTemplate)
        // 使用指定的db创建lettuce redis连接工厂(创建方式参照源码:LettuceConnectionConfiguration)
        LettuceConnectionConfigure lettuceConnectionConfigure = new LettuceConnectionConfigure(
                myRedisProperties, lettuceBuilderCustomizers, database);
        redisConnectionFactory = lettuceConnectionConfigure.redisConnectionFactory();
        Assert.notNull(redisConnectionFactory, "redisConnectionFactory is null.");
        // 通过Redis连接工厂创建RedisTemplate
        return createRedisTemplate(redisConnectionFactory);
    }

    /**
     * 通过Redis连接工厂来创建一个redisTemplate用于操作Redis db
     */
    private RedisTemplate<K, V> createRedisTemplate(RedisConnectionFactory factory) {
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        RedisTemplate<K, V> redisTemplate = new RedisTemplate<>();
        redisTemplate.setKeySerializer(stringRedisSerializer);
        redisTemplate.setStringSerializer(stringRedisSerializer);
        redisTemplate.setDefaultSerializer(stringRedisSerializer);
        redisTemplate.setHashKeySerializer(stringRedisSerializer);
        redisTemplate.setHashValueSerializer(stringRedisSerializer);
        redisTemplate.setValueSerializer(stringRedisSerializer);
        // 设置Redis连接工厂用于创建连接
        redisTemplate.setConnectionFactory(factory);
        // 调用afterPropertiesSet方法,在属性设置完成后做一些检查和额外工作
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }


}

存放和获取RedisTemplate - AbstractRoutingRedisTemplate.java
package com.flybees.pass32960.data.redis.gps.template;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.core.query.SortQuery;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.data.redis.core.script.ScriptExecutor;
import org.springframework.data.redis.core.types.RedisClientInfo;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.lang.NonNull;

import java.io.Closeable;
import java.util.*;
import java.util.concurrent.TimeUnit;

public abstract class AbstractRoutingRedisTemplate<K, V> extends RedisTemplate<K, V> implements InitializingBean {

    /**
     * 存放对应库的redisTemplate,用于操作对应的db
     */
    private Map<Object, RedisTemplate<K, V>> redisTemplates;

    /**
     * 当不指定库时默认使用的redisTemplate
     */
    private RedisTemplate<K, V> defaultRedisTemplate;

    /**
     * 当类被加载到容器中属性设置完毕后检查redisTemplates和defaultRedisTemplate是否为空
     */
    @Override
    public void afterPropertiesSet() {
        if (redisTemplates == null) {
            throw new IllegalArgumentException("Property 'redisTemplates' is required");
        }
        if (defaultRedisTemplate == null) {
            throw new IllegalArgumentException("Property 'defaultRedisTemplate' is required");
        }
    }

    /**
     * 获取要操作的RedisTemplate
     */
    protected RedisTemplate<K, V>   determineTargetRedisTemplate() {
        // 当前要操作的DB
        Object lookupKey = determineCurrentLookupKey();
        // 如果当前要操作的DB为空则使用默认的RedisTemplate(使用0号库)
        if (lookupKey == null) {
            return defaultRedisTemplate;
        }
        RedisTemplate<K, V> redisTemplate = redisTemplates.get(lookupKey);
        // 如果当前要操作的db还没有维护到redisTemplates中,则创建一个对该库的连接并缓存起来
        if (redisTemplate == null) {
            // 双重检查,这里直接使用synchronized锁,因为创建redisTemplate不会很频繁,一般整个生命周期只有几次,不会有性能问题
            synchronized (DynamicRedisTemplate.class) {
                if (null == redisTemplates.get(lookupKey)) {
                    redisTemplate = createRedisTemplateOnMissing(lookupKey);
                    redisTemplates.put(lookupKey, redisTemplate);
                }
            }
        }
        return redisTemplate;
    }

    /**
     * 获取当前 Redis db
     *
     * @return current redis db
     */
    protected abstract Object determineCurrentLookupKey();

    /**
     * 没有对应 db 的 RedisTemplate 时,则调用此方法创建 RedisTemplate
     *
     * @param lookupKey RedisDB
     * @return RedisTemplate
     */
    public abstract RedisTemplate<K, V> createRedisTemplateOnMissing(Object lookupKey);

    public void setRedisTemplates(Map<Object, RedisTemplate<K, V>> redisTemplates) {
        this.redisTemplates = redisTemplates;
    }

    public void setDefaultRedisTemplate(RedisTemplate<K, V> defaultRedisTemplate) {
        this.defaultRedisTemplate = defaultRedisTemplate;
    }

    public Map<Object, RedisTemplate<K, V>> getRedisTemplates() {
        return redisTemplates;
    }

    public RedisTemplate<K, V> getDefaultRedisTemplate() {
        return defaultRedisTemplate;
    }


    // ====================以下都是继承自父类的方法,RedisTemplate中的方法执行时会调用下面的方法=====================

    @Override
    public <T> T execute(@NonNull RedisCallback<T> action) {
        return determineTargetRedisTemplate().execute(action);
    }

    @Override
    public <T> T execute(@NonNull RedisCallback<T> action, boolean exposeConnection) {
        return determineTargetRedisTemplate().execute(action, exposeConnection);
    }

    @Override
    public <T> T execute(RedisCallback<T> action, boolean exposeConnection, boolean pipeline) {
        return determineTargetRedisTemplate().execute(action, exposeConnection, pipeline);
    }

    @Override
    public <T> T execute(SessionCallback<T> session) {
        return determineTargetRedisTemplate().execute(session);
    }

    @NonNull
    @Override
    public List<Object> executePipelined(@NonNull SessionCallback<?> session) {
        return determineTargetRedisTemplate().executePipelined(session);
    }

    @NonNull
    @Override
    public List<Object> executePipelined(SessionCallback<?> session, RedisSerializer<?> resultSerializer) {
        return determineTargetRedisTemplate().executePipelined(session, resultSerializer);
    }

    @NonNull
    @Override
    public List<Object> executePipelined(@NonNull RedisCallback<?> action) {
        return determineTargetRedisTemplate().executePipelined(action);
    }

    @NonNull
    @Override
    public List<Object> executePipelined(RedisCallback<?> action, RedisSerializer<?> resultSerializer) {
        return determineTargetRedisTemplate().executePipelined(action, resultSerializer);
    }

    @Override
    public <T> T execute(@NonNull RedisScript<T> script, @NonNull List<K> keys, @NonNull Object... args) {
        return determineTargetRedisTemplate().execute(script, keys, args);
    }

    @Override
    public <T> T execute(@NonNull RedisScript<T> script,
                         @NonNull RedisSerializer<?> argsSerializer,
                         @NonNull RedisSerializer<T> resultSerializer,
                         @NonNull List<K> keys, @NonNull Object... args) {
        return determineTargetRedisTemplate().execute(script, argsSerializer, resultSerializer, keys, args);
    }

    @Override
    public <T extends Closeable> T executeWithStickyConnection(RedisCallback<T> callback) {
        return determineTargetRedisTemplate().executeWithStickyConnection(callback);
    }

    @Override
    public boolean isExposeConnection() {
        return determineTargetRedisTemplate().isExposeConnection();
    }

    @Override
    public void setExposeConnection(boolean exposeConnection) {
        determineTargetRedisTemplate().setExposeConnection(exposeConnection);
    }

    @Override
    public boolean isEnableDefaultSerializer() {
        return determineTargetRedisTemplate().isEnableDefaultSerializer();
    }

    @Override
    public void setEnableDefaultSerializer(boolean enableDefaultSerializer) {
        determineTargetRedisTemplate().setEnableDefaultSerializer(enableDefaultSerializer);
    }

    @Override
    public RedisSerializer<?> getDefaultSerializer() {
        return determineTargetRedisTemplate().getDefaultSerializer();
    }

    @Override
    public void setDefaultSerializer(@NonNull RedisSerializer<?> serializer) {
        determineTargetRedisTemplate().setDefaultSerializer(serializer);
    }

    @Override
    public void setKeySerializer(@NonNull RedisSerializer<?> serializer) {
        determineTargetRedisTemplate().setKeySerializer(serializer);
    }

    @NonNull
    @Override
    public RedisSerializer<?> getKeySerializer() {
        return determineTargetRedisTemplate().getKeySerializer();
    }

    @Override
    public void setValueSerializer(@NonNull RedisSerializer<?> serializer) {
        determineTargetRedisTemplate().setValueSerializer(serializer);
    }

    @NonNull
    @Override
    public RedisSerializer<?> getValueSerializer() {
        return determineTargetRedisTemplate().getValueSerializer();
    }

    @NonNull
    @Override
    public RedisSerializer<?> getHashKeySerializer() {
        return determineTargetRedisTemplate().getHashKeySerializer();
    }

    @Override
    public void setHashKeySerializer(@NonNull RedisSerializer<?> hashKeySerializer) {
        determineTargetRedisTemplate().setHashKeySerializer(hashKeySerializer);
    }

    @NonNull
    @Override
    public RedisSerializer<?> getHashValueSerializer() {
        return determineTargetRedisTemplate().getHashValueSerializer();
    }

    @Override
    public void setHashValueSerializer(@NonNull RedisSerializer<?> hashValueSerializer) {
        determineTargetRedisTemplate().setHashValueSerializer(hashValueSerializer);
    }

    @NonNull
    @Override
    public RedisSerializer<String> getStringSerializer() {
        return determineTargetRedisTemplate().getStringSerializer();
    }

    @Override
    public void setStringSerializer(@NonNull RedisSerializer<String> stringSerializer) {
        determineTargetRedisTemplate().setStringSerializer(stringSerializer);
    }

    @Override
    public void setScriptExecutor(@NonNull ScriptExecutor<K> scriptExecutor) {
        determineTargetRedisTemplate().setScriptExecutor(scriptExecutor);
    }

    @NonNull
    @Override
    public List<Object> exec() {
        return determineTargetRedisTemplate().exec();
    }

    @NonNull
    @Override
    public List<Object> exec(@NonNull RedisSerializer<?> valueSerializer) {
        return determineTargetRedisTemplate().exec(valueSerializer);
    }

    @Override
    public Boolean delete(K key) {
        return determineTargetRedisTemplate().delete(key);
    }

    @Override
    public Long delete(@NonNull Collection<K> keys) {
        return determineTargetRedisTemplate().delete(keys);
    }

    @Override
    public Boolean hasKey(K key) {
        return determineTargetRedisTemplate().hasKey(key);
    }

    @Override
    public Boolean expire(K key, long timeout, TimeUnit unit) {
        return determineTargetRedisTemplate().expire(key, timeout, unit);
    }

    @Override
    public Boolean expireAt(K key, Date date) {
        return determineTargetRedisTemplate().expireAt(key, date);
    }

    @Override
    public void convertAndSend(@NonNull String channel, @NonNull Object message) {
        determineTargetRedisTemplate().convertAndSend(channel, message);
    }

    @Override
    public Long getExpire(K key) {
        return determineTargetRedisTemplate().getExpire(key);
    }

    @Override
    public Long getExpire(K key, @NonNull TimeUnit timeUnit) {
        return determineTargetRedisTemplate().getExpire(key, timeUnit);
    }

    @Override
    public Set<K> keys(K pattern) {
        return determineTargetRedisTemplate().keys(pattern);
    }

    @Override
    public Boolean persist(K key) {
        return determineTargetRedisTemplate().persist(key);
    }

    @Override
    public Boolean move(K key, int dbIndex) {
        return determineTargetRedisTemplate().move(key, dbIndex);
    }

    @Override
    public K randomKey() {
        return determineTargetRedisTemplate().randomKey();
    }

    @Override
    public void rename(K oldKey, K newKey) {
        determineTargetRedisTemplate().rename(oldKey, newKey);
    }

    @Override
    public Boolean renameIfAbsent(K oldKey, K newKey) {
        return determineTargetRedisTemplate().renameIfAbsent(oldKey, newKey);
    }

    @Override
    public DataType type(K key) {
        return determineTargetRedisTemplate().type(key);
    }

    @Override
    public byte[] dump(K key) {
        return determineTargetRedisTemplate().dump(key);
    }

    @Override
    public void restore(@NonNull K key, @NonNull byte[] value, long timeToLive, @NonNull TimeUnit unit) {
        determineTargetRedisTemplate().restore(key, value, timeToLive, unit);
    }

    @Override
    public void multi() {
        determineTargetRedisTemplate().multi();
    }

    @Override
    public void discard() {
        determineTargetRedisTemplate().discard();
    }

    @Override
    public void watch(K key) {
        determineTargetRedisTemplate().watch(key);
    }

    @Override
    public void watch(Collection<K> keys) {
        determineTargetRedisTemplate().watch(keys);
    }

    @Override
    public void unwatch() {
        determineTargetRedisTemplate().unwatch();
    }

    @Override
    public List<V> sort(@NonNull SortQuery<K> query) {
        return determineTargetRedisTemplate().sort(query);
    }

    @Override
    public <T> List<T> sort(SortQuery<K> query, RedisSerializer<T> resultSerializer) {
        return determineTargetRedisTemplate().sort(query, resultSerializer);
    }

    @Override
    public <T> List<T> sort(@NonNull SortQuery<K> query, @NonNull BulkMapper<T, V> bulkMapper) {
        return determineTargetRedisTemplate().sort(query, bulkMapper);
    }

    @Override
    public <T, S> List<T> sort(@NonNull SortQuery<K> query,
                               @NonNull BulkMapper<T, S> bulkMapper,
                               RedisSerializer<S> resultSerializer) {
        return determineTargetRedisTemplate().sort(query, bulkMapper, resultSerializer);
    }

    @Override
    public Long sort(SortQuery<K> query, K storeKey) {
        return determineTargetRedisTemplate().sort(query, storeKey);
    }

    @NonNull
    @Override
    public BoundValueOperations<K, V> boundValueOps(@NonNull K key) {
        return determineTargetRedisTemplate().boundValueOps(key);
    }

    @NonNull
    @Override
    public ValueOperations<K, V> opsForValue() {
        return determineTargetRedisTemplate().opsForValue();
    }

    @NonNull
    @Override
    public ListOperations<K, V> opsForList() {
        return determineTargetRedisTemplate().opsForList();
    }

    @NonNull
    @Override
    public BoundListOperations<K, V> boundListOps(@NonNull K key) {
        return determineTargetRedisTemplate().boundListOps(key);
    }

    @NonNull
    @Override
    public BoundSetOperations<K, V> boundSetOps(@NonNull K key) {
        return determineTargetRedisTemplate().boundSetOps(key);
    }

    @NonNull
    @Override
    public SetOperations<K, V> opsForSet() {
        return determineTargetRedisTemplate().opsForSet();
    }

    @NonNull
    @Override
    public BoundZSetOperations<K, V> boundZSetOps(@NonNull K key) {
        return determineTargetRedisTemplate().boundZSetOps(key);
    }

    @NonNull
    @Override
    public ZSetOperations<K, V> opsForZSet() {
        return determineTargetRedisTemplate().opsForZSet();
    }

    @NonNull
    @Override
    public GeoOperations<K, V> opsForGeo() {
        return determineTargetRedisTemplate().opsForGeo();
    }

    @NonNull
    @Override
    public BoundGeoOperations<K, V> boundGeoOps(@NonNull K key) {
        return determineTargetRedisTemplate().boundGeoOps(key);
    }

    @NonNull
    @Override
    public HyperLogLogOperations<K, V> opsForHyperLogLog() {
        return determineTargetRedisTemplate().opsForHyperLogLog();
    }

    @NonNull
    @Override
    public <HK, HV> BoundHashOperations<K, HK, HV> boundHashOps(@NonNull K key) {
        return determineTargetRedisTemplate().boundHashOps(key);
    }

    @NonNull
    @Override
    public <HK, HV> HashOperations<K, HK, HV> opsForHash() {
        return determineTargetRedisTemplate().opsForHash();
    }

    @NonNull
    @Override
    public ClusterOperations<K, V> opsForCluster() {
        return determineTargetRedisTemplate().opsForCluster();
    }

    @Override
    public void killClient(@NonNull String host, int port) {
        determineTargetRedisTemplate().killClient(host, port);
    }

    @Override
    public List<RedisClientInfo> getClientList() {
        return determineTargetRedisTemplate().getClientList();
    }

    @Override
    public void slaveOf(@NonNull String host, int port) {
        determineTargetRedisTemplate().slaveOf(host, port);
    }

    @Override
    public void slaveOfNoOne() {
        determineTargetRedisTemplate().slaveOfNoOne();
    }

    @Override
    public void setEnableTransactionSupport(boolean enableTransactionSupport) {
        determineTargetRedisTemplate().setEnableTransactionSupport(enableTransactionSupport);
    }

    @Override
    public void setBeanClassLoader(@NonNull ClassLoader classLoader) {
        determineTargetRedisTemplate().setBeanClassLoader(classLoader);
    }

    @Override
    public RedisConnectionFactory getConnectionFactory() {
        return determineTargetRedisTemplate().getConnectionFactory();
    }

    @Override
    public void setConnectionFactory(@NonNull RedisConnectionFactory connectionFactory) {
        determineTargetRedisTemplate().setConnectionFactory(connectionFactory);
    }

}

通过指定的db创建RedisTemplate - DynamicRedisTemplate.java
package com.yebuxiu.template;

import com.yebuxiu.config.DynamicRedisTemplateFactory;
import com.yebuxiu.helper.RedisDatabaseThreadLocalHelper;
import org.springframework.data.redis.core.RedisTemplate;

/**
 * 动态 RedisTemplate ,以支持动态切换 redis database
 */
public class DynamicRedisTemplate<K, V> extends AbstractRoutingRedisTemplate<K, V> {

    /**
     * 动态RedisTemplate工厂,用于创建管理动态DynamicRedisTemplate
     */
    private final DynamicRedisTemplateFactory<K, V> dynamicRedisTemplateFactory;

    public DynamicRedisTemplate(DynamicRedisTemplateFactory<K, V> dynamicRedisTemplateFactory) {
        this.dynamicRedisTemplateFactory = dynamicRedisTemplateFactory;
    }

    @Override
    protected Object determineCurrentLookupKey() {
        return RedisDatabaseThreadLocalHelper.get();
    }

    /**
     * 通过制定的db创建RedisTemplate
     *
     * @param lookupKey db号
     * @return org.springframework.data.redis.core.RedisTemplate<K, V>
     */
    @Override
    public RedisTemplate<K, V> createRedisTemplateOnMissing(Object lookupKey) {
        return dynamicRedisTemplateFactory.createRedisTemplate((Integer) lookupKey);
    }

}

(3)自定义的包装类

获取和设置当前线程db - RedisDatabaseThreadLocalHelper.java
package com.yebuxiu.helper;

import org.springframework.util.CollectionUtils;

import java.util.ArrayDeque;
import java.util.Deque;


/**
 * redis动态切换数据库帮助器
 */
public class RedisDatabaseThreadLocalHelper {

    private static final ThreadLocal<Deque<Integer>> THREAD_DB = new ThreadLocal<>();

    /**
     * 更改当前线程 RedisTemplate db
     *
     * @param db set current redis db
     */
    public static void set(int db) {
        Deque<Integer> deque = THREAD_DB.get();
        if (deque == null) {
            deque = new ArrayDeque<>();
        }
        deque.addFirst(db);
        THREAD_DB.set(deque);
    }

    /**
     * @return get current redis db
     */
    public static Integer get() {
        Deque<Integer> deque = THREAD_DB.get();
        if (CollectionUtils.isEmpty(deque)) {
            return null;
        }
        return deque.getFirst();
    }

    /**
     * 清理
     */
    public static void clear() {
        Deque<Integer> deque = THREAD_DB.get();
        if (deque == null || deque.size() <= 1) {
            THREAD_DB.remove();
            return;
        }
        deque.removeFirst();
        THREAD_DB.set(deque);
    }
}

提供操作数据库时指定db方法 - RedisHelper.java
package com.yebuxiu.helper;

import com.yebuxiu.template.DynamicRedisTemplate;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.util.Assert;

import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * Redis操作工具类 集成封装一些常用方法
 */
public class RedisHelper implements InitializingBean {

    /**
     * 不设置过期时长
     */
    public static final long NOT_EXPIRE = -1;

    private final DynamicRedisTemplate<String, String> redisTemplate;
    public RedisHelper(DynamicRedisTemplate<String, String> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        Assert.notNull(redisTemplate, "redisTemplate must not be null.");
    }


    public RedisTemplate<String, String> getRedisTemplate() {
        return redisTemplate;
    }

    public Map<Object, RedisTemplate<String, String>> getRedisTemplates() {
        return redisTemplate.getRedisTemplates();
    }

    protected ValueOperations<String, String> getValueOperations() {
        return redisTemplate.opsForValue();
    }

    /**
     * 设置当前线程操作 redis database,同一个线程内操作多次redis,不同database,
     * 需要调用 {@link RedisHelper#clearCurrentDatabase()} 清除当前线程redis database,从而使用默认的db.
     * 如果静态RedisHelper进行db切换,这是不被允许的,需要抛出异常
     *
     * @param database redis database
     */
    public void setCurrentDatabase(int database) {
//        logger.warn("Use default RedisHelper, you'd better use a DynamicRedisHelper instead.");
//        throw new RuntimeException("static redisHelper can't change db.");
        RedisDatabaseThreadLocalHelper.set(database);
    }

    /**
     * 清除当前线程 redis database.
     */
    public void clearCurrentDatabase() {
//        logger.warn("Use default RedisHelper, you'd better use a DynamicRedisHelper instead.");
        RedisDatabaseThreadLocalHelper.clear();
    }


    /**
     * String 设置值
     *
     * @param key    key
     * @param value  value
     * @param expire 过期时间
     */
    public void strSet(String key, String value, long expire, TimeUnit timeUnit) {
        getValueOperations().set(key, value);
        if (expire != NOT_EXPIRE) {
            setExpire(key, expire, timeUnit == null ? TimeUnit.SECONDS : timeUnit);
        }
    }

    /**
     * String 获取值
     *
     * @param key key
     */
    public String strGet(String key) {
        return getValueOperations().get(key);
    }

    /**
     * 设置过期时间
     *
     * @param key      key
     * @param expire   存活时长
     * @param timeUnit 时间单位
     */
    public Boolean setExpire(String key, long expire, TimeUnit timeUnit) {
        return redisTemplate.expire(key, expire, timeUnit == null ? TimeUnit.SECONDS : timeUnit);
    }

    public void strSetWithDb(int db, String key, String value, long expire, TimeUnit timeUnit) {
        try {
            setCurrentDatabase(db);
            strSet(key,value,expire,timeUnit);
        } finally {
            clearCurrentDatabase();
        }
    }

    public String strGetWithDb(int db, String key) {
        try {
            setCurrentDatabase(db);
            return strGet(key);
        } finally {
            clearCurrentDatabase();
        }
    }
}

(4)配置bean

配置bean-EnhanceDataRedisAutoConfiguration.java
package com.yebuxiu.config;

import com.yebuxiu.config.properties.MyRedisProperties;
import com.yebuxiu.helper.RedisHelper;
import com.yebuxiu.template.DynamicRedisTemplate;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.data.redis.LettuceClientConfigurationBuilderCustomizer;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * stone-redis自动配置,必须要容器中有RedisConnectionFactory才启动该配置类
 */
@Configuration
@EnableConfigurationProperties(MyRedisProperties.class)
@ConditionalOnClass(name = {"org.springframework.data.redis.connection.RedisConnectionFactory"})
public class EnhanceDataRedisAutoConfiguration {


    /**
     * 注入RedisTemplate,key-value都使用string类型
     * RedisConnectionFactory由对应的spring-boot-autoconfigure自动配置到容器
     */
    @Bean
    @ConditionalOnMissingBean(RedisTemplate.class)
    public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, String> redisTemplate = new RedisTemplate<>();
        buildRedisTemplate(redisTemplate, redisConnectionFactory);
        return redisTemplate;
    }

//    /**
//     * 注入StringRedisTemplate,key-value序列化器都使用String序列化器
//     */
//    @Bean
//    @ConditionalOnMissingBean(StringRedisTemplate.class)
//    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
//        StringRedisTemplate redisTemplate = new StringRedisTemplate();
//        buildRedisTemplate(redisTemplate, redisConnectionFactory);
//        return redisTemplate;
//    }

    /**
     * 通过Redis连接工厂构建一个RedisTemplate
     */
    private static void buildRedisTemplate(RedisTemplate<String, String> redisTemplate,
                                           RedisConnectionFactory redisConnectionFactory) {
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        redisTemplate.setKeySerializer(stringRedisSerializer);
        redisTemplate.setStringSerializer(stringRedisSerializer);
        redisTemplate.setDefaultSerializer(stringRedisSerializer);
        redisTemplate.setHashKeySerializer(stringRedisSerializer);
        redisTemplate.setHashValueSerializer(stringRedisSerializer);
        redisTemplate.setValueSerializer(stringRedisSerializer);
        redisTemplate.setConnectionFactory(redisConnectionFactory);
    }


    /**
     * 默认数据源的动态redisHelper
     * 这些参数由lettuce客户端帮我们自动配置并注入到容器
     */
    @Primary
    @Bean(name = {"redisHelper", "default", "default-helper"})
    public RedisHelper dynamicRedisHelper(StringRedisTemplate redisTemplate,
                                          MyRedisProperties myRedisProperties,
                                          ObjectProvider<List<LettuceClientConfigurationBuilderCustomizer>> builderCustomizers) {

        // 构建动态RedisTemplate工厂
        DynamicRedisTemplateFactory<String, String> dynamicRedisTemplateFactory =
                new DynamicRedisTemplateFactory<>(myRedisProperties,
                        builderCustomizers.getIfAvailable());
        // ======================================================================================================
        // 这里在注入的时候默认值注入一个默认的redisTemplate,以及将这个redisTemplate放入到map中,该redisTemplate
        // 操作的是配置文件中使用spring.redis.database属性指定的db(若不显示指定,则使用的0号db)
        // 注入的时候值放入这个默认的的redisTemplate到map中,在使用的时候如果需要动态切换库那么会通过连接工厂
        // 重新去创建一个redisTemplate,并缓存到map中(懒加载模式),并不会一次性创建出多个redisTemplate然后缓存起来(连接很昂贵)
        // ======================================================================================================

        DynamicRedisTemplate<String, String> dynamicRedisTemplate = new DynamicRedisTemplate<>(dynamicRedisTemplateFactory);
        // 当不指定库时,默认使用的RedisTemplate来操作Redis(直接获取容器中的)
        dynamicRedisTemplate.setDefaultRedisTemplate(redisTemplate);
        Map<Object, RedisTemplate<String, String>> map = new HashMap<>(8);
        // 配置文件中指定使用几号db
        map.put(myRedisProperties.getRedisProperties().getDatabase(), redisTemplate);
        // 将redisTemplate缓存起来
        dynamicRedisTemplate.setRedisTemplates(map);

        return new RedisHelper(dynamicRedisTemplate);
    }

}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值