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 constructorLettuceConnectionConfigurationThe emergence ofRedisPropertiesAnd twoObjectProvider, and calls the superclass constructor
  • redisConnectionFactoryContains two important methodsgetLettuceClientConfigurationcreateLettuceConnectionFactory, includinggetLettuceClientConfigurationThis section describes how to configure connection pools in the Pool.propertiesIn fact, isRedisPropertiesTo 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 emptyObjectProviderIt avoids the exception of dependent object caused by strong dependency
  • If there are multiple instances,ObjectProviderThe method is implemented according to the BeanOrderedInterface or@OrderGets 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 useBeanPostProcessorTo rewriteRedisPropertiesIn 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.