一句话总结:
@ConditionalOnMissingBean 是 Spring Boot 提供的一个 条件注解(Conditional Annotation),意思是:
只有当 Spring 容器中 不存在 某个 Bean 时,当前的 Bean 或配置才会被加载。
这是一种典型的“按需装配”策略,常用于自动配置类(Auto Configuration)中。
如果大家有看源码的习惯,会发现到处都有这个注解:
RestTemplate:
Jackson - ObjectMapper:
Spring Boot 默认提供的 JSON 解析工具 ObjectMapper
如果你没自己定义一个,它就帮你创建一个默认的
你可以完全覆盖它,比如配置命名策略、日期格式等
框架或者组件库想要提供一个默认实现,但又允许使用者自定义自己的实现。这个时候就可以使用 @ConditionalOnMissingBean。这就达到了 可扩展+默认兜底 的目的。
同理,还有个相对应的是:@ConditionalOnBean
对比:@ConditionalOnBean vs @ConditionalOnMissingBean
注解 | 含义 |
---|---|
@ConditionalOnBean(SomeClass.class) |
只有当容器中存在 SomeClass 类型的 Bean 时才执行 |
@ConditionalOnMissingBean(SomeClass.class) |
只有当容器中不存在 SomeClass 类型的 Bean 时才执行 |
这两个注解是互补的,分别用于不同的场景。
支持的用法形式
@ConditionalOnMissingBean(SomeClass.class) // 类型判断
@ConditionalOnMissingBean(name = "someBeanName") // 名称判断
@ConditionalOnMissingBean(value = SomeClass.class) // 等价于上面的类型判断
@ConditionalOnMissingBean(type = "com.example.SomeClass") // 用字符串形式避免类加载
你也可以组合使用,如:
@ConditionalOnMissingBean(
value = SomeInterface.class,
name = "customBean"
)
特别注意:
不能直接加在 @Component、@Service、@Controller 等用于直接注册 Bean 的注解上,因为 Spring 在解析这些注解时并不会走自动配置的判断逻辑,因此不会触发 @ConditionalOnMissingBean 的条件判断,最终会导致Bean 被强制注册或 注册失败报错!
代码示例
自定义一个接口:
public interface MyService {
void hello();
}
错误示例
先来一个错误示例(直接在@Component下加上了):
@Slf4j
@Component
@ConditionalOnMissingBean(MyService.class)
public class MyServiceDefault implements MyService {
@Override
public void hello() {
log.info("use default bean...");
}
}
项目不会报错,直接启动后查找Bean:
会发现根本没注册上。所以,虽然加在@Component等注解的类上语法允许,但 Spring 不会进行条件判断和直接注册,可能导致意料之外的报错。所以不要这么用。
正确用法:
@Configuration + @Bean 的方式(大家回头看看我最上面截的源码截图中,也都是这种方式)
@Configuration
public class MyServiceAutoConfiguration {
@Bean
@ConditionalOnMissingBean(MyService.class)
public MyService myService() {
return new MyServiceDefault();
}
}
默认实现类:
@Slf4j
public class MyServiceDefault implements MyService {
@Override
public void hello() {
log.info("use default bean...");
}
}
找个地方调用一下:
public class StarApplication {
@Autowired
private MyService myService;
public static void main(String[] args) {
SpringApplication.run(StarApplication.class, args);
}
@PostConstruct
public void test() {
myService.hello();
}
}
现在模拟用户实现了这个接口:
@Slf4j
@Component
public class MyServiceUser implements MyService {
@Override
public void hello() {
log.info("use self bean...");
}
}
可以看到就一个Bean:
当然,你也可以去掉这个注解试试:
再次运行:
可以看到两个地方都注册上了。