非Spring Boot环境下能否实现等效@ConditionalOnMissingBean的自定义条件
实现结论
完全可以实现。Spring 5.3.13 原生提供了条件装配扩展点,不需要引入Spring Boot依赖,就能复刻@ConditionalOnMissingBean的核心行为。
具体实现
1. 自定义条件注解
先定义和@ConditionalOnMissingBean用法一致的注解,支持指定要检查的Bean类型、Bean名称:
import org.springframework.context.annotation.Conditional; import java.lang.annotation.*; @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(OnMissingBeanCondition.class) public @interface ConditionalOnMissingBean { // 要检查存在性的Bean类型 Class<?>[] value() default {}; // 要检查存在性的Bean名称 String[] beanNames() default {}; }
2. 实现条件判断逻辑
注意不要直接实现Condition接口,要实现ConfigurationCondition接口,指定条件判断在Bean注册阶段执行,避免因为判断时机过早,其他Bean还没完成扫描导致结果错误:
import org.springframework.context.annotation.ConditionContext; import org.springframework.context.annotation.ConfigurationCondition; import org.springframework.core.type.AnnotatedTypeMetadata; import org.springframework.core.type.MethodMetadata; import org.springframework.util.MultiValueMap; import java.util.List; public class OnMissingBeanCondition implements ConfigurationCondition { @Override public ConfigurationPhase getConfigurationPhase() { return ConfigurationPhase.REGISTER_BEAN; } @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { MultiValueMap<String, Object> annotationAttrs = metadata.getAllAnnotationAttributes( ConditionalOnMissingBean.class.getName() ); // 注解没传参数时,默认检查当前标注的Bean类型是否已存在 if (annotationAttrs == null || annotationAttrs.isEmpty()) { if (metadata instanceof MethodMetadata methodMetadata) { try { Class<?> targetType = Class.forName( methodMetadata.getReturnTypeName(), false, context.getClassLoader() ); String[] existingBeans = context.getBeanFactory().getBeanNamesForType(targetType); return existingBeans.length == 0; } catch (ClassNotFoundException e) { return true; } } return true; } // 检查指定类型的Bean是否已存在 List<Object> targetTypes = annotationAttrs.get("value"); if (targetTypes != null) { for (Object type : targetTypes) { Class<?> clazz = (Class<?>) type; if (context.getBeanFactory().getBeanNamesForType(clazz).length > 0) { return false; } } } // 检查指定名称的Bean是否已存在 List<Object> targetBeanNames = annotationAttrs.get("beanNames"); if (targetBeanNames != null) { for (Object beanName : targetBeanNames) { String name = (String) beanName; if (context.getBeanFactory().containsBeanDefinition(name) || context.getBeanFactory().containsSingleton(name)) { return false; } } } return true; } }
使用方式
和Spring Boot提供的@ConditionalOnMissingBean用法完全一致,直接标注在@Bean方法或者组件类上即可:
@Configuration public class DefaultServiceConfiguration { @Bean @ConditionalOnMissingBean(UserService.class) public UserService defaultUserService() { return new DefaultUserServiceImpl(); } }
注意事项
- 必须指定条件判断的执行阶段为
REGISTER_BEAN,如果用默认的PARSE_CONFIGURATION阶段,会出现部分Bean还没被扫描注册就提前判断,导致结果不符合预期 - 上述实现覆盖了最常用的按类型、按名称判断的场景,如果需要支持按注解匹配、忽略指定类型、考虑父容器Bean等高级特性,只要在注解中新增对应参数,在
matches方法中补充判断逻辑即可 - 判断过程中不要调用
getBean()方法提前实例化Bean,仅通过BeanFactory检查Bean定义、已注册的单例对象即可,避免破坏Spring Bean的生命周期
内容的提问来源于stack exchange,提问作者Itzhak Eretz Kdosha
相关产品推荐
相关产品推荐

