Как я могу найти все 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:<[[]]>
. Почему?
Ответы
Ответ 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);
});
}