Use Redis client configuration to configure connection factories

We now use LettuceClientConfiguration and JedisClientConfiguration to
configure connection factories. Client-specific configuration can be
customized by providing LettuceClientConfigurationBuilderCustomizer and
JedisClientConfigurationBuilderCustomizer beans.

See gh-9510, gh-8894, gh-9490
pull/9224/merge
Mark Paluch 8 years ago committed by Stephane Nicoll
parent eb764a715e
commit 866fdb5d91

@ -0,0 +1,40 @@
/*
* Copyright 2012-2017 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
*
* http://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.autoconfigure.data.redis;
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} via a {@link JedisClientConfigurationBuilder
* JedisClientConfiguration.JedisClientConfigurationBuilder} whilst retaining default
* auto-configuration.
*
* @author Mark Paluch
* @since 2.0.0
*/
@FunctionalInterface
public interface JedisClientConfigurationBuilderCustomizer {
/**
* Customize the {@link JedisClientConfigurationBuilder}.
* @param clientConfigurationBuilder the builder to customize
*/
void customize(JedisClientConfigurationBuilder clientConfigurationBuilder);
}

@ -17,6 +17,9 @@
package org.springframework.boot.autoconfigure.data.redis;
import java.net.UnknownHostException;
import java.time.Duration;
import java.util.Collections;
import java.util.List;
import org.apache.commons.pool2.impl.GenericObjectPool;
import redis.clients.jedis.Jedis;
@ -30,6 +33,9 @@ import org.springframework.context.annotation.Configuration;
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.JedisConnection;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.util.StringUtils;
@ -46,66 +52,70 @@ class JedisConnectionConfiguration extends RedisConnectionConfiguration {
private final RedisProperties properties;
private final List<JedisClientConfigurationBuilderCustomizer> builderCustomizers;
JedisConnectionConfiguration(RedisProperties properties,
ObjectProvider<RedisStandaloneConfiguration> standaloneConfiguration,
ObjectProvider<RedisSentinelConfiguration> sentinelConfiguration,
ObjectProvider<RedisClusterConfiguration> clusterConfiguration) {
super(properties, sentinelConfiguration, clusterConfiguration);
ObjectProvider<RedisClusterConfiguration> clusterConfiguration,
ObjectProvider<List<JedisClientConfigurationBuilderCustomizer>> builderCustomizers) {
super(properties, standaloneConfiguration, sentinelConfiguration,
clusterConfiguration);
this.properties = properties;
this.builderCustomizers = builderCustomizers
.getIfAvailable(Collections::emptyList);
}
@Bean
@ConditionalOnMissingBean(RedisConnectionFactory.class)
public JedisConnectionFactory redisConnectionFactory() throws UnknownHostException {
return applyProperties(createJedisConnectionFactory());
return createJedisConnectionFactory();
}
private JedisConnectionFactory applyProperties(JedisConnectionFactory factory) {
configureConnection(factory);
if (this.properties.isSsl()) {
factory.setUseSsl(true);
private JedisConnectionFactory createJedisConnectionFactory() {
JedisClientConfiguration clientConfiguration = getJedisClientConfiguration();
if (getSentinelConfig() != null) {
return new JedisConnectionFactory(getSentinelConfig(), clientConfiguration);
}
factory.setDatabase(this.properties.getDatabase());
if (this.properties.getTimeout() > 0) {
factory.setTimeout(this.properties.getTimeout());
if (getClusterConfiguration() != null) {
return new JedisConnectionFactory(getClusterConfiguration(),
clientConfiguration);
}
return factory;
return new JedisConnectionFactory(getStandaloneConfig(), clientConfiguration);
}
private void configureConnection(JedisConnectionFactory factory) {
if (StringUtils.hasText(this.properties.getUrl())) {
configureConnectionFromUrl(factory);
private JedisClientConfiguration getJedisClientConfiguration() {
JedisClientConfigurationBuilder builder = applyProperties(
JedisClientConfiguration.builder());
RedisProperties.Pool pool = this.properties.getJedis().getPool();
if (pool != null) {
applyPooling(pool, builder);
}
else {
factory.setHostName(this.properties.getHost());
factory.setPort(this.properties.getPort());
if (this.properties.getPassword() != null) {
factory.setPassword(this.properties.getPassword());
}
if (StringUtils.hasText(this.properties.getUrl())) {
customizeConfigurationFromUrl(builder);
}
customize(builder);
return builder.build();
}
private void configureConnectionFromUrl(JedisConnectionFactory factory) {
ConnectionInfo connectionInfo = parseUrl(this.properties.getUrl());
factory.setUseSsl(connectionInfo.isUseSsl());
factory.setHostName(connectionInfo.getHostName());
factory.setPort(connectionInfo.getPort());
if (connectionInfo.getPassword() != null) {
factory.setPassword(connectionInfo.getPassword());
private JedisClientConfigurationBuilder applyProperties(
JedisClientConfigurationBuilder builder) {
if (this.properties.isSsl()) {
builder.useSsl();
}
if (this.properties.getTimeout() != 0) {
Duration timeout = Duration.ofMillis(this.properties.getTimeout());
builder.readTimeout(timeout).connectTimeout(timeout);
}
return builder;
}
private JedisConnectionFactory createJedisConnectionFactory() {
RedisProperties.Pool pool = this.properties.getJedis().getPool();
JedisPoolConfig poolConfig = pool != null ? jedisPoolConfig(pool)
: new JedisPoolConfig();
if (getSentinelConfig() != null) {
return new JedisConnectionFactory(getSentinelConfig(), poolConfig);
}
if (getClusterConfiguration() != null) {
return new JedisConnectionFactory(getClusterConfiguration(), poolConfig);
}
return new JedisConnectionFactory(poolConfig);
private void applyPooling(RedisProperties.Pool pool,
JedisClientConfiguration.JedisClientConfigurationBuilder builder) {
builder.usePooling().poolConfig(jedisPoolConfig(pool));
}
private JedisPoolConfig jedisPoolConfig(RedisProperties.Pool pool) {
@ -117,4 +127,19 @@ class JedisConnectionConfiguration extends RedisConnectionConfiguration {
return config;
}
private void customizeConfigurationFromUrl(
JedisClientConfiguration.JedisClientConfigurationBuilder builder) {
ConnectionInfo connectionInfo = parseUrl(this.properties.getUrl());
if (connectionInfo.isUseSsl()) {
builder.useSsl();
}
}
private void customize(
JedisClientConfiguration.JedisClientConfigurationBuilder builder) {
for (JedisClientConfigurationBuilderCustomizer customizer : this.builderCustomizers) {
customizer.customize(builder);
}
}
}

@ -0,0 +1,40 @@
/*
* Copyright 2012-2017 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
*
* http://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.autoconfigure.data.redis;
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} via a {@link LettuceClientConfigurationBuilder
* LettuceClientConfiguration.LettuceClientConfigurationBuilder} whilst retaining default
* auto-configuration.
*
* @author Mark Paluch
* @since 2.0.0
*/
@FunctionalInterface
public interface LettuceClientConfigurationBuilderCustomizer {
/**
* Customize the {@link LettuceClientConfigurationBuilder}.
* @param clientConfigurationBuilder the builder to customize
*/
void customize(LettuceClientConfigurationBuilder clientConfigurationBuilder);
}

@ -17,6 +17,9 @@
package org.springframework.boot.autoconfigure.data.redis;
import java.net.UnknownHostException;
import java.time.Duration;
import java.util.Collections;
import java.util.List;
import io.lettuce.core.RedisClient;
import io.lettuce.core.cluster.RedisClusterClient;
@ -33,7 +36,10 @@ import org.springframework.context.annotation.Configuration;
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.DefaultLettucePool;
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.util.StringUtils;
@ -49,11 +55,18 @@ class LettuceConnectionConfiguration extends RedisConnectionConfiguration {
private final RedisProperties properties;
private final List<LettuceClientConfigurationBuilderCustomizer> builderCustomizers;
LettuceConnectionConfiguration(RedisProperties properties,
ObjectProvider<RedisStandaloneConfiguration> standaloneConfiguration,
ObjectProvider<RedisSentinelConfiguration> sentinelConfigurationProvider,
ObjectProvider<RedisClusterConfiguration> clusterConfigurationProvider) {
super(properties, sentinelConfigurationProvider, clusterConfigurationProvider);
ObjectProvider<RedisClusterConfiguration> clusterConfigurationProvider,
ObjectProvider<List<LettuceClientConfigurationBuilderCustomizer>> builderCustomizers) {
super(properties, standaloneConfiguration, sentinelConfigurationProvider,
clusterConfigurationProvider);
this.properties = properties;
this.builderCustomizers = builderCustomizers
.getIfAvailable(Collections::emptyList);
}
@Bean(destroyMethod = "shutdown")
@ -66,52 +79,26 @@ class LettuceConnectionConfiguration extends RedisConnectionConfiguration {
@ConditionalOnMissingBean(RedisConnectionFactory.class)
public LettuceConnectionFactory redisConnectionFactory(
ClientResources clientResources) throws UnknownHostException {
return applyProperties(createLettuceConnectionFactory(clientResources));
}
private LettuceConnectionFactory applyProperties(LettuceConnectionFactory factory) {
configureConnection(factory);
if (this.properties.isSsl()) {
factory.setUseSsl(true);
}
if (this.properties.getLettuce() != null) {
RedisProperties.Lettuce lettuce = this.properties.getLettuce();
if (lettuce.getShutdownTimeout() >= 0) {
factory.setShutdownTimeout(
this.properties.getLettuce().getShutdownTimeout());
}
if (this.properties.getLettuce().getPool() != null) {
return createLettuceConnectionFactory(clientResources);
}
return factory;
return createLettuceConnectionFactory(
getLettuceClientConfiguration(clientResources));
}
private void configureConnection(LettuceConnectionFactory factory) {
if (StringUtils.hasText(this.properties.getUrl())) {
configureConnectionFromUrl(factory);
}
else {
factory.setHostName(this.properties.getHost());
factory.setPort(this.properties.getPort());
if (this.properties.getPassword() != null) {
factory.setPassword(this.properties.getPassword());
}
factory.setDatabase(this.properties.getDatabase());
if (this.properties.getTimeout() > 0) {
factory.setTimeout(this.properties.getTimeout());
}
}
private LettuceConnectionFactory createLettuceConnectionFactory(
ClientResources clientResources) {
return new LettuceConnectionFactory(applyProperties(createLettucePool(),
this.properties.getLettuce().getPool(), clientResources));
}
private void configureConnectionFromUrl(LettuceConnectionFactory factory) {
ConnectionInfo connectionInfo = parseUrl(this.properties.getUrl());
factory.setUseSsl(connectionInfo.isUseSsl());
factory.setHostName(connectionInfo.getHostName());
factory.setPort(connectionInfo.getPort());
if (connectionInfo.getPassword() != null) {
factory.setPassword(connectionInfo.getPassword());
}
private DefaultLettucePool createLettucePool() {
return getSentinelConfig() != null ? new DefaultLettucePool(getSentinelConfig())
: new DefaultLettucePool();
}
private DefaultLettucePool applyProperties(DefaultLettucePool pool) {
private DefaultLettucePool applyProperties(DefaultLettucePool pool,
RedisProperties.Pool properties, ClientResources clientResources) {
if (StringUtils.hasText(this.properties.getUrl())) {
configureConnectionFromUrl(pool);
}
@ -126,6 +113,8 @@ class LettuceConnectionConfiguration extends RedisConnectionConfiguration {
if (this.properties.getTimeout() > 0) {
pool.setTimeout(this.properties.getTimeout());
}
pool.setPoolConfig(lettucePoolConfig(properties));
pool.setClientResources(clientResources);
pool.afterPropertiesSet();
return pool;
}
@ -139,59 +128,73 @@ class LettuceConnectionConfiguration extends RedisConnectionConfiguration {
}
}
private static GenericObjectPoolConfig lettucePoolConfig(RedisProperties.Pool props) {
GenericObjectPoolConfig config = new GenericObjectPoolConfig();
config.setMaxTotal(props.getMaxActive());
config.setMaxIdle(props.getMaxIdle());
config.setMinIdle(props.getMinIdle());
config.setMaxWaitMillis(props.getMaxWait());
return config;
}
private LettuceConnectionFactory createLettuceConnectionFactory(
ClientResources clientResources) {
LettuceClientConfiguration clientConfiguration) {
if (getSentinelConfig() != null) {
if (this.properties.getLettuce() != null
&& this.properties.getLettuce().getPool() != null) {
DefaultLettucePool lettucePool = new DefaultLettucePool(
getSentinelConfig());
return new LettuceConnectionFactory(applyProperties(
applyClientResources(lettucePool, clientResources)));
}
return applyClientResources(new LettuceConnectionFactory(getSentinelConfig()),
clientResources);
return new LettuceConnectionFactory(getSentinelConfig(), clientConfiguration);
}
if (getClusterConfiguration() != null) {
return applyClientResources(
new LettuceConnectionFactory(getClusterConfiguration()),
clientResources);
return new LettuceConnectionFactory(getClusterConfiguration(),
clientConfiguration);
}
if (this.properties.getLettuce() != null
&& this.properties.getLettuce().getPool() != null) {
GenericObjectPoolConfig config = lettucePoolConfig(
this.properties.getLettuce().getPool());
DefaultLettucePool lettucePool = new DefaultLettucePool(
this.properties.getHost(), this.properties.getPort(), config);
return new LettuceConnectionFactory(
applyProperties(applyClientResources(lettucePool, clientResources)));
}
return applyClientResources(new LettuceConnectionFactory(), clientResources);
return new LettuceConnectionFactory(getStandaloneConfig(), clientConfiguration);
}
private DefaultLettucePool applyClientResources(DefaultLettucePool lettucePool,
private LettuceClientConfiguration getLettuceClientConfiguration(
ClientResources clientResources) {
lettucePool.setClientResources(clientResources);
return lettucePool;
LettuceClientConfigurationBuilder builder = applyProperties(
LettuceClientConfiguration.builder());
if (StringUtils.hasText(this.properties.getUrl())) {
customizeConfigurationFromUrl(builder);
}
builder.clientResources(clientResources);
customize(builder);
return builder.build();
}
private LettuceConnectionFactory applyClientResources(
LettuceConnectionFactory factory, ClientResources clientResources) {
factory.setClientResources(clientResources);
return factory;
private LettuceClientConfigurationBuilder applyProperties(
LettuceClientConfiguration.LettuceClientConfigurationBuilder builder) {
if (this.properties.isSsl()) {
builder.useSsl();
}
if (this.properties.getTimeout() != 0) {
builder.commandTimeout(Duration.ofMillis(this.properties.getTimeout()));
}
if (this.properties.getLettuce() != null) {
RedisProperties.Lettuce lettuce = this.properties.getLettuce();
if (lettuce.getShutdownTimeout() >= 0) {
builder.shutdownTimeout(Duration
.ofSeconds(this.properties.getLettuce().getShutdownTimeout()));
}
}
return builder;
}
private GenericObjectPoolConfig lettucePoolConfig(RedisProperties.Pool props) {
GenericObjectPoolConfig config = new GenericObjectPoolConfig();
config.setMaxTotal(props.getMaxActive());
config.setMaxIdle(props.getMaxIdle());
config.setMinIdle(props.getMinIdle());
config.setMaxWaitMillis(props.getMaxWait());
return config;
private void customizeConfigurationFromUrl(
LettuceClientConfiguration.LettuceClientConfigurationBuilder builder) {
ConnectionInfo connectionInfo = parseUrl(this.properties.getUrl());
if (connectionInfo.isUseSsl()) {
builder.useSsl();
}
}
private void customize(
LettuceClientConfiguration.LettuceClientConfigurationBuilder builder) {
for (LettuceClientConfigurationBuilderCustomizer customizer : this.builderCustomizers) {
customizer.customize(builder);
}
}
}

@ -24,7 +24,9 @@ import java.util.List;
import org.springframework.beans.factory.ObjectProvider;
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.Assert;
import org.springframework.util.StringUtils;
@ -38,18 +40,43 @@ abstract class RedisConnectionConfiguration {
private final RedisProperties properties;
private final RedisStandaloneConfiguration standaloneConfiguration;
private final RedisSentinelConfiguration sentinelConfiguration;
private final RedisClusterConfiguration clusterConfiguration;
protected RedisConnectionConfiguration(RedisProperties properties,
ObjectProvider<RedisStandaloneConfiguration> standaloneConfigurationProvider,
ObjectProvider<RedisSentinelConfiguration> sentinelConfigurationProvider,
ObjectProvider<RedisClusterConfiguration> clusterConfigurationProvider) {
this.properties = properties;
this.standaloneConfiguration = standaloneConfigurationProvider.getIfAvailable();
this.sentinelConfiguration = sentinelConfigurationProvider.getIfAvailable();
this.clusterConfiguration = clusterConfigurationProvider.getIfAvailable();
}
protected final RedisStandaloneConfiguration getStandaloneConfig() {
if (this.standaloneConfiguration != null) {
return this.standaloneConfiguration;
}
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
if (StringUtils.hasText(this.properties.getUrl())) {
ConnectionInfo connectionInfo = parseUrl(this.properties.getUrl());
config.setHostName(connectionInfo.getHostName());
config.setPort(connectionInfo.getPort());
config.setPassword(RedisPassword.of(connectionInfo.getPassword()));
}
else {
config.setHostName(this.properties.getHost());
config.setPort(this.properties.getPort());
config.setPassword(RedisPassword.of(this.properties.getPassword()));
}
config.setDatabase(this.properties.getDatabase());
return config;
}
protected final RedisSentinelConfiguration getSentinelConfig() {
if (this.sentinelConfiguration != null) {
return this.sentinelConfiguration;

@ -28,6 +28,9 @@ import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.boot.testsupport.runner.classpath.ClassPathExclusions;
import org.springframework.boot.testsupport.runner.classpath.ModifiedClassPathRunner;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration.JedisClientConfigurationBuilder;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.util.StringUtils;
@ -62,6 +65,17 @@ public class RedisAutoConfigurationJedisTests {
assertThat(cf.isUseSsl()).isFalse();
}
@Test
public void testCustomizeRedisConfiguration() throws Exception {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(RedisAutoConfiguration.class);
ctx.register(CustomConfiguration.class);
ctx.refresh();
JedisConnectionFactory cf = ctx.getBean(JedisConnectionFactory.class);
assertThat(cf.isUseSsl()).isTrue();
}
@Test
public void testRedisUrlConfiguration() throws Exception {
load("spring.redis.host:foo",
@ -172,4 +186,14 @@ public class RedisAutoConfigurationJedisTests {
}
}
@Configuration
static class CustomConfiguration {
@Bean
JedisClientConfigurationBuilderCustomizer customizer() {
return JedisClientConfigurationBuilder::useSsl;
}
}
}

@ -28,7 +28,10 @@ import org.junit.Test;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.lettuce.DefaultLettucePool;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration.LettuceClientConfigurationBuilder;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
@ -78,6 +81,17 @@ public class RedisAutoConfigurationTests {
assertThat(cf.isUseSsl()).isFalse();
}
@Test
public void testCustomizeRedisConfiguration() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(RedisAutoConfiguration.class);
ctx.register(CustomConfiguration.class);
ctx.refresh();
LettuceConnectionFactory cf = ctx.getBean(LettuceConnectionFactory.class);
assertThat(cf.isUseSsl()).isTrue();
}
@Test
public void testRedisUrlConfiguration() throws Exception {
load("spring.redis.host:foo",
@ -111,7 +125,6 @@ public class RedisAutoConfigurationTests {
"spring.redis.lettuce.pool.max-wait:2000");
LettuceConnectionFactory cf = this.context
.getBean(LettuceConnectionFactory.class);
assertThat(cf.getHostName()).isEqualTo("foo");
assertThat(getDefaultLettucePool(cf).getHostName()).isEqualTo("foo");
assertThat(getDefaultLettucePool(cf).getPoolConfig().getMinIdle()).isEqualTo(1);
assertThat(getDefaultLettucePool(cf).getPoolConfig().getMaxIdle()).isEqualTo(4);
@ -197,4 +210,14 @@ public class RedisAutoConfigurationTests {
this.context = ctx;
}
@Configuration
static class CustomConfiguration {
@Bean
LettuceClientConfigurationBuilderCustomizer customizer() {
return LettuceClientConfigurationBuilder::useSsl;
}
}
}

Loading…
Cancel
Save