Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Copy link

Copilot AI Jan 27, 2026

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"

Suggested change
+ "] due to @ConditionalOnMissingBean condition not satisfied");
+ "] due to @ConditionalOnMissingBean - bean of required type already exists");

Copilot uses AI. Check for mistakes.
}
return;
}

Map<String, Object> serviceAnnotationAttributes = new LinkedHashMap<>(attributes);

// get bean class from return type
Expand Down Expand Up @@ -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
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code checks if beanNames is not null but doesn't check if it's an empty array before iterating. While iterating over an empty array is safe and won't cause errors, it would be more explicit and consistent with the beanTypes handling (line 697) to check for both null and empty array. Consider adding a length check: if (beanNames != null && beanNames.length > 0) for consistency and clarity.

Copilot uses AI. Check for mistakes.
}

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));
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The call to resolveClassName at line 715 could throw IllegalArgumentException or LinkageError if the class cannot be loaded, even though ClassUtils.isPresent checked for the class presence. This can happen due to ClassLoader issues or race conditions. Consider wrapping this call in a try-catch block to handle potential exceptions gracefully and continue processing other type names instead of failing the entire operation.

Suggested change
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 uses AI. Check for mistakes.
}
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)) {
Comment on lines +722 to +727
Copy link

Copilot AI Jan 27, 2026

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.

Suggested change
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)) {

Copilot uses AI. Check for mistakes.
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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test coverage is missing several important edge cases for the @ConditionalOnMissingBean feature:

  1. Testing @ConditionalOnMissingBean without any parameters (should default to return type)
  2. Testing with the 'name' attribute to check bean by name
  3. Testing with the 'type' attribute using string class names
  4. Testing behavior when multiple beans of the target type exist
  5. Testing interaction with bean aliases

Consider adding additional test methods to cover these scenarios to ensure the implementation handles all supported @ConditionalOnMissingBean configurations correctly.

Copilot uses AI. Check for mistakes.

@EnableDubbo(scanBasePackages = "org.apache.dubbo.config.spring.annotation.consumer")
@Configuration
static class TestConfiguration {
Expand Down Expand Up @@ -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 {

Expand All @@ -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();
}
}
}
Loading