diff --git a/spring-boot-autoconfigure/pom.xml b/spring-boot-autoconfigure/pom.xml index 784782fb929..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 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 b9882aaafdc..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,36 +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 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.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.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.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. @@ -58,193 +41,33 @@ 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) +@Import({ LettuceConnectionConfiguration.class, + JedisConnectionConfiguration.class }) public class RedisAutoConfiguration { - /** - * Redis connection configuration. - */ - @Configuration - @ConditionalOnClass(GenericObjectPool.class) - protected static class RedisConnectionConfiguration { - - private final RedisProperties properties; - - private final RedisSentinelConfiguration sentinelConfiguration; - - private final RedisClusterConfiguration clusterConfiguration; - - public RedisConnectionConfiguration(RedisProperties properties, - ObjectProvider sentinelConfiguration, - ObjectProvider clusterConfiguration) { - this.properties = properties; - this.sentinelConfiguration = sentinelConfiguration.getIfAvailable(); - this.clusterConfiguration = clusterConfiguration.getIfAvailable(); - } - - @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); - } - } - - 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; - } - - 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; - } - + @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 cd182a485d8..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 @@ -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. @@ -27,6 +27,8 @@ import org.springframework.boot.context.properties.ConfigurationProperties; * @author Christoph Strobl * @author Eddú Meléndez * @author Marco Aust + * @author Mark Paluch + * @author Stephane Nicoll */ @ConfigurationProperties(prefix = "spring.redis") public class RedisProperties { @@ -66,12 +68,14 @@ public class RedisProperties { */ private int timeout; - private Pool pool; - private Sentinel sentinel; private Cluster cluster; + private final Jedis jedis = new Jedis(); + + private final Lettuce lettuce = new Lettuce(); + public int getDatabase() { return this.database; } @@ -136,14 +140,6 @@ public class RedisProperties { this.sentinel = sentinel; } - public Pool getPool() { - return this.pool; - } - - public void setPool(Pool pool) { - this.pool = pool; - } - public Cluster getCluster() { return this.cluster; } @@ -152,6 +148,14 @@ public class RedisProperties { this.cluster = cluster; } + public Jedis getJedis() { + return this.jedis; + } + + public Lettuce getLettuce() { + return this.lettuce; + } + /** * Pool properties. */ @@ -284,4 +288,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. + */ + 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/RedisAutoConfigurationJedisTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationJedisTests.java new file mode 100644 index 00000000000..2311e455636 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationJedisTests.java @@ -0,0 +1,176 @@ +/* + * 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 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: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); + assertThat(cf.getPassword()).isEqualTo("password"); + assertThat(cf.isUseSsl()).isTrue(); + } + + @Test + public void testRedisConfigurationWithPool() throws Exception { + 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().getMinIdle()).isEqualTo(1); + assertThat(cf.getPoolConfig().getMaxIdle()).isEqualTo(4); + assertThat(cf.getPoolConfig().getMaxTotal()).isEqualTo(16); + assertThat(cf.getPoolConfig().getMaxWaitMillis()) + .isEqualTo(2000); + } + + @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 ab313ee5c57..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 @@ -18,18 +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.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; @@ -42,16 +45,13 @@ import static org.assertj.core.api.Assertions.assertThat; * @author Christoph Strobl * @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) { @@ -60,7 +60,7 @@ public class RedisAutoConfigurationTests { } @Test - public void testDefaultRedisConfiguration() throws Exception { + public void testDefaultRedisConfiguration() { load(); assertThat(this.context.getBean("redisTemplate", RedisOperations.class)) .isNotNull(); @@ -68,45 +68,60 @@ 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 { - load("spring.redis.host:foo", "spring.redis.password:xyz", - "spring.redis.port:1000", "spring.redis.ssl:true", + public void testRedisUrlConfiguration() throws Exception { + load("spring.redis.host:foo", "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()).isFalse(); + } + + @Test + public void testOverrideUrlRedisConfiguration() { + load("spring.redis.host:foo", "spring.redis.password:xyz", + "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); + assertThat(cf.getPassword()).isEqualTo("password"); + assertThat(cf.isUseSsl()).isTrue(); } @Test public void testRedisConfigurationWithPool() throws Exception { - load("spring.redis.host:foo", "spring.redis.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.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().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 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 @@ -115,7 +130,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(); } } @@ -123,12 +138,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) { @@ -142,22 +159,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 @@ -167,16 +185,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.class, - PropertyPlaceholderAutoConfiguration.class); - applicationContext.refresh(); - return applicationContext; + 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 6a7f3c6c26f..589ed7e9842 100644 --- a/spring-boot-dependencies/pom.xml +++ b/spring-boot-dependencies/pom.xml @@ -570,11 +570,6 @@ antlr ${antlr2.version} - - io.lettuce - lettuce-core - ${lettuce.version} - ch.qos.logback logback-access @@ -830,6 +825,11 @@ metrics-servlets ${dropwizard-metrics.version} + + io.lettuce + lettuce-core + ${lettuce.version} + io.netty netty-bom diff --git a/spring-boot-docs/pom.xml b/spring-boot-docs/pom.xml index 33788d4b041..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 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..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,15 +850,20 @@ 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.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.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.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.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]) diff --git a/spring-boot-docs/src/main/asciidoc/howto.adoc b/spring-boot-docs/src/main/asciidoc/howto.adoc index dad769934d9..46b8627de39 100644 --- a/spring-boot-docs/src/main/asciidoc/howto.adoc +++ b/spring-boot-docs/src/main/asciidoc/howto.adoc @@ -3074,3 +3074,46 @@ 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 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 manages that 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}") + // ... + } +---- 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.