So I have read dosens af articles on how to configure Spring boot to be aware of more yml files than application.yml and how to include these - even from subprojects. It is however hard to come by articles describing the same for "pure" Spring. I think however that i'm heading in the right direction I just can't get my configuration values back.
It's a straight forward multi-project gradle build with - for simplicity - two projects. One project is the "main" spring project - ie. Spring Context is initialized in this project. The other is a "support" module with some database entities and datasource configuration. We use annotation based configuration.
I would like to be able to define a set of configuration properties in the support module and based on whatever spring profile is active, the datasource configuration is loaded accordingly.
This SA post got me quite far following the different links in the different answers and composing my solution from this. The structure and code is as follows:
mainproject
src
main
groovy
Application.groovy
resourcers
application.yml
submodule
src
main
groovy
PropertiesConfiguration.groovy
DataSource.groovy
resources
datasource.yml
The PropertiesConfiguration.groovy adds the datasource.yml by using PropertySourcesPlaceholderConfigurer:
#Configuration
class PropertiesConfiguration {
#Bean
public PropertySourcesPlaceholderConfigurer configure() {
PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer()
YamlPropertiesFactoryBean yamlPropertiesFactoryBean = new YamlPropertiesFactoryBean()
yamlPropertiesFactoryBean.setResources(new ClassPathResource("datasource.yml"))
configurer.setProperties(yamlPropertiesFactoryBean.getObject())
return configurer
}
}
The Datasource.groovy should then read values based on the spring profile using (code reduced for readability):
#Autowired
Environment env
datasource.username = env.getProperty("datasource.username")
The env.getProperty returns null. No matter what spring profile is active. I can access the configuration value using the #Value annotation, however then the active profile is not respected and it return a value even if it is not defined for that profile. My yml looks (something) like this:
---
spring:
profiles: development
datasource:
username: sa
password:
databaseUrl: jdbc:h2:mem:tests
databaseDriver: org.h2.Driver
I can from Application.groovy inspect my ApplicationContext using a debugger and confirm that my PropertySourcesPlaceholderConfigurer exist and the values are loaded. Inspecting applicationContext.environment.propertySources it is NOT there.
What am I missing?
Using a PropertySourcesPlaceholderConfigurer does not add properties to Environment. Using something like #PropertySource("classpath:something.properties") on the class level of your configuration class will add properties to Environment, but sadly this does not work with yaml-files.
So, you would have to manually add the properties read from the yaml file to your Environment. Here is one way to do this:
#Bean
public PropertySourcesPlaceholderConfigurer config(final ConfigurableEnvironment confenv) {
final PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
final YamlPropertiesFactoryBean yamlProperties = new YamlPropertiesFactoryBean();
yamlProperties.setResources(new ClassPathResource("datasource.yml"));
configurer.setProperties(yamlProperties.getObject());
confenv.getPropertySources().addFirst(new PropertiesPropertySource("datasource", yamlProperties.getObject()));
return configurer;
}
With this code, you can inject properties in either of these two fashions:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = PropertiesConfiguration.class)
public class ConfigTest {
#Autowired
private Environment environment;
#Value("${datasource.username}")
private String username;
#Test
public void props() {
System.out.println(environment.getProperty("datasource.username"));
System.out.println(username);
}
}
With the properties supplied in the question, this will print "sa" two times.
Edit: It doesn't seem that the PropertySourcesPlaceholderConfigurer is actually needed now, so the code can be simplified to the below and still produce the same output.
#Autowired
public void config(final ConfigurableEnvironment confenv) {
final YamlPropertiesFactoryBean yamlProperties = new YamlPropertiesFactoryBean();
yamlProperties.setResources(new ClassPathResource("datasource.yml"));
confenv.getPropertySources().addFirst(new PropertiesPropertySource("datasource", yamlProperties.getObject()));
}
Edit 2:
I see now that you are looking to use the yaml-file with multiple documents in one file, and Spring boot-style selection by profile. It does not seem to be possible using regular Spring. So I think you have to split your yaml files into several, named "datasource-{profile}.yml". Then, this should work (perhaps with some more advanced checking for multiple profiles, etc)
#Autowired
public void config(final ConfigurableEnvironment confenv) {
final YamlPropertiesFactoryBean yamlProperties = new YamlPropertiesFactoryBean();
yamlProperties.setResources(new ClassPathResource("datasource-" + confenv.getActiveProfiles()[0] + ".yml"));
confenv.getPropertySources().addFirst(new PropertiesPropertySource("datasource", yamlProperties.getObject()));
}
Edit 3:
It could also be possible to use functionality from Spring boot without doing a full conversion of your project (I haven't actually tried it on a real project though). By adding a dependency to org.springframework.boot:spring-boot:1.5.9.RELEASE I was able to get it working with the single datasource.yml and multiple profiles, like this:
#Autowired
public void config (final ConfigurableEnvironment confenv) {
final YamlPropertySourceLoader yamlPropertySourceLoader = new YamlPropertySourceLoader();
try {
final PropertySource<?> datasource =
yamlPropertySourceLoader.load("datasource",
new ClassPathResource("datasource.yml"),
confenv.getActiveProfiles()[0]);
confenv.getPropertySources().addFirst(datasource);
} catch (final IOException e) {
throw new RuntimeException("Failed to load datasource properties", e);
}
}
Related
I am trying to load 2 properties file in spring boot.
One of them contains the metadata (Database connection and other such properties). The other contains business logic (mapping between upstream and downstream Entity. This mapping is different in Dev and Prod, hence can't have a single resource file for these).
I want to use Spring Profiles for different environments (Dev, Stage, Prod).
So, I created 3 different folders in src/main/resources 1 for each environment.
Using spring profies, I am aware how to have env specific application-env.properties file. However, I am unable to move forward on how to use the same for my use case.
PS : Not adding any code snippet, because the question doesn't require one.
Here's an example from the docs:
$ java -jar myproject.jar --spring.config.location=\
optional:classpath:/default.properties,\
optional:classpath:/override.properties
You could also define this in your code before starting Spring Boot:
public static void main(String[] args) {
System.setProperty("spring.config.location", "optional:classpath:/default.properties,optional:classpath:/override.properties");
SpringApplication.run(Application.class, args);
}
To use custom prefixes for your app specific properties you can define #ConfigurationProperties class(es):
#Data
#ConfigurationProperties(prefix = "app.mapper")
public class MapperProperties {
private String foo;
}
and use it in any component:
#Component
#RequiredArgsConstructor
#EnableConfigurationProperties
public class YourComponent {
private final MapperProperties properties;
}
I am creating a spring-boot application which also creates bean for one of the classes of an external lib, this external java bean needs java.util.properties as one of the constructor parameter. Although I can use configurationPropeties with prefix to read properties from the spring boot loaded property file and convert it to java.util.properties.However, I don't want any additional prefix in the property file. is there any other way where I can convert the spring-boot loaded env or property source to java.util.properties
here is the code for reference
#Configuration
public class AppConfig {
#ConfigurationProperties(prefix = "some.prefix")
#Bean
public Properties getProperties() {
return new Properties();
}
#Bean
public ExternalClass externalClass() throws ConfigException {
return ExternalClass.getInstance(getProperties());
}
}
the above code work nicely, but I need to add an unnecessary prefix to the properties for conversion. could someone suggest any other approach apart from adding prefix to the propeties
Take a look at this documentation. It explains property binding techniques used in spring-boot.
To my surprise I have had a difficult time finding an answer to this question. I have Seen many examples where you can use #PropertySource to load a specific properties file for a class. I have also seen examples where you can easily add different property files in spring boot projects. But what I want to do is to do this for a spring project that is NOT spring boot and load a properties file so that the values of this file can be injected in classes annotated with #Component which is dependent on the server environment. So for example if I am on development server I want a particular properties file loaded and on production a different properties file. The reason that I am doing it like this is because my data and service layers are their own modules. These modules contain their own unit tests and can be imported as their own modules in other spring boot projects. I need properties files to be loaded to serve these modules which use spring but not spring boot. I have tried the following, but this does not work.
#Configuration
#Profile("test")
#EnableJpaRepositories("com.hi.repository")
#EnableTransactionManagement
#EnableScheduling
public class InfrastructureConfig {
...
#Bean
public static PropertySourcesPlaceholderConfigurer placeHolderConfigurer() {
PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
Map<String, String> env = System.getenv();
String propertiesFile=null;
String e = env.get("SERVER_ENV");
if (e.equals("dev")) {
propertiesFile = "environment/development.properties";
} else if (e.equals("prod")) {
propertiesFile = "environment/production.properties";
}
configurer.setLocation(new ClassPathResource(propertiesFile));
return configurer;
}
Then I have a test which looks like this
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = {"classpath:/spring/DealServiceTest-context.xml"})
#ActiveProfiles("test")
public class LogTest {
private static final Logger log = LogManager.getLogger(LogTest.class);
#Autowired
PathsService pathsService;
#Autowired
Environment environment;
#Test
public void testBeans(){
System.out.println("********** WASSUP from LogTest");
System.out.println(environment.getProperty("imageBucket"));
}
Although the test prints out null which indicates to me the properties file has not been loaded and prepared for its values to be injected. How can I achieve this?
You don't really need to set properties yourself, but you can do this using spring configuration. Check the documentation: https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html#boot-features-external-config-profile-specific-properties
If you're using spring boot - all you need to do is create multiple properties file for your environments. And only for properties you need to override.
So your main properties file would be at
src/main/resources/application.properties
Production
src/main/resources/application-prod.properties
Development
src/main/resources/application-dev.properties
Testing
src/main/resources/application-test.properties
And then just use the profile name as your environment variable
java -jar -Dspring.profiles.active=prod demo-0.0.1-SNAPSHOT.jar
Actually, you can just use a placeholder in #PropertySource annotation.
See documentation:
Any ${...} placeholders present in a #PropertySource resource location will be resolved against the set of property sources already registered against the environment.
Assuming that placeholder is present in one of the property sources already registered, e.g. system properties or environment variables, the placeholder will be resolved to the corresponding value.
I've made a simple example, it receives a 'property.environment' value to choose, which .properties file should be used as property source. I have two resource files in my classpath - application-test.properties and application-dev.properties, each one contains a 'test.property' value ('test-env' and 'dev-env' respectively).
Property configuration:
#Configuration
#PropertySource("classpath:/config/application-${property.environment}.properties")
public class PropertyConfig {
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
return propertySourcesPlaceholderConfigurer;
}
}
Component with #Value
#Component
public class TestService {
#Value("${test.property}")
String testProperty;
#PostConstruct
void init() {
System.out.println("---------------------------------------------------------");
System.out.println("Running in " + testProperty + " environment");
System.out.println("---------------------------------------------------------");
}
}
Build command line example (it runs tests with test environment properties)
mvn clean install -DargLine="-Dproperty.environment=test"
Output
---------------------------------------------------------
Running in test-env environment
---------------------------------------------------------
Run command line example
java -jar -Dproperty.environment=dev PATH_TO_YOUR_JAR.jar
Output
---------------------------------------------------------
Running in dev-env environment
---------------------------------------------------------
Don't hard code based on different environment, in spring boot you can able to maintain properties specific environment easily. Refer https://spapas.github.io/2016/03/31/spring-boot-settings/
I would try to take advantage of the profile mechanism already in place in Spring. You basically have done the job yourself already, the only thing you need to change is to have different configurations for "test" and "production" profiles. I prefer to keep everything related to test away from production code (allowing me to place the TestConfig class below in the test source path), so I would probably do something like this:
#Configuration
#Profile("!test")
#PropertySource(value = "classpath:/environment/production.properties")
#Import(AppConfig.class)
public class ProductionConfig
{
// Your production-specific config goes here
}
#Configuration
#Profile("test")
#PropertySource(value = "classpath:/environment/development.properties")
#Import(AppConfig.class)
public class TestConfig
{
// Your test-specific config goes here
}
#Configuration
public class AppConfig
{
// Needed for spring to handle ${property:default} syntax
#Bean
public static PropertySourcesPlaceholderConfigurer propertyConfigIn() {
return new PropertySourcesPlaceholderConfigurer();
}
}
If you prefer to have one config for both cases, you can let the AppConfig import the TestConfig and the ProductionConfig instead, but that will put test code in to production...
Good luck with your project!
How to create project architecture to support multiple envionment. Each environment will have different datasource from different property file like(dev-propertfile,test-propertyFil,Production-propertyfile) with help of spring's
org.springframework.core.env.Environment;
I'll give step by step procedure for Spring boot applications.
Inside /src/main/resources/application.properties mention spring.profiles.active=dev (or Prod)
Create /src/main/resources/application-dev.properties and give your custom dev configurations here.
Create /src/main/resources/application-prod.properties and give your custom prod configurations here.
Run.
Put property file in same location as application.property and follow
the naming convention application-{profile}.properties like
application-dev.properties,application-test.properties,
application-prod.properties
And in application.properties set spring.profiles.active=dev,test etc
For Spring Boot applications it will work easily even by using a YAML File
spring:
profiles: dev
property: this is a dev env
---
spring:
profiles: prod
property: this is a production env
---
However, for a Spring MVC application, it needs more work. Have a look at this link
Basically, it involves 2 steps
Get the Spring Profile within servlet context
If you have set the profile on the server and want it to retrieve it within your application you can use System.getProperty or System.getenv methods.
Here is the code which fetches the profile and defaults it to a local profile, if no profile has been found.
private static final String SPRING_PROFILES_ACTIVE = "SPRING_PROFILES_ACTIVE";
String profile;
/**
* In local system getProperty() returns the profile correctly, however in docker getenv() return profile correctly
* */
protected void setSpringProfile(ServletContext servletContext) {
if(null!= System.getenv(SPRING_PROFILES_ACTIVE)){
profile=System.getenv(SPRING_PROFILES_ACTIVE);
}else if(null!= System.getProperty(SPRING_PROFILES_ACTIVE)){
profile=System.getProperty(SPRING_PROFILES_ACTIVE);
}else{
profile="local";
}
log.info("***** Profile configured is ****** "+ profile);
servletContext.setInitParameter("spring.profiles.active", profile);
}
To access the application-dev.properties, say now you will need to use
#Profile("dev") at the class level
The following code will fetch the application-dev.properties and common.properties
#Configuration
#Profile("dev")
public class DevPropertyReader {
#Bean
public static PropertyPlaceholderConfigurer properties() {
PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer();
Resource[] resources = new ClassPathResource[] { new ClassPathResource("properties/common.properties"), new ClassPathResource("properties/application-dev.properties") };
ppc.setLocations(resources);
ppc.setIgnoreUnresolvablePlaceholders(true);
return ppc;
}
}
For accessing say application-prod.properties you have to use #Profile("prod") at the class level. More details can be found here
Take a look at Spring Profile. You will define a set of profiles configurations, like Test, Dev, Production. And then, when you launch the application, you can define wich profile it should use.
Here are some tutorials of how to use.
And this guys had the same problem as yours: How to config #ComponentScan dynamic?
We wanted a way to load different properties from application-<your_env>.properties file depending on the environment (spring profile) in a Spring MVC project, so we implemented a configuration class something like this.
#Configuration
#PropertySource({ "classpath:application-${envTarget:dev}.properties" })
#Data
public class EnvironmentConfig {
#Value("${files.s3.accessId:}")
String s3AccessId;
#Value("${files.s3.accessToken:}")
String s3AccessToken;
.
.
.
}
Then we loaded the EnvironmentConfig in the class where we needed to use it.
While running the application, you just need to pass the -DenvTarget=<your_env>, and it will pick up the application-<your_env>.properties file from src/resources folder of the project.
In the above code, it will load values from application-dev.properties files when no envTarget is specified.
Thanks to Karthikeyan Muthurangam for suggesting this clever solution.
I need to read java properties file inside my Spring MVC app but I can't find the way to do that. I tried several answers from similar question here on SO, but I was unsuccessful. I'm new to Java, and especially Spring MVC so I probably messed up something.
I'm not sure anymore that the file is being successfully deployed. I'm using Tomcat btw.
If you are using Spring 3.1+ you can use the #PropertySource annotation:
#Configuration
#PropertySource("classpath:/com/example/app.properties")
public class AppConfig {
// create beans
}
or for XML-based configuration you can use the <context:property-placeholder>:
<beans>
<context:property-placeholder location="classpath:com/example/app.properties"/>
<!-- bean declarations -->
</beans>
then you can autowire the key in the properties file using the #Value annotation:
#Value("${property.key}") String propertyValue;
Read more details in the Spring reference docs.
You can have properties files automatically loaded in Spring by using the PropertySourcesPlaceholderConfigurer.
Here is an example of configuring a PropertySourcesPlaceholderConfigurer using Spring JavaConfig:
#Bean
public static PropertySourcesPlaceholderConfigurer properties() {
PropertySourcesPlaceholderConfigurer props = new PropertySourcesPlaceholderConfigurer();
props.setLocations(new Resource[] {
new ClassPathResource("/config/myconfig.properties"),
new ClassPathResource("version.properties")
});
}
This will load the properties from the files above on the classpath.
You can use these properties in property replacements within your application. For example, assume that there is a property in one of those files above named myprop. You could inject myprop's value into a field using the following:
#Value(${myprop})
private String someProperty;
You can also access the values of the properties by injecting Spring's Environment object into your classes.
#Resource
private Environment environment;
public void doSomething() {
String myPropValue = environment.getProperty("myprop");
}
In order to read any old file from within a web application the link that Frederic posted in the comments above provides a good explanation of the normal classloader hurdles one encounters when attempting to read files from within their war file and the solutions around it.
You can try the below code.
Add this to servelt-context.xml
<context:property-placeholder location="classpath:config.properties"/>
And to access the contents of config file in java,
#Value("${KEY}")
private String value;