Quick basic Spring question about #ConditionalOnProperty and #ImportResource - spring

I have a very basic and quick question about the use of #ConditionalOnProperty and #ImportResource annotations.
#Configuration
#ConditionalOnProperty(value="module.enabled", havingValue = "true", matchIfMissing = true)
#ImportResource(locations={"classpath:/cnf/myconf.xml"})
public class ConfigA {
}
If the condition is not met (that is the module.enable property is set to false in a properties file) then the ConfigA bean will not be loaded but the resources (myconf.xml) will still be imported. Am i right ?
Whether the condition is true or false, the resources will always be loaded. Correct ?
Thanks for helping.

When the property doesn't meet the condition the whole Spring Bean is not loaded, in this case the #ImportResource is not activated at all.

Related

Why #ConditionalOnMissingBean is invoked before the bean it should check?

In my Spring Boot application I want beans to be created only if a module is not enabled.
In CoreConfig I've defined two beans (geocoder and travelDistanceCalculator)
annotated with #ConditionalOnMissingBean referencing the interfaces those beans should initialize.
#Configuration
class CoreConfig {
private val logger = loggerFor<CoreConfig>()
#Bean
#ConditionalOnMissingBean(Geocoder::class)
fun geocoder(): Geocoder {
logger.warn("no Geocoder bean instance has been provided. Instantiating default.")
return object : Geocoder {
override fun getGeocode(address: String) = Coordinates.Unavailable
}
}
#Bean
#ConditionalOnMissingBean(TravelDistanceCalculator::class)
fun travelDistanceCalculator(): TravelDistanceCalculator {
logger.warn("no TravelDistanceCalculator bean instance has been provided. Instantiating default.")
return object : TravelDistanceCalculator {
override fun getTravelDistanceInKm(origin: Coordinates, destination: Coordinates) = Double.NaN
}
}
// other beans definitions...
}
Then GeolocationConfig defines a bean (HereApiClient) implementing both Geocoder and TravelDistanceCalculator interfaces.
#Configuration
#ConditionalOnProperty("app.geolocation.enable")
class GeolocationConfig {
#Bean
fun hereApiClient(
geolocationProperties: GeolocationProperties,
restTemplate: RestTemplate
): HereApiClient =
HereApiClient(restTemplate, geolocationProperties)
}
app.geolocation.enable is defined as true in application.yml.
What happens here is that, on startup, geocoder and travelDistanceCalculator default beans defined in CoreConfig are initialized even if GeolocationConfig is enabled and HereApiClient is initialized shortly after them.
What am I missing here?
ConditionalOnMissingBean is dependent on the order in which #Configuration classes are processed and their beans are defined. In your example, I suspect that CoreConfig is being processed, its conditions evaluated, and its beans defined before your hereApiClient bean has been defined. As a result, when the missing bean conditions are evaluated, there is no matching bean found and the geocoder and travelDistanceCalculator beans are defined.
The javadoc for ConditionalOnMissingBean makes the following recommendation:
The condition can only match the bean definitions that have been processed by the application context so far and, as such, it is strongly recommended to use this condition on auto-configuration classes only. If a candidate bean may be created by another auto-configuration, make sure that the one using this condition runs after.
You could follow this recommendation by using #Order on your configuration classes. Alternatively (and preferably, in my opinion) you could make CoreConfig an auto-configuration class by listing it in META-INF/spring.factories under the org.springframework.boot.autoconfigure.EnableAutoConfiguration key and moving it to a package where it will not be picked up by component scanning.
#ConditionalOn(Missing)Bean is intended to be used on auto-configuration classes. see javadoc
If used in common configiruation-classes then the outcome depends on whichever configuration is loaded first. See andy-wilkinson answer
In your case you could easily use the same property (you are already providing) to configure the correct beans.
Use #ConditionalOnProperty(name="app.geolocation.enable") on GeolocationConfig
Use #ConditionalOnProperty(name="app.geolocation.enable",havingValue="false") on/in CoreConfig
EDIT
Use #ConditionalOnProperty(name="app.geolocation.enable",havingValue="false",matchIfMissing = true) if you want the CoreConfig to be used if the property is missing.

`#ConditionalOnProperty` used with multiple `#PropertySource`

I have some beans with #ConditionalOnProperty, where the property is taken from some #PropertySource. But I have multiple #PropertySources. Only in one of them, given property for condition will be defined. But for my surprise, the #ConditionalOnProperty annotation consults every property source. And since property isn't in every property source, PropertyResolver will flag bean as non-mathing.
What I'd like to have, is interface, with actual implementation and no-op implementation, and control which implementation will be used. I don't want to control it using profile, but profile is based on conditionals so there should be a way. So what is happening for me is, that I'm regardless of setting left out with no implementation. Sure, I can add matchIfMissing, and then I will be left out with both, regardless of setting.
Annotation is:
#ConditionalOnProperty(name = "feature.enabled", havingValue = "true")
and in property file is
feature.enabled=true
What is wrong here? But it really cannot be, that if I'm using #Conditional... I have to use just one property source, right?
UPDATE:
having following beans definitions, I have behavior as described: no bean is registered, because feature.enabled is not defined in application.properties. Adding matchIfMissing=true does not help, because bean with this parameter will be added always, since it's (always) not present in application.properties. Adding #ConditionalOnMissingBean did not help me also. I set feature.enabled to true in another.properties, but that FeatureImpl got vetoed, because there is property source, where feature.enabled isn't true. And surprisingly FeatureNoOp, at that time annotated with #ConditionalOnMissingBean, wasn't registered either. No idea why.
#Service
#Slf4j
#ConditionalOnProperty(name = "feature.enabled", havingValue = "false")
public class FeatureNoOp implements Feature {
vs
#Service
#Slf4j
#ConditionalOnProperty(name = "feature.enabled", havingValue = "true")
public class FeatureImpl implements Feature {
And configuration looks like:
#Configuration
#PropertySource("classpath:application.properties")
public class Config {
//...
#Order(Ordered.HIGHEST_PRECEDENCE)
#Configuration
#PropertySource("classpath:another.properties")
#Profile("war-deployment")
public static class WarDeploymentConfig {
//...
}
}
The true cause was, as M.Deinum correctly suggested (thanks!), in #PropertySource annotations.
I had #PropertySource for application.properties, plus I have some configuration classes like one below, just to bring another property source based on active profile:
#Configuration
#PropertySource("classpath:profile-related-properties.properties")
#Profile("some-profile")
public static class SomeProfileConfig {}
even if this provide some explicit saying what's being used, it apparently spells some issues. I changed the code, so that I do not 'repeatedly' declare usage on application.properties and renamed files/profiles so that another automatically discovered properties pattern can be used: application-{profile_name}.properties. After this change, everything works as expected.

#AutoConfigureAfter not working as desired

I have 3 spring-boot-starter projects
One of the autoconfiguration class has the following code:
#Configuration
#ConditionalOnClass(value = Config.class)
#AutoConfigureAfter(value = {FileGeneratorConfig.class, FileUploaderConfig.class})
public class JobConfig
FileGeneratorConfig and FileUploaderConfig are also autoconfiguration classes.
I was expecting that beans created in FileUploaderConfig will be created first. So test this I had put a break point in the method that creates bean in JobConfig and FileUploaderConfig. But the break point hits JobConfig first which makes me believe that my #AutoConfigureAfter is not working. Is that the right assumption.
Also in FileUploaderConfig i have this:
#Bean
FileUtilContainer fileUtilContainer(FileUtilContainerProperties fileUtilContainerProperties){
return new FileUtilContainer(FileUtil.createDirectory(fileUtilContainerProperties.getArchive()),
FileUtil.createDirectory(fileUtilContainerProperties.getWorking()),
FileUtil.createDirectory(fileUtilContainerProperties.getConfirmation()),
FileUtil.createDirectory(fileUtilContainerProperties.getConfirmationProcessed()),
FileUtil.createDirectory(fileUtilContainerProperties.getError()),
FileUtil.createDirectory(fileUtilContainerProperties.getErrorProcessed()));
}
and FileUtilContainerProperties:
#Component
#ConfigurationProperties(prefix = "batch.letter.directory", ignoreUnknownFields = false)
public class FileUtilContainerProperties
but it is not creating FileUtilContainerProperties bean. Am I missing something here?
AutoConfigureAfter controls the order in which the configuration files are processed and their bean definitions are created. The order in which beans are created from those definitions is a separate concern and depends on, among other things, the dependencies that exist between your beans.

How can I set the default behavior of lazy init in Spring Boot?

I am working on my first Spring Boot application and I have the following problem.
I want to set the that for default all beans are lazy loaded. I know that I can add the #Lazy to all my #Component beans but I want that for default all beans are setted at lazy...
In Spring Boot I don't have an XML configuration file or a configuration class but I only have an application.properties configuration file.
So, how can I set that the default behavior for all the bean is lazy=true
To implement a BeanFactoryPostProcessor that sets lazy initialization by default (which can be required if you are e.g. defining some of the beans dynamically, outside of your #Configuration class(es)), the following approach worked for me:
#Component
public class LazyBeansFactoryPostProcessor implements BeanFactoryPostProcessor {
#Override
public void postProcessBeanFactory( ConfigurableListableBeanFactory beanFactory ) throws BeansException {
for ( String name : beanFactory.getBeanDefinitionNames() ) {
beanFactory.getBeanDefinition( name ).setLazyInit( true );
}
}
}
This essentially puts the #Lazy annotation on all your #Component and #Services. You might want to invent a mechanism to annotate classes with #Eager if you go this route, or just hardwire a list in the LazyBeansFactoryPostProcessor above.
Further reading
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/beans/factory/config/BeanFactoryPostProcessor.html
Since the version 2.2.2.RELEASE of spring-boot you can use the property below in your application.properties file
spring.main.lazy-initialization=true
for further reading and a good example please refer to
https://www.baeldung.com/spring-boot-lazy-initialization
https://spring.io/blog/2019/03/14/lazy-initialization-in-spring-boot-2-2

Spring annotation conditionalOnBean not working

I defined a class with annotation Configuration
#Configuration
#AutoConfigureAfter(EndpointAutoConfiguration.class)
public class EndpointConfiguration {
#Resource
private MetricsEndpoint metricsEndpoint;
#Bean
public MetricsFormatEndpoint metricsFormatEndpoint() {
return new MetricsFormatEndpoint(metricsEndpoint);
}
}
the MetricsFormatEndpoint works well.
but I use the annotation conditionalOnBean, it doesn't work at all.
#Bean
#ConditionalOnBean(MetricsEndpoint.class)
public MetricsFormatEndpoint metricsFormatEndpoint() {
return new MetricsFormatEndpoint(metricsEndpoint);
}
see the localhost:8080/beans,the spring applicationContext has the bean 'metricsEndpoint',
{"bean":"metricsEndpoint","scope":"singleton",
"type":"org.springframework.boot.actuate.endpoint.MetricsEndpoint",
"resource":"class path resource
[org/springframework/boot/actuate/autoconfigure/EndpointAutoConfiguration.class]",
"dependencies":[]}
I read the document of the annotation #ConditionalOnBean, it says The class type of bean that should be checked. The condition matches when any of the classes specified is contained in the {#link ApplicationContext}.
who can tell me why
The javadoc for #ConditionalOnBean describes it as:
Conditional that only matches when the specified bean classes and/or names are already contained in the BeanFactory.
In this case, the key part is "already contained in the BeanFactory". Your own configuration classes are considered before any auto-configuration classes. This means that the auto-configuration of the MetricsEndpoint bean hasn't happened by the time that your own configuration is checking for its existence and, as a result, your MetricsFormatEndpoint bean isn't created.
One approach to take would be to create your own auto-configuration class for your MetricsFormatEndpoint bean and annotate it with #AutoConfigureAfter(EndpointAutoConfiguration.class). That will ensure that its conditions are evaluated after the MetricsEndpoint bean has been defined.
ConditionalOnClass worked as well.
Javadoc said that AutoConfigureAfter should be applied after other specified auto-configuration classes.
And ConditionalOnClass matches when the specified classes are on the classpath. I think it's properer

Resources