Polishing.

Move executor from ClusterConfiguration to connection factories as the executor is a Spring concept that isn't tied to endpoint details or the client config.

Reorder static factory methods after constructors and property accessors after static factory methods. Inline single-line single-use methods that aren't intended as extension hooks for easier readability.

Disable TaskExecutor disposal on ClusterCommandExecutor.destroy().

Remove NonNull annotations as default non-nullability is defined on the package level.

Simplify tests to use integration tests to avoid excessive mocking.

See #2594
Original pull request: #2669
This commit is contained in:
Mark Paluch
2023-08-17 10:04:55 +02:00
parent 8d6ebb4e99
commit f5f0c3b259
9 changed files with 817 additions and 925 deletions

View File

@@ -16,12 +16,16 @@
package org.springframework.data.redis.connection.jedis;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.data.redis.SettingsUtils;
import org.springframework.data.redis.connection.ClusterCommandExecutor;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.test.condition.EnabledOnRedisClusterAvailable;
import org.springframework.lang.Nullable;
/**
@@ -93,4 +97,20 @@ class JedisConnectionFactoryIntegrationTests {
factory.destroy();
}
@Test // GH-2594
@EnabledOnRedisClusterAvailable
void configuresExecutorCorrectly() {
AsyncTaskExecutor mockTaskExecutor = mock(AsyncTaskExecutor.class);
JedisConnectionFactory factory = new JedisConnectionFactory(SettingsUtils.clusterConfiguration());
factory.setExecutor(mockTaskExecutor);
factory.start();
ClusterCommandExecutor clusterCommandExecutor = factory.getRequiredClusterCommandExecutor();
assertThat(clusterCommandExecutor).extracting("executor").isEqualTo(mockTaskExecutor);
factory.destroy();
}
}

View File

@@ -15,16 +15,12 @@
*/
package org.springframework.data.redis.connection.jedis;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import redis.clients.jedis.JedisClientConfig;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisPoolConfig;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
@@ -37,11 +33,8 @@ import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSocketFactory;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.junit.jupiter.api.Test;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.data.redis.connection.ClusterCommandExecutor;
import org.springframework.data.redis.connection.ClusterTopologyProvider;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
@@ -50,17 +43,12 @@ import org.springframework.data.redis.connection.jedis.JedisConnectionFactory.St
import org.springframework.lang.Nullable;
import org.springframework.test.util.ReflectionTestUtils;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import redis.clients.jedis.JedisClientConfig;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisPoolConfig;
/**
* Unit tests for {@link JedisConnectionFactory}.
*
* @author Christoph Strobl
* @author Mark Paluch
* @author John Blum
*/
class JedisConnectionFactoryUnitTests {
@@ -349,41 +337,13 @@ class JedisConnectionFactoryUnitTests {
assertThat(connectionFactory.isRunning()).isTrue();
}
@Test // GH-2594
void configuresCustomTaskExecutorCorrectly() {
AsyncTaskExecutor mockTaskExecutor = mock(AsyncTaskExecutor.class);
ClusterTopologyProvider mockClusterTopologyProvider = mock(ClusterTopologyProvider.class);
JedisCluster mockJedisCluster = mock(JedisCluster.class);
RedisClusterConfiguration clusterConfiguration = new RedisClusterConfiguration();
clusterConfiguration.setAsyncTaskExecutor(mockTaskExecutor);
JedisConnectionFactory connectionFactory = initSpyedConnectionFactory(clusterConfiguration, null);
doReturn(false).when(connectionFactory).getUsePool();
doReturn(mockJedisCluster).when(connectionFactory).createCluster();
doReturn(mockClusterTopologyProvider).when(connectionFactory).createTopologyProvider(eq(mockJedisCluster));
connectionFactory.start();
assertThat(connectionFactory.isRunning()).isTrue();
ClusterCommandExecutor clusterCommandExecutor = connectionFactory.getClusterCommandExecutor();
assertThat(clusterCommandExecutor).isNotNull();
assertThat(ReflectionTestUtils.getField(clusterCommandExecutor, "executor")).isEqualTo(mockTaskExecutor);
}
private JedisConnectionFactory initSpyedConnectionFactory(RedisSentinelConfiguration sentinelConfiguration,
@Nullable JedisPoolConfig poolConfig) {
// we have to use a spy here as jedis would start connecting to redis sentinels when the pool is created.
JedisConnectionFactory connectionFactorySpy = spy(new JedisConnectionFactory(sentinelConfiguration, poolConfig));
doReturn(null).when(connectionFactorySpy)
.createRedisSentinelPool(any(RedisSentinelConfiguration.class));
doReturn(null).when(connectionFactorySpy).createRedisSentinelPool(any(RedisSentinelConfiguration.class));
doReturn(null).when(connectionFactorySpy).createRedisPool();
@@ -397,8 +357,8 @@ class JedisConnectionFactoryUnitTests {
JedisConnectionFactory connectionFactorySpy = spy(new JedisConnectionFactory(clusterConfiguration, poolConfig));
doReturn(clusterMock).when(connectionFactorySpy)
.createCluster(any(RedisClusterConfiguration.class), any(GenericObjectPoolConfig.class));
doReturn(clusterMock).when(connectionFactorySpy).createCluster(any(RedisClusterConfiguration.class),
any(GenericObjectPoolConfig.class));
doReturn(null).when(connectionFactorySpy).createRedisPool();

View File

@@ -17,6 +17,7 @@ package org.springframework.data.redis.connection.lettuce;
import static org.assertj.core.api.Assertions.*;
import static org.junit.Assume.*;
import static org.mockito.Mockito.*;
import io.lettuce.core.EpollProvider;
import io.lettuce.core.KqueueProvider;
@@ -36,10 +37,12 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.data.redis.ConnectionFactoryTracker;
import org.springframework.data.redis.RedisConnectionFailureException;
import org.springframework.data.redis.RedisSystemException;
import org.springframework.data.redis.SettingsUtils;
import org.springframework.data.redis.connection.ClusterCommandExecutor;
import org.springframework.data.redis.connection.DefaultStringRedisConnection;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
@@ -570,6 +573,24 @@ class LettuceConnectionFactoryTests {
factory.destroy();
}
@Test // GH-2594
@EnabledOnRedisClusterAvailable
void configuresExecutorCorrectly() {
LettuceClientConfiguration configuration = LettuceTestClientConfiguration.builder().build();
AsyncTaskExecutor mockTaskExecutor = mock(AsyncTaskExecutor.class);
LettuceConnectionFactory factory = new LettuceConnectionFactory(SettingsUtils.clusterConfiguration(),
configuration);
factory.setExecutor(mockTaskExecutor);
factory.start();
ClusterCommandExecutor clusterCommandExecutor = factory.getRequiredClusterCommandExecutor();
assertThat(clusterCommandExecutor).extracting("executor").isEqualTo(mockTaskExecutor);
factory.destroy();
}
@Test // GH-2503
void startStopStartConnectionFactory() {

View File

@@ -15,15 +15,12 @@
*/
package org.springframework.data.redis.connection.lettuce;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import static org.springframework.data.redis.connection.ClusterTestVariables.CLUSTER_NODE_1;
import static org.springframework.data.redis.connection.RedisConfiguration.WithHostAndPort;
import static org.springframework.data.redis.test.extension.LettuceTestClientResources.getSharedClientResources;
import static org.springframework.test.util.ReflectionTestUtils.getField;
import static org.springframework.data.redis.connection.ClusterTestVariables.*;
import static org.springframework.data.redis.connection.RedisConfiguration.*;
import static org.springframework.data.redis.test.extension.LettuceTestClientResources.*;
import static org.springframework.test.util.ReflectionTestUtils.*;
import io.lettuce.core.AbstractRedisClient;
import io.lettuce.core.ClientOptions;
@@ -54,10 +51,8 @@ import org.junit.jupiter.api.Test;
import org.mockito.ArgumentMatchers;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.data.redis.ConnectionFactoryTracker;
import org.springframework.data.redis.RedisConnectionFailureException;
import org.springframework.data.redis.connection.ClusterCommandExecutor;
import org.springframework.data.redis.connection.PoolException;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisClusterConnection;
@@ -1050,8 +1045,7 @@ class LettuceConnectionFactoryUnitTests {
clusterConfiguration.clusterNode("localhost", 1234).setMaxRedirects(42);
LettuceClientConfiguration clientConfiguration = LettuceTestClientConfiguration.builder()
.clientOptions(ClusterClientOptions.builder().validateClusterNodeMembership(false).build())
.build();
.clientOptions(ClusterClientOptions.builder().validateClusterNodeMembership(false).build()).build();
LettuceConnectionFactory connectionFactory = new LettuceConnectionFactory(clusterConfiguration,
clientConfiguration);
@@ -1246,55 +1240,24 @@ class LettuceConnectionFactoryUnitTests {
@Test // GH-2594
void createRedisConfigurationWithNullInvalidRedisUriString() {
Arrays.asList(" ", "", null).forEach(redisUri ->
assertThatIllegalArgumentException()
.isThrownBy(() -> LettuceConnectionFactory.createRedisConfiguration(redisUri))
.withMessage("RedisURI must not be null or empty")
.withNoCause());
Arrays.asList(" ", "", null)
.forEach(redisUri -> assertThatIllegalArgumentException()
.isThrownBy(() -> LettuceConnectionFactory.createRedisConfiguration(redisUri))
.withMessage("RedisURI must not be null or empty").withNoCause());
}
@Test // GH-2594
void createRedisConfigurationWithValidRedisUriString() {
RedisConfiguration redisConfiguration =
LettuceConnectionFactory.createRedisConfiguration("redis://skullbox:6789");
RedisConfiguration redisConfiguration = LettuceConnectionFactory.createRedisConfiguration("redis://skullbox:6789");
assertThat(redisConfiguration).isInstanceOf(RedisStandaloneConfiguration.class);
assertThat(redisConfiguration)
.asInstanceOf(InstanceOfAssertFactories.type(RedisStandaloneConfiguration.class))
.extracting(RedisStandaloneConfiguration::getHostName)
.isEqualTo("skullbox");
assertThat(redisConfiguration).asInstanceOf(InstanceOfAssertFactories.type(RedisStandaloneConfiguration.class))
.extracting(RedisStandaloneConfiguration::getHostName).isEqualTo("skullbox");
assertThat(redisConfiguration)
.asInstanceOf(InstanceOfAssertFactories.type(RedisStandaloneConfiguration.class))
.extracting(RedisStandaloneConfiguration::getPort)
.isEqualTo(6789);
}
@Test // GH-2594
void configuresCustomTaskExecutorCorrectly() {
AsyncTaskExecutor mockTaskExecutor = mock(AsyncTaskExecutor.class);
LettuceConnectionProvider mockConnectionProvider = mock(LettuceConnectionProvider.class);
RedisClusterClient mockRedisClient = mock(RedisClusterClient.class);
RedisClusterConfiguration clusterConfiguration = new RedisClusterConfiguration();
clusterConfiguration.setAsyncTaskExecutor(mockTaskExecutor);
LettuceConnectionFactory connectionFactory = spy(new LettuceConnectionFactory(clusterConfiguration));
doReturn(mockRedisClient).when(connectionFactory).createClient();
doReturn(mockConnectionProvider).when(connectionFactory).createConnectionProvider(eq(mockRedisClient), any());
connectionFactory.start();
assertThat(connectionFactory.isRunning()).isTrue();
ClusterCommandExecutor clusterCommandExecutor = connectionFactory.getClusterCommandExecutor();
assertThat(getField(clusterCommandExecutor, "executor")).isEqualTo(mockTaskExecutor);
assertThat(redisConfiguration).asInstanceOf(InstanceOfAssertFactories.type(RedisStandaloneConfiguration.class))
.extracting(RedisStandaloneConfiguration::getPort).isEqualTo(6789);
}
static class CustomRedisConfiguration implements RedisConfiguration, WithHostAndPort {
@@ -1342,8 +1305,7 @@ class LettuceConnectionFactoryUnitTests {
return false;
}
return Objects.equals(this.getHostName(), that.getHostName())
&& Objects.equals(this.getPort(), that.getPort());
return Objects.equals(this.getHostName(), that.getHostName()) && Objects.equals(this.getPort(), that.getPort());
}
@Override
@@ -1354,10 +1316,7 @@ class LettuceConnectionFactoryUnitTests {
@Override
public String toString() {
return "CustomRedisConfiguration{" +
"hostName='" + hostName + '\'' +
", port=" + port +
'}';
return "CustomRedisConfiguration{" + "hostName='" + hostName + '\'' + ", port=" + port + '}';
}
}
}