springboot2.x +redis使用和源码分析一(springboot自动装配源码分析)

序言:

个人感觉springboot的源码是web应用开源框架中写的最好的,观看它的源码可以学习它的编码风格,代码设计思想,如何做到给予使用者最好的使用体验,隐藏连接各应用组件的实现细节,极大降低类开发者实力的要求。查看springboot对各组件的自动装配实现,可以很详细的了解到spring对于组件核心类的初始化过程,以及核心类的作用以及之间的依赖关系。

springboot提供的所有的自动装配类都在spring-boot-autoconfigure(直接查看源码)

 也可以通过官网来进行查看(选取要看的版本):

https://docs.spring.io/spring-boot/docs/2.2.3.BUILD-SNAPSHOT/reference/html/appendix-auto-configuration-classes.html#auto-configuration-classes-from-autoconfigure-module


如图所示是spring-boot-autoconfigure下对于redis提供类:

springboot2.x +redis使用和源码分析一(springboot自动装配源码分析)_第1张图片

1:源码分析

1.1:RedisProperties

@ConfigurationProperties(prefix = "spring.redis")
public class RedisProperties {
    //使用redis database数 默认为0
    private int database = 0;
    //连接url 可包含用户和密码 例如 redis://user:[email protected]:6379
    private String url;
    //redis 端口号
    private String host = "localhost";
    //密码
    private String password;
    //端口号
    private int port = 6379;
    //是否开启SSL
    private boolean ssl;
    //超时时间
    private Duration timeout;
    //设置客户端名
    private String clientName;
    //支持哨兵机制
    private Sentinel sentinel;
    //支持集群配置
    private Cluster cluster;
    //Jedis客户端
    private final Jedis jedis = new Jedis();
    //Lettuce客户端
    private final Lettuce lettuce = new Lettuce();
    /**
     * 连接池一些属性
     */
    public static class Pool {
        //池中维护的最大空闲数 为负值代表
        private int maxIdle = 8;
        //池中要维护的最小空闲连接数 此值必须为正值
        private int minIdle = 0;
        //最大连接数 -数代表无限制
        private int maxActive = 8;
        //最大等待时间
        private Duration maxWait = Duration.ofMillis(-1);
        //
        private Duration timeBetweenEvictionRuns;

    }
    /**
     * 集群配置
     */
    public static class Cluster {
        //集群各节点地址host:port 多个以,隔开隔开
        private List nodes;
        //集群中执行命令时重定向node的最大数量
        private Integer maxRedirects;
    }
    /**
     *Redis 哨兵参数属性
     */
    public static class Sentinel {
        //集群中master名
        private String master;
        //集群各节点地址host:port 多个以,隔开隔开
        private List nodes;
    }
    /**
     * jedis客户端属性
     */
    public static class Jedis {
        //连接池一些配置
        private Pool pool;
    }
    /**
     *Lettuce客户端属性
     */
    public static class Lettuce {
        //停止超时时间
        private Duration shutdownTimeout = Duration.ofMillis(100);
       //连接池一些配置
        private Pool pool;
    }

}

1.2 RedisAutoConfiguration

RedisAutoConfiguration是springboot结合Redis配置核心类。

//禁止其它配置类使用该配置类的@Bean方法
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
//初始化Redis连接工厂类 Import执行的初始化类是顺序执行,这里面会存在一个使用问题须谨慎(后续描述)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {

      //若系统中不存在默认的redisTemplateBean 进行初始化 依赖Redis连接工厂类
	@Bean
	@ConditionalOnMissingBean(name = "redisTemplate")
	public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory)
			throws UnknownHostException {
		RedisTemplate template = new RedisTemplate<>();
		template.setConnectionFactory(redisConnectionFactory);
		return template;
	}

      //若系统中不存在StringRedisTemplate类的任何Bean 进行初始化 依赖Redis连接工厂类
      //StringRedisTemplate实际是RedisTemplate子类用于简化操作数据为字符串类型(毕竟转换为json类型的string可以处理大部分的需求)
	@Bean
	@ConditionalOnMissingBean
	public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)
			throws UnknownHostException {
		StringRedisTemplate template = new StringRedisTemplate();
		template.setConnectionFactory(redisConnectionFactory);
		return template;
	}

}

1.3:RedisConnectionConfiguration

此类是下述LettuceConnectionConfiguration与JedisConnectionConfiguration的父类,提供对一些基础类的管理。例如RedisProperties,RedisSentinelConfiguration,RedisClusterConfiguration类

1.4:LettuceConnectionConfiguration与JedisConnectionConfiguration

这里我将这两个类放在一块来解释,因为两者的功能都是一致为了创建初始化Redis连接工厂类

LettuceConnectionConfiguration:

//禁止其它配置类使用该配置类的@Bean方法
@Configuration(proxyBeanMethods = false)
//应用包含RedisClient 此类在io.lettuce.core包中
@ConditionalOnClass(RedisClient.class)
class LettuceConnectionConfiguration extends RedisConnectionConfiguration {

	LettuceConnectionConfiguration(RedisProperties properties,
			ObjectProvider sentinelConfigurationProvider,
			ObjectProvider clusterConfigurationProvider) {
		super(properties, sentinelConfigurationProvider, clusterConfigurationProvider);
	}

	@Bean(destroyMethod = "shutdown")
	@ConditionalOnMissingBean(ClientResources.class)
	DefaultClientResources lettuceClientResources() {
		return DefaultClientResources.create();
	}

	@Bean
        //如果上下文不包含RedisConnectionFactory的bean初始化
	@ConditionalOnMissingBean(RedisConnectionFactory.class)
	LettuceConnectionFactory redisConnectionFactory(
                         //ObjectProvider 作用和@Autowired作用一致和Autowired不同在于对于实例对象为null的处理ObjectProvider只有当实例对象的确存在时才检索bean,而Autowired会报错 ObjectProvider可以支持多实例
			ObjectProvider builderCustomizers,
			ClientResources clientResources) throws UnknownHostException {
		LettuceClientConfiguration clientConfig = getLettuceClientConfiguration(builderCustomizers, clientResources,
				getProperties().getLettuce().getPool());
                //调用Lettuce API创建LettuceConnectionFactory 有兴趣可以自己看下
		return createLettuceConnectionFactory(clientConfig);
	}

	。。。。。

}

JedisConnectionConfiguration:

//禁止其它配置类使用该配置类的@Bean方法
@Configuration(proxyBeanMethods = false)
//上下文引入Jedis包
@ConditionalOnClass({ GenericObjectPool.class, JedisConnection.class, Jedis.class })
class JedisConnectionConfiguration extends RedisConnectionConfiguration {

	JedisConnectionConfiguration(RedisProperties properties,
			ObjectProvider sentinelConfiguration,
			ObjectProvider clusterConfiguration) {
		super(properties, sentinelConfiguration, clusterConfiguration);
	}

	@Bean
        //如果上下文不包含RedisConnectionFactory的bean初始化
	@ConditionalOnMissingBean(RedisConnectionFactory.class)
	JedisConnectionFactory redisConnectionFactory(
                         //ObjectProvider 作用和@Autowired作用一致和Autowired不同在于对于实例对象为null的处理ObjectProvider只有当实例对象的确存在时才检索bean,而Autowired会报错 ObjectProvider可以支持多实例
			ObjectProvider builderCusto    mizers) throws UnknownHostException {
                //调用Jedis API创建JedisConnectionFactory 有兴趣可以自己看下
		return createJedisConnectionFactory(builderCustomizers);
	}
    。。。。。

}

从上述代码可以看出在整个应用上下文只会存在一个RedisConnectionFactory的实例,如果LettuceConnectionConfiguration和JedisConnectionConfiguration都满足要求都会执行,那么springboot会按照Import中顺序执行初始化,也就是第一优先执行LettuceConnectionConfiguration来创建RedisConnectionFactory实例。例如如果我们使用spring-boot-starter-parent来作为应用的父工程,又想使用Jedis客户端,那么就会出现上述的问题,原因在于spring-boot-starter-data-redis(默认推荐的是基于Lettcue)中会存在Lettuce包的依赖,如果我们不注意这块就会出现不管怎么设置jedis pool的属性发现都毫无作用,因为创建的是LettuceConnectionFactory实例对象。

解决方案:

  1. 在应用中自己初始化RedisConnectionFactory
  2. 在pom引用中去除lettuce包会存在Lettuce包的依赖 如下所示:

    org.springframework.boot
    spring-boot-starter-data-redis
    
        
            io.lettuce
            lettuce-core
        
    

1.5:LettuceClientConfigurationBuilderCustomizer和JedisClientConfigurationBuilderCustomizer

两者的功能作用一致,所以将两者放到一起描述 ,这里以JedisClientConfigurationBuilderCustomizer做为Demo来描述。

我们可以使用JedisClientConfigurationBuilderCustomizer来做一些定制化参数配置,也可以在多Redis下使不同客户端使用不同的定制化配置(后续会详细介绍)。

springboot执行定制操作代码:

class JedisConnectionConfiguration extends RedisConnectionConfiguration {

	
	private JedisClientConfiguration getJedisClientConfiguration(
			ObjectProvider builderCustomizers) {
		。。。。。
           //执行应用中被注入到spring类JedisClientConfigurationBuilderCustomizer的Bean
		builderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
		return builder.build();
	}

}

测试Demo:

@Configuration
class RedisCustomizerConfiguration {

    @Bean
    public MyJedisClientConfigurationBuilderCustomizer builderCustomizers(){

        return new MyJedisClientConfigurationBuilderCustomizer();
    }

    @Bean
    public MyJedisClientConfigurationBuilderCustomizerTwo builderCustomizersTow(){

        return new MyJedisClientConfigurationBuilderCustomizerTwo();
    }


    public class MyJedisClientConfigurationBuilderCustomizer implements JedisClientConfigurationBuilderCustomizer {

        @Override
        public void customize(JedisClientConfiguration.JedisClientConfigurationBuilder clientConfigurationBuilder) {


            JedisClientConfiguration jedisClientConfiguration =  clientConfigurationBuilder.build();

            //获取连接池信息 并做定制化设置
            GenericObjectPoolConfig genericObjectPoolConfig =  jedisClientConfiguration.getPoolConfig().get();

            if("test".equals(jedisClientConfiguration.getClientName().get())){

                //此两个参数在RedisProperties中统一为timeout 所以默认connectTimeout与readTimeout相同
                //可以通过JedisClientConfigurationBuilderCustomizer进行细致化配置
                clientConfigurationBuilder.connectTimeout(Duration.ofSeconds(10));
                clientConfigurationBuilder.readTimeout(Duration.ofSeconds(10));
                clientConfigurationBuilder.usePooling();
            }

        }
    }

    public class MyJedisClientConfigurationBuilderCustomizerTwo implements JedisClientConfigurationBuilderCustomizer {

        @Override
        public void customize(JedisClientConfiguration.JedisClientConfigurationBuilder clientConfigurationBuilder) {

            JedisClientConfiguration jedisClientConfiguration =  clientConfigurationBuilder.build();

            //获取连接池信息 并做定制化设置
            GenericObjectPoolConfig genericObjectPoolConfig =  jedisClientConfiguration.getPoolConfig().get();

            if("test1".equals(jedisClientConfiguration.getClientName().get())) {

                //此两个参数在RedisProperties中统一为timeout 所以默认connectTimeout与readTimeout相同
                //可以通过JedisClientConfigurationBuilderCustomizer进行细致化配置
                clientConfigurationBuilder.connectTimeout(Duration.ofSeconds(6));

                clientConfigurationBuilder.readTimeout(Duration.ofSeconds(6));

                clientConfigurationBuilder.usePooling();
            }
        }
    }
}

LettuceClientConfigurationBuilderCustomizer的使用方式和JedisClientConfigurationBuilderCustomizer是一致

1.6:RedisRepositoriesAutoConfiguration和RedisRepositoriesRegistrar

两类是为了简化spring Data中Repository对于redis的支持的一些列初始化的操作

//禁止其它配置类使用该配置类的@Bean方法
@Configuration(proxyBeanMethods = false)
//应用上下文必须包含 @EnableRedisRepositories注解类 EnableRedisRepositories中会Import RedisRepositoriesRegistrar类,会根据注解中的各参数进行各种初始化
@ConditionalOnClass(EnableRedisRepositories.class)
@ConditionalOnBean(RedisConnectionFactory.class)
//通过配置文件中spring.data.redis.repositories属性来配置是否执行RedisRepositoriesAutoConfiguration
@ConditionalOnProperty(prefix = "spring.data.redis.repositories", name = "enabled", havingValue = "true",
		matchIfMissing = true)
//必须包含RedisRepositoryFactoryBean类实例对象
@ConditionalOnMissingBean(RedisRepositoryFactoryBean.class)
//执行仓库注册类
@Import(RedisRepositoriesRegistrar.class)
//在RedisAutoConfiguration之后执行
@AutoConfigureAfter(RedisAutoConfiguration.class)
public class RedisRepositoriesAutoConfiguration {

}

//整体使用了模版方法设计模式(可以了解下24种设计模式)
//提供有关于Redis对于Repository模块基础参数
class RedisRepositoriesRegistrar extends AbstractRepositoryConfigurationSourceSupport {

	@Override
	protected Class getAnnotation() {
		return EnableRedisRepositories.class;
	}

	@Override
	protected Class getConfiguration() {
		return EnableRedisRepositoriesConfiguration.class;
	}

	@Override
	protected RepositoryConfigurationExtension getRepositoryConfigurationExtension() {
            //初始化Redis Repository配置扩展类 用于初始化注册一些基础Bean
		return new RedisRepositoryConfigurationExtension();
	}

        //启用spring Data中Repository 对Redis的支持
	@EnableRedisRepositories
	private static class EnableRedisRepositoriesConfiguration {

	}

}

//
public abstract class AbstractRepositoryConfigurationSourceSupport
        //实现该接口可以做到动态注册Bean 详细可以了解下ImportBeanDefinitionRegistrar接口的作用
		implements ImportBeanDefinitionRegistrar
        //初始化时可以BeanFactory写入类属性中
        ,BeanFactoryAware
        //初始化时可以ResourceLoader写入类属性中
        , ResourceLoaderAware
        //初始化时可以Environment写入类属性中
        , EnvironmentAware {

	private ResourceLoader resourceLoader;

	private BeanFactory beanFactory;

	private Environment environment;

        //结合注入类(RedisRepositoriesAutoConfiguration)的Metadata信息将需要初始化的Bean注入
	@Override
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
			BeanNameGenerator importBeanNameGenerator) {
        //RepositoryConfigurationDelegate Repository仓库代理类是spring-data对于Repository模块核心类,用于代理执行注册执行Bean
        //有兴趣可以查看下spring-data Repository源码 这里不详细介绍
		RepositoryConfigurationDelegate delegate = new RepositoryConfigurationDelegate(
				getConfigurationSource(registry, importBeanNameGenerator), this.resourceLoader, this.environment);
        //注册和Repository有关bean
		delegate.registerRepositoriesIn(registry, getRepositoryConfigurationExtension());
	}

    。。。。。

}

Spring Data中Repository对于Redis的支持可以查看此篇博客
 

1.7:RedisReactiveAutoConfiguration 

此类是redis对于响应式编程的支

@Configuration(proxyBeanMethods = false)
//Flux类 所属reactor包中 需要在maven依赖中添加
@ConditionalOnClass({ ReactiveRedisConnectionFactory.class, ReactiveRedisTemplate.class, Flux.class })
//在RedisAutoConfiguration启动之后执行
@AutoConfigureAfter(RedisAutoConfiguration.class)
public class RedisReactiveAutoConfiguration {

        //创建响应式编码类
	@Bean
	@ConditionalOnMissingBean(name = "reactiveRedisTemplate")
        //必须要求自定义创建ReactiveRedisConnectionFactory实例bean
	@ConditionalOnBean(ReactiveRedisConnectionFactory.class)
	public ReactiveRedisTemplate reactiveRedisTemplate(
			ReactiveRedisConnectionFactory reactiveRedisConnectionFactory, ResourceLoader resourceLoader) {
		JdkSerializationRedisSerializer jdkSerializer = new JdkSerializationRedisSerializer(
				resourceLoader.getClassLoader());
		RedisSerializationContext serializationContext = RedisSerializationContext
				.newSerializationContext().key(jdkSerializer).value(jdkSerializer).hashKey(jdkSerializer)
				.hashValue(jdkSerializer).build();
		return new ReactiveRedisTemplate<>(reactiveRedisConnectionFactory, serializationContext);
	}

	@Bean
	@ConditionalOnMissingBean(name = "reactiveStringRedisTemplate")
        //必须要求自定义创建ReactiveRedisConnectionFactory实例bean
	@ConditionalOnBean(ReactiveRedisConnectionFactory.class)
	public ReactiveStringRedisTemplate reactiveStringRedisTemplate(
			ReactiveRedisConnectionFactory reactiveRedisConnectionFactory) {
		return new ReactiveStringRedisTemplate(reactiveRedisConnectionFactory);
	}

}

Demo所在Git地址:https://github.com/fangyuan94/redisDemo

 

 

你可能感兴趣的:(redis,SpringBoot,Spring)