web-dev-qa-db-fra.com

Comment puis-je trouver tous les beans avec l'annotation personnalisée @Foo?

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?

28
Aaron Digulla

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

23
Aaron Digulla

Utilisez la méthode getBeansWithAnnotation () pour obtenir des beans avec annotation.

Map<String,Object> beans = applicationContext.getBeansWithAnnotation(Foo.class);

Ici est une discussion similaire.

37
Avinash T.

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.

21
kaqqao

Histoire courte

Il ne suffit pas de mettre @Foo Sur la méthode a() pour faire annoter le bean a avec @Foo.

Longue histoire

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:

  • Utilisez un BeanPostProcessor
  • Utilisez le beanName fourni aux méthodes BPP pour rechercher le BeanDefinition associé dans le BeanFactory
  • Recherchez que BeanDefinition pour son factoryBeanName (le bean @Configuration) Et factoryMethodName (le nom @Bean)
  • utiliser la réflexion pour saisir le Method le bean provient de
  • utiliser la réflexion pour interroger les annotations personnalisées de cette méthode
12
Grzegorz Oledzki