8

Is it possible to autowire a bean that does NOT have the given qualifier in spring? The use case would be to have a list of all beans, but exclude one:

@Autowired
@NotQualifier("excludedBean")    // <-- can we do something like this?
List<SomeBean> someBeanList;


public class Bean1 implements SomeBean {}

public class Bean2 implements SomeBean {}

@Qualifier("excludedBean")
public class Bean3 implements SomeBean {}

In the example above someList should contain an instance of Bean1 and Bean2 but not Bean3.

(Remark: I'm aware that the opposite would work, i.e. add some qualifier to Bean1 and Bean2 and then autowire with that qualifier.)

EDIT: Some further clarifications:

  • All beans are in the spring context (also the one being excluded).
  • Configuration needs to be annotation-based, not xml-based. Therefore, e.g. turning off autowired-candidate does not work.
  • Autowire capability of the bean must remain in general. In other words, I want to exclude the bean from the injection point List<SomeBean> someBeanList;, but I want to autowire it somewhere else.
Attilio
  • 1,624
  • 1
  • 17
  • 27

4 Answers4

5

You can introduce you own annotation with meta annotations @Conditional and @Qualifier

@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
@Conditional(MyCondition.class)
public @interface ExcludeBean {

and then introduce class where you can do your conditional logic

public class MyCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        return !metadata.equals(ExcludeBean.class);
    }

}

In your Configuration class

  @Bean
  @ExcludeBean
  public BeanA beanA() {
      return new BeanA();
  }

You can also exclude bean from being candidate for autowiring by setting autowire-candidate on particular bean or by specifying default-autowire-candidates="list of candidates here"

fg78nc
  • 4,774
  • 3
  • 19
  • 32
  • 1
    Thanks, that worked for me. One thing I had to change is that the condition should return something like `return !metadata.isAnnotated("com.test.ExcludeBean");` (full example [here](https://gist.github.com/attil-io/111c1ebbe70560c1a8c5d11dc6ca5025#file-mycondition-java)). I figured, that I can even just `return false`, as `MyCondition` is only invoked with the annotated beans anyway. What are your thoughts on this? – Attilio Jun 18 '17 at 18:10
  • Re autowired-candidate: I had found about that, but as far as I understand it [only works with xml configuration](https://stackoverflow.com/a/40011075/3398271), not annotation-based configuration, which is used in the project I'm working on (so that is not an option). I will edit the question to make this clear. – Attilio Jun 18 '17 at 18:17
  • Unluckily this is only part of the solution :( The thing is, I still need to be able to wire the excluded bean somewhere else, I want to exclude it only from that single injection point (I will clarify the question). – Attilio Jun 18 '17 at 18:28
  • Sorry, I did not get the last statement, please elaborate. – fg78nc Jun 19 '17 at 13:32
3

main point it's bean for exclude is in context but not injected into some cases with exclude condition .

you can do exclude bean with qualifier with custom annotaion and BeanPostProcessor. (I did as example for simple case , for collection of bean type , but you can extend it)

annotaion for exclude :

@Component
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcludeBeanByQualifierForCollectionAutowired {

    String qualifierToExcludeValue();

    Class<?> aClass();
}

bean post processor with injection

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;

@Component
public class ExcludeAutowiredBeanPostProcessor implements BeanPostProcessor {

    @Autowired
    private ConfigurableListableBeanFactory configurableBeanFactory;

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        Field[] fields = bean.getClass().getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            ExcludeBeanByQualifierForCollectionAutowired myAutowiredExcludeAnnotation = field.getAnnotation(ExcludeBeanByQualifierForCollectionAutowired.class);
            if (myAutowiredExcludeAnnotation != null) {

                Collection<Object> beanForInjection = new ArrayList<>();

                String[] beanNamesOfType = configurableBeanFactory.getBeanNamesForType(myAutowiredExcludeAnnotation.aClass());
                for (String injectedCandidateBeanName : beanNamesOfType) {

                    Object beanCandidate = configurableBeanFactory.getBean(injectedCandidateBeanName);

                    Qualifier qualifierForBeanCandidate = beanCandidate.getClass().getDeclaredAnnotation(Qualifier.class);

                    if (qualifierForBeanCandidate == null || !qualifierForBeanCandidate.value().equals(myAutowiredExcludeAnnotation.qualifierToExcludeValue())) {
                        beanForInjection.add(beanCandidate);
                    }
                }
                try {
                    field.set(bean, beanForInjection);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }

        return bean;
    }


    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        return bean;
    }
}

and example:

public class ParentBean {}

public class Bean1Included extends ParentBean {}

public class Bean2Included extends ParentBean {}

public class Bean3Included extends ParentBean {}

@Qualifier("excludedBean")
public class BeanExcluded extends ParentBean {}

configuration

@Configuration
public class BeanConfiguration {

    @Bean
    public Bean1Included getBean1(){
        return new Bean1Included();
    }

    @Bean
    public Bean2Included getBean2(){
        return new Bean2Included();
    }

    @Bean
    public Bean3Included getBean3(){
        return new Bean3Included();
    }

    @Bean
    public BeanExcluded getExcludedBean(){
        return new BeanExcluded();
    }

    @Bean
    public ExcludeAutowiredBeanPostProcessor excludeAutowiredBeanPostProcessor(){
        return new ExcludeAutowiredBeanPostProcessor();
    }
}

and result:

@ExtendWith(SpringExtension.class) // assumes Junit 5
@ContextConfiguration(classes = BeanConfiguration.class)
public class ExcludeConditionTest {

    @Autowired
    private ApplicationContext context;
    @Autowired
    private BeanExcluded beanExcluded;
    @ExcludeBeanByQualifierForCollectionAutowired(qualifierToExcludeValue = "excludedBean" , aClass = ParentBean.class)
    private List<ParentBean> beensWithoutExclude;

    @Test
    void should_not_inject_excluded_bean() {
        assertThat(context.getBeansOfType(ParentBean.class).values())
                .hasOnlyElementsOfTypes(Bean1Included.class,
                                        Bean2Included.class,
                                        Bean3Included.class,
                                        BeanExcluded.class);

        assertThat(beansWithoutExclude)
                .hasOnlyElementsOfTypes(Bean1Included.class,
                                        Bean2Included.class,
                                        Bean3Included.class)
                .doesNotHaveAnyElementsOfTypes(BeanExcluded.class);

        assertThat(beanExcluded).isNotNull();
    }
}
bric3
  • 40,072
  • 9
  • 91
  • 111
xyz
  • 5,228
  • 2
  • 26
  • 35
  • I think there is a small typo: in `ExcludeAutowiredBeanPostProcessor` instead of `beanForInjection.add(bean);`, it should be actually `beanForInjection.add(beanCandidate);`. Other than that, this worked for me. – Attilio Jun 25 '17 at 10:21
  • Sorry.ill fix that. Yes it' typo. I changed so.e code in web not in idea...sorry – xyz Jun 25 '17 at 10:34
  • Do you accept my solution? It' s not general approache it's only for case with collection but yoou can extend it – xyz Jun 25 '17 at 10:36
1

There might be two cases :

case 1 : Bean3 in not in spring context;

case 2 : Bean3 is in spring context but not injected in some cases with @Autowired ,

  1. if you need to exclude bean with Qualifier from context at all ,use

    • Condition. This bean is not registered in application conxtet if matches returns false. as result :

    @Autowired List someBeanList; -- here injected all beans instanceof SomeBean and registered in application context.

    from spring api

    Condition A single condition that must be matched in order for a component to be registered. Conditions are checked immediately before the bean-definition is due to be registered and are free to veto registration based on any criteria that can be determined at that point.

  2. autowired with qualifier :

    2.1 if you want to exclude bean with the some qualifier from autowired value in some bean/beans and in xml configuration you can use autowire-candidate

    2.2 also you can get all autowired values by Setter Injection and filter only beans that you need.

    //no Autowired. Autowired in method
     private List<ParentBean> someBeen = new ArrayList<>();
    
     @Autowired
     public void setSomeBeen(List<ParentBean> beens){
         // if you use java 8 use stream api
         for (ParentBean bean:beens) {                 
             Qualifier qualifier = bean.getClass().getAnnotation(Qualifier.class);
             if(qualifier == null ||!qualifier.value().equals("excludedBean")){
                 someBeen.add(bean);
             }
         }
     }
    

    2.3 you can use custome AutowiredAnnotationBeanPostProcessor :) and customise @Autowired for you requirements if you need something realy custom.

from spring api AutowiredAnnotationBeanPostProcessor :

Note: A default AutowiredAnnotationBeanPostProcessor will be registered by the "context:annotation-config" and "context:component-scan" XML tags. Remove or turn off the default annotation configuration there if you intend to specify a custom AutowiredAnnotationBeanPostProcessor bean definition.

xyz
  • 5,228
  • 2
  • 26
  • 35
  • I might try `AutowiredAnnotationBeanPostProcessor`, as the solution based on `Condition` completely prevents the been from being autowired (which is not what I need). – Attilio Jun 18 '17 at 18:31
  • What about 2.2? – xyz Jun 18 '17 at 18:34
  • I wrote about condition. It will be out of spring context. In case 2.2 you filter by qualifaer bean that yoyu need – xyz Jun 18 '17 at 18:35
  • 2.2 would work of course, but it is more a "workaround" (not so elegant as having a single annotation) – Attilio Jun 18 '17 at 18:39
0

Another way to do this, is creating a custom component with @Qualifier


@Component
@Qualifier
public @interface MyComponent {
    public boolean isMock() default false;
}

@Autowired
@MyComponent(true)
List<SomeBean> mockList; // will inject just component with "isMock = true"

@Autowired
@MyComponent(false)
List<SomeBean> notMockList; // will inject just component with "isMock = false"


@MyComponent
public class Bean1 implements SomeBean {}

@MyComponent
public class Bean2 implements SomeBean {}

@MyComponent(isMock = true)
public class Bean3 implements SomeBean {}

Obs: this code is not tested, just giving an idea