What is purpose of #ConditionalOnProperty annotation? - spring

I just modified spring boot configuration, and encountered
#ConditionalOnProperty(prefix = "spring.social.", value = "auto-connection-views")
from org.springframework.boot.autoconfigure.social.TwitterAutoConfiguration
#Bean(name = { "connect/twitterConnect", "connect/twitterConnected" })
#ConditionalOnProperty(prefix = "spring.social.", value = "auto-connection-views")
public View twitterConnectView() {
return new GenericConnectionStatusView("twitter", "Twitter");
}
I don't understand purpose of this annotation. I guess this might be enable to use bean only if property value exist(e.g. "spring.social", "auto-connection-views").

The annotation is used to conditionally create a Spring bean depending on the configuration of a property. In the usage you've shown in the question the bean will only be created if the spring.social.auto-connection-views property exists and it has a value other than false. This means that, for this View bean to be created, you need to set the spring.social.auto-connection-views property and it has to have a value other than false.
You can find numerous other uses of this annotation throughout the Spring Boot code base. Another example is:
#ConditionalOnProperty(prefix = "spring.rabbitmq", name = "dynamic", matchIfMissing = true)
public AmqpAdmin amqpAdmin(CachingConnectionFactory connectionFactory) {
return new RabbitAdmin(connectionFactory);
}
Note the use of matchIfMissing. In this case the AmqpAdmin bean will be created if the spring.rabbitmq.dynamic property exists and has a value other than false or the property doesn't exist at all. This makes the creation of the bean opt-out rather than the example in the question which is opt-in.

In case you are using this property on TYPE-level, i.e. on one of your #Configuration classes... Keep in mind that in such case the annotation is evaluated/checked against the default properties file, i.e. application.properties
#ConditionalOnProperty on TYPE level w/ #Configuration

Rather, it is the opposite. A precondition for implementing the method, if the property is set in the environment (development, approval, production) and is true value with the method can be executed.
If the property is not set in the environment annotation not prevented the execution of the method.

Related

Bean is created regardless of #ConditionalOnProperty matchIfMissing's value

I am trying to create a bean for cacheManager only when a specific cachemanager is not configured.
#Bean
#ConditionalOnProperty(name = "spring.cache.type", matchIfMissing = true)
public CacheManager cacheManager() {
ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager() {
#Override
protected Cache createConcurrentMapCache(final String name) {
return new ConcurrentMapCache(name,
CacheBuilder.newBuilder().expireAfterWrite(30, TimeUnit.SECONDS).build().asMap(), false);
}
};
return cacheManager;
}
This bean is created even when I have the property
spring.cache.type=redis
is configured. Did try different combinations with prefix with no luck. This bean is injected regardless of whether the cache type is specified or not in the application.properties.
The issue seems to be that the default value of having attribute does not work as you expect it to. Reading the reference documentation you will find the following for having:
The string representation of the expected value for the properties. If not specified, the property must not be equal to false.
This means, that in your case (because you are not specifying the value), the condition will always match unless you have spring.cache.type=false. This is also shown in the reference documentation in the following table (the property value "foo" will actually match the condition if havingValue="" which is actually the default if you do not specify it):
Having said all that I would say that your best option would be to create your own Condition just like #ray suggested.
Your issue is quite straightforward. You need to understand what is actually happening here.
As you already knows #ConditionalOnProperty creates a bean when the propert specify in your annotation is available in your yml. In your case it is spring.cache.type.
So if you annotate like this #ConditionalOnProperty(name = "spring.cache.type"), bean will not created when property not is not available. In other words "condition is false because condition is absent".
When you annotate like this #ConditionalOnProperty(name = "spring.cache.type", matchIfMissing = true), bean will created when the property not available because we explicitly mention that by matchIfMissing = true. In other words "assume condition is true when property is absent". So obviously condition is true when the property is available.
So to fix your issue what you can do something like this, define a havingValue that will never put as the value for that property. What happens then, bean will not created even when property is available because value of that property does matches your havingValue.
Something like this,
#ConditionalOnProperty(name = "spring.cache.type", matchIfMissing = true, havingValue = "none")
Method 2
You can create custom Condition class. Like follows,
public class ConditionalOnMisssing implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Environment env = context.getEnvironment();
return env.getProperty("spring.cache.type") == null;
}
}
Then change your CacheManager bean as follows.
#Bean
#Conditional(ConditionalOnMisssing.class)
public CacheManager cacheManager() {}

#Bean method requires to return null conditionally

I have basic authentication enabled in my system and now I am trying to integrate the SAML auth using Spring-security-SAML. I have created a method that returns the RelyingPartRegistrationRepository bean. In a condition While the user-configured values are not sufficient to Create the RelyingPartRegistration, I would have to either create a RelyingPartRegistrationRepository with Empty array which is not possible because there are checks to be not empty. another option is to return null from the Bean method. which is also a failure case because context initialization will fail in that case. All I want is to either not initialize this bean or at least not prevent context initialization. So that I can at least switch back to Basic Authentication.
You can use #ConditionalOn... to selectively enable/disable particular beans, based on things like properties
class MyConfiguration {
#Bean
#ConditionalOnProperty(name = "saml-enabled", havingValue = "true")
public RelyingPartRegistrationRepository() { ... }
}
See this article on Baeldung, and another article which covers a few other conditionals which may be useful, and of course the official documentation.

Is it possible to only load specific Annotations based on a profile?

Is it possible to only load specific Annotations only during tests or only during a run in Spring Boot?
I am facing a situation where there are Annotations affecting the tests, yet work well in the live run, so wanted to know whether it was possible to exclude them only during tests, but include them when running, similar to how one can include specific beans based on a Spring profile
Apologies if this has been asked before, I have tried searching to no avail
You could use the #ConditionalOnProperty annotation which creates a bean depending on which property (in the application.properties -> app.val = false) is set. For example for a service:
#Service
#ConditionalOnProperty(name = "app.val", havingValue = "false")
public class TestService {
...
}
Also you could use the #Profile annotation and annotate them to the methods which have for example a test profile (defined in the application.properties as well -> spring.profiles = test).
#Profile({"test"})
public String getValue() {
return "test value";
}
#Profile({"production"})
public String getValue() {
return "production value";
}

SAMLBootstrap bean wipes out #Value injection from Cloud Config Server

When I run my app, metadataFile is always null when the bean below is created. I have a Spring Cloud Config Server which I see is hit before my breakpoint and the YML is successfully retrieved.
Even if the Config Server was down, the SPEL should provide a default. Has anyone run into #Value not evaluating and injecting values before their #Bean? I have many years using XML annotations, so perhaps I never have hit this with Annotation driven config, but it seems hard to believe I would not have run into this before. Very confused...
Within it:
#Configuration
public class Test {
#Value("${someplace.saml.idp.metadata.file:'classpath:idp-metadata.xml'}")
String metadataFile;
#Bean
MetadataProvider metadataProvider() {
if(!StringUtils.isBlank(metadataFile) && metadataFile.startsWith("classpath:/")) {
// do some stuff
} else {
File metadatFile = new File(metadataFile);
}
}
UPDATE:
I shortened my example above for sake of brevity. The culprit wiping out the configuration values is this SAMLBootstrap bean. It seems to be required for annotation-configured Spring SAML2.
#Bean
SAMLBootstrap samlBootstrap() {
return new SAMLBootstrap();
}
UPDATE 2 (THE SOLUTION):
The SAMLBootstrap bean needs to be declared with static. I found this in the last comment on another post: Spring Bean without id or name in Java Config

Spring beans. Is it possible to return a default boolean if no bean exists

I have an interface which gets parameter values from a spring bean xml file.
The problem is that I need to have the get-method in classes where there will be no parameter to fetch. The bean exists but this particular parameter does not exist in the xml-file. In those cases I would like the interface method to return false.
I was looking at the annotation #ReturnValue which looked promising but seems to only be able to return a String[], not a boolean.
This works for a String[] but I want to return false instead, is that possible?
#ReturnValue(defaultsTo = "")
String[] getMyvalue();
Best Regards / Jan

Resources