Skip to content
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

ObjectProvider Map resolution inconsistency between resolveDependency and getBeanProvider [SPR-17577] #22109

Closed
spring-projects-issues opened this issue Dec 7, 2018 · 4 comments
Assignees
Labels
in: core status: declined type: task

Comments

@spring-projects-issues
Copy link
Collaborator

@spring-projects-issues spring-projects-issues commented Dec 7, 2018

Dave Syer opened SPR-17577 and commented

DefaultListableBeanFactory has 2 methods that can be used to extract an ObjectProvider, and they behave inconsistently if the dependency is of type Map<String,X>. Here's a test that fails:

@SpringBootTest
@RunWith(SpringRunner.class)
public class SampleApplicationTests {

	@Autowired
	public ObjectProvider<Map<String, Bar>> provider;

	@Autowired
	private Bar bar;

	@Autowired
	private DefaultListableBeanFactory beans;

	@Test
	public void test() {
		assertThat(bar).isNotNull();
	}

	@Test
	public void fails() {
		ObjectProvider<Map<String, Bar>> provider = beans.getBeanProvider(
				ResolvableType.forClassWithGenerics(Map.class, String.class, Bar.class));
		assertThat(provider.getIfAvailable()).isNotEmpty();
	}

	@Test
	public void auto() {
		assertThat(provider.getIfAvailable()).isNotEmpty();
	}

	@Test
	public void descriptor() {
		DependencyDescriptor descriptor = new DependencyDescriptor(
				ReflectionUtils.findField(SampleApplicationTests.class, "provider"),
				true);
		@SuppressWarnings("unchecked")
		ObjectProvider<Map<String, Bar>> provider = (ObjectProvider<Map<String, Bar>>) beans
				.resolveDependency(descriptor, "test");
		assertThat(provider.getIfAvailable()).isNotEmpty();
	}

}

@SpringBootConfiguration
class SampleConfiguration {

	@Bean
	public Foo foo() {
		return new Foo();
	}

	@Bean
	public Bar bar(Map<String, Foo> foo) {
		return new Bar(foo.values().iterator().next());
	}

}

class Bar {

    private final Foo foo;

    public Bar(Foo foo) {
        this.foo = foo;
    }

    public Foo getFoo() {
        return this.foo;
    }
}

class Foo {

    private String value;

    public Foo() {
    }

    public Foo(String value) {
        this.value = value;
    }

    public String getValue() {
        return this.value;
    }

    public void setValue(String value) {
        this.value = value;
    }

    @Override
    public String toString() {
        return "Foo [value=" + this.value + "]";
    }

}

Affects: 5.1.3

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented Dec 7, 2018

Juergen Hoeller commented

This is by design: Resolution of Collection or Map as a collection of individual beans is only supported for declarative injection points. Such resolution is ambiguous with beans which are of type Collection or Map themselves, and it's convoluted enough that we have to support this for annotation-based resolution still. For ObjectProvider usage and even more so for plain programmatic usage, the recommended way to access multiple beans is through the ObjectProvider's iteration/stream access operators, with the declared bean type always indicating the individual bean type to match.

In any case, we need to document this properly, so I'm turning this into a documentation ticket.

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented Dec 7, 2018

Dave Syer commented

There's no way to access the bean names from an ObjectProvider is there? So there is no direct replacement in the functional approach? I guess there is always getBeansOfType(). I assume they didn't want to use that in Boot for some reason (see spring-projects/spring-boot#15419).

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented Dec 7, 2018

Juergen Hoeller commented

Indeed, if you need the bean names as well, an on-demand call to getBeansOfType is a solution... or getBeanNamesForType plus individual getBean calls as the recommended variant.

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented Jan 8, 2019

Juergen Hoeller commented

It turns out that there is a corresponding note in the getBeanProvider(ResolvableType) javadoc already.

@spring-projects-issues spring-projects-issues added status: declined in: core type: task labels Jan 11, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: core status: declined type: task
Projects
None yet
Development

No branches or pull requests

2 participants