J'ai cette configuration de printemps:
@Lazy
@Configuration
public class MyAppConfig {
@Foo @Bean
public IFooService service1() { return new SpecialFooServiceImpl(); }
}
Comment puis-je obtenir une liste de tous les beans annotés avec @Foo
?
Remarque: @Foo
est une annotation personnalisée que j'ai définie. Ce n'est pas une des annotations "officielles" du printemps.
[EDIT] En suivant les suggestions d'Avinash T., j'ai écrit ce cas de test:
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" ); }
}
}
mais il échoue avec org.junit.ComparisonFailure: expected:<[[a]]> but was:<[[]]>
. Pourquoi?
Avec l'aide de quelques experts Spring, j'ai trouvé une solution: la propriété source
d'un BeanDefinition
peut être AnnotatedTypeMetadata
. Cette interface a une méthode getAnnotationAttributes()
que je peux utiliser pour obtenir les annotations d'une méthode bean:
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 avec le code complet de la classe d'assistance et du cas de test
Utilisez la méthode getBeansWithAnnotation () pour obtenir des beans avec annotation.
Map<String,Object> beans = applicationContext.getBeansWithAnnotation(Foo.class);
Ici est une discussion similaire.
Alors que la réponse acceptée et la réponse de Grzegorz contiennent des approches qui fonctionneront dans tous les cas , j'en ai trouvé une beaucoup plus simple qui fonctionnait aussi bien pour les cas les plus courants.
1) Méta-annoter @Foo
avec @Qualifier
:
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Foo {
}
2) Saupoudrer @Foo
sur les méthodes d'usine, comme décrit dans la question:
@Foo @Bean
public IFooService service1() { return new SpecialFooServiceImpl(); }
Mais cela fonctionnera également au niveau du type:
@Foo
@Component
public class EvenMoreSpecialFooServiceImpl { ... }
3) Ensuite, injectez toutes les instances qualifiées par @Foo
, quels que soient leur type et leur méthode de création:
@Autowired
@Foo
List<Object> fooBeans;
fooBeans
contiendra alors toutes les instances produites par un @Foo
- méthode annotée (comme requis dans la question), ou créée à partir d'un @Foo
classe annotée.
La liste peut en outre être filtrée par type si nécessaire:
@Autowired
@Foo
List<SpecialFooServiceImpl> fooBeans;
La bonne partie est qu'il n'interfère pas avec les autres @Qualifier
(méta) annotations sur les méthodes, ni @Component
et d'autres au niveau du type. Il n'applique pas non plus un nom ou un type particulier sur les beans cibles.
Il ne suffit pas de mettre @Foo
Sur la méthode a()
pour faire annoter le bean a
avec @Foo
.
Je ne m'en étais pas rendu compte avant de déboguer le code Spring, un point d'arrêt sur org.springframework.beans.factory.support.DefaultListableBeanFactory.findAnnotationOnBean(String, Class<A>)
m'a aidé à le comprendre.
Bien sûr, si vous avez déplacé votre annotation dans la classe Named:
@Foo
public static class Named {
...
et corrigé quelques détails mineurs de votre test (cible d'annotation, etc.) le test fonctionne.
Après y avoir réfléchi, c'est assez naturel. Lorsque getBeansWithAnnotation()
est appelée, les seules informations dont dispose Spring sont les beans. Et les beans sont des objets, les objets ont des classes. Et Spring ne semble pas avoir besoin de stocker des informations supplémentaires, y compris. quelle était la méthode d'usine utilisée pour créer le bean annoté, etc.
[~ # ~] modifier [~ # ~] Il y a un problème qui demande de conserver les annotations pour les méthodes @Bean
: https://jira.springsource.org/browse/SPR-5611
Il a été fermé en tant que "ne résoudra pas" avec la solution de contournement suivante:
BeanPostProcessor
beanName
fourni aux méthodes BPP pour rechercher le BeanDefinition
associé dans le BeanFactory
BeanDefinition
pour son factoryBeanName
(le bean @Configuration
) Et factoryMethodName
(le nom @Bean
)Method
le bean provient de