Here’s the thing
Springboot + Spring Data Redis is used in the project, but the company stipulates that redis passwords are all managed and can only be obtained remotely.
In addition, the Spring Data redis code should be automatically loaded into the project. In addition, the Spring Data Redis code should be automatically loaded into the project. In addition, the Spring Data Redis code should be automatically loaded into the project. There is no way to inherit and reference externally.
However, the good Samaritan, that is, I, felt that this was “not Spring enough”, so I dug deeply and after some analysis, proposed a relatively pertinent Issue to the community, which was adopted.
Spring Data Redis autowiring mechanism
In the org. Springframework. Boot. Autoconfigure. Data. With RedisAutoConfiguration in redis, through the @ Import depends on LettuceConnectionConfiguration
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
returntemplate; }}Copy the code
LettuceConnectionConfiguration inherited from RedisConnectionConfiguration, core code is as follows
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisClient.class) / / -- > (1)
@ConditionalOnProperty(name = "spring.redis.client-type", havingValue = "lettuce", matchIfMissing = true) / / -- > (2)
class LettuceConnectionConfiguration extends RedisConnectionConfiguration {
LettuceConnectionConfiguration(RedisProperties properties,
ObjectProvider<RedisSentinelConfiguration> sentinelConfigurationProvider,
ObjectProvider<RedisClusterConfiguration> clusterConfigurationProvider) {
super(properties, sentinelConfigurationProvider, clusterConfigurationProvider);
}
@Bean
@ConditionalOnMissingBean(RedisConnectionFactory.class) / / -- > (3)
LettuceConnectionFactory redisConnectionFactory( ObjectProvider
builderCustomizers, ClientResources clientResources)
{
LettuceClientConfiguration clientConfig = getLettuceClientConfiguration(builderCustomizers, clientResources,
getProperties().getLettuce().getPool());
returncreateLettuceConnectionFactory(clientConfig); }}Copy the code
As can be seen from the above, the conditions for Spring Boot auto assembly are as follows
① There is a RedisClient, a built-in Redis client class in weir. IO
In the project, use spring.redis.client-type as lettuce
LettuceConnectionFactory will be automatically created according to the configuration file if RedisConnectionFactory is not defined in the project code
There are two key things,
- The constructor
LettuceConnectionConfiguration
The emergence ofRedisProperties
And twoObjectProvider
, and calls the superclass constructor redisConnectionFactory
Contains two important methodsgetLettuceClientConfiguration
和createLettuceConnectionFactory
, includinggetLettuceClientConfiguration
This section describes how to configure connection pools in the Pool.properties
In fact, isRedisProperties
To focus oncreateLettuceConnectionFactory
Let’s examine each of these key points one by one.
Superclass constructorRedisConnectionConfiguration
protected RedisConnectionConfiguration(RedisProperties properties, ObjectProvider
sentinelConfigurationProvider, ObjectProvider
clusterConfigurationProvider)
{
this.properties = properties;
this.sentinelConfiguration = sentinelConfigurationProvider.getIfAvailable();
this.clusterConfiguration = clusterConfigurationProvider.getIfAvailable();
}
Copy the code
The key to understanding this code is ObjectProvider. In fact, if you look carefully, you’ll see that Springboot code, especially constructors, uses ObjectProvider a lot
ObjectProvider
For ObjectProvider, a quick word on some of Spring 4.3’s improvements
When the constructor argument is a single constructor argument, you can annotate it without @autowired
@Service
public class FooService {
private final FooRepository repository;
public FooService(FooRepository repository) {
this.repository = repository
}
}
Copy the code
For example, the code above is a post-Spring 4.3 version and works without @Autowired.
Also in Spring 4.3, the ObjectProvider interface was introduced as well as the implicit injection of single-constructor parameter properties.
//A variant of ObjectFactory designed specifically for injection points, allowing for programmatic optionality and lenient not-unique handling.
public interface ObjectProvider<T> extends ObjectFactory<T>, 可迭代<T> {
/ /... Part of the code is omitted
@Nullable
T getIfAvailable(a) throws BeansException;
}
Copy the code
As you can see from the source code comments, the ObjectProvider interface is an extension of the ObjectFactory interface, designed specifically for injection points to make injection looser and more optional.
Where, visible from getIfAvailable(), ObjectProvider comes into play when the Bean to inject parameters is empty or has more than one Bean.
- If the injected instance is empty
ObjectProvider
It avoids the exception of dependent object caused by strong dependency - If there are multiple instances,
ObjectProvider
The method is implemented according to the BeanOrdered
Interface or@Order
Gets one in the order specified by the annotationBean
, thus providing a looser dependency injection approach
Back, RedisConnectionConfiguration the superclass constructor itself, actually is to implement this function: If the user provides RedisSentinelConfiguration and RedisSentinelConfiguration, will be loaded in the constructor to come in, but RedisProperties is more simple, is the redis configuration.
RedisProperties
Read the configuration of Redis from the configuration. The simplest single-machine REDis is configured with simple attributes. Sentinel is the sentinel configuration, cluster is the cluster configuration, and Pool is the connection Pool configuration
@ConfigurationProperties(prefix = "spring.redis")
public class RedisProperties {
private int database = 0;
private String url;
private String host = "localhost";
private int port = 6379;
private String username;
private String password;
private Sentinel sentinel;
private Cluster cluster;
public static class Pool {}
public static class Cluster {}
public static class Sentinel {}
/ /... Omit unnecessary code
}
Copy the code
To summarize, at present, we can see RedisAutoConfiguration LettuceConnectionConfiguration depends on the configuration class, its constructor reads the user defined redis configuration, It includes single-machine configuration + cluster configuration + sentinel configuration + connection pool configuration, where cluster configuration and sentinel configuration are two beans that allow user customization.
createLettuceConnectionFactory
LettuceConnectionConfiguration of connection pool in the method to call the createLettuceConnectionFactory, its implementation is as follows
private LettuceConnectionFactory createLettuceConnectionFactory(LettuceClientConfiguration clientConfiguration) {
if(getSentinelConfig() ! =null) {
return new LettuceConnectionFactory(getSentinelConfig(), clientConfiguration);
}
if(getClusterConfiguration() ! =null) {
return new LettuceConnectionFactory(getClusterConfiguration(), clientConfiguration);
}
return new LettuceConnectionFactory(getStandaloneConfig(), clientConfiguration);
}
Copy the code
Read sentinel configuration, cluster configuration and single machine configuration, if there is a connection pool to return.
GetSentinelConfig () and getClusterConfiguration() are methods of the parent class, which are implemented as follows:
protected final RedisSentinelConfiguration getSentinelConfig(a) {
if (this.sentinelConfiguration ! =null) {
return this.sentinelConfiguration;
}
RedisProperties.Sentinel sentinelProperties = this.properties.getSentinel();
if(sentinelProperties ! =null) {
RedisSentinelConfiguration config = new RedisSentinelConfiguration();
// omit the load code
config.setDatabase(this.properties.getDatabase());
return config;
}
return null;
}
protected final RedisClusterConfiguration getClusterConfiguration(a) {
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());
// omit the load code
return config;
}
Copy the code
From this, we can see that it reads first any possible user-defined configuration beans introduced by ObjectProvider in the constructor, and if not, it completes the assembly by reading RedisProperties.
However, careful readers have to ask, How about standalone configuration?
protected final RedisStandaloneConfiguration getStandaloneConfig(a) {
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
if (StringUtils.hasText(this.properties.getUrl())) {
// omit the load code
}
else {
// omit the load code
}
config.setDatabase(this.properties.getDatabase());
return config;
}
Copy the code
Yes, you read that right, single dogs don’t deserve…
To sum up, get the appropriate configuration bean in the constructor and look for it in the method that creates the connection pool. If not, construct one from the configuration file, but single-instance Redis is not supported.
Make an issue
It is everyone’s responsibility to protect single dogs, so I made an issue to the SpringBoot community in the name of “Single Dog Protection Association”
And then, the big guy replies,Can protectI’m glad I can support it.
Among them, there is mention of useBeanPostProcessor
To rewriteRedisProperties
In the middle, I thought of it, so I closed the issue. After pondering for a while, I felt inelegant and unhappy, so I opened the issue again. I am very grateful for the support and understanding of the open source team, and I am very encouraged.