Springboot自动装配原理

概要

背景是我在写测试类的时候,出现以下报错

Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.data.redis.connection.RedisConnectionFactory' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}

下面是我的依赖和配置类,主要是使用的springboot-starter-data-redis

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
@Configuration
//@ConditionalOnProperty("spring.redis.host")
public class RedisConfig {
    @Bean("uidGeneratorRedisTemplate")
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, String> redisTemplate = new RedisTemplate<>();
        redisTemplate.setDefaultSerializer(StringRedisSerializer.UTF_8);
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        return redisTemplate;
    }

    @Bean
    @ConditionalOnMissingBean
    public RedisConnectionFactory redisConnectionFactory() {
        LettuceConnectionFactory factory =  new LettuceConnectionFactory();
        factory.setHostName("127.0.0.1");
        factory.setPort(4040);
        return factory;
    }
}

在我的预想中,

  • 由于springboot-web包引入了spring-boot-autoconfigure,其中加载了配置类RedisAutoConfiguration(见下图),
    图1
  • 在LettuceConnectionConfiguration中配置了redisConnectionFactory,因此我自己写的RedisConnectionFactory bean应该不会生效才对。
    @Bean
    @ConditionalOnMissingBean({RedisConnectionFactory.class})
    LettuceConnectionFactory redisConnectionFactory(ObjectProvider<LettuceClientConfigurationBuilderCustomizer> builderCustomizers, ClientResources clientResources) throws UnknownHostException {
        LettuceClientConfiguration clientConfig = this.getLettuceClientConfiguration(builderCustomizers, clientResources, this.getProperties().getLettuce().getPool());
        return this.createLettuceConnectionFactory(clientConfig);
    }

但是事实恰恰相反,我在application.properties配置端口为4041,而在代码中配置为4040,看错误日志可看出实际是代码中的bean被加载了。

自动装配流程及调用链

针对上面问题先说结论:排查发现是装配顺序问题,由于先装配了自己写的bean,然后再去装配autoconfigure中的默认配置,导致后者的条件不满足@ConditionalOnMissingBean({RedisConnectionFactory.class})

我们先看看 @ConditionalOnMissingBean注解做了什么,其他同理:

OnBeanCondition源码:可以看出引入了OnBeanCondition
在这里插入图片描述 OnBeanCondition的继承结构:重点在于Condition接口的match方法在这里插入图片描述

  1. org.springframework.context.annotation.ConditionEvaluator shouldSkip`
{
    ... // 获取Condition集合
        do {
            do {
                if (!var4.hasNext()) {
                    return false;
                }

                condition = (Condition)var4.next();
                requiredPhase = null;
                if (condition instanceof ConfigurationCondition) {
                    requiredPhase = ((ConfigurationCondition)condition).getConfigurationPhase();
                }
            } while(requiredPhase != null && requiredPhase != phase);
            // 遍历condition逐个判断是否应该跳过bean注册流程
        } while(condition.matches(this.context, metadata));

        return true;
    }
} else {
    return false;
}

重点逻辑在condition.matches(this.context, metadata)
2.org.springframework.boot.autoconfigure.condition.SpringBootCondition matches(this.context, metadata)
其中调用了 this.getMatchOutcome(context, metadata);是一个抽象方法,由OnBeanCondition实现

public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
        // 用于保存检测结果
        ConditionMessage matchMessage = ConditionMessage.empty();
        // 获取注解
        MergedAnnotations annotations = metadata.getAnnotations();
        Spec spec;
        MatchResult matchResult;
        String reason;
        if (annotations.isPresent(ConditionalOnBean.class)) {
            spec = new Spec(context, metadata, annotations, ConditionalOnBean.class);
            matchResult = this.getMatchingBeans(context, spec);
            if (!matchResult.isAllMatched()) {
                reason = this.createOnBeanNoMatchReason(matchResult);
                return ConditionOutcome.noMatch(spec.message().because(reason));
            }

            matchMessage = spec.message(matchMessage).found("bean", "beans").items(Style.QUOTE, matchResult.getNamesOfAllMatches());
        }
        ......
        if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) {
            spec = new Spec(context, metadata, annotations, ConditionalOnMissingBean.class);
            matchResult = this.getMatchingBeans(context, spec);
            if (matchResult.isAnyMatched()) {
                reason = this.createOnMissingBeanNoMatchReason(matchResult);
                return ConditionOutcome.noMatch(spec.message().because(reason));
            }

            matchMessage = spec.message(matchMessage).didNotFind("any beans").atAll();
        }

        return ConditionOutcome.match(matchMessage);
    }

其中spec描述了Conditional注解类型和参数
在这里插入图片描述
4. this.getMatchingBeans(context, spec);检查是否存在参数中配置的每个类是否存在,最终根据不同的Conditional类别综合getMatchingBeans的结果判断是否应该跳过

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值