From 4563da9ac7072e4bfba6bd8abe83c4eb1c61e8f6 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Wed, 2 Mar 2016 16:04:44 +0100 Subject: [PATCH 1/3] Add Lettuce Redis driver autoconfiguration Introduce an alternative autoconfiguration if the lettuce Redis driver is available. Add Lettuce-specific configuration property options "spring.redis.lettuce.shutdown-timeout" to control the shutdown timeout of the lettuce driver. Add documentation for the properties, the supported drivers, and how to switch between drivers. Split client-specific properties from spring.redis.pool to spring.redis.jedis.pool and introduce spring.redis.lettuce namespace. Deprecate spring.redis.pool property. See gh-5311 --- spring-boot-actuator/pom.xml | 5 + spring-boot-autoconfigure/pom.xml | 5 + .../data/redis/RedisAutoConfiguration.java | 285 +++++++++++++++--- .../data/redis/RedisProperties.java | 84 +++++- .../LettuceRedisAutoConfigurationTests.java | 198 ++++++++++++ .../redis/RedisAutoConfigurationTests.java | 13 +- spring-boot-dependencies/pom.xml | 6 + spring-boot-docs/pom.xml | 5 + .../appendix-application-properties.adoc | 27 +- spring-boot-docs/src/main/asciidoc/howto.adoc | 41 +++ .../main/asciidoc/spring-boot-features.adoc | 7 +- 11 files changed, 627 insertions(+), 49 deletions(-) create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/LettuceRedisAutoConfigurationTests.java diff --git a/spring-boot-actuator/pom.xml b/spring-boot-actuator/pom.xml index 3eb06c8c7a2..11f8d6cfb72 100644 --- a/spring-boot-actuator/pom.xml +++ b/spring-boot-actuator/pom.xml @@ -244,6 +244,11 @@ jedis true + + io.lettuce + lettuce-core + true + org.springframework.amqp spring-rabbit diff --git a/spring-boot-autoconfigure/pom.xml b/spring-boot-autoconfigure/pom.xml index 784782fb929..b5d5be58399 100755 --- a/spring-boot-autoconfigure/pom.xml +++ b/spring-boot-autoconfigure/pom.xml @@ -481,6 +481,11 @@ jedis true + + io.lettuce + lettuce-core + true + org.liquibase liquibase-core diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfiguration.java index b9882aaafdc..e19139a5f0a 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfiguration.java @@ -22,7 +22,12 @@ import java.net.UnknownHostException; import java.util.ArrayList; import java.util.List; +import io.lettuce.core.RedisClient; +import io.lettuce.core.cluster.RedisClusterClient; +import io.lettuce.core.resource.ClientResources; +import io.lettuce.core.resource.DefaultClientResources; import org.apache.commons.pool2.impl.GenericObjectPool; +import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPoolConfig; @@ -31,6 +36,7 @@ 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.data.redis.RedisProperties.Cluster; +import org.springframework.boot.autoconfigure.data.redis.RedisProperties.Lettuce; import org.springframework.boot.autoconfigure.data.redis.RedisProperties.Sentinel; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; @@ -41,6 +47,8 @@ import org.springframework.data.redis.connection.RedisNode; import org.springframework.data.redis.connection.RedisSentinelConfiguration; import org.springframework.data.redis.connection.jedis.JedisConnection; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; +import org.springframework.data.redis.connection.lettuce.DefaultLettucePool; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.core.RedisOperations; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate; @@ -58,31 +66,28 @@ import org.springframework.util.StringUtils; * @author Eddú Meléndez * @author Stephane Nicoll * @author Marco Aust + * @author Mark Paluch */ @Configuration -@ConditionalOnClass({ JedisConnection.class, RedisOperations.class, Jedis.class }) +@ConditionalOnClass({ RedisOperations.class }) @EnableConfigurationProperties(RedisProperties.class) public class RedisAutoConfiguration { /** - * Redis connection configuration. + * Jedis Redis connection configuration. */ @Configuration - @ConditionalOnClass(GenericObjectPool.class) - protected static class RedisConnectionConfiguration { + @ConditionalOnClass({ GenericObjectPool.class, JedisConnection.class, Jedis.class }) + protected static class JedisRedisConnectionConfiguration + extends RedisBaseConfiguration { private final RedisProperties properties; - private final RedisSentinelConfiguration sentinelConfiguration; - - private final RedisClusterConfiguration clusterConfiguration; - - public RedisConnectionConfiguration(RedisProperties properties, + public JedisRedisConnectionConfiguration(RedisProperties properties, ObjectProvider sentinelConfiguration, ObjectProvider clusterConfiguration) { + super(properties, sentinelConfiguration, clusterConfiguration); this.properties = properties; - this.sentinelConfiguration = sentinelConfiguration.getIfAvailable(); - this.clusterConfiguration = clusterConfiguration.getIfAvailable(); } @Bean @@ -142,10 +147,239 @@ public class RedisAutoConfiguration { } } + private JedisConnectionFactory createJedisConnectionFactory() { + JedisPoolConfig poolConfig = this.properties.getPool() != null + ? jedisPoolConfig() : new JedisPoolConfig(); + + if (getSentinelConfig() != null) { + return new JedisConnectionFactory(getSentinelConfig(), poolConfig); + } + if (getClusterConfiguration() != null) { + return new JedisConnectionFactory(getClusterConfiguration(), poolConfig); + } + return new JedisConnectionFactory(poolConfig); + } + + private JedisPoolConfig jedisPoolConfig() { + JedisPoolConfig config = new JedisPoolConfig(); + RedisProperties.Pool props = this.properties.getPool(); + config.setMaxTotal(props.getMaxActive()); + config.setMaxIdle(props.getMaxIdle()); + config.setMinIdle(props.getMinIdle()); + config.setMaxWaitMillis(props.getMaxWait()); + return config; + } + + } + + /** + * Lettuce Redis connection configuration. + */ + @Configuration + @ConditionalOnClass({ GenericObjectPool.class, RedisClient.class, + RedisClusterClient.class }) + protected static class LettuceRedisConnectionConfiguration + extends RedisBaseConfiguration { + + private final RedisProperties properties; + + public LettuceRedisConnectionConfiguration(RedisProperties properties, + ObjectProvider sentinelConfigurationProvider, + ObjectProvider clusterConfigurationProvider) { + super(properties, sentinelConfigurationProvider, + clusterConfigurationProvider); + this.properties = properties; + } + + @Bean(destroyMethod = "shutdown") + @ConditionalOnMissingBean(ClientResources.class) + public DefaultClientResources lettuceClientResources() { + return DefaultClientResources.create(); + } + + @Bean + @ConditionalOnMissingBean(RedisConnectionFactory.class) + public LettuceConnectionFactory redisConnectionFactory( + ClientResources clientResources) throws UnknownHostException { + return applyProperties(createLettuceConnectionFactory(clientResources)); + } + + protected final LettuceConnectionFactory applyProperties( + LettuceConnectionFactory factory) { + configureConnection(factory); + if (this.properties.isSsl()) { + factory.setUseSsl(true); + } + if (this.properties.getLettuce() != null) { + Lettuce lettuce = this.properties.getLettuce(); + if (lettuce.getShutdownTimeout() >= 0) { + factory.setShutdownTimeout( + this.properties.getLettuce().getShutdownTimeout()); + } + } + return factory; + } + + 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 void configureConnectionFromUrl(LettuceConnectionFactory factory) { + String url = this.properties.getUrl(); + if (url.startsWith("rediss://")) { + factory.setUseSsl(true); + } + try { + URI uri = new URI(url); + factory.setHostName(uri.getHost()); + factory.setPort(uri.getPort()); + if (uri.getUserInfo() != null) { + String password = uri.getUserInfo(); + int index = password.lastIndexOf(":"); + if (index >= 0) { + password = password.substring(index + 1); + } + factory.setPassword(password); + } + } + catch (URISyntaxException ex) { + throw new IllegalArgumentException("Malformed 'spring.redis.url' " + url, + ex); + } + } + + protected final DefaultLettucePool applyProperties(DefaultLettucePool pool) { + if (StringUtils.hasText(this.properties.getUrl())) { + configureConnectionFromUrl(pool); + } + else { + pool.setHostName(this.properties.getHost()); + pool.setPort(this.properties.getPort()); + if (this.properties.getPassword() != null) { + pool.setPassword(this.properties.getPassword()); + } + pool.setDatabase(this.properties.getDatabase()); + } + if (this.properties.getTimeout() > 0) { + pool.setTimeout(this.properties.getTimeout()); + } + pool.afterPropertiesSet(); + return pool; + } + + private void configureConnectionFromUrl(DefaultLettucePool lettucePool) { + String url = this.properties.getUrl(); + try { + URI uri = new URI(url); + lettucePool.setHostName(uri.getHost()); + lettucePool.setPort(uri.getPort()); + if (uri.getUserInfo() != null) { + String password = uri.getUserInfo(); + int index = password.lastIndexOf(":"); + if (index >= 0) { + password = password.substring(index + 1); + } + lettucePool.setPassword(password); + } + } + catch (URISyntaxException ex) { + throw new IllegalArgumentException("Malformed 'spring.redis.url' " + url, + ex); + } + } + + private LettuceConnectionFactory createLettuceConnectionFactory( + ClientResources clientResources) { + + 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); + } + + if (getClusterConfiguration() != null) { + return applyClientResources( + new LettuceConnectionFactory(getClusterConfiguration()), + clientResources); + } + + 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); + } + + private DefaultLettucePool applyClientResources(DefaultLettucePool lettucePool, + ClientResources clientResources) { + lettucePool.setClientResources(clientResources); + return lettucePool; + } + + private LettuceConnectionFactory applyClientResources( + LettuceConnectionFactory factory, ClientResources clientResources) { + factory.setClientResources(clientResources); + return factory; + } + + 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; + } + + } + + protected abstract static class RedisBaseConfiguration { + + private final RedisProperties properties; + + private final RedisSentinelConfiguration sentinelConfiguration; + + private final RedisClusterConfiguration clusterConfiguration; + + protected RedisBaseConfiguration(RedisProperties properties, + ObjectProvider sentinelConfigurationProvider, + ObjectProvider clusterConfigurationProvider) { + this.properties = properties; + this.sentinelConfiguration = sentinelConfigurationProvider.getIfAvailable(); + this.clusterConfiguration = clusterConfigurationProvider.getIfAvailable(); + } + protected final RedisSentinelConfiguration getSentinelConfig() { if (this.sentinelConfiguration != null) { return this.sentinelConfiguration; } + Sentinel sentinelProperties = this.properties.getSentinel(); if (sentinelProperties != null) { RedisSentinelConfiguration config = new RedisSentinelConfiguration(); @@ -164,9 +398,11 @@ public class RedisAutoConfiguration { if (this.clusterConfiguration != null) { return this.clusterConfiguration; } + if (this.properties.getCluster() == null) { return null; } + Cluster clusterProperties = this.properties.getCluster(); RedisClusterConfiguration config = new RedisClusterConfiguration( clusterProperties.getNodes()); @@ -194,29 +430,6 @@ public class RedisAutoConfiguration { return nodes; } - private JedisConnectionFactory createJedisConnectionFactory() { - JedisPoolConfig poolConfig = this.properties.getPool() != null - ? jedisPoolConfig() : new JedisPoolConfig(); - - if (getSentinelConfig() != null) { - return new JedisConnectionFactory(getSentinelConfig(), poolConfig); - } - if (getClusterConfiguration() != null) { - return new JedisConnectionFactory(getClusterConfiguration(), poolConfig); - } - return new JedisConnectionFactory(poolConfig); - } - - private JedisPoolConfig jedisPoolConfig() { - JedisPoolConfig config = new JedisPoolConfig(); - RedisProperties.Pool props = this.properties.getPool(); - config.setMaxTotal(props.getMaxActive()); - config.setMaxIdle(props.getMaxIdle()); - config.setMinIdle(props.getMinIdle()); - config.setMaxWaitMillis(props.getMaxWait()); - return config; - } - } /** @@ -229,7 +442,7 @@ public class RedisAutoConfiguration { @ConditionalOnMissingBean(name = "redisTemplate") public RedisTemplate redisTemplate( RedisConnectionFactory redisConnectionFactory) - throws UnknownHostException { + throws UnknownHostException { RedisTemplate template = new RedisTemplate<>(); template.setConnectionFactory(redisConnectionFactory); return template; @@ -239,7 +452,7 @@ public class RedisAutoConfiguration { @ConditionalOnMissingBean(StringRedisTemplate.class) public StringRedisTemplate stringRedisTemplate( RedisConnectionFactory redisConnectionFactory) - throws UnknownHostException { + throws UnknownHostException { StringRedisTemplate template = new StringRedisTemplate(); template.setConnectionFactory(redisConnectionFactory); return template; diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisProperties.java index cd182a485d8..e0d9de2bdfe 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisProperties.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2016 the original author or authors. + * 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. @@ -19,6 +19,7 @@ package org.springframework.boot.autoconfigure.data.redis; import java.util.List; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.DeprecatedConfigurationProperty; /** * Configuration properties for Redis. @@ -27,6 +28,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties; * @author Christoph Strobl * @author Eddú Meléndez * @author Marco Aust + * @author Mark Paluch */ @ConfigurationProperties(prefix = "spring.redis") public class RedisProperties { @@ -72,6 +74,16 @@ public class RedisProperties { private Cluster cluster; + /** + * Jedis client properties. + */ + private Jedis jedis; + + /** + * Lettuce client properties. + */ + private Lettuce lettuce; + public int getDatabase() { return this.database; } @@ -136,6 +148,7 @@ public class RedisProperties { this.sentinel = sentinel; } + @DeprecatedConfigurationProperty(reason = "Moved to client-specific properties", replacement = "spring.redis.jedis.pool") public Pool getPool() { return this.pool; } @@ -152,6 +165,22 @@ public class RedisProperties { this.cluster = cluster; } + public Jedis getJedis() { + return jedis; + } + + public void setJedis(Jedis jedis) { + this.jedis = jedis; + } + + public Lettuce getLettuce() { + return lettuce; + } + + public void setLettuce(Lettuce lettuce) { + this.lettuce = lettuce; + } + /** * Pool properties. */ @@ -284,4 +313,57 @@ public class RedisProperties { } + /** + * Jedis client properties. + */ + public static class Jedis { + + /** + * Jedis pool configuration. + */ + private Pool pool; + + public Pool getPool() { + return this.pool; + } + + public void setPool(Pool pool) { + this.pool = pool; + } + + } + + /** + * Lettuce client properties. + */ + public static class Lettuce { + + /** + * Shutdown timeout in milliseconds for lettuce. + */ + private int shutdownTimeout = 2000; + + /** + * Lettuce pool configuration. + */ + private Pool pool; + + public int getShutdownTimeout() { + return this.shutdownTimeout; + } + + public void setShutdownTimeout(int shutdownTimeout) { + this.shutdownTimeout = shutdownTimeout; + } + + public Pool getPool() { + return this.pool; + } + + public void setPool(Pool pool) { + this.pool = pool; + } + + } + } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/LettuceRedisAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/LettuceRedisAutoConfigurationTests.java new file mode 100644 index 00000000000..d0a9ff9f088 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/LettuceRedisAutoConfigurationTests.java @@ -0,0 +1,198 @@ +/* + * Copyright 2012-2016 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 java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import io.lettuce.core.RedisClient; +import io.lettuce.core.RedisURI; +import io.lettuce.core.api.StatefulRedisConnection; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.util.EnvironmentTestUtils; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.data.redis.connection.lettuce.DefaultLettucePool; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.util.StringUtils; + +import static org.assertj.core.api.Assertions.*; + +/** + * Tests for {@link RedisAutoConfiguration} using Lettuce as client. + * + * @author Mark Paluch + */ +public class LettuceRedisAutoConfigurationTests { + + private AnnotationConfigApplicationContext context; + + @Before + public void setup() { + this.context = new AnnotationConfigApplicationContext(); + } + + @After + public void close() { + if (this.context != null) { + this.context.close(); + } + } + + @Test + public void testOverrideRedisConfiguration() throws Exception { + load("spring.redis.host:foo", "spring.redis.database:1"); + assertThat(this.context.getBean(LettuceConnectionFactory.class).getHostName()) + .isEqualTo("foo"); + assertThat(this.context.getBean(LettuceConnectionFactory.class).getDatabase()) + .isEqualTo(1); + } + + @Test + public void testOverrideUrlRedisConfiguration() throws Exception { + load("spring.redis.host:foo", "spring.redis.password:xyz", + "spring.redis.port:1000", "spring.redis.ssl:true", + "spring.redis.url:redis://user:password@example:33"); + assertThat(this.context.getBean(LettuceConnectionFactory.class).getHostName()) + .isEqualTo("example"); + assertThat(this.context.getBean(LettuceConnectionFactory.class).getPort()) + .isEqualTo(33); + assertThat(this.context.getBean(LettuceConnectionFactory.class).getPassword()) + .isEqualTo("password"); + assertThat(this.context.getBean(LettuceConnectionFactory.class).isUseSsl()) + .isEqualTo(true); + } + + @Test + public void testSslRedisConfiguration() throws Exception { + load("spring.redis.host:foo", "spring.redis.ssl:true"); + assertThat(this.context.getBean(LettuceConnectionFactory.class).getHostName()) + .isEqualTo("foo"); + assertThat(this.context.getBean(LettuceConnectionFactory.class).isUseSsl()) + .isTrue(); + } + + @Test + public void testRedisConfigurationWithPool() throws Exception { + load("spring.redis.host:foo", "spring.redis.lettuce.pool.max-idle:1"); + assertThat(this.context.getBean(LettuceConnectionFactory.class).getHostName()) + .isEqualTo("foo"); + assertThat(getDefaultLettucePool( + this.context.getBean(LettuceConnectionFactory.class)).getHostName()) + .isEqualTo("foo"); + assertThat(getDefaultLettucePool( + this.context.getBean(LettuceConnectionFactory.class)).getPoolConfig() + .getMaxIdle()).isEqualTo(1); + } + + @Test + public void testRedisConfigurationWithTimeout() throws Exception { + load("spring.redis.host:foo", "spring.redis.timeout:100"); + assertThat(this.context.getBean(LettuceConnectionFactory.class).getHostName()) + .isEqualTo("foo"); + assertThat(this.context.getBean(LettuceConnectionFactory.class).getTimeout()) + .isEqualTo(100); + } + + @Test + public void testRedisConfigurationWithSentinel() throws Exception { + List sentinels = Arrays.asList("127.0.0.1:26379", "127.0.0.1:26380"); + if (isAtLeastOneNodeAvailable(sentinels)) { + load("spring.redis.sentinel.master:mymaster", "spring.redis.sentinel.nodes:" + + StringUtils.collectionToCommaDelimitedString(sentinels)); + assertThat(this.context.getBean(LettuceConnectionFactory.class) + .isRedisSentinelAware()).isTrue(); + } + } + + @Test + public void testRedisConfigurationWithCluster() throws Exception { + List clusterNodes = Arrays.asList("127.0.0.1:27379", "127.0.0.1:27380"); + load("spring.redis.cluster.nodes[0]:" + clusterNodes.get(0), + "spring.redis.cluster.nodes[1]:" + clusterNodes.get(1)); + assertThat(this.context.getBean(LettuceConnectionFactory.class) + .getClusterConnection()).isNotNull(); + } + + private DefaultLettucePool getDefaultLettucePool(LettuceConnectionFactory factory) { + return (DefaultLettucePool) ReflectionTestUtils.getField(factory, "pool"); + } + + private boolean isAtLeastOneNodeAvailable(List nodes) { + for (String node : nodes) { + if (isAvailable(node)) { + return true; + } + } + + return false; + } + + private boolean isAvailable(String node) { + RedisClient redisClient = null; + try { + String[] hostAndPort = node.split(":"); + redisClient = RedisClient.create(new RedisURI(hostAndPort[0], + Integer.valueOf(hostAndPort[1]), 10, TimeUnit.SECONDS)); + StatefulRedisConnection connection = redisClient.connect(); + connection.sync().ping(); + connection.close(); + return true; + } + catch (Exception ex) { + return false; + } + finally { + if (redisClient != null) { + try { + redisClient.shutdown(0, 0, TimeUnit.SECONDS); + } + catch (Exception ex) { + // Continue + } + } + } + } + + private void load(String... environment) { + this.context = doLoad(environment); + } + + private AnnotationConfigApplicationContext doLoad(String... environment) { + + AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); + EnvironmentTestUtils.addEnvironment(applicationContext, environment); + applicationContext.register( + RedisAutoConfiguration.LettuceRedisConnectionConfiguration.class, + PropertyPlaceholderAutoConfiguration.class, + EnableRedisPropertiesConfiguration.class); + applicationContext.refresh(); + return applicationContext; + } + + @EnableConfigurationProperties(RedisProperties.class) + private static class EnableRedisPropertiesConfiguration { + + } + +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationTests.java index ab313ee5c57..165e5cd9a0c 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationTests.java @@ -25,6 +25,7 @@ import org.junit.Test; import redis.clients.jedis.Jedis; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.test.util.EnvironmentTestUtils; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; @@ -42,6 +43,7 @@ import static org.assertj.core.api.Assertions.assertThat; * @author Christoph Strobl * @author Eddú Meléndez * @author Marco Aust + * @author Mark Paluch */ public class RedisAutoConfigurationTests { @@ -93,7 +95,7 @@ public class RedisAutoConfigurationTests { @Test public void testRedisConfigurationWithPool() throws Exception { - load("spring.redis.host:foo", "spring.redis.pool.max-idle:1"); + load("spring.redis.host:foo", "spring.redis.jedis.pool.max-idle:1"); assertThat(this.context.getBean(JedisConnectionFactory.class).getHostName()) .isEqualTo("foo"); assertThat(this.context.getBean(JedisConnectionFactory.class).getPoolConfig() @@ -173,10 +175,15 @@ public class RedisAutoConfigurationTests { private AnnotationConfigApplicationContext doLoad(String... environment) { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); EnvironmentTestUtils.addEnvironment(applicationContext, environment); - applicationContext.register(RedisAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class); + applicationContext.register(RedisAutoConfiguration.JedisRedisConnectionConfiguration.class, RedisAutoConfiguration.RedisConfiguration.class, + PropertyPlaceholderAutoConfiguration.class, EnableRedisPropertiesConfiguration.class); applicationContext.refresh(); return applicationContext; } + @EnableConfigurationProperties(RedisProperties.class) + private static class EnableRedisPropertiesConfiguration { + + } + } diff --git a/spring-boot-dependencies/pom.xml b/spring-boot-dependencies/pom.xml index 6a7f3c6c26f..7d061aa46dd 100644 --- a/spring-boot-dependencies/pom.xml +++ b/spring-boot-dependencies/pom.xml @@ -125,6 +125,7 @@ 4.12 5.0.0.BUILD-SNAPSHOT 3.5.3 + 5.0.0.BUILD-SNAPSHOT 2.8.2 1.2.3 1.16.16 @@ -2337,6 +2338,11 @@ jedis ${jedis.version} + + io.lettuce + lettuce-core + ${lettuce.version} + wsdl4j wsdl4j diff --git a/spring-boot-docs/pom.xml b/spring-boot-docs/pom.xml index 33788d4b041..94fb2746e71 100644 --- a/spring-boot-docs/pom.xml +++ b/spring-boot-docs/pom.xml @@ -748,6 +748,11 @@ jedis true + + io.lettuce + lettuce-core + true + org.springframework.boot diff --git a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index 658f787f655..0d40b09b2f0 100644 --- a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -852,21 +852,36 @@ content into your application; rather pick only the properties that you need. spring.redis.host=localhost # Redis server host. spring.redis.password= # Login password of the redis server. spring.redis.ssl=false # Enable SSL support. - spring.redis.pool.max-active=8 # Max number of connections that can be allocated by the pool at a given time. Use a negative value for no limit. - spring.redis.pool.max-idle=8 # Max number of "idle" connections in the pool. Use a negative value to indicate an unlimited number of idle connections. - spring.redis.pool.max-wait=-1 # Maximum amount of time (in milliseconds) a connection allocation should block before throwing an exception when the pool is exhausted. Use a negative value to block indefinitely. - spring.redis.pool.min-idle=0 # Target for the minimum number of idle connections to maintain in the pool. This setting only has an effect if it is positive. + spring.redis.jedis.pool.max-active=8 # Max number of connections that can be allocated by the pool at a given time. Use a negative value for no limit. + spring.redis.jedis.pool.max-idle=8 # Max number of "idle" connections in the pool. Use a negative value to indicate an unlimited number of idle connections. + spring.redis.jedis.pool.max-wait=-1 # Maximum amount of time (in milliseconds) a connection allocation should block before throwing an exception when the pool is exhausted. Use a negative value to block indefinitely. + spring.redis.jedis.pool.min-idle=0 # Target for the minimum number of idle connections to maintain in the pool. This setting only has an effect if it is positive. spring.redis.port=6379 # Redis server port. spring.redis.sentinel.master= # Name of Redis server. spring.redis.sentinel.nodes= # Comma-separated list of host:port pairs. spring.redis.timeout=0 # Connection timeout in milliseconds. + spring.redis.ssl.enabled=false # Enable SSL support. + spring.redis.ssl.verify-peer=true # Enable SSL peer verification. + spring.redis.ssl.start-tls=false # Enable StartTLS support. + + # REDIS JEDIS DRIVER + spring.redis.jedis.pool.max-active=8 # Max number of connections that can be allocated by the pool at a given time. Use a negative value for no limit. + spring.redis.jedis.pool.max-idle=8 # Max number of "idle" connections in the pool. Use a negative value to indicate an unlimited number of idle connections. + spring.redis.jedis.pool.max-wait=-1 # Maximum amount of time (in milliseconds) a connection allocation should block before throwing an exception when the pool is exhausted. Use a negative value to block indefinitely. + spring.redis.jedis.pool.min-idle=0 # Target for the minimum number of idle connections to maintain in the pool. This setting only has an effect if it is positive. + + # REDIS LETTUCE DRIVER + spring.redis.lettuce.pool.max-active=8 # Max number of connections that can be allocated by the pool at a given time. Use a negative value for no limit. + spring.redis.lettuce.pool.max-idle=8 # Max number of "idle" connections in the pool. Use a negative value to indicate an unlimited number of idle connections. + spring.redis.lettuce.pool.max-wait=-1 # Maximum amount of time (in milliseconds) a connection allocation should block before throwing an exception when the pool is exhausted. Use a negative value to block indefinitely. + spring.redis.lettuce.pool.min-idle=0 # Target for the minimum number of idle connections to maintain in the pool. This setting only has an effect if it is positive. + spring.redis.lettuce.shutdown-timeout=2000 # Shutdown timeout in milliseconds. + # TRANSACTION ({sc-spring-boot-autoconfigure}/transaction/TransactionProperties.{sc-ext}[TransactionProperties]) spring.transaction.default-timeout= # Default transaction timeout in seconds. spring.transaction.rollback-on-commit-failure= # Perform the rollback on commit failures. - - # ---------------------------------------- # INTEGRATION PROPERTIES # ---------------------------------------- diff --git a/spring-boot-docs/src/main/asciidoc/howto.adoc b/spring-boot-docs/src/main/asciidoc/howto.adoc index dad769934d9..e05e2b95cbe 100644 --- a/spring-boot-docs/src/main/asciidoc/howto.adoc +++ b/spring-boot-docs/src/main/asciidoc/howto.adoc @@ -3074,3 +3074,44 @@ but the rest of it is normal for a Spring application in Servlet 2.5. Example: In this example we are using a single application context (the one created by the context listener) and attaching it to the `DispatcherServlet` using an init parameter. This is normal in a Spring Boot application (you normally only have one application context). + +[[howto-use-lettuce-instead-of-jedis]] +=== Use Lettuce instead of Jedis +The Spring Boot Redis starter (`spring-boot-starter-data-redis` in particular) uses +https://github.com/xetorthio/jedis/[Jedis] by default. You need to exclude that dependency +and include the https://github.com/lettuce-io/lettuce-core/[Lettuce] one instead. Spring Boot provides a managed dependency +to help make this process as easy as possible. + +Example in Maven: + +[source,xml,indent=0,subs="verbatim,quotes,attributes"] +---- + + org.springframework.boot + spring-boot-starter-data-redis + + + redis.clients + jedis + + + + + io.lettuce + lettuce-core + +---- + +Example in Gradle: + +[source,groovy,indent=0,subs="verbatim,quotes,attributes"] +---- + configurations { + compile.exclude module: "jedis" + } + + dependencies { + compile("io.lettuce:lettuce-core:{lettuce.version}") + // ... + } +---- \ No newline at end of file diff --git a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index 6c645728b39..1eed766e2fd 100644 --- a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -3262,10 +3262,11 @@ http://projects.spring.io/spring-data[projects.spring.io/spring-data]. === Redis http://redis.io/[Redis] is a cache, message broker and richly-featured key-value store. Spring Boot offers basic auto-configuration for the -https://github.com/xetorthio/jedis/[Jedis] client library and abstractions on top of it -provided by https://github.com/spring-projects/spring-data-redis[Spring Data Redis]. There +https://github.com/xetorthio/jedis/[Jedis] and and https://github.com/mp911de/lettuce/[Lettuce] +client library and abstractions on top of it provided by +https://github.com/spring-projects/spring-data-redis[Spring Data Redis]. There is a `spring-boot-starter-data-redis` '`Starter`' for collecting the dependencies in a -convenient way. +convenient way that uses https://github.com/xetorthio/jedis/[Jedis] by default. From e7efa8f133166e704c156e69ed9f0bba6b91cb69 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Tue, 2 May 2017 15:16:11 +0200 Subject: [PATCH 2/3] Polish "Add Lettuce Redis driver autoconfiguration" Closes gh-5311 --- spring-boot-actuator/pom.xml | 5 - spring-boot-autoconfigure/pom.xml | 10 +- .../redis/JedisConnectionConfiguration.java | 120 +++++ .../redis/LettuceConnectionConfiguration.java | 197 ++++++++ .../data/redis/RedisAutoConfiguration.java | 428 +----------------- .../redis/RedisConnectionConfiguration.java | 153 +++++++ .../data/redis/RedisProperties.java | 37 +- .../LettuceRedisAutoConfigurationTests.java | 198 -------- .../RedisAutoConfigurationJedisTests.java | 158 +++++++ .../redis/RedisAutoConfigurationTests.java | 114 +++-- spring-boot-dependencies/pom.xml | 16 +- spring-boot-docs/pom.xml | 10 +- .../appendix-application-properties.adoc | 26 +- spring-boot-docs/src/main/asciidoc/howto.adoc | 10 +- 14 files changed, 733 insertions(+), 749 deletions(-) create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/JedisConnectionConfiguration.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/LettuceConnectionConfiguration.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisConnectionConfiguration.java delete mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/LettuceRedisAutoConfigurationTests.java create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationJedisTests.java diff --git a/spring-boot-actuator/pom.xml b/spring-boot-actuator/pom.xml index 11f8d6cfb72..3eb06c8c7a2 100644 --- a/spring-boot-actuator/pom.xml +++ b/spring-boot-actuator/pom.xml @@ -244,11 +244,6 @@ jedis true - - io.lettuce - lettuce-core - true - org.springframework.amqp spring-rabbit diff --git a/spring-boot-autoconfigure/pom.xml b/spring-boot-autoconfigure/pom.xml index b5d5be58399..6bbd95a1ded 100755 --- a/spring-boot-autoconfigure/pom.xml +++ b/spring-boot-autoconfigure/pom.xml @@ -100,6 +100,11 @@ de.flapdoodle.embed.mongo true + + io.lettuce + lettuce-core + true + io.projectreactor.ipc reactor-netty @@ -481,11 +486,6 @@ jedis true - - io.lettuce - lettuce-core - true - org.liquibase liquibase-core diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/JedisConnectionConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/JedisConnectionConfiguration.java new file mode 100644 index 00000000000..27b13810a70 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/JedisConnectionConfiguration.java @@ -0,0 +1,120 @@ +/* + * 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 java.net.UnknownHostException; + +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.context.annotation.Bean; +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.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 + */ +@Configuration +@ConditionalOnClass({ GenericObjectPool.class, JedisConnection.class, Jedis.class }) +class JedisConnectionConfiguration extends RedisConnectionConfiguration { + + private final RedisProperties properties; + + JedisConnectionConfiguration(RedisProperties properties, + ObjectProvider sentinelConfiguration, + ObjectProvider clusterConfiguration) { + super(properties, sentinelConfiguration, clusterConfiguration); + this.properties = properties; + } + + @Bean + @ConditionalOnMissingBean(RedisConnectionFactory.class) + public JedisConnectionFactory redisConnectionFactory() throws UnknownHostException { + return applyProperties(createJedisConnectionFactory()); + } + + private JedisConnectionFactory applyProperties(JedisConnectionFactory factory) { + configureConnection(factory); + if (this.properties.isSsl()) { + factory.setUseSsl(true); + } + factory.setDatabase(this.properties.getDatabase()); + if (this.properties.getTimeout() > 0) { + factory.setTimeout(this.properties.getTimeout()); + } + return factory; + } + + private void configureConnection(JedisConnectionFactory 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()); + } + } + } + + 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 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 JedisPoolConfig jedisPoolConfig(RedisProperties.Pool pool) { + JedisPoolConfig config = new JedisPoolConfig(); + config.setMaxTotal(pool.getMaxActive()); + config.setMaxIdle(pool.getMaxIdle()); + config.setMinIdle(pool.getMinIdle()); + config.setMaxWaitMillis(pool.getMaxWait()); + return config; + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/LettuceConnectionConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/LettuceConnectionConfiguration.java new file mode 100644 index 00000000000..d052b3ff134 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/LettuceConnectionConfiguration.java @@ -0,0 +1,197 @@ +/* + * 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 java.net.UnknownHostException; + +import io.lettuce.core.RedisClient; +import io.lettuce.core.cluster.RedisClusterClient; +import io.lettuce.core.resource.ClientResources; +import io.lettuce.core.resource.DefaultClientResources; +import org.apache.commons.pool2.impl.GenericObjectPool; +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.context.annotation.Bean; +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.lettuce.DefaultLettucePool; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.util.StringUtils; + +/** + * Redis connection configuration using Lettuce. + * + * @author Mark Paluch + */ +@Configuration +@ConditionalOnClass({ GenericObjectPool.class, RedisClient.class, RedisClusterClient.class }) +class LettuceConnectionConfiguration extends RedisConnectionConfiguration { + + private final RedisProperties properties; + + LettuceConnectionConfiguration(RedisProperties properties, + ObjectProvider sentinelConfigurationProvider, + ObjectProvider clusterConfigurationProvider) { + super(properties, sentinelConfigurationProvider, clusterConfigurationProvider); + this.properties = properties; + } + + @Bean(destroyMethod = "shutdown") + @ConditionalOnMissingBean(ClientResources.class) + public DefaultClientResources lettuceClientResources() { + return DefaultClientResources.create(); + } + + @Bean + @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()); + } + } + return factory; + } + + 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 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 applyProperties(DefaultLettucePool pool) { + if (StringUtils.hasText(this.properties.getUrl())) { + configureConnectionFromUrl(pool); + } + else { + pool.setHostName(this.properties.getHost()); + pool.setPort(this.properties.getPort()); + if (this.properties.getPassword() != null) { + pool.setPassword(this.properties.getPassword()); + } + pool.setDatabase(this.properties.getDatabase()); + } + if (this.properties.getTimeout() > 0) { + pool.setTimeout(this.properties.getTimeout()); + } + pool.afterPropertiesSet(); + return pool; + } + + private void configureConnectionFromUrl(DefaultLettucePool lettucePool) { + ConnectionInfo connectionInfo = parseUrl(this.properties.getUrl()); + lettucePool.setHostName(connectionInfo.getHostName()); + lettucePool.setPort(connectionInfo.getPort()); + if (connectionInfo.getPassword() != null) { + lettucePool.setPassword(connectionInfo.getPassword()); + } + } + + private LettuceConnectionFactory createLettuceConnectionFactory( + ClientResources clientResources) { + + 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); + } + + if (getClusterConfiguration() != null) { + return applyClientResources( + new LettuceConnectionFactory(getClusterConfiguration()), + clientResources); + } + + 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); + } + + private DefaultLettucePool applyClientResources(DefaultLettucePool lettucePool, + ClientResources clientResources) { + lettucePool.setClientResources(clientResources); + return lettucePool; + } + + private LettuceConnectionFactory applyClientResources( + LettuceConnectionFactory factory, ClientResources clientResources) { + factory.setClientResources(clientResources); + return factory; + } + + 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; + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfiguration.java index e19139a5f0a..629cf2d3811 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfiguration.java @@ -16,44 +16,19 @@ package org.springframework.boot.autoconfigure.data.redis; -import java.net.URI; -import java.net.URISyntaxException; import java.net.UnknownHostException; -import java.util.ArrayList; -import java.util.List; -import io.lettuce.core.RedisClient; -import io.lettuce.core.cluster.RedisClusterClient; -import io.lettuce.core.resource.ClientResources; -import io.lettuce.core.resource.DefaultClientResources; -import org.apache.commons.pool2.impl.GenericObjectPool; -import org.apache.commons.pool2.impl.GenericObjectPoolConfig; -import redis.clients.jedis.Jedis; -import redis.clients.jedis.JedisPoolConfig; - -import org.springframework.beans.factory.ObjectProvider; 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.data.redis.RedisProperties.Cluster; -import org.springframework.boot.autoconfigure.data.redis.RedisProperties.Lettuce; -import org.springframework.boot.autoconfigure.data.redis.RedisProperties.Sentinel; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.data.redis.connection.RedisClusterConfiguration; +import org.springframework.context.annotation.Import; import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.connection.RedisNode; -import org.springframework.data.redis.connection.RedisSentinelConfiguration; -import org.springframework.data.redis.connection.jedis.JedisConnection; -import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; -import org.springframework.data.redis.connection.lettuce.DefaultLettucePool; -import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.core.RedisOperations; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; /** * {@link EnableAutoConfiguration Auto-configuration} for Spring Data's Redis support. @@ -71,393 +46,28 @@ import org.springframework.util.StringUtils; @Configuration @ConditionalOnClass({ RedisOperations.class }) @EnableConfigurationProperties(RedisProperties.class) +@Import({ LettuceConnectionConfiguration.class, + JedisConnectionConfiguration.class }) public class RedisAutoConfiguration { - /** - * Jedis Redis connection configuration. - */ - @Configuration - @ConditionalOnClass({ GenericObjectPool.class, JedisConnection.class, Jedis.class }) - protected static class JedisRedisConnectionConfiguration - extends RedisBaseConfiguration { - - private final RedisProperties properties; - - public JedisRedisConnectionConfiguration(RedisProperties properties, - ObjectProvider sentinelConfiguration, - ObjectProvider clusterConfiguration) { - super(properties, sentinelConfiguration, clusterConfiguration); - this.properties = properties; - } - - @Bean - @ConditionalOnMissingBean(RedisConnectionFactory.class) - public JedisConnectionFactory redisConnectionFactory() - throws UnknownHostException { - return applyProperties(createJedisConnectionFactory()); - } - - protected final JedisConnectionFactory applyProperties( - JedisConnectionFactory factory) { - configureConnection(factory); - if (this.properties.isSsl()) { - factory.setUseSsl(true); - } - factory.setDatabase(this.properties.getDatabase()); - if (this.properties.getTimeout() > 0) { - factory.setTimeout(this.properties.getTimeout()); - } - return factory; - } - - private void configureConnection(JedisConnectionFactory 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()); - } - } - } - - private void configureConnectionFromUrl(JedisConnectionFactory factory) { - String url = this.properties.getUrl(); - if (url.startsWith("rediss://")) { - factory.setUseSsl(true); - } - try { - URI uri = new URI(url); - factory.setHostName(uri.getHost()); - factory.setPort(uri.getPort()); - if (uri.getUserInfo() != null) { - String password = uri.getUserInfo(); - int index = password.lastIndexOf(":"); - if (index >= 0) { - password = password.substring(index + 1); - } - factory.setPassword(password); - } - } - catch (URISyntaxException ex) { - throw new IllegalArgumentException("Malformed 'spring.redis.url' " + url, - ex); - } - } - - private JedisConnectionFactory createJedisConnectionFactory() { - JedisPoolConfig poolConfig = this.properties.getPool() != null - ? jedisPoolConfig() : new JedisPoolConfig(); - - if (getSentinelConfig() != null) { - return new JedisConnectionFactory(getSentinelConfig(), poolConfig); - } - if (getClusterConfiguration() != null) { - return new JedisConnectionFactory(getClusterConfiguration(), poolConfig); - } - return new JedisConnectionFactory(poolConfig); - } - - private JedisPoolConfig jedisPoolConfig() { - JedisPoolConfig config = new JedisPoolConfig(); - RedisProperties.Pool props = this.properties.getPool(); - config.setMaxTotal(props.getMaxActive()); - config.setMaxIdle(props.getMaxIdle()); - config.setMinIdle(props.getMinIdle()); - config.setMaxWaitMillis(props.getMaxWait()); - return config; - } - - } - - /** - * Lettuce Redis connection configuration. - */ - @Configuration - @ConditionalOnClass({ GenericObjectPool.class, RedisClient.class, - RedisClusterClient.class }) - protected static class LettuceRedisConnectionConfiguration - extends RedisBaseConfiguration { - - private final RedisProperties properties; - - public LettuceRedisConnectionConfiguration(RedisProperties properties, - ObjectProvider sentinelConfigurationProvider, - ObjectProvider clusterConfigurationProvider) { - super(properties, sentinelConfigurationProvider, - clusterConfigurationProvider); - this.properties = properties; - } - - @Bean(destroyMethod = "shutdown") - @ConditionalOnMissingBean(ClientResources.class) - public DefaultClientResources lettuceClientResources() { - return DefaultClientResources.create(); - } - - @Bean - @ConditionalOnMissingBean(RedisConnectionFactory.class) - public LettuceConnectionFactory redisConnectionFactory( - ClientResources clientResources) throws UnknownHostException { - return applyProperties(createLettuceConnectionFactory(clientResources)); - } - - protected final LettuceConnectionFactory applyProperties( - LettuceConnectionFactory factory) { - configureConnection(factory); - if (this.properties.isSsl()) { - factory.setUseSsl(true); - } - if (this.properties.getLettuce() != null) { - Lettuce lettuce = this.properties.getLettuce(); - if (lettuce.getShutdownTimeout() >= 0) { - factory.setShutdownTimeout( - this.properties.getLettuce().getShutdownTimeout()); - } - } - return factory; - } - - 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 void configureConnectionFromUrl(LettuceConnectionFactory factory) { - String url = this.properties.getUrl(); - if (url.startsWith("rediss://")) { - factory.setUseSsl(true); - } - try { - URI uri = new URI(url); - factory.setHostName(uri.getHost()); - factory.setPort(uri.getPort()); - if (uri.getUserInfo() != null) { - String password = uri.getUserInfo(); - int index = password.lastIndexOf(":"); - if (index >= 0) { - password = password.substring(index + 1); - } - factory.setPassword(password); - } - } - catch (URISyntaxException ex) { - throw new IllegalArgumentException("Malformed 'spring.redis.url' " + url, - ex); - } - } - - protected final DefaultLettucePool applyProperties(DefaultLettucePool pool) { - if (StringUtils.hasText(this.properties.getUrl())) { - configureConnectionFromUrl(pool); - } - else { - pool.setHostName(this.properties.getHost()); - pool.setPort(this.properties.getPort()); - if (this.properties.getPassword() != null) { - pool.setPassword(this.properties.getPassword()); - } - pool.setDatabase(this.properties.getDatabase()); - } - if (this.properties.getTimeout() > 0) { - pool.setTimeout(this.properties.getTimeout()); - } - pool.afterPropertiesSet(); - return pool; - } - - private void configureConnectionFromUrl(DefaultLettucePool lettucePool) { - String url = this.properties.getUrl(); - try { - URI uri = new URI(url); - lettucePool.setHostName(uri.getHost()); - lettucePool.setPort(uri.getPort()); - if (uri.getUserInfo() != null) { - String password = uri.getUserInfo(); - int index = password.lastIndexOf(":"); - if (index >= 0) { - password = password.substring(index + 1); - } - lettucePool.setPassword(password); - } - } - catch (URISyntaxException ex) { - throw new IllegalArgumentException("Malformed 'spring.redis.url' " + url, - ex); - } - } - - private LettuceConnectionFactory createLettuceConnectionFactory( - ClientResources clientResources) { - - 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); - } - - if (getClusterConfiguration() != null) { - return applyClientResources( - new LettuceConnectionFactory(getClusterConfiguration()), - clientResources); - } - - 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); - } - - private DefaultLettucePool applyClientResources(DefaultLettucePool lettucePool, - ClientResources clientResources) { - lettucePool.setClientResources(clientResources); - return lettucePool; - } - - private LettuceConnectionFactory applyClientResources( - LettuceConnectionFactory factory, ClientResources clientResources) { - factory.setClientResources(clientResources); - return factory; - } - - 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; - } - - } - - protected abstract static class RedisBaseConfiguration { - - private final RedisProperties properties; - - private final RedisSentinelConfiguration sentinelConfiguration; - - private final RedisClusterConfiguration clusterConfiguration; - - protected RedisBaseConfiguration(RedisProperties properties, - ObjectProvider sentinelConfigurationProvider, - ObjectProvider clusterConfigurationProvider) { - this.properties = properties; - this.sentinelConfiguration = sentinelConfigurationProvider.getIfAvailable(); - this.clusterConfiguration = clusterConfigurationProvider.getIfAvailable(); - } - - protected final RedisSentinelConfiguration getSentinelConfig() { - if (this.sentinelConfiguration != null) { - return this.sentinelConfiguration; - } - - Sentinel sentinelProperties = this.properties.getSentinel(); - if (sentinelProperties != null) { - RedisSentinelConfiguration config = new RedisSentinelConfiguration(); - config.master(sentinelProperties.getMaster()); - config.setSentinels(createSentinels(sentinelProperties)); - 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; - } - - if (this.properties.getCluster() == null) { - return null; - } - - Cluster clusterProperties = this.properties.getCluster(); - RedisClusterConfiguration config = new RedisClusterConfiguration( - clusterProperties.getNodes()); - - if (clusterProperties.getMaxRedirects() != null) { - config.setMaxRedirects(clusterProperties.getMaxRedirects()); - } - return config; - } - - private List createSentinels(Sentinel sentinel) { - List nodes = new ArrayList<>(); - for (String node : StringUtils - .commaDelimitedListToStringArray(sentinel.getNodes())) { - try { - String[] parts = StringUtils.split(node, ":"); - Assert.state(parts.length == 2, "Must be defined as 'host:port'"); - nodes.add(new RedisNode(parts[0], Integer.valueOf(parts[1]))); - } - catch (RuntimeException ex) { - throw new IllegalStateException( - "Invalid redis sentinel " + "property '" + node + "'", ex); - } - } - return nodes; - } - + @Bean + @ConditionalOnMissingBean(name = "redisTemplate") + public RedisTemplate redisTemplate( + RedisConnectionFactory redisConnectionFactory) + throws UnknownHostException { + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(redisConnectionFactory); + return template; } - /** - * Standard Redis configuration. - */ - @Configuration - protected static class RedisConfiguration { - - @Bean - @ConditionalOnMissingBean(name = "redisTemplate") - public RedisTemplate redisTemplate( - RedisConnectionFactory redisConnectionFactory) - throws UnknownHostException { - RedisTemplate template = new RedisTemplate<>(); - template.setConnectionFactory(redisConnectionFactory); - return template; - } - - @Bean - @ConditionalOnMissingBean(StringRedisTemplate.class) - public StringRedisTemplate stringRedisTemplate( - RedisConnectionFactory redisConnectionFactory) - throws UnknownHostException { - StringRedisTemplate template = new StringRedisTemplate(); - template.setConnectionFactory(redisConnectionFactory); - return template; - } - + @Bean + @ConditionalOnMissingBean(StringRedisTemplate.class) + public StringRedisTemplate stringRedisTemplate( + RedisConnectionFactory redisConnectionFactory) + throws UnknownHostException { + StringRedisTemplate template = new StringRedisTemplate(); + template.setConnectionFactory(redisConnectionFactory); + return template; } } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisConnectionConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisConnectionConfiguration.java new file mode 100644 index 00000000000..b8ae36329f2 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisConnectionConfiguration.java @@ -0,0 +1,153 @@ +/* + * 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 java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +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.RedisSentinelConfiguration; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * Base Redis connection configuration. + * + * @author Mark Paluch + * @author Stephane Nicoll + */ +abstract class RedisConnectionConfiguration { + + private final RedisProperties properties; + + private final RedisSentinelConfiguration sentinelConfiguration; + + private final RedisClusterConfiguration clusterConfiguration; + + protected RedisConnectionConfiguration(RedisProperties properties, + ObjectProvider sentinelConfigurationProvider, + ObjectProvider clusterConfigurationProvider) { + this.properties = properties; + this.sentinelConfiguration = sentinelConfigurationProvider.getIfAvailable(); + this.clusterConfiguration = clusterConfigurationProvider.getIfAvailable(); + } + + protected final RedisSentinelConfiguration getSentinelConfig() { + if (this.sentinelConfiguration != null) { + return this.sentinelConfiguration; + } + RedisProperties.Sentinel sentinelProperties = this.properties.getSentinel(); + if (sentinelProperties != null) { + RedisSentinelConfiguration config = new RedisSentinelConfiguration(); + config.master(sentinelProperties.getMaster()); + config.setSentinels(createSentinels(sentinelProperties)); + 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; + } + if (this.properties.getCluster() == null) { + return null; + } + RedisProperties.Cluster clusterProperties = this.properties.getCluster(); + RedisClusterConfiguration config = new RedisClusterConfiguration( + clusterProperties.getNodes()); + if (clusterProperties.getMaxRedirects() != null) { + config.setMaxRedirects(clusterProperties.getMaxRedirects()); + } + return config; + } + + private List createSentinels(RedisProperties.Sentinel sentinel) { + List nodes = new ArrayList<>(); + for (String node : StringUtils + .commaDelimitedListToStringArray(sentinel.getNodes())) { + try { + String[] parts = StringUtils.split(node, ":"); + Assert.state(parts.length == 2, "Must be defined as 'host:port'"); + nodes.add(new RedisNode(parts[0], Integer.valueOf(parts[1]))); + } + catch (RuntimeException ex) { + throw new IllegalStateException( + "Invalid redis sentinel " + "property '" + node + "'", ex); + } + } + return nodes; + } + + protected ConnectionInfo parseUrl(String url) { + try { + URI uri = new URI(url); + boolean useSsl = (url.startsWith("rediss://")); + String password = null; + if (uri.getUserInfo() != null) { + password = uri.getUserInfo(); + int index = password.lastIndexOf(":"); + if (index >= 0) { + password = password.substring(index + 1); + } + } + return new ConnectionInfo(uri, useSsl, password); + } + catch (URISyntaxException ex) { + throw new IllegalArgumentException("Malformed url '" + url + "'", ex); + } + } + + protected static class ConnectionInfo { + + private final URI uri; + private final boolean useSsl; + private final String password; + + public ConnectionInfo(URI uri, boolean useSsl, String password) { + this.uri = uri; + this.useSsl = useSsl; + this.password = password; + } + + public boolean isUseSsl() { + return this.useSsl; + } + + public String getHostName() { + return this.uri.getHost(); + } + + public int getPort() { + return this.uri.getPort(); + } + + public String getPassword() { + return this.password; + } + + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisProperties.java index e0d9de2bdfe..f9b34bbb151 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisProperties.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisProperties.java @@ -19,7 +19,6 @@ package org.springframework.boot.autoconfigure.data.redis; import java.util.List; import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.DeprecatedConfigurationProperty; /** * Configuration properties for Redis. @@ -29,6 +28,7 @@ import org.springframework.boot.context.properties.DeprecatedConfigurationProper * @author Eddú Meléndez * @author Marco Aust * @author Mark Paluch + * @author Stephane Nicoll */ @ConfigurationProperties(prefix = "spring.redis") public class RedisProperties { @@ -68,21 +68,13 @@ public class RedisProperties { */ private int timeout; - private Pool pool; - private Sentinel sentinel; private Cluster cluster; - /** - * Jedis client properties. - */ - private Jedis jedis; + private final Jedis jedis = new Jedis(); - /** - * Lettuce client properties. - */ - private Lettuce lettuce; + private final Lettuce lettuce = new Lettuce(); public int getDatabase() { return this.database; @@ -148,15 +140,6 @@ public class RedisProperties { this.sentinel = sentinel; } - @DeprecatedConfigurationProperty(reason = "Moved to client-specific properties", replacement = "spring.redis.jedis.pool") - public Pool getPool() { - return this.pool; - } - - public void setPool(Pool pool) { - this.pool = pool; - } - public Cluster getCluster() { return this.cluster; } @@ -166,19 +149,11 @@ public class RedisProperties { } public Jedis getJedis() { - return jedis; - } - - public void setJedis(Jedis jedis) { - this.jedis = jedis; + return this.jedis; } public Lettuce getLettuce() { - return lettuce; - } - - public void setLettuce(Lettuce lettuce) { - this.lettuce = lettuce; + return this.lettuce; } /** @@ -339,7 +314,7 @@ public class RedisProperties { public static class Lettuce { /** - * Shutdown timeout in milliseconds for lettuce. + * Shutdown timeout in milliseconds. */ private int shutdownTimeout = 2000; diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/LettuceRedisAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/LettuceRedisAutoConfigurationTests.java deleted file mode 100644 index d0a9ff9f088..00000000000 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/LettuceRedisAutoConfigurationTests.java +++ /dev/null @@ -1,198 +0,0 @@ -/* - * Copyright 2012-2016 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 java.util.Arrays; -import java.util.List; -import java.util.concurrent.TimeUnit; - -import io.lettuce.core.RedisClient; -import io.lettuce.core.RedisURI; -import io.lettuce.core.api.StatefulRedisConnection; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.boot.test.util.EnvironmentTestUtils; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.data.redis.connection.lettuce.DefaultLettucePool; -import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; -import org.springframework.test.util.ReflectionTestUtils; -import org.springframework.util.StringUtils; - -import static org.assertj.core.api.Assertions.*; - -/** - * Tests for {@link RedisAutoConfiguration} using Lettuce as client. - * - * @author Mark Paluch - */ -public class LettuceRedisAutoConfigurationTests { - - private AnnotationConfigApplicationContext context; - - @Before - public void setup() { - this.context = new AnnotationConfigApplicationContext(); - } - - @After - public void close() { - if (this.context != null) { - this.context.close(); - } - } - - @Test - public void testOverrideRedisConfiguration() throws Exception { - load("spring.redis.host:foo", "spring.redis.database:1"); - assertThat(this.context.getBean(LettuceConnectionFactory.class).getHostName()) - .isEqualTo("foo"); - assertThat(this.context.getBean(LettuceConnectionFactory.class).getDatabase()) - .isEqualTo(1); - } - - @Test - public void testOverrideUrlRedisConfiguration() throws Exception { - load("spring.redis.host:foo", "spring.redis.password:xyz", - "spring.redis.port:1000", "spring.redis.ssl:true", - "spring.redis.url:redis://user:password@example:33"); - assertThat(this.context.getBean(LettuceConnectionFactory.class).getHostName()) - .isEqualTo("example"); - assertThat(this.context.getBean(LettuceConnectionFactory.class).getPort()) - .isEqualTo(33); - assertThat(this.context.getBean(LettuceConnectionFactory.class).getPassword()) - .isEqualTo("password"); - assertThat(this.context.getBean(LettuceConnectionFactory.class).isUseSsl()) - .isEqualTo(true); - } - - @Test - public void testSslRedisConfiguration() throws Exception { - load("spring.redis.host:foo", "spring.redis.ssl:true"); - assertThat(this.context.getBean(LettuceConnectionFactory.class).getHostName()) - .isEqualTo("foo"); - assertThat(this.context.getBean(LettuceConnectionFactory.class).isUseSsl()) - .isTrue(); - } - - @Test - public void testRedisConfigurationWithPool() throws Exception { - load("spring.redis.host:foo", "spring.redis.lettuce.pool.max-idle:1"); - assertThat(this.context.getBean(LettuceConnectionFactory.class).getHostName()) - .isEqualTo("foo"); - assertThat(getDefaultLettucePool( - this.context.getBean(LettuceConnectionFactory.class)).getHostName()) - .isEqualTo("foo"); - assertThat(getDefaultLettucePool( - this.context.getBean(LettuceConnectionFactory.class)).getPoolConfig() - .getMaxIdle()).isEqualTo(1); - } - - @Test - public void testRedisConfigurationWithTimeout() throws Exception { - load("spring.redis.host:foo", "spring.redis.timeout:100"); - assertThat(this.context.getBean(LettuceConnectionFactory.class).getHostName()) - .isEqualTo("foo"); - assertThat(this.context.getBean(LettuceConnectionFactory.class).getTimeout()) - .isEqualTo(100); - } - - @Test - public void testRedisConfigurationWithSentinel() throws Exception { - List sentinels = Arrays.asList("127.0.0.1:26379", "127.0.0.1:26380"); - if (isAtLeastOneNodeAvailable(sentinels)) { - load("spring.redis.sentinel.master:mymaster", "spring.redis.sentinel.nodes:" - + StringUtils.collectionToCommaDelimitedString(sentinels)); - assertThat(this.context.getBean(LettuceConnectionFactory.class) - .isRedisSentinelAware()).isTrue(); - } - } - - @Test - public void testRedisConfigurationWithCluster() throws Exception { - List clusterNodes = Arrays.asList("127.0.0.1:27379", "127.0.0.1:27380"); - load("spring.redis.cluster.nodes[0]:" + clusterNodes.get(0), - "spring.redis.cluster.nodes[1]:" + clusterNodes.get(1)); - assertThat(this.context.getBean(LettuceConnectionFactory.class) - .getClusterConnection()).isNotNull(); - } - - private DefaultLettucePool getDefaultLettucePool(LettuceConnectionFactory factory) { - return (DefaultLettucePool) ReflectionTestUtils.getField(factory, "pool"); - } - - private boolean isAtLeastOneNodeAvailable(List nodes) { - for (String node : nodes) { - if (isAvailable(node)) { - return true; - } - } - - return false; - } - - private boolean isAvailable(String node) { - RedisClient redisClient = null; - try { - String[] hostAndPort = node.split(":"); - redisClient = RedisClient.create(new RedisURI(hostAndPort[0], - Integer.valueOf(hostAndPort[1]), 10, TimeUnit.SECONDS)); - StatefulRedisConnection connection = redisClient.connect(); - connection.sync().ping(); - connection.close(); - return true; - } - catch (Exception ex) { - return false; - } - finally { - if (redisClient != null) { - try { - redisClient.shutdown(0, 0, TimeUnit.SECONDS); - } - catch (Exception ex) { - // Continue - } - } - } - } - - private void load(String... environment) { - this.context = doLoad(environment); - } - - private AnnotationConfigApplicationContext doLoad(String... environment) { - - AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); - EnvironmentTestUtils.addEnvironment(applicationContext, environment); - applicationContext.register( - RedisAutoConfiguration.LettuceRedisConnectionConfiguration.class, - PropertyPlaceholderAutoConfiguration.class, - EnableRedisPropertiesConfiguration.class); - applicationContext.refresh(); - return applicationContext; - } - - @EnableConfigurationProperties(RedisProperties.class) - private static class EnableRedisPropertiesConfiguration { - - } - -} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationJedisTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationJedisTests.java new file mode 100644 index 00000000000..e4e1d23c9ac --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationJedisTests.java @@ -0,0 +1,158 @@ +/* + * Copyright 2012-2016 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 java.util.Arrays; +import java.util.List; + +import org.junit.After; +import org.junit.Test; +import org.junit.runner.RunWith; +import redis.clients.jedis.Jedis; + +import org.springframework.boot.junit.runner.classpath.ClassPathExclusions; +import org.springframework.boot.junit.runner.classpath.ModifiedClassPathRunner; +import org.springframework.boot.test.util.EnvironmentTestUtils; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; +import org.springframework.util.StringUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link RedisAutoConfiguration} when Lettuce is not on the classpath. + * + * @author Mark Paluch + * @author Stephane Nicoll + */ +@RunWith(ModifiedClassPathRunner.class) +@ClassPathExclusions("lettuce-core-*.jar") +public class RedisAutoConfigurationJedisTests { + + private AnnotationConfigApplicationContext context; + + @After + public void close() { + if (this.context != null) { + this.context.close(); + } + } + + @Test + public void testOverrideRedisConfiguration() throws Exception { + load("spring.redis.host:foo", "spring.redis.database:1"); + JedisConnectionFactory cf = this.context.getBean(JedisConnectionFactory.class); + assertThat(cf.getHostName()).isEqualTo("foo"); + assertThat(cf.getDatabase()).isEqualTo(1); + assertThat(cf.getPassword()).isNull(); + assertThat(cf.isUseSsl()).isFalse(); + } + + @Test + public void testOverrideUrlRedisConfiguration() throws Exception { + load("spring.redis.host:foo", "spring.redis.password:xyz", + "spring.redis.port:1000", "spring.redis.ssl:true", + "spring.redis.url:redis://user:password@example:33"); + JedisConnectionFactory cf = this.context.getBean(JedisConnectionFactory.class); + assertThat(cf.getHostName()).isEqualTo("example"); + assertThat(cf.getPort()).isEqualTo(33); + assertThat(cf.getPassword()).isEqualTo("password"); + assertThat(cf.isUseSsl()).isTrue(); + } + + @Test + public void testRedisConfigurationWithPool() throws Exception { + load("spring.redis.host:foo", "spring.redis.jedis.pool.max-idle:1"); + JedisConnectionFactory cf = this.context.getBean(JedisConnectionFactory.class); + assertThat(cf.getHostName()).isEqualTo("foo"); + assertThat(cf.getPoolConfig().getMaxIdle()).isEqualTo(1); + } + + @Test + public void testRedisConfigurationWithTimeout() throws Exception { + load("spring.redis.host:foo", "spring.redis.timeout:100"); + JedisConnectionFactory cf = this.context.getBean(JedisConnectionFactory.class); + assertThat(cf.getHostName()).isEqualTo("foo"); + assertThat(cf.getTimeout()).isEqualTo(100); + } + + @Test + public void testRedisConfigurationWithSentinel() throws Exception { + List sentinels = Arrays.asList("127.0.0.1:26379", "127.0.0.1:26380"); + if (isAtLeastOneNodeAvailable(sentinels)) { + load("spring.redis.sentinel.master:mymaster", "spring.redis.sentinel.nodes:" + + StringUtils.collectionToCommaDelimitedString(sentinels)); + assertThat(this.context.getBean(JedisConnectionFactory.class) + .isRedisSentinelAware()).isTrue(); + } + } + + @Test + public void testRedisConfigurationWithCluster() throws Exception { + List clusterNodes = Arrays.asList("127.0.0.1:27379", "127.0.0.1:27380"); + if (isAtLeastOneNodeAvailable(clusterNodes)) { + load("spring.redis.cluster.nodes[0]:" + clusterNodes.get(0), + "spring.redis.cluster.nodes[1]:" + clusterNodes.get(1)); + assertThat(this.context.getBean(JedisConnectionFactory.class) + .getClusterConnection()).isNotNull(); + } + } + + private void load(String... environment) { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + EnvironmentTestUtils.addEnvironment(ctx, environment); + ctx.register(RedisAutoConfiguration.class); + ctx.refresh(); + this.context = ctx; + } + + private boolean isAtLeastOneNodeAvailable(List nodes) { + for (String node : nodes) { + if (isAvailable(node)) { + return true; + } + } + + return false; + } + + private boolean isAvailable(String node) { + Jedis jedis = null; + try { + String[] hostAndPort = node.split(":"); + jedis = new Jedis(hostAndPort[0], Integer.valueOf(hostAndPort[1])); + jedis.connect(); + jedis.ping(); + return true; + } + catch (Exception ex) { + return false; + } + finally { + if (jedis != null) { + try { + jedis.disconnect(); + jedis.close(); + } + catch (Exception ex) { + // Continue + } + } + } + } + +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationTests.java index 165e5cd9a0c..90202137cee 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationTests.java @@ -18,19 +18,21 @@ package org.springframework.boot.autoconfigure.data.redis; import java.util.Arrays; import java.util.List; +import java.util.concurrent.TimeUnit; +import io.lettuce.core.RedisClient; +import io.lettuce.core.RedisURI; +import io.lettuce.core.api.StatefulRedisConnection; import org.junit.After; -import org.junit.Before; import org.junit.Test; -import redis.clients.jedis.Jedis; -import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; -import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.test.util.EnvironmentTestUtils; import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; +import org.springframework.data.redis.connection.lettuce.DefaultLettucePool; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.core.RedisOperations; import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.test.util.ReflectionTestUtils; import org.springframework.util.StringUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -44,16 +46,12 @@ import static org.assertj.core.api.Assertions.assertThat; * @author Eddú Meléndez * @author Marco Aust * @author Mark Paluch + * @author Stephane Nicoll */ public class RedisAutoConfigurationTests { private AnnotationConfigApplicationContext context; - @Before - public void setup() { - this.context = new AnnotationConfigApplicationContext(); - } - @After public void close() { if (this.context != null) { @@ -62,7 +60,7 @@ public class RedisAutoConfigurationTests { } @Test - public void testDefaultRedisConfiguration() throws Exception { + public void testDefaultRedisConfiguration() { load(); assertThat(this.context.getBean("redisTemplate", RedisOperations.class)) .isNotNull(); @@ -70,45 +68,42 @@ public class RedisAutoConfigurationTests { } @Test - public void testOverrideRedisConfiguration() throws Exception { + public void testOverrideRedisConfiguration() { load("spring.redis.host:foo", "spring.redis.database:1"); - assertThat(this.context.getBean(JedisConnectionFactory.class).getHostName()) - .isEqualTo("foo"); - assertThat(this.context.getBean(JedisConnectionFactory.class).getDatabase()) - .isEqualTo(1); + LettuceConnectionFactory cf = this.context.getBean(LettuceConnectionFactory.class); + assertThat(cf.getHostName()).isEqualTo("foo"); + assertThat(cf.getDatabase()).isEqualTo(1); + assertThat(cf.getPassword()).isNull(); + assertThat(cf.isUseSsl()).isFalse(); } @Test - public void testOverrideUrlRedisConfiguration() throws Exception { + public void testOverrideUrlRedisConfiguration() { load("spring.redis.host:foo", "spring.redis.password:xyz", "spring.redis.port:1000", "spring.redis.ssl:true", "spring.redis.url:redis://user:password@example:33"); - assertThat(this.context.getBean(JedisConnectionFactory.class).getHostName()) - .isEqualTo("example"); - assertThat(this.context.getBean(JedisConnectionFactory.class).getPort()) - .isEqualTo(33); - assertThat(this.context.getBean(JedisConnectionFactory.class).getPassword()) - .isEqualTo("password"); - assertThat(this.context.getBean(JedisConnectionFactory.class).isUseSsl()) - .isEqualTo(true); + LettuceConnectionFactory cf = this.context.getBean(LettuceConnectionFactory.class); + assertThat(cf.getHostName()).isEqualTo("example"); + assertThat(cf.getPort()).isEqualTo(33); + assertThat(cf.getPassword()).isEqualTo("password"); + assertThat(cf.isUseSsl()).isTrue(); } @Test public void testRedisConfigurationWithPool() throws Exception { - load("spring.redis.host:foo", "spring.redis.jedis.pool.max-idle:1"); - assertThat(this.context.getBean(JedisConnectionFactory.class).getHostName()) - .isEqualTo("foo"); - assertThat(this.context.getBean(JedisConnectionFactory.class).getPoolConfig() - .getMaxIdle()).isEqualTo(1); + load("spring.redis.host:foo", "spring.redis.lettuce.pool.max-idle:1"); + LettuceConnectionFactory cf = this.context.getBean(LettuceConnectionFactory.class); + assertThat(cf.getHostName()).isEqualTo("foo"); + assertThat(getDefaultLettucePool(cf).getHostName()).isEqualTo("foo"); + assertThat(getDefaultLettucePool(cf).getPoolConfig().getMaxIdle()).isEqualTo(1); } @Test public void testRedisConfigurationWithTimeout() throws Exception { load("spring.redis.host:foo", "spring.redis.timeout:100"); - assertThat(this.context.getBean(JedisConnectionFactory.class).getHostName()) - .isEqualTo("foo"); - assertThat(this.context.getBean(JedisConnectionFactory.class).getTimeout()) - .isEqualTo(100); + LettuceConnectionFactory cf = this.context.getBean(LettuceConnectionFactory.class); + assertThat(cf.getHostName()).isEqualTo("foo"); + assertThat(cf.getTimeout()).isEqualTo(100); } @Test @@ -117,7 +112,7 @@ public class RedisAutoConfigurationTests { if (isAtLeastOneNodeAvailable(sentinels)) { load("spring.redis.sentinel.master:mymaster", "spring.redis.sentinel.nodes:" + StringUtils.collectionToCommaDelimitedString(sentinels)); - assertThat(this.context.getBean(JedisConnectionFactory.class) + assertThat(this.context.getBean(LettuceConnectionFactory.class) .isRedisSentinelAware()).isTrue(); } } @@ -125,12 +120,14 @@ public class RedisAutoConfigurationTests { @Test public void testRedisConfigurationWithCluster() throws Exception { List clusterNodes = Arrays.asList("127.0.0.1:27379", "127.0.0.1:27380"); - if (isAtLeastOneNodeAvailable(clusterNodes)) { - load("spring.redis.cluster.nodes[0]:" + clusterNodes.get(0), - "spring.redis.cluster.nodes[1]:" + clusterNodes.get(1)); - assertThat(this.context.getBean(JedisConnectionFactory.class) - .getClusterConnection()).isNotNull(); - } + load("spring.redis.cluster.nodes[0]:" + clusterNodes.get(0), + "spring.redis.cluster.nodes[1]:" + clusterNodes.get(1)); + assertThat(this.context.getBean(LettuceConnectionFactory.class) + .getClusterConnection()).isNotNull(); + } + + private DefaultLettucePool getDefaultLettucePool(LettuceConnectionFactory factory) { + return (DefaultLettucePool) ReflectionTestUtils.getField(factory, "pool"); } private boolean isAtLeastOneNodeAvailable(List nodes) { @@ -144,22 +141,23 @@ public class RedisAutoConfigurationTests { } private boolean isAvailable(String node) { - Jedis jedis = null; + RedisClient redisClient = null; try { String[] hostAndPort = node.split(":"); - jedis = new Jedis(hostAndPort[0], Integer.valueOf(hostAndPort[1])); - jedis.connect(); - jedis.ping(); + redisClient = RedisClient.create(new RedisURI(hostAndPort[0], + Integer.valueOf(hostAndPort[1]), 10, TimeUnit.SECONDS)); + StatefulRedisConnection connection = redisClient.connect(); + connection.sync().ping(); + connection.close(); return true; } catch (Exception ex) { return false; } finally { - if (jedis != null) { + if (redisClient != null) { try { - jedis.disconnect(); - jedis.close(); + redisClient.shutdown(0, 0, TimeUnit.SECONDS); } catch (Exception ex) { // Continue @@ -169,21 +167,11 @@ public class RedisAutoConfigurationTests { } private void load(String... environment) { - this.context = doLoad(environment); - } - - private AnnotationConfigApplicationContext doLoad(String... environment) { - AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); - EnvironmentTestUtils.addEnvironment(applicationContext, environment); - applicationContext.register(RedisAutoConfiguration.JedisRedisConnectionConfiguration.class, RedisAutoConfiguration.RedisConfiguration.class, - PropertyPlaceholderAutoConfiguration.class, EnableRedisPropertiesConfiguration.class); - applicationContext.refresh(); - return applicationContext; - } - - @EnableConfigurationProperties(RedisProperties.class) - private static class EnableRedisPropertiesConfiguration { - + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + EnvironmentTestUtils.addEnvironment(ctx, environment); + ctx.register(RedisAutoConfiguration.class); + ctx.refresh(); + this.context = ctx; } } diff --git a/spring-boot-dependencies/pom.xml b/spring-boot-dependencies/pom.xml index 7d061aa46dd..589ed7e9842 100644 --- a/spring-boot-dependencies/pom.xml +++ b/spring-boot-dependencies/pom.xml @@ -125,7 +125,6 @@ 4.12 5.0.0.BUILD-SNAPSHOT 3.5.3 - 5.0.0.BUILD-SNAPSHOT 2.8.2 1.2.3 1.16.16 @@ -571,11 +570,6 @@ antlr ${antlr2.version} - - io.lettuce - lettuce-core - ${lettuce.version} - ch.qos.logback logback-access @@ -831,6 +825,11 @@ metrics-servlets ${dropwizard-metrics.version} + + io.lettuce + lettuce-core + ${lettuce.version} + io.netty netty-bom @@ -2338,11 +2337,6 @@ jedis ${jedis.version} - - io.lettuce - lettuce-core - ${lettuce.version} - wsdl4j wsdl4j diff --git a/spring-boot-docs/pom.xml b/spring-boot-docs/pom.xml index 94fb2746e71..564a47d8f7b 100644 --- a/spring-boot-docs/pom.xml +++ b/spring-boot-docs/pom.xml @@ -172,6 +172,11 @@ metrics-core true + + io.lettuce + lettuce-core + true + io.projectreactor.ipc reactor-netty @@ -748,11 +753,6 @@ jedis true - - io.lettuce - lettuce-core - true - org.springframework.boot diff --git a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index 0d40b09b2f0..0d3c57fac00 100644 --- a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -850,38 +850,28 @@ content into your application; rather pick only the properties that you need. spring.redis.database=0 # Database index used by the connection factory. spring.redis.url= # Connection URL, will override host, port and password (user will be ignored), e.g. redis://user:password@example.com:6379 spring.redis.host=localhost # Redis server host. - spring.redis.password= # Login password of the redis server. - spring.redis.ssl=false # Enable SSL support. - spring.redis.jedis.pool.max-active=8 # Max number of connections that can be allocated by the pool at a given time. Use a negative value for no limit. - spring.redis.jedis.pool.max-idle=8 # Max number of "idle" connections in the pool. Use a negative value to indicate an unlimited number of idle connections. - spring.redis.jedis.pool.max-wait=-1 # Maximum amount of time (in milliseconds) a connection allocation should block before throwing an exception when the pool is exhausted. Use a negative value to block indefinitely. - spring.redis.jedis.pool.min-idle=0 # Target for the minimum number of idle connections to maintain in the pool. This setting only has an effect if it is positive. - spring.redis.port=6379 # Redis server port. - spring.redis.sentinel.master= # Name of Redis server. - spring.redis.sentinel.nodes= # Comma-separated list of host:port pairs. - spring.redis.timeout=0 # Connection timeout in milliseconds. - spring.redis.ssl.enabled=false # Enable SSL support. - spring.redis.ssl.verify-peer=true # Enable SSL peer verification. - spring.redis.ssl.start-tls=false # Enable StartTLS support. - - # REDIS JEDIS DRIVER spring.redis.jedis.pool.max-active=8 # Max number of connections that can be allocated by the pool at a given time. Use a negative value for no limit. spring.redis.jedis.pool.max-idle=8 # Max number of "idle" connections in the pool. Use a negative value to indicate an unlimited number of idle connections. spring.redis.jedis.pool.max-wait=-1 # Maximum amount of time (in milliseconds) a connection allocation should block before throwing an exception when the pool is exhausted. Use a negative value to block indefinitely. spring.redis.jedis.pool.min-idle=0 # Target for the minimum number of idle connections to maintain in the pool. This setting only has an effect if it is positive. - - # REDIS LETTUCE DRIVER spring.redis.lettuce.pool.max-active=8 # Max number of connections that can be allocated by the pool at a given time. Use a negative value for no limit. spring.redis.lettuce.pool.max-idle=8 # Max number of "idle" connections in the pool. Use a negative value to indicate an unlimited number of idle connections. spring.redis.lettuce.pool.max-wait=-1 # Maximum amount of time (in milliseconds) a connection allocation should block before throwing an exception when the pool is exhausted. Use a negative value to block indefinitely. spring.redis.lettuce.pool.min-idle=0 # Target for the minimum number of idle connections to maintain in the pool. This setting only has an effect if it is positive. spring.redis.lettuce.shutdown-timeout=2000 # Shutdown timeout in milliseconds. - + spring.redis.password= # Login password of the redis server. + spring.redis.port=6379 # Redis server port. + spring.redis.sentinel.master= # Name of Redis server. + spring.redis.sentinel.nodes= # Comma-separated list of host:port pairs. + spring.redis.ssl=false # Enable SSL support. + spring.redis.timeout=0 # Connection timeout in milliseconds. # TRANSACTION ({sc-spring-boot-autoconfigure}/transaction/TransactionProperties.{sc-ext}[TransactionProperties]) spring.transaction.default-timeout= # Default transaction timeout in seconds. spring.transaction.rollback-on-commit-failure= # Perform the rollback on commit failures. + + # ---------------------------------------- # INTEGRATION PROPERTIES # ---------------------------------------- diff --git a/spring-boot-docs/src/main/asciidoc/howto.adoc b/spring-boot-docs/src/main/asciidoc/howto.adoc index e05e2b95cbe..46b8627de39 100644 --- a/spring-boot-docs/src/main/asciidoc/howto.adoc +++ b/spring-boot-docs/src/main/asciidoc/howto.adoc @@ -3075,12 +3075,14 @@ In this example we are using a single application context (the one created by th listener) and attaching it to the `DispatcherServlet` using an init parameter. This is normal in a Spring Boot application (you normally only have one application context). + + [[howto-use-lettuce-instead-of-jedis]] === Use Lettuce instead of Jedis -The Spring Boot Redis starter (`spring-boot-starter-data-redis` in particular) uses +The Spring Boot starter (`spring-boot-starter-data-redis`) uses https://github.com/xetorthio/jedis/[Jedis] by default. You need to exclude that dependency -and include the https://github.com/lettuce-io/lettuce-core/[Lettuce] one instead. Spring Boot provides a managed dependency -to help make this process as easy as possible. +and include the https://github.com/lettuce-io/lettuce-core/[Lettuce] one instead. Spring +Boot manages that dependency to help make this process as easy as possible. Example in Maven: @@ -3114,4 +3116,4 @@ Example in Gradle: compile("io.lettuce:lettuce-core:{lettuce.version}") // ... } ----- \ No newline at end of file +---- From 47783e258aa02af4dd1b3014787a1fd5757236d3 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Tue, 2 May 2017 15:57:51 +0200 Subject: [PATCH 3/3] Add missing tests --- .../RedisAutoConfigurationJedisTests.java | 26 ++++++++++++++++--- .../redis/RedisAutoConfigurationTests.java | 26 ++++++++++++++++--- 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationJedisTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationJedisTests.java index e4e1d23c9ac..2311e455636 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationJedisTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationJedisTests.java @@ -62,11 +62,22 @@ public class RedisAutoConfigurationJedisTests { assertThat(cf.isUseSsl()).isFalse(); } + @Test + public void testRedisUrlConfiguration() throws Exception { + load("spring.redis.host:foo", + "spring.redis.url:redis://user:password@example:33"); + JedisConnectionFactory cf = this.context.getBean(JedisConnectionFactory.class); + assertThat(cf.getHostName()).isEqualTo("example"); + assertThat(cf.getPort()).isEqualTo(33); + assertThat(cf.getPassword()).isEqualTo("password"); + assertThat(cf.isUseSsl()).isFalse(); + } + @Test public void testOverrideUrlRedisConfiguration() throws Exception { load("spring.redis.host:foo", "spring.redis.password:xyz", - "spring.redis.port:1000", "spring.redis.ssl:true", - "spring.redis.url:redis://user:password@example:33"); + "spring.redis.port:1000", "spring.redis.ssl:false", + "spring.redis.url:rediss://user:password@example:33"); JedisConnectionFactory cf = this.context.getBean(JedisConnectionFactory.class); assertThat(cf.getHostName()).isEqualTo("example"); assertThat(cf.getPort()).isEqualTo(33); @@ -76,10 +87,17 @@ public class RedisAutoConfigurationJedisTests { @Test public void testRedisConfigurationWithPool() throws Exception { - load("spring.redis.host:foo", "spring.redis.jedis.pool.max-idle:1"); + load("spring.redis.host:foo", "spring.redis.jedis.pool.min-idle:1", + "spring.redis.jedis.pool.max-idle:4", + "spring.redis.jedis.pool.max-active:16", + "spring.redis.jedis.pool.max-wait:2000"); JedisConnectionFactory cf = this.context.getBean(JedisConnectionFactory.class); assertThat(cf.getHostName()).isEqualTo("foo"); - assertThat(cf.getPoolConfig().getMaxIdle()).isEqualTo(1); + assertThat(cf.getPoolConfig().getMinIdle()).isEqualTo(1); + assertThat(cf.getPoolConfig().getMaxIdle()).isEqualTo(4); + assertThat(cf.getPoolConfig().getMaxTotal()).isEqualTo(16); + assertThat(cf.getPoolConfig().getMaxWaitMillis()) + .isEqualTo(2000); } @Test diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationTests.java index 90202137cee..f878064764f 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationTests.java @@ -77,11 +77,22 @@ public class RedisAutoConfigurationTests { assertThat(cf.isUseSsl()).isFalse(); } + @Test + public void testRedisUrlConfiguration() throws Exception { + load("spring.redis.host:foo", + "spring.redis.url:redis://user:password@example:33"); + LettuceConnectionFactory cf = this.context.getBean(LettuceConnectionFactory.class); + assertThat(cf.getHostName()).isEqualTo("example"); + assertThat(cf.getPort()).isEqualTo(33); + assertThat(cf.getPassword()).isEqualTo("password"); + assertThat(cf.isUseSsl()).isFalse(); + } + @Test public void testOverrideUrlRedisConfiguration() { load("spring.redis.host:foo", "spring.redis.password:xyz", - "spring.redis.port:1000", "spring.redis.ssl:true", - "spring.redis.url:redis://user:password@example:33"); + "spring.redis.port:1000", "spring.redis.ssl:false", + "spring.redis.url:rediss://user:password@example:33"); LettuceConnectionFactory cf = this.context.getBean(LettuceConnectionFactory.class); assertThat(cf.getHostName()).isEqualTo("example"); assertThat(cf.getPort()).isEqualTo(33); @@ -91,11 +102,18 @@ public class RedisAutoConfigurationTests { @Test public void testRedisConfigurationWithPool() throws Exception { - load("spring.redis.host:foo", "spring.redis.lettuce.pool.max-idle:1"); + load("spring.redis.host:foo", "spring.redis.lettuce.pool.min-idle:1", + "spring.redis.lettuce.pool.max-idle:4", + "spring.redis.lettuce.pool.max-active:16", + "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().getMaxIdle()).isEqualTo(1); + assertThat(getDefaultLettucePool(cf).getPoolConfig().getMinIdle()).isEqualTo(1); + assertThat(getDefaultLettucePool(cf).getPoolConfig().getMaxIdle()).isEqualTo(4); + assertThat(getDefaultLettucePool(cf).getPoolConfig().getMaxTotal()).isEqualTo(16); + assertThat(getDefaultLettucePool(cf).getPoolConfig().getMaxWaitMillis()) + .isEqualTo(2000); } @Test