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 @@
jedistrue
+
+ io.lettuce
+ lettuce-core
+ true
+ org.springframework.amqpspring-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 @@
jedistrue
+
+ io.lettuce
+ lettuce-core
+ true
+ org.liquibaseliquibase-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
+
+ io.lettuce
+ lettuce-core
+ ${lettuce.version}
+ wsdl4jwsdl4j
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 @@
jedistrue
+
+ 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.