问题
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?
回答1:
Use getBeansWithAnnotation() method to get beans with annotation.
Map<String,Object> beans = applicationContext.getBeansWithAnnotation(Foo.class);
Here is similar discussion.
回答2:
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
回答3:
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.
1) Meta-annotate @Foo
with @Qualifier
:
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Foo {
}
2) 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 { ... }
3) 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.
回答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
回答5:
This is how you can beans which are annotated
@Autowired
private ApplicationContext ctx;
public void processAnnotation() {
// Getting annotated beans with names
Map<String, Object> allBeansWithNames = ctx.getBeansWithAnnotation(TestDetails.class);
//If you want the annotated data
allBeansWithNames.forEach((beanName, bean) -> {
TestDetails testDetails = (TestDetails) ctx.findAnnotationOnBean(beanName, TestDetails.class);
LOGGER.info("testDetails: {}", testDetails);
});
}
来源:https://stackoverflow.com/questions/14236424/how-can-i-find-all-beans-with-the-custom-annotation-foo