From 7e51859d3002b1bd122faa57e45d726ca02b2465 Mon Sep 17 00:00:00 2001 From: heliang666s <3596006474@qq.com> Date: Tue, 16 Dec 2025 01:59:21 +0800 Subject: [PATCH 1/5] feat: Support @ConditionalOnMissingBean for @DubboService --- .../ServiceAnnotationPostProcessor.java | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/beans/factory/annotation/ServiceAnnotationPostProcessor.java b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/beans/factory/annotation/ServiceAnnotationPostProcessor.java index 9d22f4476a95..8e44b321142d 100644 --- a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/beans/factory/annotation/ServiceAnnotationPostProcessor.java +++ b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/beans/factory/annotation/ServiceAnnotationPostProcessor.java @@ -618,6 +618,14 @@ private void processAnnotatedBeanDefinition( AnnotatedBeanDefinition refServiceBeanDefinition, Map attributes) { + if (shouldSkipDueToConditionalOnMissingBean(refServiceBeanDefinition)) { + if (logger.isDebugEnabled()) { + logger.debug("Skip registering ServiceBean for bean [" + refServiceBeanName + + "] due to @ConditionalOnMissingBean condition not satisfied"); + } + return; + } + Map serviceAnnotationAttributes = new LinkedHashMap<>(attributes); // get bean class from return type @@ -662,6 +670,43 @@ private void registerServiceBeanDefinition( } } + private boolean shouldSkipDueToConditionalOnMissingBean(AnnotatedBeanDefinition beanDefinition) { + MethodMetadata factoryMethod = SpringCompatUtils.getFactoryMethodMetadata(beanDefinition); + if (factoryMethod == null) { + return false; + } + + Map conditionalAttrs = factoryMethod.getAnnotationAttributes( + "org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean"); + + if (conditionalAttrs == null || conditionalAttrs.isEmpty()) { + return false; + } + + Class[] beanTypes = (Class[]) conditionalAttrs.get("value"); + if (beanTypes == null) { + return false; + } + + for (Class beanType : beanTypes) { + if (hasExistingBeanOfType(beanType)) { + return true; + } + } + + return false; + } + + private boolean hasExistingBeanOfType(Class beanType) { + if (registry instanceof ConfigurableListableBeanFactory) { + ConfigurableListableBeanFactory beanFactory = (ConfigurableListableBeanFactory) registry; + + String[] beanNames = beanFactory.getBeanNamesForType(beanType); + return beanNames.length > 0; + } + return false; + } + @Override public void setEnvironment(Environment environment) { this.environment = environment; From 41922efffbd796fc2714f7000c5da116ba934e74 Mon Sep 17 00:00:00 2001 From: heliang666s <3596006474@qq.com> Date: Sun, 25 Jan 2026 19:48:46 +0800 Subject: [PATCH 2/5] test: add cases for ConditionalOnMissingBean with DubboService --- .../config/spring/JavaConfigBeanTest.java | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/JavaConfigBeanTest.java b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/JavaConfigBeanTest.java index e6e9c91213fd..125247b316b7 100644 --- a/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/JavaConfigBeanTest.java +++ b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/JavaConfigBeanTest.java @@ -40,6 +40,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -125,6 +126,36 @@ void testBean() { } } + @Test + void testConditionalOnMissingBeanForDubboService() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( + ConditionalTestConfiguration.class, + ExistingServiceConfiguration.class, + ConditionalServiceConfiguration.class); + try { + Map serviceBeans = context.getBeansOfType(ServiceBean.class); + Assertions.assertEquals(0, serviceBeans.size()); + + Map demoServices = context.getBeansOfType(DemoService.class); + Assertions.assertEquals(1, demoServices.size()); + Assertions.assertNotNull(demoServices.get("existingDemoService")); + } finally { + context.close(); + } + } + + @Test + void testConditionalOnMissingBeanForDubboServiceWhenMissing() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( + ConditionalTestConfiguration.class, ConditionalServiceConfiguration.class); + try { + Map serviceBeans = context.getBeansOfType(ServiceBean.class); + Assertions.assertEquals(1, serviceBeans.size()); + } finally { + context.close(); + } + } + @EnableDubbo(scanBasePackages = "org.apache.dubbo.config.spring.annotation.consumer") @Configuration static class TestConfiguration { @@ -162,6 +193,33 @@ public ConsumerConfig consumerConfig() { } } + @EnableDubbo + @Configuration + static class ConditionalTestConfiguration { + + @Bean("dubbo-demo-application") + public ApplicationConfig applicationConfig() { + ApplicationConfig applicationConfig = new ApplicationConfig(); + applicationConfig.setName("dubbo-demo-application"); + return applicationConfig; + } + + @Bean(MY_PROTOCOL_ID) + public ProtocolConfig protocolConfig() { + ProtocolConfig protocolConfig = new ProtocolConfig(); + protocolConfig.setName("dubbo"); + protocolConfig.setPort(12345); + return protocolConfig; + } + + @Bean(MY_REGISTRY_ID) + public RegistryConfig registryConfig() { + RegistryConfig registryConfig = new RegistryConfig(); + registryConfig.setAddress("N/A"); + return registryConfig; + } + } + @Configuration static class ConsumerConfiguration { @@ -181,4 +239,24 @@ public DemoService demoServiceImpl() { return new DemoServiceImpl(); } } + + @Configuration + static class ExistingServiceConfiguration { + + @Bean + public DemoService existingDemoService() { + return new DemoServiceImpl(); + } + } + + @Configuration + static class ConditionalServiceConfiguration { + + @Bean + @ConditionalOnMissingBean(DemoService.class) + @DubboService(group = "demo") + public DemoService conditionalDemoService() { + return new DemoServiceImpl(); + } + } } From dd3403fe85bc461032e3c67c5676dbda32606af6 Mon Sep 17 00:00:00 2001 From: heliang666s <3596006474@qq.com> Date: Tue, 27 Jan 2026 11:46:54 +0800 Subject: [PATCH 3/5] test: add ConditionalOnMissingBean coverage for @DubboService --- .../ServiceAnnotationPostProcessor.java | 15 ++++++++++----- .../dubbo/config/spring/JavaConfigBeanTest.java | 2 +- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/beans/factory/annotation/ServiceAnnotationPostProcessor.java b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/beans/factory/annotation/ServiceAnnotationPostProcessor.java index 8e44b321142d..aab110d424ac 100644 --- a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/beans/factory/annotation/ServiceAnnotationPostProcessor.java +++ b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/beans/factory/annotation/ServiceAnnotationPostProcessor.java @@ -618,7 +618,7 @@ private void processAnnotatedBeanDefinition( AnnotatedBeanDefinition refServiceBeanDefinition, Map attributes) { - if (shouldSkipDueToConditionalOnMissingBean(refServiceBeanDefinition)) { + if (shouldSkipDueToConditionalOnMissingBean(refServiceBeanName, refServiceBeanDefinition)) { if (logger.isDebugEnabled()) { logger.debug("Skip registering ServiceBean for bean [" + refServiceBeanName + "] due to @ConditionalOnMissingBean condition not satisfied"); @@ -670,7 +670,8 @@ private void registerServiceBeanDefinition( } } - private boolean shouldSkipDueToConditionalOnMissingBean(AnnotatedBeanDefinition beanDefinition) { + private boolean shouldSkipDueToConditionalOnMissingBean( + String refServiceBeanName, AnnotatedBeanDefinition beanDefinition) { MethodMetadata factoryMethod = SpringCompatUtils.getFactoryMethodMetadata(beanDefinition); if (factoryMethod == null) { return false; @@ -689,7 +690,7 @@ private boolean shouldSkipDueToConditionalOnMissingBean(AnnotatedBeanDefinition } for (Class beanType : beanTypes) { - if (hasExistingBeanOfType(beanType)) { + if (hasExistingBeanOfType(beanType, refServiceBeanName)) { return true; } } @@ -697,12 +698,16 @@ private boolean shouldSkipDueToConditionalOnMissingBean(AnnotatedBeanDefinition return false; } - private boolean hasExistingBeanOfType(Class beanType) { + private boolean hasExistingBeanOfType(Class beanType, String refServiceBeanName) { if (registry instanceof ConfigurableListableBeanFactory) { ConfigurableListableBeanFactory beanFactory = (ConfigurableListableBeanFactory) registry; String[] beanNames = beanFactory.getBeanNamesForType(beanType); - return beanNames.length > 0; + for (String beanName : beanNames) { + if (!beanName.equals(refServiceBeanName)) { + return true; + } + } } return false; } diff --git a/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/JavaConfigBeanTest.java b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/JavaConfigBeanTest.java index 125247b316b7..0c8ce476832a 100644 --- a/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/JavaConfigBeanTest.java +++ b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/JavaConfigBeanTest.java @@ -193,7 +193,7 @@ public ConsumerConfig consumerConfig() { } } - @EnableDubbo + @EnableDubbo(scanBasePackages = "") @Configuration static class ConditionalTestConfiguration { From de3e49000182e809d792788ee3387f506e052a92 Mon Sep 17 00:00:00 2001 From: heliang666s <3596006474@qq.com> Date: Tue, 27 Jan 2026 14:57:54 +0800 Subject: [PATCH 4/5] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../factory/annotation/ServiceAnnotationPostProcessor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/beans/factory/annotation/ServiceAnnotationPostProcessor.java b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/beans/factory/annotation/ServiceAnnotationPostProcessor.java index aab110d424ac..07013317e14a 100644 --- a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/beans/factory/annotation/ServiceAnnotationPostProcessor.java +++ b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/beans/factory/annotation/ServiceAnnotationPostProcessor.java @@ -685,7 +685,7 @@ private boolean shouldSkipDueToConditionalOnMissingBean( } Class[] beanTypes = (Class[]) conditionalAttrs.get("value"); - if (beanTypes == null) { + if (beanTypes == null || beanTypes.length == 0) { return false; } From 541dcefb299b4b17e0861a989247d30acd96c8df Mon Sep 17 00:00:00 2001 From: heliang666s <3596006474@qq.com> Date: Tue, 27 Jan 2026 16:09:52 +0800 Subject: [PATCH 5/5] fix: tolerate missing/optional types in ConditionalOnMissingBean --- .../ServiceAnnotationPostProcessor.java | 69 +++++++++++++++++++ .../config/spring/JavaConfigBeanTest.java | 8 +++ 2 files changed, 77 insertions(+) diff --git a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/beans/factory/annotation/ServiceAnnotationPostProcessor.java b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/beans/factory/annotation/ServiceAnnotationPostProcessor.java index 07013317e14a..2f35ab00d19b 100644 --- a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/beans/factory/annotation/ServiceAnnotationPostProcessor.java +++ b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/beans/factory/annotation/ServiceAnnotationPostProcessor.java @@ -684,7 +684,41 @@ private boolean shouldSkipDueToConditionalOnMissingBean( return false; } + String[] beanNames = (String[]) conditionalAttrs.get("name"); + if (beanNames != null) { + for (String beanName : beanNames) { + if (hasExistingBeanName(beanName, refServiceBeanName)) { + return true; + } + } + } + Class[] beanTypes = (Class[]) conditionalAttrs.get("value"); + if (beanTypes == null || beanTypes.length == 0) { + Object typeAttr = conditionalAttrs.get("type"); + if (typeAttr instanceof Class[]) { + beanTypes = (Class[]) typeAttr; + } else if (typeAttr instanceof String[]) { + String[] typeNames = (String[]) typeAttr; + List> resolvedTypes = new ArrayList<>(typeNames.length); + for (String typeName : typeNames) { + if (StringUtils.isEmpty(typeName)) { + continue; + } + String resolvedName = typeName; + if (environment != null) { + resolvedName = environment.resolvePlaceholders(typeName); + } + if (!ClassUtils.isPresent(resolvedName, classLoader)) { + continue; + } + resolvedTypes.add(resolveClassName(resolvedName, classLoader)); + } + if (!resolvedTypes.isEmpty()) { + beanTypes = resolvedTypes.toArray(new Class[0]); + } + } + } if (beanTypes == null || beanTypes.length == 0) { return false; } @@ -712,6 +746,41 @@ private boolean hasExistingBeanOfType(Class beanType, String refServiceBeanNa return false; } + private boolean hasExistingBeanName(String beanName, String refServiceBeanName) { + if (beanName == null || beanName.isEmpty()) { + return false; + } + String resolvedName = beanName; + if (environment != null) { + resolvedName = environment.resolvePlaceholders(beanName); + } + if (registry instanceof ConfigurableListableBeanFactory) { + ConfigurableListableBeanFactory beanFactory = (ConfigurableListableBeanFactory) registry; + if (isCurrentBeanNameOrAlias(beanFactory, resolvedName, refServiceBeanName)) { + return false; + } + return beanFactory.containsBean(resolvedName); + } + if (resolvedName.equals(refServiceBeanName)) { + return false; + } + return registry.containsBeanDefinition(resolvedName); + } + + private boolean isCurrentBeanNameOrAlias( + ConfigurableListableBeanFactory beanFactory, String candidateName, String refServiceBeanName) { + if (candidateName.equals(refServiceBeanName)) { + return true; + } + String[] aliases = beanFactory.getAliases(refServiceBeanName); + for (String alias : aliases) { + if (candidateName.equals(alias)) { + return true; + } + } + return false; + } + @Override public void setEnvironment(Environment environment) { this.environment = environment; diff --git a/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/JavaConfigBeanTest.java b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/JavaConfigBeanTest.java index 0c8ce476832a..cac62158b3a8 100644 --- a/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/JavaConfigBeanTest.java +++ b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/JavaConfigBeanTest.java @@ -133,6 +133,8 @@ void testConditionalOnMissingBeanForDubboService() { ExistingServiceConfiguration.class, ConditionalServiceConfiguration.class); try { + Assertions.assertFalse(context.containsBeanDefinition("conditionalDemoService")); + Map serviceBeans = context.getBeansOfType(ServiceBean.class); Assertions.assertEquals(0, serviceBeans.size()); @@ -149,8 +151,14 @@ void testConditionalOnMissingBeanForDubboServiceWhenMissing() { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( ConditionalTestConfiguration.class, ConditionalServiceConfiguration.class); try { + Assertions.assertTrue(context.containsBeanDefinition("conditionalDemoService")); + Map serviceBeans = context.getBeansOfType(ServiceBean.class); Assertions.assertEquals(1, serviceBeans.size()); + + Map demoServices = context.getBeansOfType(DemoService.class); + Assertions.assertEquals(1, demoServices.size()); + Assertions.assertNotNull(demoServices.get("conditionalDemoService")); } finally { context.close(); }