问题
I am defining conditions that I will check to dynamically load one of the two implementations of my service interface later.
@Component
public class IsPolicyEnabled implements Condition {
@Autowired
private MyProperties props;
@Override
public boolean matches(ConditionContext arg0, AnnotatedTypeMetadata arg1) {
return props.isPolicyEnabled();
}
}
And
@Component
public class MyProperties {...}
And
@Service
@Conditional(IsPolicyEnabled.class)
public class ServiceA implements Service {...}
However, I am running into a runtime error as.
java.lang.IllegalStateException: Failed to load ApplicationContext
Caused by: java.lang.NullPointerException
at com.xyz.utils.IsPolicyEnabled.matches(IsPolicyEnabled.java:9)
at org.springframework.context.annotation.ConditionEvaluator.shouldSkip(ConditionEvaluator.java:108)
at org.springframework.context.annotation.ConditionEvaluator.shouldSkip(ConditionEvaluator.java:88)
at org.springframework.context.annotation.ConditionEvaluator.shouldSkip(ConditionEvaluator.java:71)
at org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider.isConditionMatch(ClassPathScanningCandidateComponentProvider.java:515)
Basically, it failed to initialize props object that has been auto-wired inside the condition implementation. Is that not allowed?
How can I auto wire another dependency inside the condition implementation since my condition evaluation depends on a value provided by that dependency?
回答1:
Conditions are checked immediately before the bean-definition is due to be registered [...]
Condition, Spring Framework 5.0.8.RELEASE API documentation
You can't inject a bean into a Condition
instance because there are no bean-definitions in the context yet1.
Moreover, you are not supposed to work with beans within Condition
classes:
Conditions must follow the same restrictions as
BeanFactoryPostProcessor
and take care to never interact with bean instances.Condition, Spring Framework 5.0.8.RELEASE API documentation
You should rethink the design because
[...] my condition evaluation depends on a value provided by that dependency.
indicates that it's not quite right.
1 Precisely speaking, there are a few beans already registered by Spring for its own needs.
回答2:
There are two issues:
1) There is no injection for Condition classes
Solution is to retrieve beans from ConditionContext:
@Component
public class IsPolicyEnabled implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
MyProperties props = context.getBeanFactory().getBean(MyProperties.class);
return props.isPolicyEnabled();
}
}
2) Condition initialization happens very early
Trying to retrieve beans from ConditionContext fails with NoSuchBeanDefinitionException: No qualifying bean of type ...
because condition check happens very early in the Spring lifecycle.
A solution is to have two Spring contexts:
- A parentContext which defines only
MyProperties
- A childContext which defines the rest of the application and has parentContext as its parent
So when the Condition is invoked, MyProperties
is already created in the parent context:
ApplicationContext parentContext = new AnnotationConfigApplicationContext(MyProperties.class);
ApplicationContext childContext = new AnnotationConfigApplicationContext();
childContext.setParent(parent);
childContext.register(ApplicationConfiguration.class);
childContext.refresh();
Service service = childContext.getBean(Service.class);
// do something with service
来源:https://stackoverflow.com/questions/52071886/how-to-inject-a-bean-into-a-spring-condition-class