How can I find all beans with the custom annotation @Foo?

坚强是说给别人听的谎言 提交于 2019-12-27 22:07:31

问题


I have this spring configuration:

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

How can I get a list of all beans that are annotated with @Foo?

Note: @Foo is a custom annotation defined by me. It's not one of the "official" Spring annotations.

[EDIT] Following the suggestions of Avinash T., I wrote this test case:

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

but it fails with org.junit.ComparisonFailure: expected:<[[a]]> but was:<[[]]>. Why?


回答1:


Use getBeansWithAnnotation() method to get beans with annotation.

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

Here is similar discussion.




回答2:


With the help of a couple of Spring experts, I found a solution: The source property of a BeanDefinition can be AnnotatedTypeMetadata. This interface has a method getAnnotationAttributes() which I can use to get the annotations of a bean method:

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 with full code of helper class and test case




回答3:


While the accepted answer and Grzegorz's answer contain approaches that will work in all cases, I found a much much simpler one that worked equally well for the most common cases.

1) Meta-annotate @Foo with @Qualifier:

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

2) Sprinkle @Foo onto the factory methods, as described in the question:

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

But it will also work on the type level:

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

3) Then, inject all the instances qualified by @Foo, regardless of their type and creation method:

@Autowired
@Foo
List<Object> fooBeans; 

fooBeans will then contain all the instances produced by a @Foo-annotated method (as required in the question), or created from a discovered @Foo annotated class.

The list can additionally be filtered by type if needed:

@Autowired
@Foo
List<SpecialFooServiceImpl> fooBeans;

The good part is that it will not interfere with any other @Qualifier (meta)annotations on the methods, nor @Component and others on the type level. Nor does it enforce any particular name or type on the target beans.




回答4:


Short story

It is not enough to put @Foo on the a() method in order to make the a bean annotated with @Foo.

Long story

I didn't realize it before I started debugging Spring code, a breakpoint at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAnnotationOnBean(String, Class<A>) helped me understand it.

Of course, if you moved your annotation to the Named class:

  @Foo
  public static class Named {
  ...

and fixed some minor details of your test (annotation target, etc.) the test works.

After giving it a second thought, it's quite natural. When getBeansWithAnnotation() is called, the only information Spring has are the beans. And beans are objects, objects have classes. And Spring doesn't seem to need to store any additional information, incl. what was the factory method used to create the bean annotated with, etc.

EDIT There is an issue which requests to preserve annotations for @Bean methods: https://jira.springsource.org/browse/SPR-5611

It has been closed as "Won't fix" with the following workaround:

  • Employ a BeanPostProcessor
  • Use the beanName provided to the BPP methods to look up the associated BeanDefinition from the enclosing BeanFactory
  • Query that BeanDefinition for its factoryBeanName (the @Configuration bean) and factoryMethodName (the @Bean name)
  • use reflection to get hold of the Method the bean originated from
  • use reflection to interrogate any custom annotations from that method



回答5:


This is how you can beans which are annotated

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


来源:https://stackoverflow.com/questions/14236424/how-can-i-find-all-beans-with-the-custom-annotation-foo

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!