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..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 @@ -618,6 +618,14 @@ private void processAnnotatedBeanDefinition( AnnotatedBeanDefinition refServiceBeanDefinition, Map attributes) { + if (shouldSkipDueToConditionalOnMissingBean(refServiceBeanName, 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,117 @@ private void registerServiceBeanDefinition( } } + private boolean shouldSkipDueToConditionalOnMissingBean( + String refServiceBeanName, 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; + } + + 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; + } + + for (Class beanType : beanTypes) { + if (hasExistingBeanOfType(beanType, refServiceBeanName)) { + return true; + } + } + + return false; + } + + private boolean hasExistingBeanOfType(Class beanType, String refServiceBeanName) { + if (registry instanceof ConfigurableListableBeanFactory) { + ConfigurableListableBeanFactory beanFactory = (ConfigurableListableBeanFactory) registry; + + String[] beanNames = beanFactory.getBeanNamesForType(beanType); + for (String beanName : beanNames) { + if (!beanName.equals(refServiceBeanName)) { + return true; + } + } + } + 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 e6e9c91213fd..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 @@ -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 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 { + 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(); + } + } + @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(); + } + } }