-
Notifications
You must be signed in to change notification settings - Fork 26.6k
feat: Support @ConditionalOnMissingBean for @DubboService #15875
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: 3.3
Are you sure you want to change the base?
Changes from all commits
7e51859
41922ef
dd3403f
de3e490
541dcef
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -618,6 +618,14 @@ private void processAnnotatedBeanDefinition( | |||||||||||||||||||||||||||||||||
| AnnotatedBeanDefinition refServiceBeanDefinition, | ||||||||||||||||||||||||||||||||||
| Map<String, Object> attributes) { | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| if (shouldSkipDueToConditionalOnMissingBean(refServiceBeanName, refServiceBeanDefinition)) { | ||||||||||||||||||||||||||||||||||
| if (logger.isDebugEnabled()) { | ||||||||||||||||||||||||||||||||||
| logger.debug("Skip registering ServiceBean for bean [" + refServiceBeanName | ||||||||||||||||||||||||||||||||||
| + "] due to @ConditionalOnMissingBean condition not satisfied"); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| Map<String, Object> serviceAnnotationAttributes = new LinkedHashMap<>(attributes); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| // get bean class from return type | ||||||||||||||||||||||||||||||||||
|
|
@@ -662,6 +670,117 @@ private void registerServiceBeanDefinition( | |||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| private boolean shouldSkipDueToConditionalOnMissingBean( | ||||||||||||||||||||||||||||||||||
| String refServiceBeanName, AnnotatedBeanDefinition beanDefinition) { | ||||||||||||||||||||||||||||||||||
| MethodMetadata factoryMethod = SpringCompatUtils.getFactoryMethodMetadata(beanDefinition); | ||||||||||||||||||||||||||||||||||
| if (factoryMethod == null) { | ||||||||||||||||||||||||||||||||||
| return false; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| Map<String, Object> conditionalAttrs = factoryMethod.getAnnotationAttributes( | ||||||||||||||||||||||||||||||||||
| "org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean"); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| if (conditionalAttrs == null || conditionalAttrs.isEmpty()) { | ||||||||||||||||||||||||||||||||||
| return false; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| String[] beanNames = (String[]) conditionalAttrs.get("name"); | ||||||||||||||||||||||||||||||||||
| if (beanNames != null) { | ||||||||||||||||||||||||||||||||||
| for (String beanName : beanNames) { | ||||||||||||||||||||||||||||||||||
| if (hasExistingBeanName(beanName, refServiceBeanName)) { | ||||||||||||||||||||||||||||||||||
| return true; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+688
to
+693
|
||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| 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<Class<?>> 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)); | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
| resolvedTypes.add(resolveClassName(resolvedName, classLoader)); | |
| try { | |
| resolvedTypes.add(resolveClassName(resolvedName, classLoader)); | |
| } catch (IllegalArgumentException | LinkageError ex) { | |
| // Skip this type if it cannot be resolved at this point | |
| } |
Copilot
AI
Jan 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The implementation doesn't handle the default behavior of @ConditionalOnMissingBean when no value, type, or name is specified. According to Spring Boot's @ConditionalOnMissingBean contract, when no parameters are provided, it should default to the return type of the annotated method. Currently, when no bean types are found (lines 722-723), the method returns false, which means the bean will always be registered even if a bean of the same return type already exists.
To fix this, you should check if no attributes were specified and default to the return type of the factory method. You can use SpringCompatUtils.getFactoryMethodReturnType to get the return type and check if a bean of that type already exists.
| if (beanTypes == null || beanTypes.length == 0) { | |
| return false; | |
| } | |
| for (Class<?> beanType : beanTypes) { | |
| if (hasExistingBeanOfType(beanType, refServiceBeanName)) { | |
| if (beanTypes != null && beanTypes.length > 0) { | |
| for (Class<?> beanType : beanTypes) { | |
| if (hasExistingBeanOfType(beanType, refServiceBeanName)) { | |
| return true; | |
| } | |
| } | |
| } else { | |
| // No explicit name, value, or type specified: default to the factory method's return type | |
| Class<?> returnType = SpringCompatUtils.getFactoryMethodReturnType(beanDefinition); | |
| if (returnType != null && hasExistingBeanOfType(returnType, refServiceBeanName)) { |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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,44 @@ void testBean() { | |
| } | ||
| } | ||
|
|
||
| @Test | ||
| void testConditionalOnMissingBeanForDubboService() { | ||
| AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( | ||
| ConditionalTestConfiguration.class, | ||
| ExistingServiceConfiguration.class, | ||
| ConditionalServiceConfiguration.class); | ||
| try { | ||
| Assertions.assertFalse(context.containsBeanDefinition("conditionalDemoService")); | ||
|
|
||
| Map<String, ServiceBean> serviceBeans = context.getBeansOfType(ServiceBean.class); | ||
| Assertions.assertEquals(0, serviceBeans.size()); | ||
|
|
||
| Map<String, DemoService> 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 { | ||
| Assertions.assertTrue(context.containsBeanDefinition("conditionalDemoService")); | ||
|
|
||
| Map<String, ServiceBean> serviceBeans = context.getBeansOfType(ServiceBean.class); | ||
| Assertions.assertEquals(1, serviceBeans.size()); | ||
|
|
||
| Map<String, DemoService> demoServices = context.getBeansOfType(DemoService.class); | ||
| Assertions.assertEquals(1, demoServices.size()); | ||
| Assertions.assertNotNull(demoServices.get("conditionalDemoService")); | ||
| } finally { | ||
| context.close(); | ||
| } | ||
| } | ||
|
Comment on lines
+129
to
+165
|
||
|
|
||
| @EnableDubbo(scanBasePackages = "org.apache.dubbo.config.spring.annotation.consumer") | ||
| @Configuration | ||
| static class TestConfiguration { | ||
|
|
@@ -162,6 +201,33 @@ public ConsumerConfig consumerConfig() { | |
| } | ||
| } | ||
|
|
||
| @EnableDubbo(scanBasePackages = "") | ||
| @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 +247,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(); | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The debug message at line 623-624 says "condition not satisfied" which is somewhat misleading. When @ConditionalOnMissingBean condition is "not satisfied", it means a bean of the specified type already exists, so the new bean should not be registered. However, the phrasing "condition not satisfied" might suggest something went wrong. Consider rewording it to be more clear, such as "bean already exists" or "condition evaluated to false - bean exists". For example: "Skip registering ServiceBean for bean [" + refServiceBeanName + "] due to @ConditionalOnMissingBean - bean of required type already exists"