Create spring-boot-data-redis module

This commit is contained in:
Stéphane Nicoll
2025-03-28 14:11:55 +01:00
committed by Phillip Webb
parent 10db864d35
commit fa0bdc895f
73 changed files with 499 additions and 462 deletions

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.data.redis.autoconfigure;
import io.lettuce.core.resource.ClientResources;
import io.lettuce.core.resource.ClientResources.Builder;
/**
* Callback interface that can be implemented by beans wishing to customize the
* {@link ClientResources} through a {@link Builder} whilst retaining default
* auto-configuration.
*
* @author Stephane Nicoll
* @since 4.0.0
*/
public interface ClientResourcesBuilderCustomizer {
/**
* Customize the {@link Builder}.
* @param clientResourcesBuilder the builder to customize
*/
void customize(Builder clientResourcesBuilder);
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.data.redis.autoconfigure;
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration;
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration.JedisClientConfigurationBuilder;
/**
* Callback interface that can be implemented by beans wishing to customize the
* {@link JedisClientConfiguration} through a {@link JedisClientConfigurationBuilder
* JedisClientConfiguration.JedisClientConfigurationBuilder} whilst retaining default
* auto-configuration.
*
* @author Mark Paluch
* @since 4.0.0
*/
@FunctionalInterface
public interface JedisClientConfigurationBuilderCustomizer {
/**
* Customize the {@link JedisClientConfigurationBuilder}.
* @param clientConfigurationBuilder the builder to customize
*/
void customize(JedisClientConfigurationBuilder clientConfigurationBuilder);
}

View File

@@ -0,0 +1,163 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.data.redis.autoconfigure;
import javax.net.ssl.SSLParameters;
import org.apache.commons.pool2.impl.GenericObjectPool;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPoolConfig;
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.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnThreading;
import org.springframework.boot.autoconfigure.thread.Threading;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.ssl.SslBundle;
import org.springframework.boot.ssl.SslOptions;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration;
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration.JedisClientConfigurationBuilder;
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration.JedisSslClientConfigurationBuilder;
import org.springframework.data.redis.connection.jedis.JedisConnection;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.util.StringUtils;
/**
* Redis connection configuration using Jedis.
*
* @author Mark Paluch
* @author Stephane Nicoll
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
* @author Scott Frederick
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ GenericObjectPool.class, JedisConnection.class, Jedis.class })
@ConditionalOnMissingBean(RedisConnectionFactory.class)
@ConditionalOnProperty(name = "spring.data.redis.client-type", havingValue = "jedis", matchIfMissing = true)
class JedisConnectionConfiguration extends RedisConnectionConfiguration {
JedisConnectionConfiguration(RedisProperties properties,
ObjectProvider<RedisStandaloneConfiguration> standaloneConfigurationProvider,
ObjectProvider<RedisSentinelConfiguration> sentinelConfiguration,
ObjectProvider<RedisClusterConfiguration> clusterConfiguration, RedisConnectionDetails connectionDetails) {
super(properties, connectionDetails, standaloneConfigurationProvider, sentinelConfiguration,
clusterConfiguration);
}
@Bean
@ConditionalOnThreading(Threading.PLATFORM)
JedisConnectionFactory redisConnectionFactory(
ObjectProvider<JedisClientConfigurationBuilderCustomizer> builderCustomizers) {
return createJedisConnectionFactory(builderCustomizers);
}
@Bean
@ConditionalOnThreading(Threading.VIRTUAL)
JedisConnectionFactory redisConnectionFactoryVirtualThreads(
ObjectProvider<JedisClientConfigurationBuilderCustomizer> builderCustomizers) {
JedisConnectionFactory factory = createJedisConnectionFactory(builderCustomizers);
SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor("redis-");
executor.setVirtualThreads(true);
factory.setExecutor(executor);
return factory;
}
private JedisConnectionFactory createJedisConnectionFactory(
ObjectProvider<JedisClientConfigurationBuilderCustomizer> builderCustomizers) {
JedisClientConfiguration clientConfiguration = getJedisClientConfiguration(builderCustomizers);
return switch (this.mode) {
case STANDALONE -> new JedisConnectionFactory(getStandaloneConfig(), clientConfiguration);
case CLUSTER -> new JedisConnectionFactory(getClusterConfiguration(), clientConfiguration);
case SENTINEL -> new JedisConnectionFactory(getSentinelConfig(), clientConfiguration);
};
}
private JedisClientConfiguration getJedisClientConfiguration(
ObjectProvider<JedisClientConfigurationBuilderCustomizer> builderCustomizers) {
JedisClientConfigurationBuilder builder = applyProperties(JedisClientConfiguration.builder());
applySslIfNeeded(builder);
RedisProperties.Pool pool = getProperties().getJedis().getPool();
if (isPoolEnabled(pool)) {
applyPooling(pool, builder);
}
if (StringUtils.hasText(getProperties().getUrl())) {
customizeConfigurationFromUrl(builder);
}
builderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
return builder.build();
}
private JedisClientConfigurationBuilder applyProperties(JedisClientConfigurationBuilder builder) {
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(getProperties().getTimeout()).to(builder::readTimeout);
map.from(getProperties().getConnectTimeout()).to(builder::connectTimeout);
map.from(getProperties().getClientName()).whenHasText().to(builder::clientName);
return builder;
}
private void applySslIfNeeded(JedisClientConfigurationBuilder builder) {
SslBundle sslBundle = getSslBundle();
if (sslBundle == null) {
return;
}
JedisSslClientConfigurationBuilder sslBuilder = builder.useSsl();
sslBuilder.sslSocketFactory(sslBundle.createSslContext().getSocketFactory());
SslOptions sslOptions = sslBundle.getOptions();
SSLParameters sslParameters = new SSLParameters();
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(sslOptions.getCiphers()).to(sslParameters::setCipherSuites);
map.from(sslOptions.getEnabledProtocols()).to(sslParameters::setProtocols);
sslBuilder.sslParameters(sslParameters);
}
private void applyPooling(RedisProperties.Pool pool,
JedisClientConfiguration.JedisClientConfigurationBuilder builder) {
builder.usePooling().poolConfig(jedisPoolConfig(pool));
}
private JedisPoolConfig jedisPoolConfig(RedisProperties.Pool pool) {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(pool.getMaxActive());
config.setMaxIdle(pool.getMaxIdle());
config.setMinIdle(pool.getMinIdle());
if (pool.getTimeBetweenEvictionRuns() != null) {
config.setTimeBetweenEvictionRuns(pool.getTimeBetweenEvictionRuns());
}
if (pool.getMaxWait() != null) {
config.setMaxWait(pool.getMaxWait());
}
return config;
}
private void customizeConfigurationFromUrl(JedisClientConfiguration.JedisClientConfigurationBuilder builder) {
if (urlUsesSsl()) {
builder.useSsl();
}
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.data.redis.autoconfigure;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration.LettuceClientConfigurationBuilder;
/**
* Callback interface that can be implemented by beans wishing to customize the
* {@link LettuceClientConfiguration} through a {@link LettuceClientConfigurationBuilder
* LettuceClientConfiguration.LettuceClientConfigurationBuilder} whilst retaining default
* auto-configuration. To customize only the
* {@link LettuceClientConfiguration#getClientOptions() client options} of the
* configuration, use {@link LettuceClientOptionsBuilderCustomizer} instead.
*
* @author Mark Paluch
* @since 4.0.0
*/
@FunctionalInterface
public interface LettuceClientConfigurationBuilderCustomizer {
/**
* Customize the {@link LettuceClientConfigurationBuilder}.
* @param clientConfigurationBuilder the builder to customize
*/
void customize(LettuceClientConfigurationBuilder clientConfigurationBuilder);
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.data.redis.autoconfigure;
import io.lettuce.core.ClientOptions;
import io.lettuce.core.ClientOptions.Builder;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
/**
* Callback interface that can be implemented by beans wishing to customize the
* {@link ClientOptions} of the {@link LettuceClientConfiguration} through a
* {@link Builder} whilst retaining default auto-configuration. To customize the entire
* configuration, use {@link LettuceClientConfigurationBuilderCustomizer} instead.
*
* @author Soohyun Lim
* @since 4.0.0
*/
@FunctionalInterface
public interface LettuceClientOptionsBuilderCustomizer {
/**
* Customize the {@link Builder}.
* @param clientOptionsBuilder the builder to customize
*/
void customize(Builder clientOptionsBuilder);
}

View File

@@ -0,0 +1,266 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.data.redis.autoconfigure;
import java.time.Duration;
import io.lettuce.core.ClientOptions;
import io.lettuce.core.ReadFrom;
import io.lettuce.core.RedisClient;
import io.lettuce.core.SocketOptions;
import io.lettuce.core.TimeoutOptions;
import io.lettuce.core.api.StatefulConnection;
import io.lettuce.core.cluster.ClusterClientOptions;
import io.lettuce.core.cluster.ClusterTopologyRefreshOptions;
import io.lettuce.core.cluster.ClusterTopologyRefreshOptions.Builder;
import io.lettuce.core.resource.ClientResources;
import io.lettuce.core.resource.DefaultClientResources;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
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.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnThreading;
import org.springframework.boot.autoconfigure.thread.Threading;
import org.springframework.boot.data.redis.autoconfigure.RedisProperties.Lettuce.Cluster.Refresh;
import org.springframework.boot.data.redis.autoconfigure.RedisProperties.Pool;
import org.springframework.boot.ssl.SslBundle;
import org.springframework.boot.ssl.SslOptions;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
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;
/**
* Redis connection configuration using Lettuce.
*
* @author Mark Paluch
* @author Andy Wilkinson
* @author Moritz Halbritter
* @author Phillip Webb
* @author Scott Frederick
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisClient.class)
@ConditionalOnProperty(name = "spring.data.redis.client-type", havingValue = "lettuce", matchIfMissing = true)
class LettuceConnectionConfiguration extends RedisConnectionConfiguration {
LettuceConnectionConfiguration(RedisProperties properties,
ObjectProvider<RedisStandaloneConfiguration> standaloneConfigurationProvider,
ObjectProvider<RedisSentinelConfiguration> sentinelConfigurationProvider,
ObjectProvider<RedisClusterConfiguration> clusterConfigurationProvider,
RedisConnectionDetails connectionDetails) {
super(properties, connectionDetails, standaloneConfigurationProvider, sentinelConfigurationProvider,
clusterConfigurationProvider);
}
@Bean(destroyMethod = "shutdown")
@ConditionalOnMissingBean(ClientResources.class)
DefaultClientResources lettuceClientResources(ObjectProvider<ClientResourcesBuilderCustomizer> customizers) {
DefaultClientResources.Builder builder = DefaultClientResources.builder();
customizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
return builder.build();
}
@Bean
@ConditionalOnMissingBean(RedisConnectionFactory.class)
@ConditionalOnThreading(Threading.PLATFORM)
LettuceConnectionFactory redisConnectionFactory(
ObjectProvider<LettuceClientConfigurationBuilderCustomizer> clientConfigurationBuilderCustomizers,
ObjectProvider<LettuceClientOptionsBuilderCustomizer> clientOptionsBuilderCustomizers,
ClientResources clientResources) {
return createConnectionFactory(clientConfigurationBuilderCustomizers, clientOptionsBuilderCustomizers,
clientResources);
}
@Bean
@ConditionalOnMissingBean(RedisConnectionFactory.class)
@ConditionalOnThreading(Threading.VIRTUAL)
LettuceConnectionFactory redisConnectionFactoryVirtualThreads(
ObjectProvider<LettuceClientConfigurationBuilderCustomizer> clientConfigurationBuilderCustomizers,
ObjectProvider<LettuceClientOptionsBuilderCustomizer> clientOptionsBuilderCustomizers,
ClientResources clientResources) {
LettuceConnectionFactory factory = createConnectionFactory(clientConfigurationBuilderCustomizers,
clientOptionsBuilderCustomizers, clientResources);
SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor("redis-");
executor.setVirtualThreads(true);
factory.setExecutor(executor);
return factory;
}
private LettuceConnectionFactory createConnectionFactory(
ObjectProvider<LettuceClientConfigurationBuilderCustomizer> clientConfigurationBuilderCustomizers,
ObjectProvider<LettuceClientOptionsBuilderCustomizer> clientOptionsBuilderCustomizers,
ClientResources clientResources) {
LettuceClientConfiguration clientConfiguration = getLettuceClientConfiguration(
clientConfigurationBuilderCustomizers, clientOptionsBuilderCustomizers, clientResources,
getProperties().getLettuce().getPool());
return switch (this.mode) {
case STANDALONE -> new LettuceConnectionFactory(getStandaloneConfig(), clientConfiguration);
case CLUSTER -> new LettuceConnectionFactory(getClusterConfiguration(), clientConfiguration);
case SENTINEL -> new LettuceConnectionFactory(getSentinelConfig(), clientConfiguration);
};
}
private LettuceClientConfiguration getLettuceClientConfiguration(
ObjectProvider<LettuceClientConfigurationBuilderCustomizer> clientConfigurationBuilderCustomizers,
ObjectProvider<LettuceClientOptionsBuilderCustomizer> clientOptionsBuilderCustomizers,
ClientResources clientResources, Pool pool) {
LettuceClientConfigurationBuilder builder = createBuilder(pool);
SslBundle sslBundle = getSslBundle();
applyProperties(builder, sslBundle);
if (StringUtils.hasText(getProperties().getUrl())) {
customizeConfigurationFromUrl(builder);
}
builder.clientOptions(createClientOptions(clientOptionsBuilderCustomizers, sslBundle));
builder.clientResources(clientResources);
clientConfigurationBuilderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
return builder.build();
}
private LettuceClientConfigurationBuilder createBuilder(Pool pool) {
if (isPoolEnabled(pool)) {
return new PoolBuilderFactory().createBuilder(pool);
}
return LettuceClientConfiguration.builder();
}
private void applyProperties(LettuceClientConfigurationBuilder builder, SslBundle sslBundle) {
if (sslBundle != null) {
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());
}
String readFrom = lettuce.getReadFrom();
if (readFrom != null) {
builder.readFrom(getReadFrom(readFrom));
}
}
if (StringUtils.hasText(getProperties().getClientName())) {
builder.clientName(getProperties().getClientName());
}
}
private ReadFrom getReadFrom(String readFrom) {
int index = readFrom.indexOf(':');
if (index == -1) {
return ReadFrom.valueOf(getCanonicalReadFromName(readFrom));
}
String name = getCanonicalReadFromName(readFrom.substring(0, index));
String value = readFrom.substring(index + 1);
return ReadFrom.valueOf(name + ":" + value);
}
private String getCanonicalReadFromName(String name) {
StringBuilder canonicalName = new StringBuilder(name.length());
name.chars()
.filter(Character::isLetterOrDigit)
.map(Character::toLowerCase)
.forEach((c) -> canonicalName.append((char) c));
return canonicalName.toString();
}
private ClientOptions createClientOptions(
ObjectProvider<LettuceClientOptionsBuilderCustomizer> clientConfigurationBuilderCustomizers,
SslBundle sslBundle) {
ClientOptions.Builder builder = initializeClientOptionsBuilder();
Duration connectTimeout = getProperties().getConnectTimeout();
if (connectTimeout != null) {
builder.socketOptions(SocketOptions.builder().connectTimeout(connectTimeout).build());
}
if (sslBundle != null) {
io.lettuce.core.SslOptions.Builder sslOptionsBuilder = io.lettuce.core.SslOptions.builder();
sslOptionsBuilder.keyManager(sslBundle.getManagers().getKeyManagerFactory());
sslOptionsBuilder.trustManager(sslBundle.getManagers().getTrustManagerFactory());
SslOptions sslOptions = sslBundle.getOptions();
if (sslOptions.getCiphers() != null) {
sslOptionsBuilder.cipherSuites(sslOptions.getCiphers());
}
if (sslOptions.getEnabledProtocols() != null) {
sslOptionsBuilder.protocols(sslOptions.getEnabledProtocols());
}
builder.sslOptions(sslOptionsBuilder.build());
}
builder.timeoutOptions(TimeoutOptions.enabled());
clientConfigurationBuilderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
return builder.build();
}
private ClientOptions.Builder initializeClientOptionsBuilder() {
if (getProperties().getCluster() != null) {
ClusterClientOptions.Builder builder = ClusterClientOptions.builder();
Refresh refreshProperties = getProperties().getLettuce().getCluster().getRefresh();
Builder refreshBuilder = ClusterTopologyRefreshOptions.builder()
.dynamicRefreshSources(refreshProperties.isDynamicRefreshSources());
if (refreshProperties.getPeriod() != null) {
refreshBuilder.enablePeriodicRefresh(refreshProperties.getPeriod());
}
if (refreshProperties.isAdaptive()) {
refreshBuilder.enableAllAdaptiveRefreshTriggers();
}
return builder.topologyRefreshOptions(refreshBuilder.build());
}
return ClientOptions.builder();
}
private void customizeConfigurationFromUrl(LettuceClientConfiguration.LettuceClientConfigurationBuilder builder) {
if (urlUsesSsl()) {
builder.useSsl();
}
}
/**
* Inner class to allow optional commons-pool2 dependency.
*/
private static final class PoolBuilderFactory {
LettuceClientConfigurationBuilder createBuilder(Pool properties) {
return LettucePoolingClientConfiguration.builder().poolConfig(getPoolConfig(properties));
}
private GenericObjectPoolConfig<StatefulConnection<?, ?>> getPoolConfig(Pool properties) {
GenericObjectPoolConfig<StatefulConnection<?, ?>> config = new GenericObjectPoolConfig<>();
config.setMaxTotal(properties.getMaxActive());
config.setMaxIdle(properties.getMaxIdle());
config.setMinIdle(properties.getMinIdle());
if (properties.getTimeBetweenEvictionRuns() != null) {
config.setTimeBetweenEvictionRuns(properties.getTimeBetweenEvictionRuns());
}
if (properties.getMaxWait() != null) {
config.setMaxWait(properties.getMaxWait());
}
return config;
}
}
}

View File

@@ -0,0 +1,176 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.data.redis.autoconfigure;
import java.util.List;
import org.springframework.boot.ssl.SslBundle;
import org.springframework.boot.ssl.SslBundles;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Adapts {@link RedisProperties} to {@link RedisConnectionDetails}.
*
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
* @author Scott Frederick
* @author Yanming Zhou
* @author Phillip Webb
*/
class PropertiesRedisConnectionDetails implements RedisConnectionDetails {
private final RedisProperties properties;
private final SslBundles sslBundles;
PropertiesRedisConnectionDetails(RedisProperties properties, SslBundles sslBundles) {
this.properties = properties;
this.sslBundles = sslBundles;
}
@Override
public String getUsername() {
RedisUrl redisUrl = getRedisUrl();
return (redisUrl != null) ? redisUrl.credentials().username() : this.properties.getUsername();
}
@Override
public String getPassword() {
RedisUrl redisUrl = getRedisUrl();
return (redisUrl != null) ? redisUrl.credentials().password() : this.properties.getPassword();
}
@Override
public Standalone getStandalone() {
RedisUrl redisUrl = getRedisUrl();
return (redisUrl != null)
? Standalone.of(redisUrl.uri().getHost(), redisUrl.uri().getPort(), redisUrl.database(), getSslBundle())
: Standalone.of(this.properties.getHost(), this.properties.getPort(), this.properties.getDatabase(),
getSslBundle());
}
private SslBundle getSslBundle() {
if (!this.properties.getSsl().isEnabled()) {
return null;
}
String bundleName = this.properties.getSsl().getBundle();
if (StringUtils.hasLength(bundleName)) {
Assert.notNull(this.sslBundles, "SSL bundle name has been set but no SSL bundles found in context");
return this.sslBundles.getBundle(bundleName);
}
return SslBundle.systemDefault();
}
@Override
public Sentinel getSentinel() {
RedisProperties.Sentinel sentinel = this.properties.getSentinel();
return (sentinel != null) ? new PropertiesSentinel(getStandalone().getDatabase(), sentinel) : null;
}
@Override
public Cluster getCluster() {
RedisProperties.Cluster cluster = this.properties.getCluster();
return (cluster != null) ? new PropertiesCluster(cluster) : null;
}
private RedisUrl getRedisUrl() {
return RedisUrl.of(this.properties.getUrl());
}
private List<Node> asNodes(List<String> nodes) {
return nodes.stream().map(this::asNode).toList();
}
private Node asNode(String node) {
int portSeparatorIndex = node.lastIndexOf(':');
String host = node.substring(0, portSeparatorIndex);
int port = Integer.parseInt(node.substring(portSeparatorIndex + 1));
return new Node(host, port);
}
/**
* {@link Cluster} implementation backed by properties.
*/
private class PropertiesCluster implements Cluster {
private final List<Node> nodes;
PropertiesCluster(RedisProperties.Cluster properties) {
this.nodes = asNodes(properties.getNodes());
}
@Override
public List<Node> getNodes() {
return this.nodes;
}
@Override
public SslBundle getSslBundle() {
return PropertiesRedisConnectionDetails.this.getSslBundle();
}
}
/**
* {@link Sentinel} implementation backed by properties.
*/
private class PropertiesSentinel implements Sentinel {
private final int database;
private final RedisProperties.Sentinel properties;
PropertiesSentinel(int database, RedisProperties.Sentinel properties) {
this.database = database;
this.properties = properties;
}
@Override
public int getDatabase() {
return this.database;
}
@Override
public String getMaster() {
return this.properties.getMaster();
}
@Override
public List<Node> getNodes() {
return asNodes(this.properties.getNodes());
}
@Override
public String getUsername() {
return this.properties.getUsername();
}
@Override
public String getPassword() {
return this.properties.getPassword();
}
@Override
public SslBundle getSslBundle() {
return PropertiesRedisConnectionDetails.this.getSslBundle();
}
}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.data.redis.autoconfigure;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.ssl.SslBundles;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
/**
* {@link EnableAutoConfiguration Auto-configuration} for Spring Data's Redis support.
*
* @author Dave Syer
* @author Andy Wilkinson
* @author Christian Dupuis
* @author Christoph Strobl
* @author Phillip Webb
* @author Eddú Meléndez
* @author Stephane Nicoll
* @author Marco Aust
* @author Mark Paluch
* @since 4.0.0
*/
@AutoConfiguration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean(RedisConnectionDetails.class)
PropertiesRedisConnectionDetails redisConnectionDetails(RedisProperties properties,
ObjectProvider<SslBundles> sslBundles) {
return new PropertiesRedisConnectionDetails(properties, sslBundles.getIfAvailable());
}
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
return new StringRedisTemplate(redisConnectionFactory);
}
}

View File

@@ -0,0 +1,203 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.data.redis.autoconfigure;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.data.redis.autoconfigure.RedisConnectionDetails.Cluster;
import org.springframework.boot.data.redis.autoconfigure.RedisConnectionDetails.Node;
import org.springframework.boot.data.redis.autoconfigure.RedisConnectionDetails.Sentinel;
import org.springframework.boot.data.redis.autoconfigure.RedisProperties.Pool;
import org.springframework.boot.ssl.SslBundle;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisNode;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.util.ClassUtils;
/**
* Base Redis connection configuration.
*
* @author Mark Paluch
* @author Stephane Nicoll
* @author Alen Turkovic
* @author Scott Frederick
* @author Eddú Meléndez
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
* @author Yanming Zhou
*/
abstract class RedisConnectionConfiguration {
private static final boolean COMMONS_POOL2_AVAILABLE = ClassUtils.isPresent("org.apache.commons.pool2.ObjectPool",
RedisConnectionConfiguration.class.getClassLoader());
private final RedisProperties properties;
private final RedisStandaloneConfiguration standaloneConfiguration;
private final RedisSentinelConfiguration sentinelConfiguration;
private final RedisClusterConfiguration clusterConfiguration;
private final RedisConnectionDetails connectionDetails;
protected final Mode mode;
protected RedisConnectionConfiguration(RedisProperties properties, RedisConnectionDetails connectionDetails,
ObjectProvider<RedisStandaloneConfiguration> standaloneConfigurationProvider,
ObjectProvider<RedisSentinelConfiguration> sentinelConfigurationProvider,
ObjectProvider<RedisClusterConfiguration> clusterConfigurationProvider) {
this.properties = properties;
this.standaloneConfiguration = standaloneConfigurationProvider.getIfAvailable();
this.sentinelConfiguration = sentinelConfigurationProvider.getIfAvailable();
this.clusterConfiguration = clusterConfigurationProvider.getIfAvailable();
this.connectionDetails = connectionDetails;
this.mode = determineMode();
}
protected final RedisStandaloneConfiguration getStandaloneConfig() {
if (this.standaloneConfiguration != null) {
return this.standaloneConfiguration;
}
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
config.setHostName(this.connectionDetails.getStandalone().getHost());
config.setPort(this.connectionDetails.getStandalone().getPort());
config.setUsername(this.connectionDetails.getUsername());
config.setPassword(RedisPassword.of(this.connectionDetails.getPassword()));
config.setDatabase(this.connectionDetails.getStandalone().getDatabase());
return config;
}
protected final RedisSentinelConfiguration getSentinelConfig() {
if (this.sentinelConfiguration != null) {
return this.sentinelConfiguration;
}
if (this.connectionDetails.getSentinel() != null) {
RedisSentinelConfiguration config = new RedisSentinelConfiguration();
config.master(this.connectionDetails.getSentinel().getMaster());
config.setSentinels(createSentinels(this.connectionDetails.getSentinel()));
config.setUsername(this.connectionDetails.getUsername());
String password = this.connectionDetails.getPassword();
if (password != null) {
config.setPassword(RedisPassword.of(password));
}
config.setSentinelUsername(this.connectionDetails.getSentinel().getUsername());
String sentinelPassword = this.connectionDetails.getSentinel().getPassword();
if (sentinelPassword != null) {
config.setSentinelPassword(RedisPassword.of(sentinelPassword));
}
config.setDatabase(this.connectionDetails.getSentinel().getDatabase());
return config;
}
return null;
}
/**
* Create a {@link RedisClusterConfiguration} if necessary.
* @return {@literal null} if no cluster settings are set.
*/
protected final RedisClusterConfiguration getClusterConfiguration() {
if (this.clusterConfiguration != null) {
return this.clusterConfiguration;
}
RedisProperties.Cluster clusterProperties = this.properties.getCluster();
if (this.connectionDetails.getCluster() != null) {
RedisClusterConfiguration config = new RedisClusterConfiguration();
config.setClusterNodes(getNodes(this.connectionDetails.getCluster()));
if (clusterProperties != null && clusterProperties.getMaxRedirects() != null) {
config.setMaxRedirects(clusterProperties.getMaxRedirects());
}
config.setUsername(this.connectionDetails.getUsername());
String password = this.connectionDetails.getPassword();
if (password != null) {
config.setPassword(RedisPassword.of(password));
}
return config;
}
return null;
}
private List<RedisNode> getNodes(Cluster cluster) {
return cluster.getNodes().stream().map(this::asRedisNode).toList();
}
private RedisNode asRedisNode(Node node) {
return new RedisNode(node.host(), node.port());
}
protected final RedisProperties getProperties() {
return this.properties;
}
protected SslBundle getSslBundle() {
return switch (this.mode) {
case STANDALONE -> (this.connectionDetails.getStandalone() != null)
? this.connectionDetails.getStandalone().getSslBundle() : null;
case CLUSTER -> (this.connectionDetails.getCluster() != null)
? this.connectionDetails.getCluster().getSslBundle() : null;
case SENTINEL -> (this.connectionDetails.getSentinel() != null)
? this.connectionDetails.getSentinel().getSslBundle() : null;
};
}
protected final boolean isSslEnabled() {
return getProperties().getSsl().isEnabled();
}
protected final boolean urlUsesSsl() {
return RedisUrl.of(this.properties.getUrl()).useSsl();
}
protected boolean isPoolEnabled(Pool pool) {
Boolean enabled = pool.getEnabled();
return (enabled != null) ? enabled : COMMONS_POOL2_AVAILABLE;
}
private List<RedisNode> createSentinels(Sentinel sentinel) {
List<RedisNode> nodes = new ArrayList<>();
for (Node node : sentinel.getNodes()) {
nodes.add(asRedisNode(node));
}
return nodes;
}
protected final RedisConnectionDetails getConnectionDetails() {
return this.connectionDetails;
}
private Mode determineMode() {
if (getSentinelConfig() != null) {
return Mode.SENTINEL;
}
if (getClusterConfiguration() != null) {
return Mode.CLUSTER;
}
return Mode.STANDALONE;
}
enum Mode {
STANDALONE, CLUSTER, SENTINEL
}
}

View File

@@ -0,0 +1,260 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.data.redis.autoconfigure;
import java.util.List;
import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails;
import org.springframework.boot.ssl.SslBundle;
import org.springframework.util.Assert;
/**
* Details required to establish a connection to a Redis service.
*
* @author Moritz Halbritter
* @author Andy Wilkinson
* @since 4.0.0
*/
public interface RedisConnectionDetails extends ConnectionDetails {
/**
* Login username of the redis server.
* @return the login username of the redis server
*/
default String getUsername() {
return null;
}
/**
* Login password of the redis server.
* @return the login password of the redis server
*/
default String getPassword() {
return null;
}
/**
* Redis standalone configuration. Mutually exclusive with {@link #getSentinel()} and
* {@link #getCluster()}.
* @return the Redis standalone configuration
*/
default Standalone getStandalone() {
return null;
}
/**
* Redis sentinel configuration. Mutually exclusive with {@link #getStandalone()} and
* {@link #getCluster()}.
* @return the Redis sentinel configuration
*/
default Sentinel getSentinel() {
return null;
}
/**
* Redis cluster configuration. Mutually exclusive with {@link #getStandalone()} and
* {@link #getSentinel()}.
* @return the Redis cluster configuration
*/
default Cluster getCluster() {
return null;
}
/**
* Redis standalone configuration.
*/
interface Standalone {
/**
* Redis server host.
* @return the redis server host
*/
String getHost();
/**
* Redis server port.
* @return the redis server port
*/
int getPort();
/**
* Database index used by the connection factory.
* @return the database index used by the connection factory
*/
default int getDatabase() {
return 0;
}
/**
* SSL bundle to use.
* @return the SSL bundle to use
* @since 3.5.0
*/
default SslBundle getSslBundle() {
return null;
}
/**
* Creates a new instance with the given host and port.
* @param host the host
* @param port the port
* @return the new instance
*/
static Standalone of(String host, int port) {
return of(host, port, 0, null);
}
/**
* Creates a new instance with the given host, port and SSL bundle.
* @param host the host
* @param port the port
* @param sslBundle the SSL bundle
* @return the new instance
* @since 3.5.0
*/
static Standalone of(String host, int port, SslBundle sslBundle) {
return of(host, port, 0, sslBundle);
}
/**
* Creates a new instance with the given host, port and database.
* @param host the host
* @param port the port
* @param database the database
* @return the new instance
*/
static Standalone of(String host, int port, int database) {
return of(host, port, database, null);
}
/**
* Creates a new instance with the given host, port, database and SSL bundle.
* @param host the host
* @param port the port
* @param database the database
* @param sslBundle the SSL bundle
* @return the new instance
* @since 3.5.0
*/
static Standalone of(String host, int port, int database, SslBundle sslBundle) {
Assert.hasLength(host, "'host' must not be empty");
return new Standalone() {
@Override
public String getHost() {
return host;
}
@Override
public int getPort() {
return port;
}
@Override
public int getDatabase() {
return database;
}
@Override
public SslBundle getSslBundle() {
return sslBundle;
}
};
}
}
/**
* Redis sentinel configuration.
*/
interface Sentinel {
/**
* Database index used by the connection factory.
* @return the database index used by the connection factory
*/
int getDatabase();
/**
* Name of the Redis server.
* @return the name of the Redis server
*/
String getMaster();
/**
* List of nodes.
* @return the list of nodes
*/
List<Node> getNodes();
/**
* Login username for authenticating with sentinel(s).
* @return the login username for authenticating with sentinel(s) or {@code null}
*/
String getUsername();
/**
* Password for authenticating with sentinel(s).
* @return the password for authenticating with sentinel(s) or {@code null}
*/
String getPassword();
/**
* SSL bundle to use.
* @return the SSL bundle to use
* @since 3.5.0
*/
default SslBundle getSslBundle() {
return null;
}
}
/**
* Redis cluster configuration.
*/
interface Cluster {
/**
* Nodes to bootstrap from. This represents an "initial" list of cluster nodes and
* is required to have at least one entry.
* @return nodes to bootstrap from
*/
List<Node> getNodes();
/**
* SSL bundle to use.
* @return the SSL bundle to use
* @since 3.5.0
*/
default SslBundle getSslBundle() {
return null;
}
}
/**
* A node in a sentinel or cluster configuration.
*
* @param host the hostname of the node
* @param port the port of the node
*/
record Node(String host, int port) {
}
}

View File

@@ -0,0 +1,565 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.data.redis.autoconfigure;
import java.time.Duration;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* Configuration properties for Redis.
*
* @author Dave Syer
* @author Christoph Strobl
* @author Eddú Meléndez
* @author Marco Aust
* @author Mark Paluch
* @author Stephane Nicoll
* @author Scott Frederick
* @author Yanming Zhou
* @since 4.0.0
*/
@ConfigurationProperties("spring.data.redis")
public class RedisProperties {
/**
* Database index used by the connection factory.
*/
private int database = 0;
/**
* Connection URL. Overrides host, port, username, password, and database. Example:
* redis://user:password@example.com:6379/8
*/
private String url;
/**
* Redis server host.
*/
private String host = "localhost";
/**
* Login username of the redis server.
*/
private String username;
/**
* Login password of the redis server.
*/
private String password;
/**
* Redis server port.
*/
private int port = 6379;
/**
* Read timeout.
*/
private Duration timeout;
/**
* Connection timeout.
*/
private Duration connectTimeout;
/**
* Client name to be set on connections with CLIENT SETNAME.
*/
private String clientName;
/**
* Type of client to use. By default, auto-detected according to the classpath.
*/
private ClientType clientType;
private Sentinel sentinel;
private Cluster cluster;
private final Ssl ssl = new Ssl();
private final Jedis jedis = new Jedis();
private final Lettuce lettuce = new Lettuce();
public int getDatabase() {
return this.database;
}
public void setDatabase(int database) {
this.database = database;
}
public String getUrl() {
return this.url;
}
public void setUrl(String url) {
this.url = url;
}
public String getHost() {
return this.host;
}
public void setHost(String host) {
this.host = host;
}
public String getUsername() {
return this.username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return this.password;
}
public void setPassword(String password) {
this.password = password;
}
public int getPort() {
return this.port;
}
public void setPort(int port) {
this.port = port;
}
public Ssl getSsl() {
return this.ssl;
}
public void setTimeout(Duration timeout) {
this.timeout = timeout;
}
public Duration getTimeout() {
return this.timeout;
}
public Duration getConnectTimeout() {
return this.connectTimeout;
}
public void setConnectTimeout(Duration connectTimeout) {
this.connectTimeout = connectTimeout;
}
public String getClientName() {
return this.clientName;
}
public void setClientName(String clientName) {
this.clientName = clientName;
}
public ClientType getClientType() {
return this.clientType;
}
public void setClientType(ClientType clientType) {
this.clientType = clientType;
}
public Sentinel getSentinel() {
return this.sentinel;
}
public void setSentinel(Sentinel sentinel) {
this.sentinel = sentinel;
}
public Cluster getCluster() {
return this.cluster;
}
public void setCluster(Cluster cluster) {
this.cluster = cluster;
}
public Jedis getJedis() {
return this.jedis;
}
public Lettuce getLettuce() {
return this.lettuce;
}
/**
* Type of Redis client to use.
*/
public enum ClientType {
/**
* Use the Lettuce redis client.
*/
LETTUCE,
/**
* Use the Jedis redis client.
*/
JEDIS
}
/**
* Pool properties.
*/
public static class Pool {
/**
* Whether to enable the pool. Enabled automatically if "commons-pool2" is
* available. With Jedis, pooling is implicitly enabled in sentinel mode and this
* setting only applies to single node setup.
*/
private Boolean enabled;
/**
* Maximum number of "idle" connections in the pool. Use a negative value to
* indicate an unlimited number of idle connections.
*/
private int maxIdle = 8;
/**
* Target for the minimum number of idle connections to maintain in the pool. This
* setting only has an effect if both it and time between eviction runs are
* positive.
*/
private int minIdle = 0;
/**
* Maximum number of connections that can be allocated by the pool at a given
* time. Use a negative value for no limit.
*/
private int maxActive = 8;
/**
* Maximum amount of time a connection allocation should block before throwing an
* exception when the pool is exhausted. Use a negative value to block
* indefinitely.
*/
private Duration maxWait = Duration.ofMillis(-1);
/**
* Time between runs of the idle object evictor thread. When positive, the idle
* object evictor thread starts, otherwise no idle object eviction is performed.
*/
private Duration timeBetweenEvictionRuns;
public Boolean getEnabled() {
return this.enabled;
}
public void setEnabled(Boolean enabled) {
this.enabled = enabled;
}
public int getMaxIdle() {
return this.maxIdle;
}
public void setMaxIdle(int maxIdle) {
this.maxIdle = maxIdle;
}
public int getMinIdle() {
return this.minIdle;
}
public void setMinIdle(int minIdle) {
this.minIdle = minIdle;
}
public int getMaxActive() {
return this.maxActive;
}
public void setMaxActive(int maxActive) {
this.maxActive = maxActive;
}
public Duration getMaxWait() {
return this.maxWait;
}
public void setMaxWait(Duration maxWait) {
this.maxWait = maxWait;
}
public Duration getTimeBetweenEvictionRuns() {
return this.timeBetweenEvictionRuns;
}
public void setTimeBetweenEvictionRuns(Duration timeBetweenEvictionRuns) {
this.timeBetweenEvictionRuns = timeBetweenEvictionRuns;
}
}
/**
* Cluster properties.
*/
public static class Cluster {
/**
* List of "host:port" pairs to bootstrap from. This represents an "initial" list
* of cluster nodes and is required to have at least one entry.
*/
private List<String> nodes;
/**
* Maximum number of redirects to follow when executing commands across the
* cluster.
*/
private Integer maxRedirects;
public List<String> getNodes() {
return this.nodes;
}
public void setNodes(List<String> nodes) {
this.nodes = nodes;
}
public Integer getMaxRedirects() {
return this.maxRedirects;
}
public void setMaxRedirects(Integer maxRedirects) {
this.maxRedirects = maxRedirects;
}
}
/**
* Redis sentinel properties.
*/
public static class Sentinel {
/**
* Name of the Redis server.
*/
private String master;
/**
* List of "host:port" pairs.
*/
private List<String> nodes;
/**
* Login username for authenticating with sentinel(s).
*/
private String username;
/**
* Password for authenticating with sentinel(s).
*/
private String password;
public String getMaster() {
return this.master;
}
public void setMaster(String master) {
this.master = master;
}
public List<String> getNodes() {
return this.nodes;
}
public void setNodes(List<String> nodes) {
this.nodes = nodes;
}
public String getUsername() {
return this.username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return this.password;
}
public void setPassword(String password) {
this.password = password;
}
}
public static class Ssl {
/**
* Whether to enable SSL support. Enabled automatically if "bundle" is provided
* unless specified otherwise.
*/
private Boolean enabled;
/**
* SSL bundle name.
*/
private String bundle;
public boolean isEnabled() {
return (this.enabled != null) ? this.enabled : this.bundle != null;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getBundle() {
return this.bundle;
}
public void setBundle(String bundle) {
this.bundle = bundle;
}
}
/**
* Jedis client properties.
*/
public static class Jedis {
/**
* Jedis pool configuration.
*/
private final Pool pool = new Pool();
public Pool getPool() {
return this.pool;
}
}
/**
* Lettuce client properties.
*/
public static class Lettuce {
/**
* Shutdown timeout.
*/
private Duration shutdownTimeout = Duration.ofMillis(100);
/**
* Defines from which Redis nodes data is read.
*/
private String readFrom;
/**
* Lettuce pool configuration.
*/
private final Pool pool = new Pool();
private final Cluster cluster = new Cluster();
public Duration getShutdownTimeout() {
return this.shutdownTimeout;
}
public void setShutdownTimeout(Duration shutdownTimeout) {
this.shutdownTimeout = shutdownTimeout;
}
public void setReadFrom(String readFrom) {
this.readFrom = readFrom;
}
public String getReadFrom() {
return this.readFrom;
}
public Pool getPool() {
return this.pool;
}
public Cluster getCluster() {
return this.cluster;
}
public static class Cluster {
private final Refresh refresh = new Refresh();
public Refresh getRefresh() {
return this.refresh;
}
public static class Refresh {
/**
* Whether to discover and query all cluster nodes for obtaining the
* cluster topology. When set to false, only the initial seed nodes are
* used as sources for topology discovery.
*/
private boolean dynamicRefreshSources = true;
/**
* Cluster topology refresh period.
*/
private Duration period;
/**
* Whether adaptive topology refreshing using all available refresh
* triggers should be used.
*/
private boolean adaptive;
public boolean isDynamicRefreshSources() {
return this.dynamicRefreshSources;
}
public void setDynamicRefreshSources(boolean dynamicRefreshSources) {
this.dynamicRefreshSources = dynamicRefreshSources;
}
public Duration getPeriod() {
return this.period;
}
public void setPeriod(Duration period) {
this.period = period;
}
public boolean isAdaptive() {
return this.adaptive;
}
public void setAdaptive(boolean adaptive) {
this.adaptive = adaptive;
}
}
}
}
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.data.redis.autoconfigure;
import reactor.core.publisher.Flux;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.ResourceLoader;
import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory;
import org.springframework.data.redis.core.ReactiveRedisTemplate;
import org.springframework.data.redis.core.ReactiveStringRedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
/**
* {@link EnableAutoConfiguration Auto-configuration} for Spring Data's reactive Redis
* support.
*
* @author Mark Paluch
* @author Stephane Nicoll
* @since 4.0.0
*/
@AutoConfiguration(after = RedisAutoConfiguration.class)
@ConditionalOnClass({ ReactiveRedisConnectionFactory.class, ReactiveRedisTemplate.class, Flux.class })
public class RedisReactiveAutoConfiguration {
@Bean
@ConditionalOnMissingBean(name = "reactiveRedisTemplate")
@ConditionalOnBean(ReactiveRedisConnectionFactory.class)
public ReactiveRedisTemplate<Object, Object> reactiveRedisTemplate(
ReactiveRedisConnectionFactory reactiveRedisConnectionFactory, ResourceLoader resourceLoader) {
RedisSerializer<Object> javaSerializer = RedisSerializer.java(resourceLoader.getClassLoader());
RedisSerializationContext<Object, Object> serializationContext = RedisSerializationContext
.newSerializationContext()
.key(javaSerializer)
.value(javaSerializer)
.hashKey(javaSerializer)
.hashValue(javaSerializer)
.build();
return new ReactiveRedisTemplate<>(reactiveRedisConnectionFactory, serializationContext);
}
@Bean
@ConditionalOnMissingBean(name = "reactiveStringRedisTemplate")
@ConditionalOnBean(ReactiveRedisConnectionFactory.class)
public ReactiveStringRedisTemplate reactiveStringRedisTemplate(
ReactiveRedisConnectionFactory reactiveRedisConnectionFactory) {
return new ReactiveStringRedisTemplate(reactiveRedisConnectionFactory);
}
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.data.redis.autoconfigure;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Import;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.repository.configuration.EnableRedisRepositories;
import org.springframework.data.redis.repository.support.RedisRepositoryFactoryBean;
/**
* {@link EnableAutoConfiguration Auto-configuration} for Spring Data's Redis
* Repositories.
*
* @author Eddú Meléndez
* @author Stephane Nicoll
* @since 4.0.0
* @see EnableRedisRepositories
*/
@AutoConfiguration(after = RedisAutoConfiguration.class)
@ConditionalOnClass(EnableRedisRepositories.class)
@ConditionalOnBean(RedisConnectionFactory.class)
@ConditionalOnBooleanProperty(name = "spring.data.redis.repositories.enabled", matchIfMissing = true)
@ConditionalOnMissingBean(RedisRepositoryFactoryBean.class)
@Import(RedisRepositoriesRegistrar.class)
public class RedisRepositoriesAutoConfiguration {
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.data.redis.autoconfigure;
import java.lang.annotation.Annotation;
import org.springframework.boot.autoconfigure.data.AbstractRepositoryConfigurationSourceSupport;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.data.redis.repository.configuration.EnableRedisRepositories;
import org.springframework.data.redis.repository.configuration.RedisRepositoryConfigurationExtension;
import org.springframework.data.repository.config.RepositoryConfigurationExtension;
/**
* {@link ImportBeanDefinitionRegistrar} used to auto-configure Spring Data Redis
* Repositories.
*
* @author Eddú Meléndez
*/
class RedisRepositoriesRegistrar extends AbstractRepositoryConfigurationSourceSupport {
@Override
protected Class<? extends Annotation> getAnnotation() {
return EnableRedisRepositories.class;
}
@Override
protected Class<?> getConfiguration() {
return EnableRedisRepositoriesConfiguration.class;
}
@Override
protected RepositoryConfigurationExtension getRepositoryConfigurationExtension() {
return new RedisRepositoryConfigurationExtension();
}
@EnableRedisRepositories
private static final class EnableRedisRepositoriesConfiguration {
}
}

View File

@@ -0,0 +1,98 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.data.redis.autoconfigure;
import java.net.URI;
import java.net.URISyntaxException;
import org.springframework.util.StringUtils;
/**
* A parsed URL used to connect to Redis.
*
* @param uri the source URI
* @param useSsl if SSL is used to connect
* @param credentials the connection credentials
* @param database the database index
* @author Mark Paluch
* @author Stephane Nicoll
* @author Alen Turkovic
* @author Scott Frederick
* @author Eddú Meléndez
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
* @author Yanming Zhou
* @author Phillip Webb
*/
record RedisUrl(URI uri, boolean useSsl, Credentials credentials, int database) {
static RedisUrl of(String url) {
return (url != null) ? of(toUri(url)) : null;
}
private static RedisUrl of(URI uri) {
boolean useSsl = ("rediss".equals(uri.getScheme()));
Credentials credentials = Credentials.fromUserInfo(uri.getUserInfo());
int database = getDatabase(uri);
return new RedisUrl(uri, useSsl, credentials, database);
}
private static int getDatabase(URI uri) {
String path = uri.getPath();
String[] split = (!StringUtils.hasText(path)) ? new String[0] : path.split("/", 2);
return (split.length > 1 && !split[1].isEmpty()) ? Integer.parseInt(split[1]) : 0;
}
private static URI toUri(String url) {
try {
URI uri = new URI(url);
String scheme = uri.getScheme();
if (!"redis".equals(scheme) && !"rediss".equals(scheme)) {
throw new RedisUrlSyntaxException(url);
}
return uri;
}
catch (URISyntaxException ex) {
throw new RedisUrlSyntaxException(url, ex);
}
}
/**
* Redis connection credentials.
*
* @param username the username or {@code null}
* @param password the password
*/
record Credentials(String username, String password) {
private static final Credentials NONE = new Credentials(null, null);
private static Credentials fromUserInfo(String userInfo) {
if (userInfo == null) {
return NONE;
}
int index = userInfo.indexOf(':');
if (index != -1) {
return new Credentials(userInfo.substring(0, index), userInfo.substring(index + 1));
}
return new Credentials(null, userInfo);
}
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.data.redis.autoconfigure;
/**
* Exception thrown when a Redis URL is malformed or invalid.
*
* @author Scott Frederick
*/
class RedisUrlSyntaxException extends RuntimeException {
private final String url;
RedisUrlSyntaxException(String url, Exception cause) {
super(buildMessage(url), cause);
this.url = url;
}
RedisUrlSyntaxException(String url) {
super(buildMessage(url));
this.url = url;
}
String getUrl() {
return this.url;
}
private static String buildMessage(String url) {
return "Invalid Redis URL '" + url + "'";
}
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.data.redis.autoconfigure;
import java.net.URI;
import java.net.URISyntaxException;
import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
import org.springframework.boot.diagnostics.FailureAnalysis;
/**
* A {@code FailureAnalyzer} that performs analysis of failures caused by a
* {@link RedisUrlSyntaxException}.
*
* @author Scott Frederick
*/
class RedisUrlSyntaxFailureAnalyzer extends AbstractFailureAnalyzer<RedisUrlSyntaxException> {
@Override
protected FailureAnalysis analyze(Throwable rootFailure, RedisUrlSyntaxException cause) {
try {
URI uri = new URI(cause.getUrl());
if ("redis-sentinel".equals(uri.getScheme())) {
return new FailureAnalysis(getUnsupportedSchemeDescription(cause.getUrl(), uri.getScheme()),
"Use spring.data.redis.sentinel properties instead of spring.data.redis.url to configure Redis sentinel addresses.",
cause);
}
if ("redis-socket".equals(uri.getScheme())) {
return new FailureAnalysis(getUnsupportedSchemeDescription(cause.getUrl(), uri.getScheme()),
"Configure the appropriate Spring Data Redis connection beans directly instead of setting the property 'spring.data.redis.url'.",
cause);
}
if (!"redis".equals(uri.getScheme()) && !"rediss".equals(uri.getScheme())) {
return new FailureAnalysis(getUnsupportedSchemeDescription(cause.getUrl(), uri.getScheme()),
"Use the scheme 'redis://' for insecure or 'rediss://' for secure Redis standalone configuration.",
cause);
}
}
catch (URISyntaxException ex) {
// fall through to default description and action
}
return new FailureAnalysis(getDefaultDescription(cause.getUrl()),
"Review the value of the property 'spring.data.redis.url'.", cause);
}
private String getDefaultDescription(String url) {
return "The URL '" + url + "' is not valid for configuring Spring Data Redis. ";
}
private String getUnsupportedSchemeDescription(String url, String scheme) {
return getDefaultDescription(url) + "The scheme '" + scheme + "' is not supported.";
}
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Auto-configuration for Spring Data Redis.
*/
package org.springframework.boot.data.redis.autoconfigure;

View File

@@ -0,0 +1,319 @@
{
"groups": [],
"properties": [
{
"name": "spring.data.redis.repositories.enabled",
"type": "java.lang.Boolean",
"description": "Whether to enable Redis repositories.",
"defaultValue": true
},
{
"name": "spring.data.redis.ssl",
"type": "java.lang.Boolean",
"deprecation": {
"replacement": "spring.data.redis.ssl.enabled",
"level": "error"
}
},
{
"name": "spring.redis.client-name",
"type": "java.lang.String",
"deprecation": {
"replacement": "spring.data.redis.client-name",
"level": "error"
}
},
{
"name": "spring.redis.client-type",
"type": "org.springframework.boot.data.redis.autoconfigure.RedisProperties$ClientType",
"deprecation": {
"replacement": "spring.data.redis.client-type",
"level": "error"
}
},
{
"name": "spring.redis.cluster.max-redirects",
"type": "java.lang.Integer",
"deprecation": {
"replacement": "spring.data.redis.cluster.max-redirects",
"level": "error"
}
},
{
"name": "spring.redis.cluster.nodes",
"type": "java.util.List<java.lang.String>",
"deprecation": {
"replacement": "spring.data.redis.cluster.nodes",
"level": "error"
}
},
{
"name": "spring.redis.connect-timeout",
"type": "java.time.Duration",
"deprecation": {
"replacement": "spring.data.redis.connect-timeout",
"level": "error"
}
},
{
"name": "spring.redis.database",
"type": "java.lang.Integer",
"deprecation": {
"replacement": "spring.data.redis.database",
"level": "error"
}
},
{
"name": "spring.redis.host",
"type": "java.lang.String",
"deprecation": {
"replacement": "spring.data.redis.host",
"level": "error"
}
},
{
"name": "spring.redis.jedis.pool.enabled",
"type": "java.lang.Boolean",
"deprecation": {
"level": "error"
}
},
{
"name": "spring.redis.jedis.pool.max-active",
"type": "java.lang.Integer",
"deprecation": {
"level": "error"
}
},
{
"name": "spring.redis.jedis.pool.max-idle",
"type": "java.lang.Integer",
"deprecation": {
"level": "error"
}
},
{
"name": "spring.redis.jedis.pool.max-wait",
"type": "java.time.Duration",
"deprecation": {
"level": "error"
}
},
{
"name": "spring.redis.jedis.pool.min-idle",
"type": "java.lang.Integer",
"deprecation": {
"level": "error"
}
},
{
"name": "spring.redis.jedis.pool.time-between-eviction-runs",
"type": "java.time.Duration",
"deprecation": {
"level": "error"
}
},
{
"name": "spring.redis.lettuce.cluster.refresh.adaptive",
"type": "java.lang.Boolean",
"deprecation": {
"replacement": "spring.data.redis.lettuce.cluster.refresh.adaptive",
"level": "error"
}
},
{
"name": "spring.redis.lettuce.cluster.refresh.dynamic-refresh-sources",
"type": "java.lang.Boolean",
"deprecation": {
"replacement": "spring.data.redis.lettuce.cluster.refresh.dynamic-refresh-sources",
"level": "error"
}
},
{
"name": "spring.redis.lettuce.cluster.refresh.period",
"type": "java.time.Duration",
"deprecation": {
"replacement": "spring.data.redis.lettuce.cluster.refresh.period",
"level": "error"
}
},
{
"name": "spring.redis.lettuce.pool.enabled",
"type": "java.lang.Boolean",
"deprecation": {
"level": "error"
}
},
{
"name": "spring.redis.lettuce.pool.max-active",
"type": "java.lang.Integer",
"deprecation": {
"level": "error"
}
},
{
"name": "spring.redis.lettuce.pool.max-idle",
"type": "java.lang.Integer",
"deprecation": {
"level": "error"
}
},
{
"name": "spring.redis.lettuce.pool.max-wait",
"type": "java.time.Duration",
"deprecation": {
"level": "error"
}
},
{
"name": "spring.redis.lettuce.pool.min-idle",
"type": "java.lang.Integer",
"deprecation": {
"level": "error"
}
},
{
"name": "spring.redis.lettuce.pool.time-between-eviction-runs",
"type": "java.time.Duration",
"deprecation": {
"level": "error"
}
},
{
"name": "spring.redis.lettuce.shutdown-timeout",
"type": "java.time.Duration",
"deprecation": {
"replacement": "spring.data.redis.lettuce.shutdown-timeout",
"level": "error"
}
},
{
"name": "spring.redis.password",
"type": "java.lang.String",
"deprecation": {
"replacement": "spring.data.redis.password",
"level": "error"
}
},
{
"name": "spring.redis.port",
"type": "java.lang.Integer",
"deprecation": {
"replacement": "spring.data.redis.port",
"level": "error"
}
},
{
"name": "spring.redis.sentinel.master",
"type": "java.lang.String",
"deprecation": {
"replacement": "spring.data.redis.sentinel.master",
"level": "error"
}
},
{
"name": "spring.redis.sentinel.nodes",
"type": "java.util.List<java.lang.String>",
"deprecation": {
"replacement": "spring.data.redis.sentinel.nodes",
"level": "error"
}
},
{
"name": "spring.redis.sentinel.password",
"type": "java.lang.String",
"deprecation": {
"replacement": "spring.data.redis.sentinel.password",
"level": "error"
}
},
{
"name": "spring.redis.sentinel.username",
"type": "java.lang.String",
"deprecation": {
"replacement": "spring.data.redis.sentinel.username",
"level": "error"
}
},
{
"name": "spring.redis.ssl",
"type": "java.lang.Boolean",
"deprecation": {
"replacement": "spring.data.redis.ssl",
"level": "error"
}
},
{
"name": "spring.redis.timeout",
"type": "java.time.Duration",
"deprecation": {
"replacement": "spring.data.redis.timeout",
"level": "error"
}
},
{
"name": "spring.redis.url",
"type": "java.lang.String",
"deprecation": {
"replacement": "spring.data.redis.url",
"level": "error"
}
},
{
"name": "spring.redis.username",
"type": "java.lang.String",
"deprecation": {
"replacement": "spring.data.redis.username",
"level": "error"
}
}
],
"hints": [
{
"name": "spring.data.redis.lettuce.read-from",
"values": [
{
"value": "any",
"description": "Read from any node."
},
{
"value": "any-replica",
"description": "Read from any replica node."
},
{
"value": "lowest-latency",
"description": "Read from the node with the lowest latency during topology discovery."
},
{
"value": "regex:",
"description": "Read from any node that has RedisURI matching with the given pattern."
},
{
"value": "replica",
"description": "Read from the replica only."
},
{
"value": "replica-preferred",
"description": "Read preferred from replica and fall back to upstream if no replica is available."
},
{
"value": "subnet:",
"description": "Read from any node in the subnets."
},
{
"value": "upstream",
"description": "Read from the upstream only."
},
{
"value": "upstream-preferred",
"description": "Read preferred from the upstream and fall back to a replica if the upstream is not available."
}
],
"providers": [
{
"name": "any"
}
]
}
]
}

View File

@@ -0,0 +1,4 @@
# Failure Analyzers
org.springframework.boot.diagnostics.FailureAnalyzer=\
org.springframework.boot.data.redis.autoconfigure.RedisUrlSyntaxFailureAnalyzer

View File

@@ -0,0 +1,3 @@
org.springframework.boot.data.redis.autoconfigure.RedisAutoConfiguration
org.springframework.boot.data.redis.autoconfigure.RedisReactiveAutoConfiguration
org.springframework.boot.data.redis.autoconfigure.RedisRepositoriesAutoConfiguration