I need to define Duration value (spring.redis.timeout) by application.properties.
I was trying to use one point defined in Spring boot documentation:
Spring Boot has dedicated support for expressing durations. If you expose a java.time.Duration property, the following formats in application properties are available:
A regular long representation (using milliseconds as the default unit unless a #DurationUnit has been specified)
The standard ISO-8601 format used by java.util.Duration
A more readable format where the value and the unit are coupled (e.g. 10s means 10 seconds)
When i use spring.redis.timeout=3s Spring boot application throws this exception:
Cannot convert value of type 'java.lang.String' to required type
'java.time.Duration': no matching editors or conversion strategy found
Which would it be the best way to set a correct value to a Duration property in application.properties withs last Spring boot 2 release?
Any property which is of type duration can be injected via .properties or .yml files.
All you need to do is use a proper formatting.
If you want to inject a duration of 5 seconds it should be defined as PT5S or pt5s or PT5s
case of the letters doesn't matter, so you use any combination which is readable for you
generally everyone uses all capital letters
Other examples
PT1.5S = 1.5 Seconds
PT60S = 60 Seconds
PT3M = 3 Minutes
PT2H = 2 Hours
P3DT5H40M30S = 3Days, 5Hours, 40 Minutes and 30 Seconds
You can also use +ve and -ve signs to denote positive vs negative period of time.
You can negate only one of the entity for example: PT-3H30M = -3 hours, +30 minutes, basically -2.5Hours
Or You can negate the whole entity: -PT3H30M = -3 hours, -30 minutes, basically -3.5Hours
Double negative works here too: -PT-3H+30M = +3 Hours, -30 Minutes, basically +2.5Hours
Note:
Durations can only be represented in HOURS or lower ChronoUnit (NANOS, MICROS, MILLIS, SECONDS, MINUTES, HOURS) since they represent accurate durations
Higher ChronoUnit (DAYS, WEEKS, MONTHS, YEARS, DECADES,CENTURIES, MILLENNIA, ERAS, FOREVER) are not allowed since they don't represent accurate duration. These ChronoUnits have estimated duration due to the possibility of Days varying due to daylight saving, Months have different lengths etc.
Exception - Java does automatic conversion of DAYS into HOURS, But it doesn't do it for any other higher ChronoUnit (MONTHS, YEARS etc.).
If we try to do a "P1D", java automatically converts it into "PT24H". So If we want to do duration of 1 MONTH, we will have to use PT720H or P30D. In case of P30D java's automatic conversion will take place and give us PT720H
Upvote, if it works for you or you like the explanation. Thanks,
It's possible to use #Value notation with Spring Expression Language
#Value("#{T(java.time.Duration).parse('${spring.redis.timeout}')}")
private Duration timeout;
The Duration in the moment (Spring-Boot 2.0.4.RELEASE) it is not possible to use together with #Value notation, but it is possible to use with #ConfigurationProperties
For Redis, you have RedisProperties and you can use the configuration:
spring.redis.timeout=5s
And:
#SpringBootApplication
public class DemoApplication {
#Autowired
RedisProperties redisProperties;
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
#PostConstruct
void init() {
System.out.println(redisProperties.getTimeout());
}
}
It printed (parse as 5s):
PT5S
https://docs.oracle.com/javase/8/docs/api//java/time/Duration.html#parse-java.lang.CharSequence-
Update for Spring Boot 2.5.5
We can use #Value annotation together with application.properties values.
For example you have the next property in your application.properties file:
your.amazing.duration=100ms
Then you can use it in the #Value annotation:
#Value("${your.amazing.duration}")
final Duration duration;
That is all.
Supported units:
ns for nanoseconds
us for microseconds
ms for milliseconds
s for seconds
m for minutes
h for hours
d for days
Docs: link
If your Spring-Boot version or its dependencies don't put ApplicationConversionService into context (and Spring-Boot doesn't until 2.1), you can expose it explicitly
#Bean
public ConversionService conversionService() {
return ApplicationConversionService.getSharedInstance();
}
It invokes Duration.parse, so you may use PT3S, PT1H30M, etc in properties files.
Spring Boot attempts to coerce the external application properties to the right type when it binds to the #ConfigurationProperties beans.
If you need custom type conversion, you can provide a ConversionService bean (with a bean named conversionService)
See: https://docs.spring.io/spring-boot/docs/2.0.4.RELEASE/reference/htmlsingle/#boot-features-external-config-conversion
Create new ApplicationConversionService bean (it must be named conversionService ). Here you are my code tested with Spring boot 2.0.4:
#Configuration
public class Conversion {
#Bean
public ApplicationConversionService conversionService()
{
final ApplicationConversionService applicationConversionService = new ApplicationConversionService();
return applicationConversionService;
}
Here you are an example project using this approach:
https://github.com/cristianprofile/spring-data-redis-lettuce
I was getting this error, but only during testing; the bean using a #Value-annotated Duration was otherwise working. It turned out that I was missing the #SpringBootTest annotation on the test case class (and the spring-boot-test dependency that provides it) and that was causing only a subset of standard converters to be available for use.
Related
I am exploring micrometer and aws cloudwatch. I think there is some understanding gap -
I've create a gauge which is supposed to return the number of connections being used in a connection pool.
public MetricService(CloudWatchConfig config) {
this.cloudwatchMeterRegistry = new CloudWatchMeterRegistry(config, Clock.SYSTEM, CloudWatchAsyncClient.create());
gauge = Gauge.builder("ConnectionPoolGauge", this.connectionPool, value -> {
Double usedConnections = 0.0;
for (Map.Entry<String, Boolean> entry : value.entrySet()) {
if (entry.getValue().equals(Boolean.FALSE)) {
usedConnections++;
}
}
return usedConnections;
})
.tag("GaugeName", "Bhushan's Gauge")
.strongReference(true)
.baseUnit("UsedConnections")
.description("Gauge to monitor connection pool")
.register(Metrics.globalRegistry);
Metrics.addRegistry(cloudwatchMeterRegistry);
}
As you can see, I am currently initiating this gauge in a constructor. Passing the connectionPool instance from outside.
Following is a controller method which consumes the connection -
#GetMapping("/hello")
public String hello() {
// connectionPool.consumeConnection();
// finally { connectionPool.releaseConnection();}
}
Step interval is set to 10 seconds. My understanding is - Every 10 seconds, Micrometer should automatically execute the double function passed to the gauge.
Obviously, it is not happening. I've seen some code samples here which are explicitly setting the gauge value (in a separate thread or scheduled logic).
I also tried with a counter which is instantiated only once, but I explicitly invoke the increment method per call to hello method. My expectation was this counter would keep on incrementing, but after a while, it drops to 0 and starts counting again.
I am totally confused. Appreciate if someone can put light on this concept.
Edit:
Tried following approach for creating Gauge - still no luck.
cloudwatchMeterRegistry.gauge("ConnectionPoolGauge", this.connectionPool, value -> {
Double usedConnections = 0.0;
System.out.println("Inside Guage Value function." + value.entrySet());
for (Map.Entry<String, Boolean> entry : value.entrySet()) {
if (entry.getValue().equals(Boolean.FALSE)) {
usedConnections++;
}
}
return usedConnections;
});
This doesn't return the instance of Gauge, so I cannot call value() on it. Also the gauge is not visible in AWS Cloudwatch. I can see the counter in cloudwatch that I created in the same program.
Micrometer takes the stance that gauges should be sampled and not be set, so there is no information about what might have occurred between samples. After all, any intermediate values set on a gauge are lost by the time the gauge value is reported to a metrics backend anyway, so there seems to be little value in setting those intermediate values in the first place.
If it helps, think of a Gauge as a "heisen-gauge" - a meter that only changes when it is observed. Every other meter type provided out-of-the-box accumulates intermediate counts toward the point where the data is sent to the metrics backend.
So the gauge is updated when the metrics are published, here are a few tips to troubleshooting this:
Put a brake point in the publish method of your CloudWatchMeterRegistry and see if it is called or not.
You are using the Global registry (Metrics.addRegistry) as well as keeping the reference to CloudWatchMeterRegistry (this.cloudwatchMeterRegistry = new CloudWatchMeterRegistry). You don't need both, I would suggest to do not use the Global registry and inject the registry you have wherever you need it.
I'm not sure what you are doing with the connection pool (did you implement your own one?) but there is out-of-the-box support for HikariCP and DBCP is publishing JMX counters that you can bind to Micrometer.
We store in Zookeeper nodes some standard configuration for multiple cases. It is a flat list of simple values (string, boolean, integer etc). So for now we have a class describing this config, with corresponding fields, and fill its instances using ConfigurationProperties annotation with different prefixes.
class DatasourceConfig {
var pid: String? = null
var className: String? = null
var poolSize: Int = 30
var minIdle: Int = 10
var maxIdle: Int = 10
var conTimeout: Long = 100500
...
}
The problem is that now we need to read several instances of this config from the node with underscore in the path. ConfigurationProperties does not support snake case or camel case in the prefix, only kebab case.
#ConfigurationProperties(prefix = "smth.new_path.datasources.aaa")
fun aaaDataSourceConfig() = DatasourceConfig()
#ConfigurationProperties(prefix = "smth.new_path.datasources.bbb")
fun bbbDataSourceConfig() = DatasourceConfig()
this results in error:
APPLICATION FAILED TO START
***************************
Description:
Configuration property name 'new_path' is not valid:
Invalid characters: '_'
Reason: Canonical names should be kebab-case ('-' separated), lowercase alpha-numeric characters and must start with a letter
There is no opportunity to rename the node. It is not an option.
Zookeper root node is set using spring.cloud.zookeeper.config.root in bootstrap.yml, and I guess Spring Cloud Zookeeper is used to read values. If I set this root to "new_path", ConfigurationProperties works, but I also need values from other paths in my application.
Configuration list is quite long and used multiple times, so I would like to avoid using #Value annotation for each attribute.
Is there any other way than ConfigurationProperties, or maybe any way to tweak ConfigurationProperties
or Spring Cloud Zookeeper to make it work together?
It turns out that there was no problem: you don't have to rename Zookeeper node.
ConfigurationProperties uses relaxed binding, so while prefix should be in kebab case, like new-path, it still will work correctly with nodes named like new_path or newPath.
Described here: https://docs.spring.io/spring-boot/docs/2.1.0.RELEASE/reference/html/boot-features-external-config.html#boot-features-external-config-relaxed-binding
I'm trying to implement a POST service with request validation.
My Controller method looks something like below
public void updateScore(#Valid ScoreRequest)
ScoreRequest looks like below
import javax.validation.constraints.*;
import lombok.Data;
#Data
public class ScoreRequest {
#Min(0)
#Max(100)
#Digits(fraction = 0, integer = 3)
private Integer score;
...
}
It all works fine till I pass integer values for score, however If I pass fraction part as well, request goes through and Spring somehow truncates the fraction and uses the integer part in the request.
I was expecting it to throw a validation error since datatype of score doesn't match.
It works with followling request, and uses 10 as the score value in the request object. I'm expecting it to throw an error, what am I doing wrong?
{"score": 10.234234}
Spring Boot version: 2.0.3.RELEASE
I was trying to debug Spring Boot's validation classes to find what was happening, but after looking at the comment by #M.Denium I searched for Jackson issues and found a related SO entry.
Java Jackson - prevent float to int conversion when deserializing
I'm using answer by #Jichao Zhang, however Just to confirm answer by #Eduardo Sanchez-Ros works as well. This is what works for me.
ObjectMapper.configure(DESERIALIZATION_FEATURE.ACCEPT_FLOAT_AS_INT, false);
Don't use this annotation: #Digits(fraction = 0, integer = 3 with Integer since it is useless to set fractions for Integer.
Why don't you do:
#Min(0)
#Max(100)
#Digits(fraction = 0, integer = 3)
private BigDecimal score;
If you closely look at the definition of the #digits annotation,
#Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
So this annotation can be applied to methods also.
If you read the docs, it says this annotation can be applied to the following types
BigDecimal
BigInteger
CharSequence
byte (or Byte)
short (or Short)
int (or Integer)
long (or Long)
the integer field of the annotation checks for the number of integral digits while the fraction field of the annotation checks for the number of fractional digits.
Since, you declared your field to be an Integer value. It casts the value to an integer and truncates the fractional part.
This does not fail validations as both the fields satisfy the validation.
This annotation is ideally to be only used with BigDecimal type.
I have a Spring Boot 2.0.0 / Kotlin / Gradle project.
I have a warning while injecting integers. I know the reason but don't know the fix. Is there a better way to inject these #Values?
Thanks
Warning:
\src\main\kotlin\com\tech\stands\PicturesDownloader.kt: (22, 31): This class shouldn't be used in Kotlin. Use kotlin.Int instead.
Code:
abstract class PicturesDownloader {
#Value("\${cache.adpics.concurrent}")
lateinit var MAX_CONCURRENT: Integer
#Value("\${cache.adpics.max}")
lateinit var MAX_AD_PICS: Integer
}
If there's no way to fix, is there way to suppress it?
If you want to suppress it, you can use this annotation:
#Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
I'm not familiar with Spring Boot and the value injection system, so I'm not sure if this will work. Set the value to 0, use Int and remove lateinit. As it's still a var, it can still be set after creation, meaning the value injection should be able to set it once the class is created.
#Value("\${cache.adpics.concurrent}")
var MAX_CONCURRENT: Int = 0
ehcache is a hugely configurable beast, and the examples are fairly complex, often involving many layers of interfaces.
Has anyone come across the simplest example which just caches something like a single number in memory (not distributed, no XML, just as few lines of java as possible). The number is then cached for say 60 seconds, then the next read request causes it to get a new value (e.g. by calling Random.nextInt() or similar)
Is it quicker/easier to write our own cache for something like this with a singleton and a bit of synchronization?
No Spring please.
EhCache comes with a failsafe configuration that has some reasonable expiration time (120 seconds). This is sufficient to get it up and running.
Imports:
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
Then, creating a cache is pretty simple:
CacheManager.getInstance().addCache("test");
This creates a cache called test. You can have many different, separate caches all managed by the same CacheManager. Adding (key, value) pairs to this cache is as simple as:
CacheManager.getInstance().getCache("test").put(new Element(key, value));
Retrieving a value for a given key is as simple as:
Element elt = CacheManager.getInstance().getCache("test").get(key);
return (elt == null ? null : elt.getObjectValue());
If you attempt to access an element after the default 120 second expiration period, the cache will return null (hence the check to see if elt is null). You can adjust the expiration period by creating your own ehcache.xml file - the documentation for that is decent on the ehcache site.
A working implementation of jbrookover's answer:
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
import net.sf.ehcache.Cache;
public class EHCacheDemo {
public static final void main(String[] igno_red) {
CacheManager cchm = CacheManager.getInstance();
//Create a cache
cchm.addCache("test");
//Add key-value pairs
Cache cch = cchm.getCache("test");
cch.put(new Element("tarzan", "Jane"));
cch.put(new Element("kermit", "Piggy"));
//Retrieve a value for a given key
Element elt = cch.get("tarzan");
String sPartner = (elt == null ? null : elt.getObjectValue().toString());
System.out.println(sPartner); //Outputs "Jane"
//Required or the application will hang
cchm.removeAllCaches(); //alternatively: cchm.shutdown();
}
}