How can I find all beans with the custom annotation @Foo?
Solution 1
With the help of a couple of Spring experts, I found a solution: The source
property of a BeanDefinition
can be AnnotatedTypeMetadata
. This interface has a method getAnnotationAttributes()
which I can use to get the annotations of a bean method:
public List<String> getBeansWithAnnotation( Class<? extends Annotation> type, Predicate<Map<String, Object>> attributeFilter ) {
List<String> result = Lists.newArrayList();
ConfigurableListableBeanFactory factory = applicationContext.getBeanFactory();
for( String name : factory.getBeanDefinitionNames() ) {
BeanDefinition bd = factory.getBeanDefinition( name );
if( bd.getSource() instanceof AnnotatedTypeMetadata ) {
AnnotatedTypeMetadata metadata = (AnnotatedTypeMetadata) bd.getSource();
Map<String, Object> attributes = metadata.getAnnotationAttributes( type.getName() );
if( null == attributes ) {
continue;
}
if( attributeFilter.apply( attributes ) ) {
result.add( name );
}
}
}
return result;
}
gist with full code of helper class and test case
Solution 2
Use getBeansWithAnnotation() method to get beans with annotation.
Map<String,Object> beans = applicationContext.getBeansWithAnnotation(Foo.class);
Here is similar discussion.
Solution 3
UPDATE: Spring 5.2 changed the behavior of context.getBeansWithAnnotation(...)
and it now correctly handles beans created via factory methods. So simply use that.
Original answer
While the accepted answer and Grzegorz's answer contain approaches that will work in all cases, I found a much much simpler one that worked equally well for the most common cases.
-
Meta-annotate
@Foo
with@Qualifier
:@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @Qualifier public @interface Foo { }
-
Sprinkle
@Foo
onto the factory methods, as described in the question:@Foo @Bean public IFooService service1() { return new SpecialFooServiceImpl(); }
But it will also work on the type level:
@Foo
@Component
public class EvenMoreSpecialFooServiceImpl { ... }
-
Then, inject all the instances qualified by
@Foo
, regardless of their type and creation method:@Autowired @Foo List<Object> fooBeans;
fooBeans
will then contain all the instances produced by a @Foo
-annotated method (as required in the question), or created from a discovered @Foo
annotated class.
The list can additionally be filtered by type if needed:
@Autowired
@Foo
List<SpecialFooServiceImpl> fooBeans;
The good part is that it will not interfere with any other @Qualifier
(meta)annotations on the methods, nor @Component
and others on the type level. Nor does it enforce any particular name or type on the target beans.
Solution 4
Short story
It is not enough to put @Foo
on the a()
method in order to make the a
bean annotated with @Foo
.
Long story
I didn't realize it before I started debugging Spring code, a breakpoint at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAnnotationOnBean(String, Class<A>)
helped me understand it.
Of course, if you moved your annotation to the Named class:
@Foo
public static class Named {
...
and fixed some minor details of your test (annotation target, etc.) the test works.
After giving it a second thought, it's quite natural. When getBeansWithAnnotation()
is called, the only information Spring has are the beans. And beans are objects, objects have classes. And Spring doesn't seem to need to store any additional information, incl. what was the factory method used to create the bean annotated with, etc.
EDIT There is an issue which requests to preserve annotations for @Bean
methods: https://jira.springsource.org/browse/SPR-5611
It has been closed as "Won't fix" with the following workaround:
- Employ a
BeanPostProcessor
- Use the
beanName
provided to the BPP methods to look up the associatedBeanDefinition
from the enclosingBeanFactory
- Query that
BeanDefinition
for itsfactoryBeanName
(the@Configuration
bean) andfactoryMethodName
(the@Bean
name) - use reflection to get hold of the
Method
the bean originated from - use reflection to interrogate any custom annotations from that method
Aaron Digulla
I'm a software developer living in Switzerland. You can reach me at digulla at hepe dot com.
Updated on August 01, 2022Comments
-
Aaron Digulla almost 2 years
I have this spring configuration:
@Lazy @Configuration public class MyAppConfig { @Foo @Bean public IFooService service1() { return new SpecialFooServiceImpl(); } }
How can I get a list of all beans that are annotated with
@Foo
?Note:
@Foo
is a custom annotation defined by me. It's not one of the "official" Spring annotations.[EDIT] Following the suggestions of Avinash T., I wrote this test case:
import static org.junit.Assert.*; import java.lang.annotation.ElementType; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.annotation.Retention; import java.lang.reflect.Method; import java.util.Map; import org.junit.Test; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Lazy; public class CustomAnnotationsTest { @Test public void testFindByAnnotation() throws Exception { AnnotationConfigApplicationContext appContext = new AnnotationConfigApplicationContext( CustomAnnotationsSpringCfg.class ); Method m = CustomAnnotationsSpringCfg.class.getMethod( "a" ); assertNotNull( m ); assertNotNull( m.getAnnotation( Foo.class ) ); BeanDefinition bdf = appContext.getBeanFactory().getBeanDefinition( "a" ); // Is there a way to list all annotations of bdf? Map<String, Object> beans = appContext.getBeansWithAnnotation( Foo.class ); assertEquals( "[a]", beans.keySet().toString() ); } @Retention( RetentionPolicy.RUNTIME ) @Target( ElementType.METHOD ) public static @interface Foo { } public static class Named { private final String name; public Named( String name ) { this.name = name; } @Override public String toString() { return name; } } @Lazy @Configuration public static class CustomAnnotationsSpringCfg { @Foo @Bean public Named a() { return new Named( "a" ); } @Bean public Named b() { return new Named( "b" ); } } }
but it fails with
org.junit.ComparisonFailure: expected:<[[a]]> but was:<[[]]>
. Why? -
Aaron Digulla over 11 yearsThanks, I completely missed that. I wrote a test case but the test fails (see my edited question). Any idea why?
-
Aaron Digulla over 11 yearsThat's because
getBeansWithAnnotation()
returns an empty map -> it doesn't find any beans. Why is that? -
Nandkumar Tekale over 11 years@AaronDigulla : I am not sure but can you create any object other than
String
and check? I think in Spring we can not have String as a bean. -
Nandkumar Tekale over 11 years@AaronDigulla : I am talking about
@Foo @Bean public String a() { return "a"; }
-
Aaron Digulla over 11 years*: I updated my test case. It checks that the annotation is on the method and I'm using a non-String bean type. But it still fails :-(
-
Nandkumar Tekale over 11 years@AaronDigulla OK. Can you get your bean by bean name ? like, provide name to bean
@Foo @Bean("myNameIsA") public Named a() { return new Named( "a" ); }
so if we are getting it this way, we can go to next step to solve this issue. :) -
Aaron Digulla over 11 yearsI can't use names. I want to group my beans by using several annotations (so a bean can be in several groups or none).
-
Nandkumar Tekale over 11 yearsI meant, if you could get bean by name, then you could get it by annotation too. So I suggested you to test it.
-
Hauke Ingmar Schmidt over 11 yearsThe bean just does not have the annotation, only the bean factory method has - but you are not querying for methods.
-
Aaron Digulla over 11 yearsThanks. It's a pity that I can't use annotations to create groups of beans in my Spring config without changing the types but I can probably find a way around it.
-
kaqqao over 6 yearsWhy is this answer upvoted so much? It completely misses the point!
-
Aaron Digulla about 5 yearsSorry, this doesn't work for the reasons outlined in the answer by Grzegorz Oledzki.
-
User1291 about 5 yearsGreat answer, thanks for sharing. If you could briefly explain in which situations this is not enough and why?
-
kaqqao about 5 years@User1291 You might not be able to add
@Qualifier
to@Foo
because it's coming for a 3rd party, for example. My answer depends on your ability to modify the beans and annotations, and not only on inspecting the container from the outside. -
User1291 about 5 yearsHm ... it looks like this doesn't work when I give the annotation properties (e.g.
@Foo(weight = 100)
won't get recognised as a@Foo
-qualified anymore. Any ideas? -
kaqqao about 5 years@User1291 I think that's because Spring checks for the annotation equality between the injection point and the candidates, and
@Foo
and@Foo(weight = 100)
are not equal. I think for your case you'd have to inspect the container as described in the other answers. -
User1291 about 5 yearsYup, you're right, if I specifically search for
@Foo(weight = 100)
on the collection, it autowires them again. Shame, I had hoped I could somehow turn off the property-checks and get them all regardless of their weights. Thanks, though.