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.
source to share
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();
}
}
source to share
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"
source to share
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.
source to share