diff --git a/spring-boot-actuator/pom.xml b/spring-boot-actuator/pom.xml index 3eb06c8c7a..11f8d6cfb7 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 784782fb92..b5d5be5839 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 b9882aaafd..e19139a5f0 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 cd182a485d..e0d9de2bdf 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 0000000000..d0a9ff9f08 --- /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 ab313ee5c5..165e5cd9a0 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 6a7f3c6c26..7d061aa46d 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 33788d4b04..94fb2746e7 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 658f787f65..0d40b09b2f 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 dad769934d..e05e2b95cb 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 6c645728b3..1eed766e2f 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.