springboot开箱即用原理

相信大家都知道springboot的好处,约定大约配置,开箱即用,内置tomcat等等
本人也是一直知其然不知其所以然
本次以RedisTemplate为例,结合其他博文,了解下RedisTemplate到底是如何被作为bean自动注册到spring容器中的

日常开发中,如下引入pom依赖,按照固定格式配置好yml或者properties文件即可在代码中注入使用RedisTemplate,这一切到底是因为springBoot道德的沦丧,还是因spring人性的扭曲,就让本期走进科学,还原故事的真相

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <version>x.x.x</version>
        </dependency>
  redis:
    host: 1.3.1.4
    password: 520
    port: 777
    timeOut: 5000
    maxIdle: 50
    maxWaitMillis: 5000
    maxTotal: 500

正式开始我的表演

1 RedisAutoConfiguration
代码中点击进入RedisTemplate类,再点击类名找到进入RedisAutoConfiguration类

@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })//重点
public class RedisAutoConfiguration {

	@Bean
	@ConditionalOnMissingBean(name = "redisTemplate")
	public RedisTemplate<Object, Object> redisTemplate(
			RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
		RedisTemplate<Object, Object> template = new RedisTemplate<>();
		template.setConnectionFactory(redisConnectionFactory);
		return template;
	}

	@Bean
	@ConditionalOnMissingBean
	public StringRedisTemplate stringRedisTemplate(
			RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
		StringRedisTemplate template = new StringRedisTemplate();
		template.setConnectionFactory(redisConnectionFactory);
		return template;
	}

}
  1. @Configuration常见的配置注解,内部含有一个以上的@Bean,让Spring能扫描到内部的@Bean,当然在Spring Boot中,默认只会扫描到启动类所在包或其下级包的类,所以还会通过其他的设置来让这个类被扫描到,这个后面会详细说明。

     @Configuration用于定义配置类,可替换xml配置文件,被注解的类内部包含有一个或多个被@Bean注解的方法,
     这些方法将会被AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext类进行扫描,
     并用于构建bean定义,初始化Spring容器。	
    
  2. @Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })通过@Import注解方式生成类实例并注入Spring容器。

  3. @EnableConfigurationProperties(RedisProperties.class)让RedisProperties 类被扫描到的关键。这时,如果RedisAutoConfiguration被扫描到,则同时也会去扫描RedisProperties类。

  4. @ConditionalOnClass(RedisOperations.class),当存在RedisOperations类时才会进行扫描,这个类什么时候被引入classpath的之后会提到。

2 点击进入JedisConnectionConfiguration类中

@Configuration
@ConditionalOnClass({ GenericObjectPool.class, JedisConnection.class, Jedis.class })
class JedisConnectionConfiguration extends RedisConnectionConfiguration {

	private final RedisProperties properties;

	private final ObjectProvider<JedisClientConfigurationBuilderCustomizer> builderCustomizers;

	JedisConnectionConfiguration(RedisProperties properties,
			ObjectProvider<RedisSentinelConfiguration> sentinelConfiguration,
			ObjectProvider<RedisClusterConfiguration> clusterConfiguration,
			ObjectProvider<JedisClientConfigurationBuilderCustomizer> builderCustomizers) {
		super(properties, sentinelConfiguration, clusterConfiguration);
		this.properties = properties;
		this.builderCustomizers = builderCustomizers;
	}

	@Bean
	@ConditionalOnMissingBean(RedisConnectionFactory.class)
	public JedisConnectionFactory redisConnectionFactory() throws UnknownHostException {
		return createJedisConnectionFactory();
	}

	private JedisConnectionFactory createJedisConnectionFactory() {
		JedisClientConfiguration clientConfiguration = getJedisClientConfiguration();
		if (getSentinelConfig() != null) {
			return new JedisConnectionFactory(getSentinelConfig(), clientConfiguration);
		}
		if (getClusterConfiguration() != null) {
			return new JedisConnectionFactory(getClusterConfiguration(),
					clientConfiguration);
		}
		return new JedisConnectionFactory(getStandaloneConfig(), clientConfiguration);
	}

	private JedisClientConfiguration getJedisClientConfiguration() {
		JedisClientConfigurationBuilder builder = applyProperties(
				JedisClientConfiguration.builder());
		RedisProperties.Pool pool = this.properties.getJedis().getPool();
		if (pool != null) {
			applyPooling(pool, builder);
		}
		if (StringUtils.hasText(this.properties.getUrl())) {
			customizeConfigurationFromUrl(builder);
		}
		customize(builder);
		return builder.build();
	}

	private JedisClientConfigurationBuilder applyProperties(
			JedisClientConfigurationBuilder builder) {
		if (this.properties.isSsl()) {
			builder.useSsl();
		}
		if (this.properties.getTimeout() != null) {
			Duration timeout = this.properties.getTimeout();
			builder.readTimeout(timeout).connectTimeout(timeout);
		}
		return builder;
	}

	private void applyPooling(RedisProperties.Pool pool,
			JedisClientConfiguration.JedisClientConfigurationBuilder builder) {
		builder.usePooling().poolConfig(jedisPoolConfig(pool));
	}

	private JedisPoolConfig jedisPoolConfig(RedisProperties.Pool pool) {
		JedisPoolConfig config = new JedisPoolConfig();
		config.setMaxTotal(pool.getMaxActive());
		config.setMaxIdle(pool.getMaxIdle());
		config.setMinIdle(pool.getMinIdle());
		if (pool.getMaxWait() != null) {
			config.setMaxWaitMillis(pool.getMaxWait().toMillis());
		}
		return config;
	}

	private void customizeConfigurationFromUrl(
			JedisClientConfiguration.JedisClientConfigurationBuilder builder) {
		ConnectionInfo connectionInfo = parseUrl(this.properties.getUrl());
		if (connectionInfo.isUseSsl()) {
			builder.useSsl();
		}
	}

	private void customize(
			JedisClientConfiguration.JedisClientConfigurationBuilder builder) {
		this.builderCustomizers.orderedStream()
				.forEach((customizer) -> customizer.customize(builder));
	}

}
  1. @Import注解会通过JedisConnectionConfiguration构造方法将JedisConnectionConfiguration的实例注入到Spring容器中,这里有一个RedisProperties参数,实际上就是在(3)中注入的RedisProperties,这样JedisConnectionConfiguration就获得了RedisProperties,也就获得了之前我们在application.propertie中配置的redis服务器连接属性。
  2. 通过@Configuration和@Bean的定义可知,会扫描到redisConnectionFactory()方法并返回实体,并注入到Spring容器,对应的类为RedisConnectionFactory。(JedisConnectionFactory实现了RedisConnectionFactory接口,所以可以这样)
@Bean
@ConditionalOnMissingBean(RedisConnectionFactory.class)
public JedisConnectionFactory redisConnectionFactory() throws UnknownHostException {
	return createJedisConnectionFactory();
}

private JedisConnectionFactory createJedisConnectionFactory() {
	JedisClientConfiguration clientConfiguration = getJedisClientConfiguration();
	if (getSentinelConfig() != null) {
		return new JedisConnectionFactory(getSentinelConfig(), clientConfiguration);
	}
	if (getClusterConfiguration() != null) {
		return new JedisConnectionFactory(getClusterConfiguration(),
				clientConfiguration);
	}
	return new JedisConnectionFactory(getStandaloneConfig(), clientConfiguration);
}
private JedisClientConfiguration getJedisClientConfiguration() {
	JedisClientConfigurationBuilder builder = applyProperties(
			JedisClientConfiguration.builder());
	RedisProperties.Pool pool = this.properties.getJedis().getPool();
	if (pool != null) {
		applyPooling(pool, builder);
	}
	if (StringUtils.hasText(this.properties.getUrl())) {
		customizeConfigurationFromUrl(builder);
	}
	customize(builder);
	return builder.build();
}

① getJedisClientConfiguration()方法,该方法从之前注入的RedisProperties中获取了 Jedis客户端连接池。
②createJedisConnectionFactory会根据配置的redis参数判断用单机/哨兵/集群模式来创建JedisConnectionFactory实例。

总结:创建并注入了JedisConnectionFactory实例,
JedisConnectionFactory实例中包含有Jedis的客户端连接池,之后就能用其创建连接了。

3 redisTemplate方法
以RedisAutoConfiguration中的redisTemplate为例

	@Bean
	@ConditionalOnMissingBean(name = "redisTemplate")
	public RedisTemplate<Object, Object> redisTemplate(
			RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
		RedisTemplate<Object, Object> template = new RedisTemplate<>();
		template.setConnectionFactory(redisConnectionFactory);
		return template;
	}
这是个被@Bean注解的方法,因此会被Spring扫描并注入。

@ConditionalOnMissingBean(name = “redisTemplate”)当Spring容器中不存在RedisTemplate实例时才会进行扫描注入,很明显是为了防止重复注入。
该方法有一个RedisConnectionFactory参数。
前面2.2中我们知道redisConnectionFactory方法最后会注入一个JedisConnectionFactory实例,而JedisConnectionFactory又是继承于RedisConnectionFactory。同志们,你们懂我的意思了吧∠( ᐛ 」∠)_。
so:该方法会将先前注入的redisConnectionFactory赋给新建的redisTemplate实例,
然后将redisTemplate实例注入Spring容器,我们就可以使用redisTemplate实例了。

总结

  1. 入口类-各自动装载bean的对应的AutoConfiguration类
    redis为例 RedisTemplate->RedisAutoConfiguration
  2. AutoConfiguration类上的关键注解@Import
    redis为例 @Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
  3. 导入@Import注解中的实例加到spring容器


相关注解说明

  • @Import

     	只能用在类上 ,@Import通过快速导入的方式实现把实例加入spring的IOC容器中	
    
  • @ConditionalOnClass({ GenericObjectPool.class, JedisConnection.class, Jedis.class })

     这个注解通俗的说就是Spring工程中引用了括号中相关的包 才会构建这个bean
    
  • @ConditionalOnBean

     仅仅在当前上下文中存在某个对象时,才会实例化一个Bean
    
  • @ConditionalOnClass

     某个class位于类路径上,才会实例化一个Bean
    
  • @ConditionalOnMissingBean

     仅仅在当前上下文中不存在某个对象时,才会实例化一个Bean,防止重复注入
    
  • @ConditionalOnMissingClass

     某个class类路径上不存在的时候,才会实例化一个Bean
    
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值