#Conditional matches method is invoked n+1 times - java

Why boolean matches(...) method in SpecificCaseCondition class is invoked 2 times ? I expect it to be invoked only once, on AnyConfiguration creation. In fact, it's invoked 2 times.
public class SpecificCaseCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return true;
}
}
#Configuration
#Conditional(SpecificCaseCondition.class)
public class AnyConfiguration {
#Bean
public Service firstService() {
return new RealService();
}
#Bean
public Service secondService() {
return new RealService();
}
#Bean
public Service thirdService() {
return new RealService();
}
}

When Spring Boot auto-configures the beans for the app context, it does so in multiple phases. By default, condition at the class level* will be evaluated multiple times, for each phase. That's probably why you're seeing your custom Condition method being called more than once; Spring is invoking it during each phase.
One way to avoid that would be to annotate the #Bean methods with #Conditional instead of the entire class. Like this:
#Configuration
public class AnyConfiguration {
#Bean
#Conditional(SpecificCaseCondition.class)
public Service firstService() {
return new RealService();
}
#Bean
#Conditional(SpecificCaseCondition.class)
public Service secondService() {
return new RealService();
}
#Bean
#Conditional(SpecificCaseCondition.class)
public Service thirdService() {
return new RealService();
}
}
In my experiments, method-level conditions are only evaluated during the REGISTER_BEAN phase.
There is a downside to this solution, of course - it isn't very DRY. As an alternative, you can change your condition to implement ConfigurationCondition which has a method, public ConfigurationPhase getConfigurationPhase(), to dictate which phase the condition should be evaluated in.
The various #Conditional* annotations can be placed at the class level or the method level.

Related

Dynamically load Spring Beans

I am attempting to dynamically load some functionality based off of the environment my application is running in and was wondering if there is a pattern in spring to support this.
Currently my code looks something like this:
public interface DoThingInterface {
void doThing() {}
}
#Conditional(DoThingCondition.class)
#Component
public class DoThingService implements DoThingInterface {
#Override
public doThing() {
// business logic
}
}
#Conditional(DoNotDoThingCondition.class)
#Component
public class NoopService implement DoThingInterface {
#Override
public doThing() {
// noop
}
}
public AppController {
#Autowire
private DoThingInterface doThingService;
public businessLogicMethod() {
doThingService.doThing();
}
}
I appoligise for typing doThing so many times.
But as it currently stands with this, Spring cannot differentiate between the the NoopService and the DothingService since I am autowiring in an interface that both use. The conditionals that they use are directly opposed so there will only ever be one, but Spring does not know this. I had considered using #Profile() instead of conditional, but both will be used in a lot of environment. Is there a correct way to do this so that spring will load only one of these depending on the environment it is in?
Edit: For clarification this functionality is only available in certain deployment regions which is why I chose to use the conditional annotation as the conditions check profile, region, and properties.
As requested, the Conditions are as follows:
public class DoNotDoTheThingCondition implements Condition {
#Override
public boolean matches(ConditionalContext context) {
return !(region.equals(region) && profile.contains("prod"))
}
}
public class DoThingCondition implements Condition {
#Override
public boolean matches(ConditionalContext context) {
return (region.equals(region) && profile.contains("prod"))
}
}
I have simplified the conditions a bit, but that is the general idea. With the code in the state outlined here, Spring throws the following error: no qualifying bean of type DoThingInterface available: expected single matching bean, but found two: DoThingService, NoopService
The solution I came to was to use the condition and manually create the beans as per the comment by Thomas Kasene. I am still unsure why the original did not work, but the key bit was moving the #Conditional annotations onto the beans inside the config. My biggest problem with this method is that you have to maintain parody between the two conditions. That aside it makes for incredibly easy testing as you do not have to stub the noop service if you add your testing profile to the conditions.
The solution ended up looking like this:
Conditions
public class DoNotDoTheThingCondition implements Condition {
#Override
public boolean matches(ConditionalContext context) {
return !(region.equals(region) && profile.contains("prod"))
}
}
public class DoThingCondition implements Condition {
#Override
public boolean matches(ConditionalContext context) {
return (region.equals(region) && profile.contains("prod"))
}
}
Config
#Configuration
public class DoThingConfiguration {
#Conditional(DoThingCondition.class)
#Bean
public DoThingService doThingService() { return new DoThingService(); }
#Conditional(DoNotDoThingCondition.class)
#Bean
public NoopService noopService() { return new NoopService(); }
}
Services
public interface DoThingInterface {
void doThing() {};
}
public class DoThingService {
public void doThing() { // business logic }
}
public class NoopService {
public void doThing() { //Noop }
}
Controller
public class AppController {
private DoThingInterface doThingService;
public businessLogicMethod() {
doThingService.doThing();
}
}

Multiple #ConditionalOnMissingBean bean which will be used

I have multiple bean be annotated with #ConditionalOnMissingBean, which will be used? How can i control the priority?
If you want to control #Bean creation ordering, you can use the annotation #Order
#Component
#Order(1)
public class First {
public int first() {
return 1;
}
}
#Component
#Order(2)
public class Second {
public int second() {
return 2;
}
}
Or you can also use #DependsOn
#Configuration
public class ActionCfg {
#Bean
#DependsOn({"actionA","actionB"})
public ActionC actionC(){
return new ActionC();
}
#Bean("ActionA")
public ActionA actionA() {
return new ActionA();
}
#Bean("ActionB")
public ActionB actionB() {
return new ActionB();
}
}
ActionA and ActionB will initialized before ActionC.
The bean whose auto-configuration class is run first will be taken.
There is #AutoConfigureBefore, #AutoConfigureAfter and #AutoConfigureOrder to control the order of auto-configuration classes.

Overriding spring beans with the same Qualifier Name

I have 2 configuration classes in my spring application.
Configuration and AnotherConfiguration. The AnotherConfiguration is conditioned to create beans only if a certain parameter is provided (this is handled by the ConditionalOnClass annotation).
Configuration.java
#Configuration
public class Configuration {
#Bean
public Stage testStage() {
return someStage1;
}
#Bean
public Stage testStage2() {
return someStage2;
}
}
AnotherConfiguration.java
#Configuration
#ConditionalOnClass()
public class AnotherConfiguration {
#Bean
public Stage testStage2() {
return newStage2;
}
}
The use case is that if I supply an argument that satisfies the Conditional argument for AnotherConfiguration, newStage2 should be returned to all the classes expecting a testStage2 bean. But currently, the testStage2 bean is being resolved from Configuration class instead of being overridden by AnotherConfiguration.
I have tried adding the #Primary annotation to the definition in AnotherConfiguration but that just resolves newStage2 to all the classes expecting bean of type Stage irrespective of the qualifier. Is there a way to instruct spring to override bean definitions only of the same QualifierName (here testStage2.
Due to the project constraints, I cannot make changes to Configuration.java but can make any change to AnotherConfiguration.java keeping the name (testStage2()) same.
I really don't recomend it but
use a conditional instead of an onClass because that will always be true without params
public class Cond implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return false;
}
}
and then define the overridden bean to load into the context
#Component("testStage2")
#Conditional(value = Cond.class)
#Primary
public class AnotherStage extends Stage {
public AnotherStage(){
//do whatever
}
}
Sorry bean style
#Configuration
public class AnotherConfiguration {
#Bean("testBean2")
#Conditional(value = Cond.class)
#Primary
public Stage testStage2() {
return newStage2;
}
}

Spring: How to make some non-Async methods in a Component

I've searched several times for an answer to this to no avail.
I want a Spring component with some #Async methods and some that are not. Like this:
#Component
public class MyComp {
#Autowired private OtherComp delegate;
#Async
void doAsyncAction() { delegate.doAction(); }
// non-Async
Object getData() { return delegate.getSomeData(); }
}
Unfortunately, when the getData method is called the delegate is null.
I can create a work-around that uses a CompleteableFuture to return the datum Object but that's overhead that isn't necessary.
#Component
public class MyComp {
#Autowired private OtherComp delegate;
#Async
void doAsyncAction() { delegate.doAction(); }
#Async
CompleteableFuture<Object> getData() { return CompleteableFuture.completedFuture(delegate.getSomeData()); }
}
Does anyone know of a better work-around?
Thanks,
Bryan

How to force Spring to run the BeanFactoryPostProcessors first when using #Conditional

Let's say I have a bean that needs a value to be injected into one of its fields:
#Component
#PropertySource("classpath:spring-files/my-values.properties")
public class MyArbitraryClass implements ArbitraryClass {
#Value("#{'${values.in.property.file}'.split(',')}")
private List<String> values;
private boolean myBoolean;
#PostConstruct
private void determineBoolean() {
for (String value : values)
if (System.getProperty("whatever").contains(value))
myBoolean = true;
}
#Override
public boolean getMyBoolean() {
return myBoolean;
}
}
I want to use the determineBoolean() method as a condition to instantiate another bean. So I implement the Condition interface:
public class MyCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return context.getBeanFactory().getBean(ArbitraryClass.class).getMyBoolean();
}
}
I componentScan MyArbitraryClass in order to make sure it instantiates first (as opposed to declaring the bean definition in a JavaConfig file):
#Configuration
#ComponentScan("com.package.that.contains.my.arbitrary.class")
public class Conf {
#Bean
#Conditional(MyCondition.class)
// the bean type doesn't really matter
Reader reader() throws FileNotFoundException {
return new FileReader(new File(""));
}
#Bean
static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
The value injected to the 'values' field in MyArbitraryClass will be null, when evaluated by MyCondition. I'm pretty sure that this is because the application context that is injected into the matches() method in MyCondition, contains (at the time) the beans before they were processed by the BeanFactoryPostProcessors (in this case PropertySourcesPlaceHolderConfigurer).
Is there a way to get the bean in the matches() method after being processed, and all values injected into it?
I would suggest is to encapsulate the search-for-existing-property in your custom condition rather than having the match logic on another bean.
With that approach you could get hold of the Environment directly from the exposed context, so your MyCondition would look like this:
public class MyCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return Arrays.stream(context.getEnvironment().getProperty("values.in.property.file").split(","))
.anyMatch(propValue -> propValue.equals(System.getProperty("whatever")));
}
}

Categories

Resources