Defining profile dependant YAML properties in Spring Boot 1.4 - spring-boot

When defining properties in YAML for a Spring Boot 1.4 application, I have to use the main/resources/application.yaml file. So far, so good.
How can I override these dependencies using YAML depending for a specific profile when running or testing the application.

When using Spring Boot 1.4 (this applies for release 1.3 as well) YAML properties are always defined in the main/resources/application.yaml file. Depending on the chosen profile, the properties can be overridden by another set of properties.
To override these properties for test, a YAML file has to be given in the /test/resources/application-.yaml file, where is replaced by the active profile. It is important to see that the profile always has to be given, even when there is no active profile. In that case, the profile is ‘default’.
To override the properties when running the application, properties can be overridden for a specific profile using in a file called main/resources/application-.yaml file. Even for this situation, if no profile is given, the profile for the filename is ‘default’.
Example
The test class Spring14ApplicationTests.java for Spring Boot 1.4 has the following definition
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
public class Spring14ApplicationTests {
…
}
For Spring Boot 1.3, the same file has the following definition
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes=SpringDb14Application.class)
#WebAppConfiguration
public class SpringDb14ApplicationTests {
...
}

Related

Spring Cloud Config: Bootstrap context not loading profile-specific property files for binding

Setup
Spring Boot 2.6.0
Spring Cloud Config 3.1 RC1
Apache Maven 3.8.x
OpenJDK 11
Overview
I have a multi-module Apache Maven project that is set up with the following modules:
bootstrap: contains a PropertySourceLocator for BootstrapConfiguration, defined in spring.factories file.
starter: depends on bootstrap, and it's a (servlet-based) web application
reference: deploys the starter application using the Maven Cargo plugin, deploying into an Apache Tomcat 9.0.55
Runtime
The starter module declares a configuration class, annotated with #PropertySource("wa.properties"). This wa.properties on the classpath of the starter module has a setting: cas.authn.syncope.name=Starter
The starter module has a ServletInitializer that sets the spring.config.name property to "wa" when building the spring application.
The reference module only has a wa-embedded.properties file on the classpath with a setting: cas.authn.syncope.name=Embedded
The reference module starts with the spring activated profiles: embedded,all
Note: the cas.authn.syncope.name is bound to a Java POJO, CasConfigurationProperties, that is annotated with #ConfigurationProperties("cas").
Observation
The following bean in the application exists, simplified for this post:
#Bean
#ConditionalOnMissingBean(name = "something")
#RefreshScope(proxyMode = ScopedProxyMode.DEFAULT)
public Something something(ApplicationContext ctx, CasConfigurationProperties cas) {
...
}
If I look at the contents of cas.getAuthn().getSyncope().getName()), it shows: "Starter"
If I look at ctx.getEnvironment().getProperty("cas.authn.syncope.name"), it shows "Embedded".
In other words, property binding used during the bootstrapping process does not match the actual environment for the application's context.
Analysis
It appears that when the bootstrap application context is created, wa-embedded.properties, a profile-specific property is not read. In fact, the only property source that is used for binding is wa.properties as part of "localProperties", which I believe comes from #PropertySource("wa.properties"). Nothing else is read or found.
Then, property binding takes place binding CasConfigurationProperties and cas.authn.syncope.name initialized from #PropertySource("wa.properties"). The value of this property is set to Starter.
Then, the application servlet context is initialized and its environment is post-processed with profiles and the appropriate listener and Spring beans are created. In particular, this bean:
#Bean
#ConditionalOnMissingBean(name = "something")
#RefreshScope(proxyMode = ScopedProxyMode.DEFAULT)
public Something something(ApplicationContext ctx, CasConfigurationProperties cas) {
...
}
...shows that ctx is the actual application context with an environment that is post-processed via all profiles and shows ctx.getEnvironment().getProperty("cas.authn.syncope.name") as "Embedded".
However, CasConfigurationProperties was processed using the Bootstrap context only, and its equivalent property shows "Starter".
...which means the bean would be created using the wrong values in CasConfigurationProperties.
Research
This setup works OK using Spring Boot 2.5.6 and Spring Cloud 3.0.5. I don't think anything in Spring Boot has changed that would affect this, but I do see a number of differences in Cloud between 3.0 and 3.1.
I am not sure I can create a reproducer to adequately showcase this. I'll try. In the meantime, could you evaluate this and see if this might be seen as a bug, or misconfiguration of some kind?

Upgrading from Java Config to Spring Boot

Upgrading an existing system to Spring Boot with Auto config. Currently the system is all Java config. I'm confused over whether to continue the use of #Profile. Is this annotation no longer needed? I searched extensively about upgrading and found only references to non-Spring Java migration and creating new projects.
Typical #Profile usage in our configuration classes looks something like:
#Bean
#Profile("is-standalone")
public Service unsecuredService(SomeApi someApi) {
return new ...
}
I inferred from the Spring Boot examples that using one of the #Conditional annotations is recommended like this:
#Bean
#ConditionalOnProperty("unsecured.enabled")
public Service unsecuredService(SomeApi someApi) {
return new ...
}
Then in a YAML file the is-standalone Profile enables or disables all the various properties for that Profile. Is this the proper way to upgrade? To repeat a question from above differently, can the #Profile usage be left as is? This is for a fairly large project, the upgrade is non-trivial, so I would like to do this only once!
Depends where your previous #Profile annotation is coming from. If you're using Spring's #Profile, the functionality is as follows:
Annotating a class with #Profile("dev") will load the class and register it in the Spring context only when the dev profile is active
Annotating a class with #Profile("!dev") will load the class and register it in the Spring context only when the dev profile is inactive
If this sounds like what you have already, no change is needed.

How to define the spring.config.location on Spring Boot and JUnit tests?

How we can programmatically configure Spring Boot to define new values to the spring.config.name and spring.config.location properties when running JUnit tests?
For example, if we would like to define these properties when running the application itself we could use something like (in Kotlin):
fun main(args: Array<String>) {
// SpringApplication.run(Application::class.java, *args)
val applicationContext = SpringApplicationBuilder(Application::class.java)
.properties(
"""spring.config.name:
${getSpringConfigNames()}
""",
"""spring.config.location:
${getSpringConfigLocationPaths()}
"""
)
.build()
.run(*args)
// val environment = applicationContext.getEnvironment()
}
But I wasn't able to find a way to configure this to use in the JUnit tests.
Edit
There is a complication here because of an spring boot limitation.
I would like to use an entire folder and its subfolders as valid locations to search for configuration files (so, for example, we could have folders for specific environments, databases, third-parties, and so on).
When running the application this was possible creating a method, in this case getSpringConfigLocationPaths(). And this method create a comma separated list with all folder inside the "main" folder.
For example, for the main folder src/main/resources/configuration it will output:
src/main/resources/configuration,
src/main/resources/configuration/environments,
src/main/resources/configuration/environments/development,
src/main/resources/configuration/environments/staging,
src/main/resources/configuration/environments/testing,
src/main/resources/configuration/environments/production,
src/main/resources/configuration/environments/common
How could we solve this situation when using JUnit tests and Spring Boot?
Unfortunately Spring Boot doesn't allow something like src/main/resources/configuration/**/*.
Because we have organized the system with several properties files on different subfolders we need to find a way to dinamically consider them.
I am using latest Spring Boot 2.2.0 and from my experience both #TestPropertySource and #SpringBootTest annotations can do the job because they have properties attribute.
So, you can do something like this:
#TestPropertySource(properties = ["spring.config.location=classpath:dev/", "spring.config.name=custom-app-name"]
#TestConfiguration
class DevTestCfg {} // this will make tests to look for configs in resources/dev/custom-app-name.properties
Also notice that there is a spring.config.additional-location property if you want your properties to be loaded from multiple locations.
The only problem here is that values in properties attribute must be constant.
But you can create multiple configurations for each environment and put corresponding #Profile("envName") on each configuration class. Then run your tests with different -Dspring.profiles.active and corresponding test configuration should be automatically picked up.
The tests that run spring boot should be carefully designed,
There is a whole testing framework for spring boot tests, so obviously consider using this framework.
When it comes to configuration management, I suggest considering the following:
There are two types of tests basically:
Tests that load a concrete specific configuration (set of beans), for example if you want to test only a DAO, you load a configuration for this dao.
In this case, the configuration is something that should be "tailored" to the needs of a specific test, and no "full" configuration is required.
For example, if the microservice contains a configuration for a database (user, password, schema, etc) and for, say, messaging management, there is no need to specify a configuration of a messaging system when testing a DAO, messaging beans won't be loaded anyway.
Usually, the test of this "type" will look like this:
#SpringBootTest(classes = {RelationalDbDaoConfiguration.class})
public class MyDaoTest {
}
If you don't have a configuration for your needs you can use #MockBean to mock unnecessary beans or even create a custom configuration in src/test/java so that it will be only in test classpath. It makes sense to use #TestConfiguration but it's beyond the scope of the question.
Now in order to load the configuration for db only, the are many options, to name a few:
#ActiveProfiles("dao") on a test class + putting "application-dao.properties/yaml" into the src/test/resources or src/test/resources/config
Use #TestPropertySource(locations = "classpath:whatever.properties") on test
Create a special "DbProperties" bean and initialize it programmatically in spring, it can make sense when you know some details about the context in which the test runs only during the actual test execution (for example, if you start a database before the test and the port is created dynamically, but its really a fairly advanced setup and is beyond the scope of this question) + the data source bean can read these properties
Use #SpringBootTest's properties attribute to provide 'fine-grained' properties definitions
Kind of obvious, but I'll mention it anyway: put application.properties in src/test/resources it will override regular configurations
The second type of tests is when you load the "entire" microservice, usually, these are tests that do not have "classes" parameter in #SpringBootTest annotation
#SpringBootTest // note, no actual configurations specified
public class MyMicroserviceTest {
...
}
Now, this definitely requires to specify a whole set of configurations, although the techniques for actually specifying these configurations are still applicable (just the content of configuration files will be different).
I do not suggest the usage of spring.config.location during the test, because this means that the test depends on some external resource, which makes the whole setup even more complicated.
If it's XML driven configuration,
#ContextConfiguration(locations = "/app-context.xml")
If it's annotation driven by configuration classes,
#ContextConfiguration(classes = {AppCOnfig::class, AnotherCOnfig::class}
These would be defined on the class level on the unit test class you run.
Further, if you have profiles for Junit to consider,
#ActiveProfiles("myProfile") would be added to the test class.

Common and profile specific properties in a single application.properties file

I am writing a spring mvc application. I want to make use of spring profiles' features. I want to have a single application.properties with common and profile specific properties. I also want to have a set a default profile in the same file. How do i do this?
You could create an extra spring configuration class to dynamically return the properties based on the spring profile:
#Configuration
#PropertySource("classpath:application.properties")
public class QuotePropertyConfiguration {
#Inject
private Environment environment;
#Bean
public String getMySpecificProperty() {
return environment.getProperty("myprop." + Arrays.toString(env.getActiveProfiles()) + ".thing");
}
}
That will return you e.g. myprop.dev.thing, myprop.test.thing, myprop.prod.thing etc. depending on the active spring profiles.
Alternatively you can also do this with multiple application.properties files: From the spring documentation:
In addition to application.properties files, profile-specific
properties can also be defined using the naming convention
application-{profile}.properties. The Environment has a set of default
profiles (by default [default]) which are used if no active profiles
are set (i.e. if no profiles are explicitly activated then properties
from application-default.properties are loaded).

How to add properties programmatically(just like adding key-value into application.properties)?

I have some common properties that every projects should set, such as
feign.hystrix.enabled=false
feign.httpclient.enabled=true
I don't want to repeatedly add these props in every project so I'm going to create an extra jar file containing #Configruation class. How to add properties in #Configuration class? Thanks!
PropertySources
You may load an application.properties from another jar this way:
#PropertySources({
#PropertySource("classpath:common.properties")
})
#Configuration
public class SomeJavaConfig {
}
You can find the reference in Spring's documentation:
Spring Boot uses a very particular PropertySource order that is
designed to allow sensible overriding of values. Properties are
considered in the following order:
...
#PropertySource annotations on your #Configuration classes.
Spring-cloud-config
I won't go in all the details, but another option is to use spring-cloud-config to define these properties in a git (using spring-cloud-config-server). Then, have your spring-boot application load the application.properties using spring-cloud-config-client directly from git.
Check this:
https://cloud.spring.io/spring-cloud-config/spring-cloud-config.html
https://spring.io/guides/gs/centralized-configuration/

Resources