In spring boot application, I define some config properties in yaml file as below.
my.app.maxAttempts = 10
my.app.backOffDelay = 500L
And an example bean
#ConfigurationProperties(prefix = "my.app")
public class ConfigProperties {
private int maxAttempts;
private long backOffDelay;
public int getMaxAttempts() {
return maxAttempts;
}
public void setMaxAttempts(int maxAttempts) {
this.maxAttempts = maxAttempts;
}
public void setBackOffDelay(long backOffDelay) {
this.backOffDelay = backOffDelay;
}
public long getBackOffDelay() {
return backOffDelay;
}
How can I inject the values of my.app.maxAttempts and my.app.backOffdelay to Spring Retry annotation? In the example below, I want to replace the value 10 of maxAttempts and 500Lof backoff value with the corresponding references of config properties.
#Retryable(maxAttempts=10, include=TimeoutException.class, backoff=#Backoff(value = 500L))
Staring from spring-retry-1.2.0 we can use configurable properties in #Retryable annotation.
Use "maxAttemptsExpression", Refer the below code for usage,
#Retryable(maxAttemptsExpression = "#{${my.app.maxAttempts}}",
backoff = #Backoff(delayExpression = "#{${my.app. backOffDelay}}"))
It will not work if you use any version less than 1.2.0.Also you don't require any configurable property classes.
You can also use existing beans in expression attributes.
#Retryable(include = RuntimeException.class,
maxAttemptsExpression = "#{#retryProperties.getMaxAttempts()}",
backoff = #Backoff(delayExpression = "#{#retryProperties.getBackOffInitialInterval()}",
maxDelayExpression = "#{#retryProperties.getBackOffMaxInterval" + "()}",
multiplierExpression = "#{#retryProperties.getBackOffIntervalMultiplier()}"))
String perform();
#Recover
String recover(RuntimeException exception);
where
retryProperties
is your bean which holds retry related properties as in your case.
You can use Spring EL as shown below to load the properties:
#Retryable(maxAttempts="${my.app.maxAttempts}",
include=TimeoutException.class,
backoff=#Backoff(value ="${my.app.backOffDelay}"))
Related
I have spring-boot application. application.xml and some annotated methods.
In case string properties from application.xml is OK. For example:
#KafkaListener(id = "${app.kafka.group}", topics = {"${app.kafka.topic}"}, containerFactory = "singleFactoryCap", autoStartup = "true")
public void consume(Event event) throws ParseException { ...
But how to put INTEGER property from application.xml into an annotation parameter of method?
#Scheduled(initialDelay = ???"${app.config.initialDelay:5000}"???, fixedDelay = ???"${app.config.fixedDelay:5000}"???)
public void loadEvents() {
The #Scheduled annotation takes long properties initialDelay, fixedRate and fixedDelay if you want to hard-code the values but the annotation also provides the properties initialDelayString, fixedRateString and fixedDelayString that you can use as an alternative if you want to configure the behaviour using external configuration properties.
In spring boot application, I define some config properties in yaml file as below.
my.app.maxAttempts = 10
my.app.backOffDelay = 500L
And an example bean
#ConfigurationProperties(prefix = "my.app")
public class ConfigProperties {
private int maxAttempts;
private long backOffDelay;
public int getMaxAttempts() {
return maxAttempts;
}
public void setMaxAttempts(int maxAttempts) {
this.maxAttempts = maxAttempts;
}
public void setBackOffDelay(long backOffDelay) {
this.backOffDelay = backOffDelay;
}
public long getBackOffDelay() {
return backOffDelay;
}
How can I inject the values of my.app.maxAttempts and my.app.backOffdelay to Spring Retry annotation? In the example below, I want to replace the value 10 of maxAttempts and 500Lof backoff value with the corresponding references of config properties.
#Retryable(maxAttempts=10, include=TimeoutException.class, backoff=#Backoff(value = 500L))
Staring from spring-retry-1.2.0 we can use configurable properties in #Retryable annotation.
Use "maxAttemptsExpression", Refer the below code for usage,
#Retryable(maxAttemptsExpression = "#{${my.app.maxAttempts}}",
backoff = #Backoff(delayExpression = "#{${my.app. backOffDelay}}"))
It will not work if you use any version less than 1.2.0.Also you don't require any configurable property classes.
You can also use existing beans in expression attributes.
#Retryable(include = RuntimeException.class,
maxAttemptsExpression = "#{#retryProperties.getMaxAttempts()}",
backoff = #Backoff(delayExpression = "#{#retryProperties.getBackOffInitialInterval()}",
maxDelayExpression = "#{#retryProperties.getBackOffMaxInterval" + "()}",
multiplierExpression = "#{#retryProperties.getBackOffIntervalMultiplier()}"))
String perform();
#Recover
String recover(RuntimeException exception);
where
retryProperties
is your bean which holds retry related properties as in your case.
You can use Spring EL as shown below to load the properties:
#Retryable(maxAttempts="${my.app.maxAttempts}",
include=TimeoutException.class,
backoff=#Backoff(value ="${my.app.backOffDelay}"))
I am running into an issue with unbound properties in my application.yml file. I am in the middle of migrating from Spring Boot 1.5.4 to Spring Boot 2.
My problem is that I have some properties which can optionally be left blank, for instance:
application.yml
app:
enabled: false
url: #ldap://127.0.0.1:3268
user: #admin
In this case if ldap.enabled is set to true, then the ldap properties can be set to the needed values which are currently commented out. However, if ldap.enabled is set to false then the rest of the properties are not set and are left blank.
In my Spring Boot 1.5.4 application I didn't have any problems with this, but now after upgrading to Spring Boot 2 I get the following exceptions:
org.springframework.boot.context.properties.bind.UnboundConfigurationPropertiesException: The elements [ldap.ignorecertificate] were left unbound.
org.springframework.boot.context.properties.bind.BindException: Failed to bind properties under 'ldap' to com.myapp.server.config.properties.LdapProperties
LdapProperties.java
#Component
#ConfigurationProperties(prefix = "app", ignoreUnknownFields = false)
#Getter
#Setter
public class AppProperties {
private Boolean enabled;
private String url;
private String user;
}
I know I can set ignoreUnvalidFields = true in #ConfigurationProperties, but this is not exactly the behavior I want, since an empty value is valid in my case.
Is there a way I can ignore unbound properties? What can I do to avoid this problem?
UPDATE
After further debugging I can see that because ignoreUnkownFields = false in #ConfigurationPropertes then a NoUnboundElementsBindHandler is returned, which checks for unbound properties:
class ConfigurationPropertiesBinder {
private final ApplicationContext applicationContext;
private final PropertySources propertySources;
private final Validator configurationPropertiesValidator;
private final boolean jsr303Present;
private volatile Validator jsr303Validator;
private volatile Binder binder;
...
...
...
private BindHandler getBindHandler(ConfigurationProperties annotation,
List<Validator> validators) {
BindHandler handler = new IgnoreTopLevelConverterNotFoundBindHandler();
if (annotation.ignoreInvalidFields()) {
handler = new IgnoreErrorsBindHandler(handler);
}
if (!annotation.ignoreUnknownFields()) {
UnboundElementsSourceFilter filter = new UnboundElementsSourceFilter();
handler = new NoUnboundElementsBindHandler(handler, filter);
}
if (!validators.isEmpty()) {
handler = new ValidationBindHandler(handler,
validators.toArray(new Validator[0]));
}
return handler;
}
}
Is there any way I can avoid this?
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.
Hi I was wondering if it's possible to leverage Spring annotated Caching within Scala. I have tried but am receiving the error below. I am running the application from a java package that depends on the scala package.
No cache could be resolved for 'CacheableOperation[public scala.collection.immutable.List MerchantDataGateway.getAllMerchants()]
My Configuration Class
#Configuration
#EnableCaching
#ComponentScan(basePackages = "xxx.xxx.xxx.xxx")
public class EnvironmentHelperConfig {
#Bean
public CacheManager getCacheManager() {
final int ttl = 12;
final int maxCacheSize = 1012;
GuavaCacheManager result = new GuavaCacheManager();
result.setCacheBuilder(CacheBuilder
.newBuilder()
.expireAfterWrite(ttl, TimeUnit.HOURS)
.maximumSize(maxCacheSize));
return result;
}
}
My Scala Class
#Component
class MerchantDataGateway {
#Autowired
var fmcsProxy: MerchantResource = null;
#Cacheable
def getAllMerchants(): List[MerchantViewModel] = {
val merchants = getAllMerchantsFromFMCS()
merchants.map(merchant => MerchantViewModel.getLightWeightInstance(merchant))
}
}
Add a name to the #Cacheable annotation:
#Cacheable(Array("MerchantDataGateway.getAllMerchants"))
It needed a name, or an entry for the value
#Cacheable(value = Array("MerchantDataGateway.getAllMerchants")