Подтвердить что ты не робот

Как я могу найти все beans с помощью специальной аннотации @Foo?

У меня есть эта конфигурация spring:

@Lazy
@Configuration
public class MyAppConfig {
    @Foo @Bean
    public IFooService service1() { return new SpecialFooServiceImpl(); }
}

Как я могу получить список всех beans, аннотированных с помощью @Foo?

Примечание. @Foo - это специальная аннотация, определенная мной. Это не одна из "официальных" spring аннотаций.

[EDIT] Следуя советам Avinash T., я написал этот тестовый пример:

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" ); }
    }
}

но с ошибкой org.junit.ComparisonFailure: expected:<[[a]]> but was:<[[]]>. Почему?

4b9b3361

Ответ 1

С помощью пары экспертов Spring я нашел решение: source свойством BeanDefinition может быть AnnotatedTypeMetadata. Этот интерфейс имеет метод getAnnotationAttributes() который я могу использовать для получения аннотаций метода бина:

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;
}

с полным кодом вспомогательного класса и тестового примера

Ответ 2

Используйте метод getBeansWithAnnotation(), чтобы получить beans с аннотацией.

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

Здесь - это аналогичное обсуждение.

Ответ 3

Хотя принятый ответ и ответ Гжегожа содержат подходы, которые будут работать во всех случаях, я нашел намного более простой, который работал одинаково хорошо для наиболее распространенных случаев.

1) Мета-аннотация @Foo с @Qualifier:

@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Foo {
}

2) Посыпать @Foo на фабричные методы, как описано в вопросе:

@Foo @Bean
public IFooService service1() { return new SpecialFooServiceImpl(); }

Но он также будет работать на уровне типа:

@Foo
@Component
public class EvenMoreSpecialFooServiceImpl { ... }

3) Затем введите все экземпляры, определенные с помощью @Foo, независимо от их типа и метода создания:

@Autowired
@Foo
List<Object> fooBeans; 

fooBeans будет содержать все экземпляры, созданные методом @Foo -annotated (как требуется в вопросе), или созданные из открытого аннотированного класса @Foo.

При необходимости список можно фильтровать по типу:

@Autowired
@Foo
List<SpecialFooServiceImpl> fooBeans;

Хорошая часть заключается в том, что он не будет мешать другим @Qualifier (meta) для методов, а не @Component и другим на уровне типа. Также он не применяет какое-либо конкретное имя или тип на целевых компонентах.

Ответ 4

Рассказ

Недостаточно положить @Foo в метод a(), чтобы сделать a bean аннотированным с помощью @Foo.

Длинная история

Я не понял этого, прежде чем начать отладку кода Spring, точка останова в org.springframework.beans.factory.support.DefaultListableBeanFactory.findAnnotationOnBean(String, Class<A>) помогла мне понять это.

Конечно, если вы переместили свою аннотацию на класс Named:

  @Foo
  public static class Named {
  ...

и зафиксировал некоторые незначительные детали вашего теста (цель аннотации и т.д.), который работает.

После второй мысли, это вполне естественно. Когда вызывается getBeansWithAnnotation(), единственной информацией Spring является beans. И beans - объекты, объекты имеют классы. И Spring, похоже, не нужно хранить дополнительную информацию, в том числе. какой был метод factory, используемый для создания аннотированного bean и т.д.

РЕДАКТИРОВАТЬ. Существует проблема, которая требует сохранения аннотаций для методов @Bean: https://jira.springsource.org/browse/SPR-5611 p >

Он был закрыт как "Не исправит" со следующим обходным путем:

  • Используйте BeanPostProcessor
  • Используйте beanName, предоставляемые методам BPP, чтобы найти связанный BeanDefinition из прилагаемого BeanFactory
  • Запрос, что BeanDefinition для factoryBeanName (@Configuration bean) и factoryMethodName (имя @Bean)
  • используйте отражение, чтобы получить Method bean от
  • использовать отражение для опроса любых пользовательских аннотаций из этого метода.

Ответ 5

Вот как вы можете бобы, которые аннотированы

@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);
    });
}