41

我有这个弹簧配置:

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

如何获取所有带有注释的 bean 的列表@Foo

注意:@Foo是我定义的自定义注解。它不是“官方”的 Spring 注释之一。

[编辑]按照 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:<[[]]>。为什么?

4

5 回答 5

48

使用getBeansWithAnnotation()方法获取带有注释的 bean。

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

是类似的讨论。

于 2013-01-09T13:43:20.557 回答
33

更新: Spring 5.2改变了它的行为context.getBeansWithAnnotation(...)现在它可以正确处理通过工厂方法创建的 bean。所以简单地使用它。

原始答案


虽然接受的答案和Grzegorz 的答案包含适用于所有情况的方法,但我发现了一种更简单的方法,它对最常见的情况同样适用。

  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 { ... }
  1. 然后,注入所有由 限定的实例@Foo,无论它们的类型和创建方法如何:

    @Autowired
    @Foo
    List<Object> fooBeans; 
    

fooBeans然后将包含由@Foo-annotated 方法产生的所有实例(根据问题的要求),或从发现的带@Foo注释的类创建。

如果需要,该列表还可以按类型过滤:

@Autowired
@Foo
List<SpecialFooServiceImpl> fooBeans;

好的部分是它不会干扰@Qualifier方法上的任何其他(元)注释,也不会干扰@Component类型级别的其他注释。它也不会在目标 bean 上强制执行任何特定的名称或类型。

于 2018-02-05T23:15:55.280 回答
26

在几位 Spring 专家的帮助下,我找到了一个解决方案: a 的source属性BeanDefinition可以是AnnotatedTypeMetadata。这个接口有一个方法getAnnotationAttributes(),我可以用它来获取 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;
}

带有帮助类和测试用例的完整代码的要点

于 2013-06-27T12:20:03.010 回答
13

短篇故事

仅仅为了让bean@Foo用.a()a@Foo

很长的故事

在我开始调试 Spring 代码之前我并没有意识到这一点,一个断点org.springframework.beans.factory.support.DefaultListableBeanFactory.findAnnotationOnBean(String, Class<A>)帮助我理解了它。

当然,如果您将注释移至 Named 类:

  @Foo
  public static class Named {
  ...

并修复了测试工作的一些小细节(注释目标等)。

再三考虑之后,这很自然。当getBeansWithAnnotation()被调用时,Spring 拥有的唯一信息是 bean。bean 是对象,对象有类。而且 Spring 似乎不需要存储任何额外的信息,包括。用于创建带有注释的 bean 的工厂方法是什么?

编辑有一个要求保留@Bean方法注释的问题:https ://jira.springsource.org/browse/SPR-5611

它已通过以下解决方法关闭为“无法修复”:

  • 雇用一个BeanPostProcessor
  • 使用提供给 BPP 的方法从封闭beanName中查找关联BeanDefinitionBeanFactory
  • 查询BeanDefinition它的factoryBeanName@Configurationbean)和factoryMethodName@Bean名称)
  • 使用反射来获取Method源自的 bean
  • 使用反射来询问来自该方法的任何自定义注释
于 2013-01-09T23:43:48.783 回答
0

这是获取带注释的bean的方法。

@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);
    });
}
于 2019-05-03T07:45:02.027 回答