-
Notifications
You must be signed in to change notification settings - Fork 40.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
I suggest setting RedisConnectionConfiguration to protected #39160
Comments
Thanks for the suggestion but Can you describe in more detail what you're trying to do and show the code that you currently have to write to do it? |
Here is my workaround to configure multiple redis services: import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Optional;
import java.util.concurrent.Executor;
import io.lettuce.core.resource.ClientResources;
import io.lettuce.core.resource.DefaultClientResources;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.data.redis.ClientResourcesBuilderCustomizer;
import org.springframework.boot.autoconfigure.data.redis.LettuceClientConfigurationBuilderCustomizer;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisConnectionDetails;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.ssl.SslBundles;
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.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.util.ClassUtils;
public class RedisConfigurationSupport {
private final Object configuration;
RedisConfigurationSupport(RedisProperties properties,
ObjectProvider<RedisStandaloneConfiguration> standaloneConfigurationProvider,
ObjectProvider<RedisSentinelConfiguration> sentinelConfigurationProvider,
ObjectProvider<RedisClusterConfiguration> clusterConfigurationProvider,
RedisConnectionDetails connectionDetails, ObjectProvider<SslBundles> sslBundles) {
try {
Class<?> clazz = RedisAutoConfiguration.class;
Class<?> configurationClass = ClassUtils.forName(clazz.getPackageName() + ".LettuceConnectionConfiguration",
clazz.getClassLoader());
Constructor<?> ctor = configurationClass.getDeclaredConstructor(
RedisConfigurationSupport.class.getDeclaredConstructors()[0].getParameterTypes());
ctor.setAccessible(true);
this.configuration = ctor.newInstance(properties, standaloneConfigurationProvider,
sentinelConfigurationProvider, clusterConfigurationProvider, connectionDetails, sslBundles);
}
catch (Exception ex) {
throw new RuntimeException(ex.getMessage(), ex);
}
}
protected DefaultClientResources lettuceClientResources(
ObjectProvider<ClientResourcesBuilderCustomizer> customizers) {
try {
Method m = this.configuration.getClass().getDeclaredMethod("lettuceClientResources", ObjectProvider.class);
m.setAccessible(true);
return (DefaultClientResources) m.invoke(this.configuration, customizers);
}
catch (Exception ex) {
throw new RuntimeException(ex.getMessage(), ex);
}
}
protected LettuceConnectionFactory redisConnectionFactory(
ObjectProvider<LettuceClientConfigurationBuilderCustomizer> builderCustomizers,
ClientResources clientResources) {
try {
Method m = this.configuration.getClass()
.getDeclaredMethod("redisConnectionFactory", ObjectProvider.class, ClientResources.class);
m.setAccessible(true);
return (LettuceConnectionFactory) m.invoke(this.configuration, builderCustomizers, clientResources);
}
catch (Exception ex) {
throw new RuntimeException(ex.getMessage(), ex);
}
}
protected RedisTemplate<String, ?> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
template.setKeySerializer(RedisSerializer.string());
template.setHashKeySerializer(RedisSerializer.string());
return template;
}
protected StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
return new StringRedisTemplate(redisConnectionFactory);
}
protected RedisMessageListenerContainer redisMessageListenerContainer(RedisConnectionFactory redisConnectionFactory,
Optional<Executor> taskExecutor) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(redisConnectionFactory);
taskExecutor.ifPresent(container::setTaskExecutor);
return container;
}
protected static RedisConnectionDetails redisConnectionDetails(RedisProperties properties) {
try {
Constructor<?> ctor = ClassUtils
.forName(RedisProperties.class.getPackageName() + ".PropertiesRedisConnectionDetails",
RedisProperties.class.getClassLoader())
.getDeclaredConstructor(RedisProperties.class);
ctor.setAccessible(true);
return (RedisConnectionDetails) ctor.newInstance(properties);
}
catch (Exception ex) {
throw new RuntimeException(ex.getMessage(), ex);
}
}
} import java.util.Optional;
import java.util.concurrent.Executor;
import io.lettuce.core.resource.ClientResources;
import io.lettuce.core.resource.DefaultClientResources;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.data.redis.ClientResourcesBuilderCustomizer;
import org.springframework.boot.autoconfigure.data.redis.LettuceClientConfigurationBuilderCustomizer;
import org.springframework.boot.autoconfigure.data.redis.RedisConnectionDetails;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.ssl.SslBundles;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
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.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(DefaultRedisProperties.class)
public class DefaultRedisConfiguration extends RedisConfigurationSupport {
@Bean
public static RedisConnectionDetails redisConnectionDetails(DefaultRedisProperties properties) {
return RedisConfigurationSupport.redisConnectionDetails(properties);
}
DefaultRedisConfiguration(DefaultRedisProperties properties,
ObjectProvider<RedisStandaloneConfiguration> standaloneConfigurationProvider,
ObjectProvider<RedisSentinelConfiguration> sentinelConfigurationProvider,
ObjectProvider<RedisClusterConfiguration> clusterConfigurationProvider,
RedisConnectionDetails redisConnectionDetails, ObjectProvider<SslBundles> sslBundles) {
super(properties, standaloneConfigurationProvider, sentinelConfigurationProvider, clusterConfigurationProvider,
redisConnectionDetails, sslBundles);
}
@Primary
@Bean(destroyMethod = "shutdown")
@Override
public DefaultClientResources lettuceClientResources(ObjectProvider<ClientResourcesBuilderCustomizer> customizers) {
return super.lettuceClientResources(customizers);
}
@Override
@Bean
public LettuceConnectionFactory redisConnectionFactory(
ObjectProvider<LettuceClientConfigurationBuilderCustomizer> builderCustomizers,
ClientResources lettuceClientResources) {
return super.redisConnectionFactory(builderCustomizers, lettuceClientResources);
}
@Bean
@Primary
@Override
public RedisTemplate<String, ?> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
return super.redisTemplate(redisConnectionFactory);
}
@Bean
@Override
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
return super.stringRedisTemplate(redisConnectionFactory);
}
@Bean
@Primary
@Override
public RedisMessageListenerContainer redisMessageListenerContainer(
@Qualifier("redisConnectionFactory") RedisConnectionFactory redisConnectionFactory,
Optional<Executor> taskExecutor) {
return super.redisMessageListenerContainer(redisConnectionFactory, taskExecutor);
}
@ConfigurationProperties(prefix = DefaultRedisProperties.PREFIX)
public static class DefaultRedisProperties extends RedisProperties {
public static final String PREFIX = "spring.data.redis";
}
} import java.util.Optional;
import java.util.concurrent.Executor;
import io.lettuce.core.resource.ClientResources;
import io.lettuce.core.resource.DefaultClientResources;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.data.redis.ClientResourcesBuilderCustomizer;
import org.springframework.boot.autoconfigure.data.redis.LettuceClientConfigurationBuilderCustomizer;
import org.springframework.boot.autoconfigure.data.redis.RedisConnectionDetails;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.ssl.SslBundles;
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.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(GlobalRedisProperties.class)
@ConditionalOnProperty(prefix = GlobalRedisProperties.PREFIX, name = "enabled", havingValue = "true")
public class GlobalRedisConfiguration extends RedisConfigurationSupport {
@Bean
public static RedisConnectionDetails globalRedisConnectionDetails(GlobalRedisProperties properties) {
return RedisConfigurationSupport.redisConnectionDetails(properties);
}
GlobalRedisConfiguration(GlobalRedisProperties properties,
ObjectProvider<RedisStandaloneConfiguration> standaloneConfigurationProvider,
ObjectProvider<RedisSentinelConfiguration> sentinelConfigurationProvider,
ObjectProvider<RedisClusterConfiguration> clusterConfigurationProvider,
RedisConnectionDetails globalRedisConnectionDetails, ObjectProvider<SslBundles> sslBundles) {
super(properties, standaloneConfigurationProvider, sentinelConfigurationProvider, clusterConfigurationProvider,
globalRedisConnectionDetails, sslBundles);
}
@Bean(destroyMethod = "shutdown")
public DefaultClientResources globalLettuceClientResources(
ObjectProvider<ClientResourcesBuilderCustomizer> customizers) {
return super.lettuceClientResources(customizers);
}
@Bean
public LettuceConnectionFactory globalRedisConnectionFactory(
ObjectProvider<LettuceClientConfigurationBuilderCustomizer> builderCustomizers,
@Qualifier("globalLettuceClientResources") ClientResources lettuceClientResources) {
return super.redisConnectionFactory(builderCustomizers, lettuceClientResources);
}
@Bean
public RedisTemplate<String, ?> globalRedisTemplate(
@Qualifier("globalRedisConnectionFactory") RedisConnectionFactory redisConnectionFactory) {
return super.redisTemplate(redisConnectionFactory);
}
@Bean
public StringRedisTemplate globalStringRedisTemplate(
@Qualifier("globalRedisConnectionFactory") RedisConnectionFactory redisConnectionFactory) {
return super.stringRedisTemplate(redisConnectionFactory);
}
@Bean
public RedisMessageListenerContainer globalRedisMessageListenerContainer(
@Qualifier("globalRedisConnectionFactory") RedisConnectionFactory redisConnectionFactory,
Optional<Executor> taskExecutor) {
return super.redisMessageListenerContainer(redisConnectionFactory, taskExecutor);
}
@ConfigurationProperties(prefix = GlobalRedisProperties.PREFIX)
public static class GlobalRedisProperties extends RedisProperties {
public static final String PREFIX = "global.data.redis";
@Autowired
public GlobalRedisProperties(DefaultRedisProperties defaultRedisProperties) {
BeanUtils.copyProperties(defaultRedisProperties, this);
}
}
} spring.data.redis.host: xxx
global.data.redis.enabled: true
global.data.redis.host: xxx |
@wilkinsona As shown in the code demo by @quaff , there is a substantial similarity between the code in RedisConfigurationSupport and RedisConnectionConfiguration, which seems to go against the principles of inheritance in object-oriented programming. I understand the need for privacy in the Boot project, but is there a possibility of finding a good balance between the two? |
I hope there's a way, hence my question about what you're doing at the moment, but I don't think making configuration classes part of the public API is the right approach as we may back ourselves into a corner in terms of future improvements. One option is to tackle this in a more general way:
Another option, that may be easier to implement, would be to offer something similar to |
@wilkinsona I think the #15732 method you just mentioned is very good. But when can the Spring Boot project team provide support? We really need it. |
#15732 is in the 3.x milestone at the moment. That means that we don't have any immediate plans for it and it won't be part of 3.3. It may be implemented in a later 3.x release depending on demand and whether there are other higher priorities. In the meantime, I would recommend that you continue defining the necessary beans yourself. It may be more cumbersome that we'd like, but it is at least possible to do what you want. I'm going to close this one for now as I'm not sure how useful a Redis equivalent of |
@quaff I developed a multi-datasource Spring Boot Starter, and recently, during my free time, I decided to open-source it. Here’s the link: https://github.com/ChildrenGreens/multi-source-spring-boot-starter. |
I suggest setting
RedisConnectionConfiguration
to protected, so that it can be reused in situations with multiple data sources. The current design with default access level restricts the ability of users to extend it.The text was updated successfully, but these errors were encountered: