Skip to content

Convenient programmatic bean retrieval with qualifiers [SPR-8891] #13532

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 Dec 1, 2011 · 13 comments
Closed
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 Dec 1, 2011

Ilya Kazakevich opened SPR-8891 and commented

Because of http://stackoverflow.com/questions/8345723/java-spring-tagging-beans-in-xml-to-get-specific-bean-by-class-and-tag I need to find bean which is an instance of specific interface and has special qualifier.

Spring needs to have something like this: beanFactory.getBean(new NameCondition("someName"), new ClassCondition(MyInterface.class));
beanFactory.getBean(new QualifierCondition("myQualif"), new ClassCondition(MyInterface.class));
beanFactory.getBean(new ClassCondition(MyInterface.class));

getBean should be vararg method that accepts several conditions and joins them using "AND".


Issue Links:

1 votes, 10 watchers

@spring-projects-issues
Copy link
Collaborator Author

Chris Beams commented

Hi Ilya,

It's actually quite rare that one would want to fetch a bean by qualifier from the ApplicationContext or BeanFactory APIs directly, and that's why we haven't implemented something first class there to date.

From the look of the StackOverflow thread, your use case appears to be an isolated one; that is, it doesn't seem represent a common and general-purpose need. We tend to be as conservative as possible about adding to the public API in Spring, and in this case, as 'axtavt' mentioned, it can be done, but it takes a few lines of code instead of just one. That's probably a good tradeoff in this particular case.

Of course, if other users have use cases around bean retrieval by qualifier, we can revisit this. For now, resolving this issue as "won't fix". In any case, however, thanks for the submission.

Thanks,

Chris

@spring-projects-issues
Copy link
Collaborator Author

Vasil Yordanov commented

I'm reopening this issue, because I don't think this is an isolated case.
I'm familiar with symfony2 framework and need to write some java application using tagged services that are available in symfony2.
http://symfony.com/doc/current/components/dependency_injection/tags.html
I would like to group set of services by some qualifier (tag).
I would like to give a alias of each of the services under the given qualifier (tag).

Does spring framework offer such kind of services?

@spring-projects-issues
Copy link
Collaborator Author

Oliver Drotbohm commented

In Spring Data, we've just introduced BeanLookup that is very close to an ObjectProvider but fails to properly resolve a primary candidate in case of multiple beans being available, which I'd prefer. We currently use that from within a FactoryBean implementation.

@spring-projects-issues
Copy link
Collaborator Author

Dave Syer commented

There are some newish convenience methods in BeanFactoryAnnotationUtils. E.g. you can retrieve all beans of type T with given qualifiers like this:

private <T> List<T> provide(ListableBeanFactory factory, Class<T> type,
          String... qualifiers) {
     List<T> list = new ArrayList<>();
     for (String name : factory.getBeanNamesForType(type)) {
          if (BeanFactoryAnnotationUtils.isQualifierMatch(value -> {
               for (String qualifer : qualifiers) {
                        if (!qualifer.equals(value))
                                 return false;
               }
               return true;
          }, name, factory)) {
               list.add(factory.getBean(name, type));
          }
     }
     return list;
}

@spring-projects-issues
Copy link
Collaborator Author

Dave Syer commented

It's still ugly though. See here for gorey details: spring-projects/spring-boot#14657. The main issue is that you have to force the bean definition to resolve its factory method using a dummy Field:

private static <T> List<T> beans(AutowireCapableBeanFactory beanFactory,
          Class<T> type, String qualifier) {
     List<T> list = new ArrayList<>();
     if (!(beanFactory instanceof ListableBeanFactory)) {
          return list;
     }
     ListableBeanFactory listable = (ListableBeanFactory) beanFactory;
     for (String name : listable.getBeanNamesForType(type)) {
          if (listable instanceof DefaultListableBeanFactory) {
                   // Force bean definition to resolve its factory method
                   Field dummy = ReflectionUtils.findField(Factory.class,
                                     "genericConverters");
                   ((DefaultListableBeanFactory) listable).isAutowireCandidate(name,
                                     new DependencyDescriptor(dummy, false));
          }
          if (BeanFactoryAnnotationUtils.isQualifierMatch(
                            (value) -> qualifier.equals(value), name, listable)) {
                   list.add(listable.getBean(name, type));
          }
     }
     return list;
}

The dummy field is not needed in the QualifierAnnotationAutowireCandidateResolver because it has resolved the factory method explicitly already via DefaultListableBeanFactory.isAutowireCandidate(String, RootBeanDefinition, DependencyDescriptor, AutowireCandidateResolver).

Also inconvenient is that there is no way to detect a "bare" @Qualifier with no explicit value, which is a feature already supported in the QualifierAnnotationAutowireCandidateResolver, via these lines:

if (targetAnnotation != null && targetAnnotation.equals(annotation)) {
     return true;
}

@spring-projects-issues
Copy link
Collaborator Author

Juergen Hoeller commented

Dave Syer, I'll see what I can do for 5.1.1 still. Essentially, all you need right now is an API for reliably finding all matching beans by type plus qualifier?

@spring-projects-issues
Copy link
Collaborator Author

Dave Syer commented

Yes, what we need is feature parity for functional registration with @Autowired @MyCustomQualifier on a @Bean method, e.g. if you want to create a bean of type Foo and inject all the Bar beans marked as @MyCustomQualifier. I guess that might involve machinery on both sides of the contract - defining the bean definition for both the Foo and its Bars. BeanFactoryAnnotationUtils.isQualifierMatch() is useful for the injection but doesn't quite cover it, even once you wrap it in a convenience method, because the Predicate can only match the value of a @Qualifier, whereas the @Autowired injection can match on the annotation type even if the value is empty. Plus there's the thing with the factory method in a BeanDefinition not being accessible to "outsiders" (it only gets populated by obscure code paths in the BeanFactory). And if everything is functional, I don't even know how to register a qualifier on a bean definition. Maybe it would need to be a BeanDefinitionCustomizer, but then BeanDefinition doesn't have setters for qualifiers?

@spring-projects-issues
Copy link
Collaborator Author

Juergen Hoeller commented

I'm not sure feature parity should be a goal here since some of those annotation-based variants only really make sense in the fully annotation-based context and are less than ideal in a programmatic world. Just like ObjectProvider<List<...>>, I'd rather take the opportunity to express those concerns in a cleaner way in the functional facilities, with fewer variants and therefore less semantic complexity. For adapting annotation-based components to functional registration, an agreed-on subset between the two models should be expressive enough... since we expect the target components to be designed in a functional-friendly way, not trying to adapt arbitrary components to begin with... or are we?

As for registering qualifiers on a BeanDefinition, these methods are currently only exposed on AbstractBeanDefinition, so you'd have to downcast in a BeanDefinitionCustomizer. The whole point there is that the somewhat complex qualifier arrangement is not meant to be used for common purposes, trying to sort out uniqueness issues with specific generic types, bean names or the primary flag instead... only resorting to qualifiers when there is no other way out.

That said, what we can certainly do for 5.1.1 is consistent internal resolution of the factory method (since this should be transparent to the outside when using BeanFactoryAnnotationUtils.isQualifierMatch() or the like indeed), and possibly a convenience method or two for selecting beans by qualifier... to the degree needed for current Boot adaptation needs. Anything on a wider scale will have to be 5.2 only, although even there I'd rather define a functional-friendly variant of qualifiers.

@spring-projects-issues
Copy link
Collaborator Author

Juergen Hoeller commented

I've added a BeanFactoryAnnotationUtils.qualifiedBeansOfType method as a convenience, along with consistent resolution of factory method annotations. Note that I still recommend avoiding qualifiers wherever possible. For the time being, they remain a rather annotation-oriented concept which should ideally not creep into functional configuration.

@spring-projects-issues
Copy link
Collaborator Author

Dave Syer commented

Thanks, that's useful. There's no way to match "anonymous" @Qualifiers (with empty value), as far as I can tell. That would take an extra method?

@spring-projects-issues
Copy link
Collaborator Author

spring-projects-issues commented Oct 11, 2018

Dave Syer commented

This is now broken because of #19778. If you need a test case I can probably help. The Boot PR has been updated to use the new convenience method, but it still needs the dummy field hack.

@spring-projects-issues
Copy link
Collaborator Author

Juergen Hoeller commented

I've added a variant of my previous approach: We store a factory method candidate for introspection purposes in a separate field now, not influencing the constructor resolution algorithm itself. This should not cause any regressions again and nevertheless address your case, Dave Syer. Please give it one more try!

@spring-projects-issues
Copy link
Collaborator Author

Dave Syer commented

It works and spring-projects/spring-boot#14657 was merged. Thanks.

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