Skip to content

ObjectProvider iterable/stream access for "beans of type" resolution in @Bean methods [SPR-11419] #16046

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

Closed
spring-projects-issues opened this issue Feb 12, 2014 · 8 comments
Assignees
Labels
in: core Issues in core modules (aop, beans, core, context, expression) type: enhancement A general enhancement
Milestone

Comments

@spring-projects-issues
Copy link
Collaborator

spring-projects-issues commented Feb 12, 2014

Oliver Drotbohm opened SPR-11419 and commented

In JavaConfig you can currently use an @Autowired(required = false) List<MyComponent> myComponents field to access all beans of a given type to potentially hand them into a component manually instantiated in an @Bean method.

That said, it would be nice if this pattern could be used at the @Bean method level directly such as:

@Bean public MyOtherComponent foo(List<MyComponent> myComponents) {
  …
}

This currently throws an exception if not bean of type MyComponent can be found but could just fall back to an empty list, which is what you get with the field based approach currently.


Affects: 4.0.1

Issue Links:

0 votes, 5 watchers

@spring-projects-issues
Copy link
Collaborator Author

Juergen Hoeller commented

This feature turns out to be rather involved, so I'm moving it to the 4.1 timeframe. Even now, we don't support individual nulls or empty collections for @Autowired; just non-injection of an entire constructor, method or field.

That said, the prototype indicated a general inconsistency in our factory method resolution algorithm which I am fixing for 4.0.2 and 3.2.8, even if hardly anybody will have noticed that inconsistency before.

Juergen

@spring-projects-issues
Copy link
Collaborator Author

Christopher Smith commented

I was looking through back issues before a feature request, and I wanted to note that the description of current behavior is inaccurate; instead of injecting an empty collection, the container doesn't inject anything at all. (You can manually initialize to emptySet(), but that doesn't happen by default.)

@spring-projects-issues
Copy link
Collaborator Author

Christopher Smith commented

Just ran across this issue exactly as described. I can use an Optional<Set<Thing>> things and things.orElse(Collections.emptySet()), but...

@spring-projects-issues
Copy link
Collaborator Author

spring-projects-issues commented Jul 23, 2018

Juergen Hoeller commented

While we're not going to accept empty collection injection by default (so the related #19901 is to be rejected), we have a new way of collection-style access in 5.1 which I'm repurposing this ticket for:

ObjectProvider extends Iterable and provides Stream support now. It can be therefore be used in for loops, provides forEach iteration and allows for collection-style stream() access, including easy conversion to any desired target collection type via Collectors.toSet() and co.

As a consequence, my recommendation is to declare such injection points as ObjectProvider<MyComponent> and access them like a collection, potentially even converting them to a collection. This should work fine for @Bean method purposes but also in other injection points and for programmatic access (see #21613). As a bonus, there is no confusion between beans that implement collections versus aggregating individual bean elements as collections here: ObjectProvider always resolves the declared bean type as multi-element type in that access path.

@spring-projects-issues
Copy link
Collaborator Author

Caleb Cushing commented

How easy is it to hand create an ObjectProvider, asking in part because I don't really like depending on spring from a contract purpose, I feel like reliance on Spring's classes in my contract is a violation of Dependency Inversion. If it's not easy to hand create a provider... it also means that it's not easy to test without a spring context. Just my 2 cents

@spring-projects-issues
Copy link
Collaborator Author

Juergen Hoeller commented

Admittedly, the recommendation above is primarily for typical configuration needs within configuration classes where the entire model is quite Spring-specific in any case, as well as for programmatic access where the interaction is also with Spring-specific APIs. I expect this to be used in Spring Boot and Spring Data, for example.

For autowiring purposes, aside from ObjectProvider or @Autowired(required=false) (which can also be declared for individual parameters), we only really have overloading as a solution: e.g. one constructor with a collection (to be called when the latter is non-empty), and another constructor without the collection (automatically picked if the collection is not resolvable). Such overloading scenarios are also the main reason why it's hard to change the existing collection injection semantics.

That said, we could support Stream parameters for injection points directly, resolving them the same way as ObjectProvider.stream() and therefore with stream emptiness if no elements are found (and no semantic confusion between collection-implementing beans and bean elements aggregated into a collection). Could you see yourself using Stream as an injection point type, in preference to ObjectProvider?

@spring-projects-issues
Copy link
Collaborator Author

Caleb Cushing commented

For autowiring purposes, aside from ObjectProvider or @Autowired(required=false) (which can also be declared for individual parameters), we only really have overloading as a solution: e.g. one constructor with a collection (to be called when the latter is non-empty), and another constructor without the collection (automatically picked if the collection is not resolvable). Such overloading scenarios are also the main reason why it's hard to change the existing collection injection semantics.

I didn't even know doing this was legal... in part due to my using the Spring 4.x no annotation constructor syntax. Pretty sure that doesn't allow 2 constructors?

 

Could you see yourself using Stream as an injection point type, in preference to ObjectProvider

 

only problem I see with Stream is that they aren't rewindable, and would probably result in

stream.collect(Collections.toList()) ... list.stream()... 

in most cases.

I'm not opposed to having spring dependencies in my class, just prefer to keep it's public api free of them... I wonder if perhaps an annotation could be added, or used to change the behavior. like the new org.springframework.lang.NonNull. Maybe if that's present spring could automatically inject the empty collection/iterable... (or the provider since it will be iterable?) or is it too late to have that do more things?

@spring-projects-issues
Copy link
Collaborator Author

spring-projects-issues commented Jul 23, 2018

Juergen Hoeller commented

Actually, there's still an opportunity to allow empty collection injection for the fallback constructor case, in particular a single constructor scenario. I'll give that a try as well, reopening #19901 along those lines if it works out.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: core Issues in core modules (aop, beans, core, context, expression) type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

2 participants