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 @@ -41,4 +41,24 @@ public interface Constants {
* From FactoryBean.OBJECT_TYPE_ATTRIBUTE of Spring 5.2.
*/
String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";

/**
* Environment property that, when set to {@code true}, makes auto-derived reference bean
* names (i.e. the ones taken from the annotated field/method property name, when no explicit
* {@code id} is given) unable to collide with the name of an unrelated bean registered
* elsewhere in the same Spring context, such as one looked up by an independent
* {@code @Resource} field with a default name.
* <p/>
* This is opt-in and defaults to {@code false} to preserve the existing reference bean
* naming behavior for applications that already rely on it.
*
* @see org.apache.dubbo.config.spring.beans.factory.annotation.ReferenceAnnotationBeanPostProcessor#registerReferenceBean
*/
String QUALIFY_REFERENCE_BEAN_NAME_KEY = "dubbo.application.qualify-reference-bean-name";

/**
* Suffix appended to an auto-derived reference bean name when
* {@link #QUALIFY_REFERENCE_BEAN_NAME_KEY} is enabled.
*/
String REFERENCE_BEAN_NAME_QUALIFIER = "#dubbo-reference";
}
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,17 @@ public String registerReferenceBean(
renameable = false;
} else {
referenceBeanName = propertyName;
if (isQualifyReferenceBeanNameEnabled()) {
// An auto-derived name is just the bare field/property name, which can silently
// collide with an unrelated bean registered elsewhere in the same Spring context
// (e.g. a @Resource field with a default name pointing at a different type).
// Spring's CommonAnnotationBeanPostProcessor#autowireResource only falls back to
// by-type resolution when no bean of that name exists yet, so once this reference
// bean claims the bare name first, such a collision surfaces as a confusing
// BeanNotOfRequiredTypeException instead of a successful by-type lookup.
// See: https://github.com/apache/dubbo/issues/12637
referenceBeanName = referenceBeanName + Constants.REFERENCE_BEAN_NAME_QUALIFIER;
}
}

String checkLocation = "Please check " + member.toString();
Expand Down Expand Up @@ -567,6 +578,12 @@ public String registerReferenceBean(
return referenceBeanName;
}

private boolean isQualifyReferenceBeanNameEnabled() {
return applicationContext
.getEnvironment()
.getProperty(Constants.QUALIFY_REFERENCE_BEAN_NAME_KEY, Boolean.class, Boolean.FALSE);
}

@Override
protected Object doGetInjectedBean(
AnnotationAttributes attributes,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.config.spring;

import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.dubbo.config.bootstrap.DubboBootstrap;
import org.apache.dubbo.config.spring.api.DemoService;
import org.apache.dubbo.config.spring.beans.factory.annotation.ServiceAnnotationTestConfiguration;
import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.context.ApplicationContext;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit.jupiter.SpringExtension;

import static org.springframework.test.annotation.DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD;

/**
* Companion to {@link ReferenceBeanNameCollisionTest}: same reproduction scenario for
* https://github.com/apache/dubbo/issues/12637, but with the opt-in
* {@code dubbo.application.qualify-reference-bean-name} flag enabled. Verifies that the same
* by-name lookup of an unrelated type no longer collides with the {@code @DubboReference}
* field's auto-derived bean name once qualification is turned on.
*/
@EnableDubbo(scanBasePackages = "org.apache.dubbo.config.spring.context.annotation.provider")
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = {ServiceAnnotationTestConfiguration.class, ReferenceBeanNameCollisionFixedTest.class})
@TestPropertySource(properties = "dubbo.application.qualify-reference-bean-name=true")
@DirtiesContext(classMode = AFTER_EACH_TEST_METHOD)
class ReferenceBeanNameCollisionFixedTest {

@BeforeAll
static void setUp() {
DubboBootstrap.reset();
}

@AfterEach
void tearDown() {
DubboBootstrap.reset();
}

// With qualification enabled, this no longer registers under the bare name
// "collisionProbe" - it's suffixed, so it can no longer collide with an unrelated
// by-name lookup for "collisionProbe" elsewhere in the context.
@DubboReference(version = "2", url = "dubbo://127.0.0.1:12345?version=2")
private DemoService collisionProbe;

static class UnrelatedType {}

@Test
void unrelatedByNameLookupNoLongerCollidesWhenQualificationEnabled(ApplicationContext context) {
Assertions.assertFalse(
context.containsBean("collisionProbe"),
"bare name should no longer be claimed by the reference bean when qualification is enabled");
Assertions.assertThrows(
org.springframework.beans.factory.NoSuchBeanDefinitionException.class,
() -> context.getBean("collisionProbe", UnrelatedType.class),
"no bean should exist under the bare name at all now - a real @Resource field would "
+ "correctly fall back to by-type resolution instead of hitting a type mismatch");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.config.spring;

import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.dubbo.config.bootstrap.DubboBootstrap;
import org.apache.dubbo.config.spring.api.DemoService;
import org.apache.dubbo.config.spring.beans.factory.annotation.ServiceAnnotationTestConfiguration;
import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.BeanNotOfRequiredTypeException;
import org.springframework.context.ApplicationContext;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;

import static org.springframework.test.annotation.DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD;

/**
* Reproduces https://github.com/apache/dubbo/issues/12637: an auto-derived reference bean name
* (the bare field/property name, used when no explicit {@code id} is given) can collide with the
* default name a plain {@code @Resource}-style by-name lookup elsewhere in the same Spring
* context uses for an unrelated bean. Once the reference bean claims that name, any other lookup
* of that exact name expecting a different type fails with a confusing
* {@link BeanNotOfRequiredTypeException} instead of resolving the unrelated bean by type.
*
* <p>Reuses the same {@link EnableDubbo} + {@link ServiceAnnotationTestConfiguration} bootstrap
* as {@link org.apache.dubbo.config.spring.beans.factory.annotation.ReferenceAnnotationBeanPostProcessorTest},
* which is already proven to correctly activate {@code @DubboReference} processing.
*/
@EnableDubbo(scanBasePackages = "org.apache.dubbo.config.spring.context.annotation.provider")
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = {ServiceAnnotationTestConfiguration.class, ReferenceBeanNameCollisionTest.class})
@DirtiesContext(classMode = AFTER_EACH_TEST_METHOD)
class ReferenceBeanNameCollisionTest {

@BeforeAll
static void setUp() {
DubboBootstrap.reset();
}

@AfterEach
void tearDown() {
DubboBootstrap.reset();
}

// Auto-derived reference bean name will be the bare field name: "collisionProbe".
// No other bean is declared under this name - unlike a bean already registered at
// definition time (which Dubbo's existing rename-on-collision logic already handles),
// this mirrors an independent @Resource field elsewhere with the same default name:
// a pure runtime by-name lookup that Dubbo cannot see coming at registration time.
@DubboReference(version = "2", url = "dubbo://127.0.0.1:12345?version=2")
private DemoService collisionProbe;

// Unrelated marker type, standing in for whatever type an independent @Resource
// field with the same default name would declare.
static class UnrelatedType {}

@Test
void lookupByNameWithUnrelatedTypeAfterReferenceBeanClaimsName(ApplicationContext context) {
Assertions.assertTrue(
context.containsBean("collisionProbe"),
"the @DubboReference field should have auto-registered a reference bean "
+ "under its bare property name");
Assertions.assertThrows(
BeanNotOfRequiredTypeException.class,
() -> context.getBean("collisionProbe", UnrelatedType.class),
"documents the known limitation: without opting into "
+ "dubbo.application.qualify-reference-bean-name, an unrelated by-name lookup "
+ "(e.g. an independent @Resource field with the same default name) collides "
+ "with the reference bean instead of falling back to by-type resolution");
}
}
Loading