I am looking for configurationOnProperty usage where I can specify to consider more than one value as shown below
Eg: #ConditionalOnProperty(value = "test.configname", havingValue = "value1" or "value2")
OR
I would like to know if it is possible to specify confiugrationOnProperty with condition of havingValue != "value3"
Eg: #ConditionalOnProperty(value = "test.configname", havingValue != "value3")
Please let me know if there is a way to achieve any one of the above in spring boot configuration.
Spring Boot provides AnyNestedCondition for created a condition that will match when any nested condition matches. It also provides AllNestedConditions and NoneNestedConditions for matching when all nested conditions or no nested conditions match respectively.
For your specific case where you want to match a value of value1 or value2 you would create an AnyNestedCondition like this:
class ConfigNameCondition extends AnyNestedCondition {
public ConfigNameCondition() {
super(ConfigurationPhase.PARSE_CONFIGURATION);
}
#ConditionalOnProperty(name = "test.configname", havingValue = "value1")
static class Value1Condition {
}
#ConditionalOnProperty(name = "test.configname", havingValue = "value2")
static class Value2Condition {
}
}
And then use it with #Conditional, like this for example:
#Bean
#Conditional(ConfigNameCondition.class)
public SomeBean someBean() {
return new SomeBean();
}
As shown in the javadoc for the nested condition annotations (linked to above) the nested conditions can be of any type. There's no need for them to all be of the same type as they are in this particular case.
The annotations #ConditionalOnProperty and #ConditionalOnExpression both do NOT have the java.lang.annotation.Repeatable annotation so you would not be able to just add multiple annotations for checking multiple properties.
The following syntax has been tested and works:
Solution for Two Properties
#ConditionalOnExpression("${properties.first.property.enable:true} && ${properties.second.property.startServer:false}")
Note the following:
You need to using colon notation to indicate the default value of
the property in the expression language statement
Each property is in a separate expression language block ${}
The && operator is used outside of the SpEL blocks
It allows for multiple properties that have differing values and can extend to multiple properties.
If you want to check more then 2 values and still maintain readability, you can use the concatenation operator between different conditions you are evaluating:
Solution for more then 2 properties
#ConditionalOnExpression("${properties.first.property.enable:true} " +
"&& ${properties.second.property.enable:true} " +
"&& ${properties.third.property.enable:true}")
The drawback is that you cannot use a matchIfMissing argument as you would be able to when using the #ConditionalOnProperty annotation so you will have to ensure that the properties are present in the .properties or YAML files for all your profiles/environments or just rely on the default value
Taken from here Spring Boot SpEL ConditionalOnExpression check multiple properties
I am looking for configurationOnProperty usage where I can specify to
consider more than one value
You can use Condition interface of Spring 4.0.
This interface has a method matches(...) which you can use.
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
public class TestCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String testValue = (context.getEnvironment().getProperty("test.configname");
return "value1".equalsIgnoreCase("testValue") || "value2".equalsIgnoreCase("testValue");
}
}
And then use TestCondition inside your #Configuration like below :
#Configuration
public class TestConfig {
#Conditional(value=TestCondition .class)
public MyBean getTestConfigBean() {
//TODO YOUR CODE;
}
}
I would like to know if it is possible to specify
confiugrationOnProperty with condition of havingValue != "value3"
public class TestCondition2 implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String testValue = (context.getEnvironment().getProperty("test.configname");
return ! "value3".equalsIgnoreCase("testValue");
}
}
And then use it like this :
#Configuration
public class TestConfig {
#Conditional(value=TestCondition2 .class)
public MyBean getTestConfigBean() {
//TODO YOUR CODE;
}
}
Related
I would like the execution of the event handler to depend on whether the property is set to true or false in applecation.yaml file. I have three yaml files (test, dev, prod) and I have set the settings in them:
for application-dev.yml
page-cache:
starting: false
for application-test.yml
page-cache:
starting: true
for application-prod.yml
page-cache:
starting: true
And I need not to write 'dev' or 'test' in condition myself, but to read true or false from yaml files.
For example: condition = "#serviceEnabled == true" , does not work.
#Service
public class ServiceImpl{
#Value("${page-cache.starting}")
private Boolean serviceEnabled;
/means that when running dev will be false and the method will not run and this code is working
#EventListener(
value = ApplicationReadyEvent.class,
condition = "#environment.getActiveProfiles()[0] != 'dev'")
public void updateCacheAfterStartup() {
log.info("Info add starting...");
someService.getInfo();
}
I tried to do as in this article, but it doesn't work for me .
Evaluate property from properties file in Spring's #EventListener(condition = "...")
I also tried the same option
#Service
public class ServiceImpl{
// #Lazy private final ServiceImpl serviceImpl
#Value("${page-cache.starting}")
private Boolean serviceEnabled;
public Boolean isServiceEnabled() {
return this.serviceEnabled;
}
public Boolean getServiceEnabled() {
return serviceEnabled;
}
#EventListener(
value = ApplicationReadyEvent.class,
condition = "#ServiceImpl.serviceEnabled")
public void updateCacheAfterStartup() {
log.info("Info add starting...");
someService.getInfo();
}
Make sure the YAML format is correct in application-test.yml, application-prd.yml,...
example:
application-dev.yml
page-cache:
starting: false
ServiceImpl must be a component/bean, so annotate your class with #Service, #Component or use #Bean if the instance is created in a #Configuration class.
Extra tip:
You could use condition = "! #environment.acceptsProfiles('dev')"
instead of condition = "#environment.getActiveProfiles()[0] != 'dev'"
that way the order of active profiles does not matter
or define when the condition is valid: condition = "#environment.acceptsProfiles('test', 'prod')"
UPDATE:
You could also directly use page-cache.starting in the condition.
#EventListener(
value = ApplicationReadyEvent.class,
condition = "#environment.getProperty('page-cache.starting')")
public void updateCacheAfterStartup() {
// update the cache
}
Here updateCacheAfterStartup() will only be triggered at startup when page-cache.starting is true. If set to false or when not present the method will not be called.
If you want to force that page-cache.starting is provided (in all profiles) you should use: condition="#environment.getRequiredProperty('page-cache.starting')"
you can access the property using #Value in some class whose bean is created (either by annotations Component, Service, Configuration .. etc.). Use #EventListener(condition = "#beanName.youProperty"), the event is handled when the value of yourProperty is true or string having "true", "on", "yes", or "1" values.
if yourProperty is private and doesn't have getter also, then above will fail. yourProperty on the bean should be either public or should have getter if private.
in your case:
public class ServiceImpl{
#Value("${page-cache.starting}")
private Boolean serviceEnabled;
// can be getServiceEnabled
public Boolean isServiceEnabled() {
return this.serviceEnabled;
}
#EventListener(
value = ApplicationReadyEvent.class,
condition = "#serviceImpl.serviceEnabled")
public void updateCacheAfterStartup() {
log.info("Info add starting...");
someService.getInfo();
}
I would like to implement a custom annotation that could be applied to a class (once inside an app), to enable a feature (Access to remote resources). If this annotation is placed on any config class, it will set the access for the whole app. So far it isn't that hard (see example below), but I want to include some definition fields in the #interface that will be used in the access establishing process.
As an example, Spring has something very similar: #EnableJpaRepositories. Access is enabled to the DB, with parameters in the annotation containing definitions. For example: #EnableJpaRepositories(bootstrapMode = BootstrapMode.DEFERRED)
So far, I have:
To create only the access I'm using something like that:
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#Import(AccessHandlerConfiguration.class)
public #interface EnableAccessHandlerAutoconfigure {
String name() default "";
}
Using it:
#EnableAccessHandlerAutoconfigure{name="yoni"}
#Configuration
public class config {}
AccessHandlerConfiguration is a configuration class that contains beans that establish the connection.
The problem I'm having is that I don't know how to retrieve the field name's value. What should I do?
Retrieving the value may be accomplished as follows:
this.getClass().getAnnotation(EnableAccessHandlerAutoconfigure.class).name()
To expand on my comment with an actual example configuration class that uses this:
#EnableAccessHandlerAutoconfigure(name="yoni")
#Configuration
public class SomeConfiguration {
#Bean
SomeBean makeSomeBean() {
return new SomeBean(this.getClass().getAnnotation(EnableAccessHandlerAutoconfigure.class).name());
}
}
This is how you get the value of name, as to what you are going to do next, that depends on you.
After a long research, I found a way: There is a method in Spring's ApplicationContext that retrieves bean names according to their annotations getBeanNamesForAnnotation, then get the annotation itself findAnnotationOnBean, and then simply use the field getter.
#Configuration
public class AccessHandlerConfiguration {
private final ApplicationContext applicationContext;
public AccessHandlerConfiguration(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
String[] beansWithTheAnnotation = applicationContext.getBeanNamesForAnnotation(EnableRabbitAutoconfigure.class);
for (String beanName : beansWithTheAnnotation) {
EnableRabbitAutoconfigure annotationOnBean = applicationContext.findAnnotationOnBean(beanName, EnableRabbitAutoconfigure.class);
System.out.println("**********" + beanName + "*********************" + annotationOnBean.name() + "*******************");
}
}
}
Results:
**********config*********************yoni*******************
When trying to inject an Optional<T>, Spring never calls my bean, and instead injects an Optional.empty().
Here is some sample code:
#Configuration
public class Initialize {
#Value("optionalValue")
private String testString;
#Bean (name = "getOptionalString")
public Optional<String> getOptionalString() {
return Optional.of(this.testString); //breakpoint put here, is never called
}
}
#Component
public class Test {
public Test(#Qualifier("getOptionalString") Optional<String> optional) {
// optional's value is Optional.empty() here
}
I noticed that (by putting a breakpoint) the #Bean is never called. If I were to remove the Optional<String>, and simply return a String, then it works!
I know Spring has its own optional dependency but I am perplexed as to why this doesn't work (whatever I read online says it should), and I also don't understand how it initialized it to Optional.empty()?
The documentation says:
you can express the non-required nature of a particular dependency through Java 8’s java.util.Optional, as the following example shows:
public class SimpleMovieLister {
#Autowired
public void setMovieFinder(Optional<MovieFinder> movieFinder) {
...
}
}
So using Optional as the type of a bean is not a good idea.
I have the need of enable/disable a #Aspect on a class in a Spring (non boot) application.
Spring version is: 4.1.6.RELEASE
I have a .properties file (that we already use successfully in other point of the application, for example to select the log4j2 configuration file) with the property aspect.enabled=true|false and I tried using the #ConditionalOnExpression annotation to enable|disable it.
This is my try:
application.properties file:
aspect.enabled=true
Class code:
#Configuration
#PropertySource(ignoreResourceNotFound = true, value = { "file:${catalina.home}/conf/application.properties" })
#ConditionalOnExpression("'${aspect.enabled}'=='true'")
#Aspect
#Component
public class TimingProfilerProduction {
#Value("${aspect.enabled}")
public String aspect;
With this configuration the expression is always evaluated to false.
I tried putting a single "true" to see if it works in this simple way:
#Configuration
#PropertySource(ignoreResourceNotFound = true, value = { "file:${catalina.home}/conf/application.properties" })
#ConditionalOnExpression("true")
#Aspect
#Component
public class TimingProfilerProduction {
#Value("${aspect.enabled}")
public String aspect;
Of course in this way the #ConditionalOnExpression gets evaluated to true and I can also prove that the aspect class property correctly reads the aspect.enabled property.
Try #3
I tried with a #ConditionalOnProperty:
#Configuration
#PropertySource(ignoreResourceNotFound = true, value = { "file:${catalina.home}/conf/application.properties" })
#ConditionalOnProperty(prefix="aspect", name="enabled", havingValue = "true")
but nothing, always false.
Try #4:
#ConditionalOnExpression("${aspect.enabled}")
or
#ConditionalOnExpression("!${aspect.enabled}")
Gives an error:
Caused by: org.springframework.expression.spel.SpelParseException: EL1041E:(pos 1): After parsing a valid expression, there is still more data in the expression: 'lcurly({)'
Try #5 (with default values):
#ConditionalOnExpression("${aspect.enabled:true}")
always gives true (even with aspect.enabled=false), and accordingly
#ConditionalOnExpression("${aspect.enabled:false}")
always gives false
Instead of:
#ConditionalOnExpression("'${aspect.enabled}' == 'true'")
Try:
#ConditionalOnExpression("${aspect.enabled} == true")
I'm using Spring Boot 1.4.3 #AutoConfiguration where I create beans automatically based on properties user specifies. User can specify an array of services, where name and version are required fields:
service[0].name=myServiceA
service[0].version=1.0
service[1].name=myServiceB
service[1].version=1.2
...
If the user forgets to specify a required field on even just one service, I want to back-off and not create any beans. Can I accomplish this with #ConditionalOnProperty? I want something like:
#Configuration
#ConditionalOnProperty({"service[i].name", "service[i].version"})
class AutoConfigureServices {
....
}
This is the custom Condition I created. It needs some polishing to be more generic (ie not hardcoding strings), but worked great for me.
To use, I annotated my Configuration class with #Conditional(RequiredRepeatablePropertiesCondition.class)
public class RequiredRepeatablePropertiesCondition extends SpringBootCondition {
private static final Logger LOGGER = LoggerFactory.getLogger(RequiredRepeatablePropertiesCondition.class.getName());
public static final String[] REQUIRED_KEYS = {
"my.services[i].version",
"my.services[i].name"
};
#Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
List<String> missingProperties = new ArrayList<>();
RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(context.getEnvironment());
Map<String, Object> services = resolver.getSubProperties("my.services");
if (services.size() == 0) {
missingProperties.addAll(Arrays.asList(REQUIRED_KEYS));
return getConditionOutcome(missingProperties);
}
//gather indexes to check: [0], [1], [3], etc
Pattern p = Pattern.compile("\\[(\\d+)\\]");
Set<String> uniqueIndexes = new HashSet<String>();
for (String key : services.keySet()) {
Matcher m = p.matcher(key);
if (m.find()) {
uniqueIndexes.add(m.group(1));
}
}
//loop each index and check required props
uniqueIndexes.forEach(index -> {
for (String genericKey : REQUIRED_KEYS) {
String multiServiceKey = genericKey.replace("[i]", "[" + index + "]");
if (!resolver.containsProperty(multiServiceKey)) {
missingProperties.add(multiServiceKey);
}
}
});
return getConditionOutcome(missingProperties);
}
private ConditionOutcome getConditionOutcome(List<String> missingProperties) {
if (missingProperties.isEmpty()) {
return ConditionOutcome.match(ConditionMessage.forCondition(RequiredRepeatablePropertiesCondition.class.getCanonicalName())
.found("property", "properties")
.items(Arrays.asList(REQUIRED_KEYS)));
}
return ConditionOutcome.noMatch(
ConditionMessage.forCondition(RequiredRepeatablePropertiesCondition.class.getCanonicalName())
.didNotFind("property", "properties")
.items(missingProperties)
);
}
}
Old question, but I hope my answer will help for Spring2.x:
Thanks to #Brian, I checked migration guide, where I was inspired by example code. This code works for me:
final List<String> services = Binder.get(context.getEnvironment()).bind("my.services", List.class).orElse(null);
I did try to get List of POJO (as AutoConfigureService) but my class differs from AutoConfigureServices. For that purpose, I used:
final Services services = Binder.get(context.getEnvironment()).bind("my.services", Services.class).orElse(null);
Well, keep playing :-D
Here's my take on this issue with the use of custom conditions in Spring autoconfiguration. Somewhat similar to what #Strumbels proposed but more reusable.
#Conditional annotations are executed very early in during the application startup. Properties sources are already loaded but ConfgurationProperties beans are not yet created. However we can work around that issue by binding properties to Java POJO ourselves.
First I introduce a functional interface which will enable us to define any custom logic checking if properties are in fact present or not. In your case this method will take care of checking if the property List is empty/null and if all items within are valid.
public interface OptionalProperties {
boolean isPresent();
}
Now let's create an annotation which will be metannotated with Spring #Conditional and allow us to define custom parameters. prefix represents the property namespace and targetClass represents the configuration properties model class to which properties should be mapped.
#Target({ElementType.TYPE, ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Conditional(OnConfigurationPropertiesCondition.class)
public #interface ConditionalOnConfigurationProperties {
String prefix();
Class<? extends OptionalProperties> targetClass();
}
And now the main part. The custom condition implementation.
public class OnConfigurationPropertiesCondition extends SpringBootCondition {
#Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
MergedAnnotation<ConditionalOnConfigurationProperties> mergedAnnotation = metadata.getAnnotations().get(ConditionalOnConfigurationProperties.class);
String prefix = mergedAnnotation.getString("prefix");
Class<?> targetClass = mergedAnnotation.getClass("targetClass");
// type precondition
if (!OptionalProperties.class.isAssignableFrom(targetClass)) {
return ConditionOutcome.noMatch("Target type does not implement the OptionalProperties interface.");
}
// the crux of this solution, binding properties to Java POJO
Object bean = Binder.get(context.getEnvironment()).bind(prefix, targetClass).orElse(null);
// if properties are not present at all return no match
if (bean == null) {
return ConditionOutcome.noMatch("Binding properties to target type resulted in null value.");
}
OptionalProperties props = (OptionalProperties) bean;
// execute method from OptionalProperties interface
// to check if condition should be matched or not
// can include any custom logic using property values in a type safe manner
if (props.isPresent()) {
return ConditionOutcome.match();
} else {
return ConditionOutcome.noMatch("Properties are not present.");
}
}
}
Now you should create your own configuration properties class implementing OptionalProperties interface.
#ConfigurationProperties("your.property.prefix")
#ConstructorBinding
public class YourConfigurationProperties implements OptionalProperties {
// Service is your POJO representing the name and version subproperties
private final List<Service> services;
#Override
public boolean isPresent() {
return services != null && services.stream().all(Service::isValid);
}
}
And then in Spring #Configuration class.
#Configuration
#ConditionalOnConfigurationProperties(prefix = "", targetClass = YourConfigurationProperties.class)
class AutoConfigureServices {
....
}
There are two downsides to this solution:
Property prefix must be specified in two locations: on #ConfigurationProperties annotation and on #ConditionalOnConfigurationProperties annotation. This can partially be alleviated by defining a public static final String PREFIX = "namespace" in your configuration properties POJO.
Property binding process is executed separately for each use of our custom conditional annotation and then once again to create the configuration properties bean itself. It happens only during app startup so it shouldn't be an issue but it still is an inefficiency.
You can leverage the org.springframework.boot.autoconfigure.condition.OnPropertyListCondition class. For example, given you want to check for the service property having at least one value:
class MyListCondition extends OnPropertyListCondition {
MyListCondition() {
super("service", () -> ConditionMessage.forCondition("service"));
}
}
#Configuration
#Condition(MyListCondition.class)
class AutoConfigureServices {
}
See the org.springframework.boot.autoconfigure.webservices.OnWsdlLocationsCondition used on org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration#wsdlDefinitionBeanFactoryPostProcessor for an example within Spring itself.