YAML list in spring framework without springboot - java

NOTE: NO SpringBoot. ONLY Spring Framework.
I see this works with #ConfigurationProperties in a springboot project. But I can't get it to work with #Value in a spring framework only project. It seems the #Value annotation cannot resolve the list correctly.
Here is a sample project: https://github.com/KiranMohan/spring-yaml.
For loading the yaml file, I used the YamlPropertiesFactoryBean.
The code is tested in a junit ExampleTest class.
Output of
log.debug("myList: {}",example.getMyList());
is
DEBUG [main] org.ktest.ExampleTest: myList: [${example.myList}]
Example yaml:
example:
enabled: true
name: "org.ktest"
myArray: >
abc,
def
myList:
- "ghi"
- "jkl"
Example code:
#Configuration
#PropertySource(value = "classpath:example.yml", factory = YamlPropertySourceFactory.class)
public class Example {
#Value("${example.enabled}")
private boolean enabled;
#Value("${example.name}")
private String name;
#Value("${example.myArray}")
private String[] myArray;
#Value("${example.myList}")
private List<String> myList;

Your YamlPropertySourceFactory uses YamlPropertiesFactoryBean whose Javadoc states
Lists are split as property keys with [] dereferencers, for example
this YAML:
servers:
- dev.bar.com
- foo.bar.com.
becomes properties like this:
servers[0]=dev.bar.com
servers[1]=foo.bar.com
This is also mentioned in the Spring documentation here.
In other words, using these types, you can't feed that example.myList YAML element into a List through property resolution. You can only access individual elements like
#Value("${example.myList[0]}")
private String myListFirstElement;
The Spring documentation here also mentions this. It suggests
Properties that use the [index] notation can be bound to Java List or
Set objects using Spring Boot’s Binder class.
You can try looking at how that class does it and reproduce it in your own code.

Related

Spring Boot: How to include a configuration class that is not in my base package?

I've got a fairly standard spring boot app which is built with gradle from several gradle modules. Here's the directory layout:
- root
- serviceA
- src/main/java
- org.example.serviceA
- ServiceAApplication.java
- serviceB
- serviceC
- common
- src/main/java
- org.example.common
- CommonSecurityConfiguration.java
What I would like to do is to include the CommonSecurityConfiguration class from the shared common module in serviceA. Note that ServiceAApplication and CommonSecurityConfiguration reside in different base packages.
I tried to use #Import(CommonSecurityConfiguration.class) on my ServiceAApplication, but that had no observable effect at all.
The only thing which worked was to annotate ServiceAApplication like so:
#SpringBootApplication(basePackages = { "org.example.serviceA", "org.example.common"})
public class ServiceAApplication { ... }
This approach works, but seems very coarse grained to me - it will import each and every component and configuration it finds in org.example.common.
Is there a better way to do this? Can I include individual classes into the component scan by listing them one by one?
Try to use
#Import(CommonSecurityConfiguration.class) above configuration class. So it would look like this:
#Configuration
#Import(CommonSecurityConfiguration.class)
public class ServiceAConfiguration { ... }
I believe what you are looking for is #CompnentScan("com.example"), this will tell Spring to look at all the files under the specified path recursively. (In this case it would be #ComponentScan("root"))
You find more info here: baeldun.com/spring-component-scanning
Hope this helps.
Since you want to control which components are brought in , we can make an annotation , let's call that annotation PickyComponentImport
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
public #interface PickyComponentImport{
}
Then on our SpringBootApplication annotation we can add a new filter which looks for this annotation.
#ComponentScan(basePackages = { "org.example.serviceA",
"org.example.common" }, includeFilters = #Filter(PickyComponentImport.class))
public class ServiceAApplication { ... }
Then we can just add that annotation on any class we want included
#Configuration
#PickyComponentImport
public class CommonSecurityConfiguration {
}
EDIT: I think if you go with this approach you can just componentScan basepackage as root.

Group configuration in quarkus / microprofile

I want to group configuration items in Quarkus and use them as a Map. Something along the lines of the next application.properties:
greeting.names = tom,jane
greeting.tom.message = hello
greeting.tom.name = tom
greeting.jane.message = hi
greeting.jane.name = jane
And have them in the application in a Map, like this:
#ConfigProperties
public class GreetingConfig {
private String name;
private String message;
// getters / setters
}
And use them like this:
private Map<String, GreetingConfig> config;
They are now name indexed, but a List would also be fine (and is what I actually need):
greeting.1.message = hello
greeting.1.name = tom
greeting.2.message = hi
greeting.2.name = jane
Any ideas on how to realize this? I have a programmatic solution, but would prefer a solution by annotation only
I've had a similar problem these days.
So I wrote a simple Quarkus extension which helped me with the configuration.
You can use the guides from the Quarkus site: Quarkus - Writing Your Own Extension and Quarkus - Building my first extension,
but basically these are the steps taken in order to create the configuration:
Have some maven multi module project (not the project where the configuration will be consumed).
Execute a similar command form the project directory:
mvn io.quarkus:quarkus-maven-plugin:1.4.2.Final:create-extension -N \
-Dquarkus.artifactIdBase=keycloak-extension \
-Dquarkus.artifactIdPrefix=otaibe-commons-quarkus- \
-Dquarkus.nameBase="Keycloak extension"
This will create 'sub multi module' project there with the following modules: runtume and deployment.
Go to the runtime module and add and annotate your config class. It should be something similar to this class
In runtime module create a producer class which should register the configuration class as CDI bean:
#ApplicationScoped
public class OtaibeKeycloakQuarkusProducer {
private volatile OtaibeKeycloakConfig otaibeKeycloakConfig;
void initialize(OtaibeKeycloakConfig config) {
this.otaibeKeycloakConfig = config;
}
#Singleton
#Produces
public OtaibeKeycloakConfig otaibeKeycloakConfig() {
return otaibeKeycloakConfig;
}
}
Create a Recorder class which should initialize the Producer:
#Recorder
public class OtaibeKeycloakRecorder {
public void initOtaQuarkusProducer(BeanContainer container, OtaibeKeycloakConfig configuration) {
OtaibeKeycloakQuarkusProducer producer = container.instance(OtaibeKeycloakQuarkusProducer.class);
producer.initialize(configuration);
}
}
In deployment module you have a Processor class. Go there and register your Producer to be injectable as CDI bean and your Recorder to initialize it. Add the similar code:
#BuildStep
AdditionalBeanBuildItem beans() {
return AdditionalBeanBuildItem.builder().addBeanClasses(OtaibeKeycloakQuarkusProducer.class).build();
}
#BuildStep
#Record(ExecutionTime.RUNTIME_INIT)
void otaibeKeycloackConfigBuildItem(BeanContainerBuildItem beanContainer,
OtaibeKeycloakConfig otaibeKeycloakConfig,
OtaibeKeycloakRecorder recorder) {
recorder.initOtaQuarkusProducer(beanContainer.getValue(), otaibeKeycloakConfig);
}
You can find my implementation here.
Now, go to your initial project where the configuration will be consumed and add the runtime module as a dependency.
In order to ensure that the configuration is added properly execute the following maven command:
mvn quarkus:generate-config
Now, you can check the file src/main/resources/application.properties.example and verify whether your properties are added there. The property group should start with quarkus. plus the name of your #ConfigRoot annotation. In my case for example it will start with quarkus.otaibe.keycloak
That's it!
Now in Quarkus 2.9, you do it with #ConfigMapping (#ConfigProperties is deprecated).
#StaticInitSafe
#ConfigMapping
public interface GreetingConfigMapping {
Map<Integer, GreetingDetail> greeting();
}
interface GreetingDetail {
String name();
String message();
}
Try it in a #QuarkusTest; #Inject it.

Spring boot: Exclude auto configuration based on environment variable

In spring boot we may exclude specific auto-configuration classes such that they will never be applied. Example with annotation based configuration:
#SpringBootApplication(exclude = OneConfiguration.class)
What I want is to provide an environment variable with a value that will determine some auto configurations that will be excluded. Any ideas how to achieve that?
You can exclude this configuration class by default:
#SpringBootApplication(exclude = OneConfiguration.class)
then extend it and make it conditonal:
#Configuration
#ConditionalOnProperty(name = "one.configuration.condtion", havingValue = "some-value")
public class OneConfigurationConditional extends OneConfiguration { }
Create class extending Condition checking value of enviroment variable:
public class AutoConfigurationCondition extends Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return System.getenv("EXCLUDE").equals("yes");
}
}
Then you can use it in Configuration class:
#Configuration
#Conditional(AutoConfigurationCondition.class)
#EnableAutoConfiguration(exclude = {OneConfiguration.class})
class AutoConfigurationExcludingConf {}
I managed to do what I wanted with profiles :-( So I have few files now - e.g. application-artemis.yaml and application-activemq.yaml. In each I've defined corresponding spring.autoconfigure.exclude property. I'm not proud with this solution though it works fine so far and looks more or less neat compared to other things that I did :-) .
What I've tried besides that:
Managing the value with an environment variable, e.g.:
spring:
autoconfigure:
exclude: ${ABC:com.example.Myautoconfiguration}
This does not work and I've even reported it as an issue. But it seems that I can't rely on expression for this property. Strange enough it works for other properties...
I've played around with the suggestion of #Randal Flagg but somehow I couldn't get it up and running - I'm using #SpringBootApplication, the documentation says that I can use EnableAutoConfiguration only once, etc.
I've tried with my own TypeFilter but this is also not an option - autoconfigurations have special treatment during component scan and the design does not seem very extensible there. At least I could not find a nice way to plug in.

Loading multiple YAML files (using #ConfigurationProperties?)

Using Spring Boot 1.3.0.RELEASE
I have a couple of yaml files that describe several instances of a program. I now want to parse all those files into a List<Program> (Map, whatever), so I can later on search for the most appropriate instance for a given criteria in all the programs.
I like the approach with #ConfigurationProperties a lot, and it works good enough for a single yaml-file, but I haven't found a way yet to read all files in a directory using that method.
Current approach working for a single file:
programs/program1.yml
name: Program 1
minDays: 4
maxDays: 6
can be read by
#Configuration
#ConfigurationProperties(locations = "classpath:programs/program1.yml", ignoreUnknownFields = false)
public class ProgramProperties {
private Program test; //Program is a POJO with all the fields in the yml.
//getters+setters
I tried changing the locations to an Array listing all of my files locations = {"classpath:programs/program1.yml", "classpath:programs/program2.yml"} as well as using locations = "classpath:programs/*.yml", but that still only loads the first file (array-approach) or nothing at all (wildcard-approach).
So, my question is, what is the best way in Spring Boot to load a bunch of yaml files in a classpath-directory and parse them into a (List of) POJO, so they can be autowired in a Controller? Do I need to use Snakeyaml directly, or is there an integrated mechanism that I just haven't found yet?
EDIT:
A working approach is doing it manually:
private static final Yaml yaml = new Yaml(new Constructor(Program.class));
private static final ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
try {
for (Resource resource : resolver.getResources("/programs/*.yml")) {
Object data = yaml.load(resource.getInputStream());
programList.add((Program) data);
}
}
catch (IOException ioe) {
logger.error("failed to load resource", ioe);
}
In Spring, it is possible to load multiple configuration properties files using PropertySource annotation, but not YAML files. See section 26.6.4 in link below:
https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html#boot-features-external-config-typesafe-configuration-properties
However, from your problem, it seems that you can configure all your programs in single YAML and then get all list of programs in a single list.
Sample YAML (all.yaml)
programs:
- name: A
min: 1
max: 2
- name: B
min: 3
max: 4
Config.java
#Configuration
#ConfigurationProperties(locations={"classpath:all.yaml"})
public class Config{
private List<Program> programs;
public void setPrograms(List<Program> programs) {
this.programs = programs;
}
public List<Program> getPrograms() {
return programs;
}
}
What I am currently doing, as far as I understood your question, is nearly the same.
I am having an application.yml and also profile-specific yml files, e.g. application-{profile}.yml in my src/main/resources.
In the application.yml I have defined the default profile key-values, which are partially overridden by the profile-specific yml files.
If you want to have a type-safe and well defined access of your YML key/values, then you can use the following approach:
#ConfigurationProperties
public class AppSettings {
String name; // has to be the same as the key in your yml file
// setters/getters
}
In your Spring-Boot config, you have to add the following annotations onto your config class:
#ComponentScan
#EnableAutoConfiguration
#EnableConfigurationProperties( value = { AppSettings.class, SomeOtherSettings.class } )
public class SpringContextConfig {
#Autowired
private AppSettings appSettings;
public void test() {
System.out.println(appSettings.getName());
}
}
The #Autowiring is also accessible from other Beans.
The other way around (without an extra separated and type-safe class, is to access the YML-values via #Value("${name}").
To bring it together in a short manner:
Yes, it is possible to use several YAML files for your application via Spring-profiles. You define your current active spring profile via command args, programmatically or via your system env (SPRING_PROFILES_ACTIVE=name1,name2).
Therefore you can have several application.yml files for each profile (see above).

How to load multiple contexts using jUnit and Spring

I have a junit test class with following annotations :
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration("classpath:custom-context.xml")
There exists a class in another context that is required in my test that is autowired so I require this context to be loaded. How can this be implemented ?
I've tried :
#ContextConfiguration("classpath:custom-context.xml , classpath:custom-context2.xml")
But this does now work as it does not seem to load multiple contexts using the , delimiter.
Also can use widcard like this:
#ContextConfiguration({"classpath:custom-*.xml" })
Use an array of strings, like :
#ContextConfiguration(locations={ "classpath:custom-context.xml" , "classpath:custom-context2.xml" })
from spring api doc

Categories

Resources