Spring Boot YAML config and list - java

I have to integrate a list in a YAML config file in Spring Boot, and don't see how to proceed.
I already saw other questions related : Spring Boot yaml configuration for a list of strings
And have the same issue.
I applied the solution and worked around, and found the solution a little tricky.
Is there a way to make lists work with the #Value ?
And if not now, is it expected in future ?
Thanks a lot.

According to this documentation you can do a list in yaml.
http://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html#boot-features-external-config-yaml
YAML lists are represented as property keys with [index] dereferencers, for example this YAML:
my:
servers:
- dev.bar.com
- foo.bar.com
Would be transformed into these properties:
my.servers[0]=dev.bar.com
my.servers[1]=foo.bar.com
To bind to properties like that using the Spring DataBinder utilities (which is what #ConfigurationProperties does) you need to have a property in the target bean of type java.util.List (or Set) and you either need to provide a setter, or initialize it with a mutable value, e.g. this will bind to the properties above
#ConfigurationProperties(prefix="my")
public class Config {
private List<String> servers = new ArrayList<String>();
public List<String> getServers() {
return this.servers;
}
}

https://www.youtube.com/watch?v=d6Scea1JdMg&t=9s
Please refer to the above link. Maybe it helps, shown how to read different data types in application.yml in the Spring Boot.

There is a related GitHub thread: #Value annotation should be able to inject List from YAML properties. The issue has been closed, and according to a comment in a duplicate issue, they're not considering implementing the support now. It will be reopened once they decide to work on it.
Until then, you can go the way described in #mark's answer, using #ConfigurationProperties, also mentioned on GitHub.

Related

EnvironmentPostProcessor and ConnectionProperties priorities

I use this library to replace links to AWS parameters to they actual values: https://github.com/NitorCreations/spring-property-aws-ssm-resolver/blob/master/src/main/java/com/nitorcreations/spring/aws/SpringPropertySSMParameterResolver.java
This library uses EnvironmentPostProcessor to substitude parameters. Next, I use the following component to get properties:
#Data
#Component
#ConfigurationProperties(prefix = "activemq.connection")
public class ConnectionProperties {}
This library replaces all spring properties except #ConfigurationProperties and I can't understand why. Is there any priority for processing these beans?
UPD: From the library description:
spring-property-aws-ssm-resolver is a small Spring Boot plugin for resolving AWS SSM Parameters during startup simply by using prefixed regular Spring Boot properties.
Set up your Spring Properties with the {ssmParameter} prefix.
Example application.yml:
my.regular.property: 'Foo'
my.secret.property: '{ssmParameter}/myproject/myapp/mysecret'
During startup, the plugin would look for properties with this prefix and replace the value by looking for a property called /myproject/myapp/mysecret on AWS SSM.
I expect this library will replace all values in #ConfigurationProperties on values from AWS SSM, but it actually don't, it leaves them unchanged. I think the reason is in the initialization order of ConfigurationProperties and EnvironmentPostProcessor.

How to change namingStrategy in springdoc?

I am using springdoc. It works flawlessly, except I am trying to change the default camelCase to PascalCase requests mapping.
I searched through the docs and cannot find a configuration to adjust property naming.
How can I change property naming when working with springdoc?
As #Debargha Roy mentioned in his answer, there is no clear way to do it via springdoc properties.
I propose my solution for Kotlin based on GitHub thread.
To get PascalCase or UpperCamelCase in Swagger, you can use ModelResolver:
#Bean
fun modelResolver(objectMapper: ObjectMapper): ModelResolver {
return ModelResolver(jacksonObjectMapper().registerModule(KotlinModule()).setPropertyNamingStrategy(PropertyNamingStrategy.UPPER_CAMEL_CASE))
}
It doesn't resolve the output response from Spring #RestController.
By default, Spring uses Jackson mapper to make responses follow UpperCamelCase, use this property spring.jackson.property-naming-strategy=UPPER_CAMEL_CASE in properties/yaml file.
After these 2 changes, both your Swagger and RestController will follow the UpperCamelCase style.
NOTE:
Beware, if you decided to reuse incoming objectMapper (see the snippet below), you may get duplicated payload with mixed camelCase:
#Bean
fun modelResolver(objectMapper: ObjectMapper): ModelResolver {
return ModelResolver(objectMapper.registerModule(KotlinModule()).setPropertyNamingStrategy(PropertyNamingStrategy.UPPER_CAMEL_CASE))
}
As far as I know, you can not do it. The reason being that there's no way to change the name of the request handler by using the Swagger annotations.
And lowerCamelCase for method/identifier names is promoted by the Java Naming Conventions and also by the Google Naming Convention.
You should still be able to compile and run the code if you don't follow the conventions. Other than that, I don't think there's an official way of changing the method name's case using Swagger directly.
#Dmytro Chasovskyi solution above also worked for me on SpringDoc.
I'm on java Spring though, all I had to do:
#Bean
public ModelResolver modelResolver(ObjectMapper objectMapper) {
return new ModelResolver(objectMapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE));
}

How to inject Maps using Spring #Value

We are using Spring Boot 2.
We want to use the refresh mechanism of Spring Boot, but due to a bug we can't use #Configuration, hence we are forced to replace all theses by #Value in combination with #RefreshScope.
So we used that:
#Configuration
#RefreshScope
public class MyConfig {
#Value("${myMap}")
private Map<String, String> myMap;
}
With that YAML file for example:
myMaps:
key1: Value
key2: Another Value
But we get an error out of it:
Error creating bean with name 'scopedTarget.myMap': Unsatisfied dependency expressed through field 'mapMap'; nested exception is org.springframework.beans.ConversionNotSupportedException: Failed to convert value of type 'java.lang.String' to required type 'java.util.Map'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'java.util.Map': no matching editors or conversion strategy found
We found already other thread with a similar topic:
How to fill HashMap from java property file with Spring #Value
How to inject a Map using the #Value Spring Annotation?
But we can't use other kind of property bindings, as we are deploying this app into kubernetes and use the config maps of kubernetes.
So we wonder, if there is any other chance to get #Value working together with Map
Instead of: #Value("${myMap}"), you need to write like this #Value("#{${myMap}}").
'#' treat whats in the curly bracket after it as Spring Expression Language(SpEL).
I don't think Kubernetes should prevent you from using #ConfigurationProperties (example here) with #RefreshScope. (Or is something else blocking you from using this?)
You could set up a configmap containing your properties file and mount that as a volume.
How you trigger a refresh is another question (if you need hot loading instead of changing your configmap together with your app). One option is to use spring-cloud-kubernetes which uses a similar approach and has multiple reload-triggering options. I'd expect you could use #ConfigurationProperties just like in the example for that project. Alternatively, it's also possible to watch for changes but that would require introducing more code.
I found something, that might solve my problem, but perhaps there is a better solution than this:
Based on org.springframework.cloud.logging.LoggingRebinder.setLogLevels(LoggingSystem, Environment)
private static final Bindable<Map<String, String>> STRING_STRING_MAP = Bindable
.mapOf(String.class, String.class);
protected void setLogLevels(LoggingSystem system, Environment environment) {
Map<String, String> levels = Binder.get(environment)
.bind("logging.level", STRING_STRING_MAP).orElseGet(Collections::emptyMap);
for (Entry<String, String> entry : levels.entrySet()) {
setLogLevel(system, environment, entry.getKey(), entry.getValue().toString());
}
}

How do I specify multiple templateLoaderPaths for Freemarker in Spring Boot?

I need to specify multiple template loader paths for FreeMarker in a Spring Boot web application but the FreeMarkerAutoConfigurationClass only let me specify one path using the spring.freemarker.templateLoaderPath property, which uses the setTemplateLoaderPath method in the FreeMarkerConfigurationFactory. However, this class allows me to set multiple path using the setTemplateLoaderPaths method. Which is the best way to override this auto-configuration class and specify multiple loader paths? I don't really understand well the Spring Java config classes and I want an example for this before write the code I need. I'm using Spring Boot 1.1.2. Thanks in advance.
You'll need to provide your own bean of type org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer with your desired configuration. To do so, add something similar to the following to one of your application's Java configuration classes:
#Bean
public FreeMarkerConfigurer freeMarkerConfigurer() {
FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
configurer.setTemplateLoaderPaths("one", "two", "three");
// Apply further configuration as needed
return configurer;
}
Update: the latest Spring Boot 1.2 snapshots now accept a comma-separated list for the spring.freemarker.templateLoaderPath property allowing you to specify multiple paths without declaring a custom FreeMarkerConfigurer bean.

Building custom java config annotations - similar to custom XML namespaces

We're building a framework on top of Spring & Spring MVC. Our framework is quite mature at this point - about 2 years old and is used widely within our organization. Our framework is very modular (much like spring itself is). There are various modules that can be used independently or together. When used together they provide many benefits to the end user. We have built a handful custom spring XML namespaces (NamespaceHandlers, BeanDefinitionParsers, etc). Each module provides their own which brings in its own set of XML configuration elements. This is all working great for us and has been a really big win for us.
What we'd like to do now is move away from XML-based configuration and into java config. My idea/thought is for each module to introduce a set of java config annotations that can be used (something similar to the #EnableCaching, #EnableMBeanExport annotations). My question is this - even if I create my annotations - how do I "wire" them in so that if they are present I can do "stuff"? This would be similar conceptually to the NamespaceHandlers & BeanDefinitionParsers. I can't find any documentation anywhere as to how to get started.
I've thought about creating some custom abstract base classes which do what I need them to do - but the problem is when it comes to the end user's application - they can only extend a single class. I need a flexible way for each module in my framework to expose its own custom configuration that end user applications can use, just like they use our XML namespace elements.
Here's a glimpse as to what we do in XML (not full application context file - just a blurb from it pertaining to our custom XML namespaces):
<atom-web:web/>
<atom-web:logging/>
<atom-web:security entitlementsProvider="XML" xmlRefreshInterval="${cache.refresh.interval.ms}"/>
<atom-profile:profile caching="IN_MEMORY" entryExpiryDelay="${cache.refresh.interval.ms}"/>
<atom-prefs:preferences backingStoreUrl="${pref.backingStore.url}"/>
<atom-content:content contentServerBaseUrl="${content.server.url}" contentServerFileUrl="${content.server.file.url}" site="${site.name}" contentTaskExecutor="contentTaskExecutor" snippetCaching="IN_MEMORY" cacheRefreshInterval="${cache.refresh.interval.ms}"/>
<bean id="contentTaskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor" p:corePoolSize="3" p:maxPoolSize="20"/>
What I'm envisioning is some kind of set of annotations - something like this:
#EnableAtomWebApplication
#EnableAtomWebLogging
#EnableAtomWebSecurity(entitlementsProvider=EntitlementsProvider.XML, xmlRefreshDelay=120000)
#EnableAtomProfile(caching=CachingType.IN_MEMORY, expiryDelay=120000)
// Other annotations for rest of modules
#Configuration
public class ConfigurationClass {
// Rest of configuration in here
}
Any help here would be greatly appreciated. I'm not quite sure where to start and can't really find any documentation anywhere to help me get started.
So after thinking about this a bit I think I've found the correct starting point. I wanted to throw this out there for anyone who might be able to say "yeah thats the right place" or "no you aren't looking in the correct place".
Using my example above
#EnableAtomProfile(caching=CachingType.IN_MEMORY, expiryDelay=120000)
I would create an annotation for the #EnableAtomProfile annotation like this:
#Retention(value=java.lang.annotation.RetentionPolicy.RUNTIME)
#Target(value={java.lang.annotation.ElementType.TYPE})
#Documented
#Import({AtomProfileBeanDefinitionRegistrar.class})
public #interface EnableAtomProfile {
CachingType caching() default CachingType.NONE;
long expiryDelay default 0;
}
The AtomProfileBeanDefinitionRegistrar class would implement org.springframework.context.annotation.ImportBeanDefinitionRegistrar and do any of the necessary stuff that I'm currently doing in my BeanDefinitionParser
You can have a BeanPostProcessor defined, which would basically:
inspect every single bean created
with reflection check if the object's class is annotated with #YourAnnotation
and if it is, then apply some custom logic - e.g. package the object into some other class or something
Reference:
Spring docs on BeanPostProcessors
source code for RequiredAnnotationBeanPostProcessor, which is a BeanPostProcessor which analyzes annotations

Categories