Spring: Autowire bean that has no determinant

Is it possible to autoincrement a bean that does NOT have a given classifier in spring? The use case should be to have a list of all beans, but exclude one of them:

@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 {}

      

The above example someList

should contain an instance of Bean1

and Bean2

, but not Bean3

.

(Note: I know the opposite will work, i.e. add some qualifier to Bean1

and Bean2

and then auto-erase with that qualifier.)

EDIT : some additional clarification:

  • All beans are in spring context (also excluded).
  • The configuration should be based on annotations, not xml. Therefore, for example, disabling an auto-approved candidate does not work .
  • The Autowire bean capabilities should remain broad. In other words, I want to exclude the bean from the injection point List<SomeBean> someBeanList;

    , but I want to autoincrement it somewhere else.
+4


source to share


3 answers


The gist is a bean for an exception in context, but is not injected in some cases with an exception condition .

You can exclude a qualified bean using custom annotation and BeanPostProcessor. (I did it as an example for a simple case, for collecting a type of beans, but you can extend it)

annotation for exception:

@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 an 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 the result is:

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

      

+2


source


You can inject your annotation with meta annotations @Conditional

and@Qualifier

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

      

and then inject a class where you can execute your conditional logic

public class MyCondition implements Condition {

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

}

      



In your config class

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

      

You can also exclude a bean from the auto-install candidate by setting autowire-candidate

to a specific bean or by specifyingdefault-autowire-candidates="list of candidates here"

+4


source


There can be two cases:

case 1: Bean3 is not in a spring context;

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

  • if you need to exclude the bean altogether with a class out of context use

    • Condition. This bean is not registered with the application connector if match returns false. as a result:

    @ Custom list someBeanList; - this is where all beans are injected into the SomeBean instance and registered in the application context.

    from spring api

    Condition. The only condition that must be agreed for the component to be registered. Conditions are checked just before the definition of the bean must be registered and vetoed by registration based on any criteria that might be defined at this point.

  • autowired with qualifier:

    2.1 if you want to exclude a bean with some qualifier from autoblock the value in some bean / beans and in xml config you can use the autoblock candidate

    2.2 also you can get all self-timer values ​​with Setter Injection and only filtering the beans 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 set up @Autowired for you if you need something real.

from spring api AutowiredAnnotationBeanPostProcessor:

Note. By default AutowiredAnnotationBeanPostProcessor will be registered with "context: annotation-config" and XML tags "context: component-scan". Remove or disable the default where you want to specify a custom annotation setting AutwiredAnnotationBeanPostProcessor bean.

+1


source







All Articles