I would like to check that my application runs only under given set of allowed profiles.
I would write
#Autowire
Environment environment;
#PostConstruct
public void checkAndReportProfiles() {
// checks
... environment.getActiveProfiles()
but the problem is that this runs after all beans were attempted to initialize and failed if profiles were incorrect.
I want to check profiles explicitly and fail with explicit message.
Is it possible to do this?
Option 1: you can create a bean which performes the checks for you.
#Component
public class TestBean {
public TestBean() {
throw new BeanInitializationException("error message");
}
}
to execute this before certain problematic beans you can annotate theese beans with #DependsOn
But now you have a bean with no purpose hanging around.
Option 2:
Perform the checks in a configration class.
To get access to the spring evironment or configuration properties, you can inject them via constructor.
#Configuration
public class TestConfiguration {
public TestConfiguration(Environment environment, #Value("${some.key}") String property) {
String[] profiles = environment.getActiveProfiles();
// perform tests here
throw new BeanInitializationException("error message");
}
}
Option 3: Create beans programatically and check before the bean is created.
#Configuration
public class TestConfiguration {
#Value("${some.key}")
String property;
private final Environment environment;
public TestConfiguration(Environment environment) {
this.environment = environment;
}
#Bean
public SomeBean someBean() {
if(whateverToTest) {
throw new BeanInitializationException("error message");
}
return new SomeBean();
}
}
Option 4: Perform test in the constructor of that particular bean. The constructor is called first.
#Component
public class SomeBean {
public SomeBean() {
if(whateverToTest) {
throw new BeanInitializationException("error message");
}
}
}
Option 3 is the cleanest solution in my opinion, but also the most difficult to implement. Especially if you are using constructor based injections.
You can try this.
Create a class for keeping the association profile/error
public enum ErrorProfile {
default("This is the error num 1"),
dev("This is the error num 2"),
test("This is the error num 3"),
prod("This is the error num 4");
private String profile;
private String message;
ErrorProfile(String profile, String message){
this.profile = profile;
this.profile = message;
}
}
Then create a profile checker that execute at the lunch of your spring project during the context loading.
Java provides a valueOf(String) method for all enum types.
Thus, we can always get an enum value based on the declared name. This means that, after we get the value of the profile we can retrieve the message code with:
ErrorProfile.valueOf("prod")
Then it will be enough to print it in the Logs, Exception or whatever you need.
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
#Slf4j
#Configuration
public class PropertiesLogger {
#Value("${spring.profiles.active}")
private String activeProfile;
#PostConstruct
public void printProperties() {
log.debug("Loading application-context profiles. Active profile found: {} [message: {}]",
activeProfile,
ErrorProfile.valueOf(activeProfile));
// and in this case (cause you want to rise an exception with that message)
throw new RuntimeException(ErrorProfile.valueOf(activeProfile))
}
}
To explicitly check the Spring profile, you can use the following approach:
1)Set the active profile via the --spring.profiles.active application launch option:java -jar myapp.jar --spring.profiles.active=production
2)Set the active profile via the environment:
export SPRING_PROFILES_ACTIVE=productionjava -jar myapp.jar
3)Set the active profile via an application.properties or application.yml file:
# application.properties spring.profiles.active=production
# application.yml spring: profiles: active: production
Related
I have problems with injection #Value('${mybean.secret}') property into my bean during tests in Spock with Spring Boot & Groovy.
I have a very simple test class MyBeanTest
#ContextConfiguration(classes = [
MyAppConfig
])
#PropertySource([
"classpath:context/default-properties.yml"
])
class MyBeanTest extends Specification {
#Autowired
MyBean myBean
def "should populate properties "() {
expect:
myBean.secretProperty == "iCantTellYou"
}
}
And MyAppConfig.groovy as this:
#Configuration
class MyAppConfig {
#Bean
MyBean credential(#Value('${mybean.secret}') String secret) {
return new MyBean(secret)
}
}
When I run tests the value that is injected into secret is simply ${mybean.secret}.
The real value is not injected from properties file I enclose on test specification.
I'm using single-quote on #Value because of Groovy. Double quote with $ sign makes it being processed by groovy GString mechanism.
However, the problem doesn't occur on regular application run.
If I start application and put the breakpoint on the MyAppConfig#credential method the secret value is correctly read from the properties file, which are configured as follow:
#Configuration
#PropertySource(["classpath:context/default-properties.yml"])
class PropertiesConfig {
}
When I specify property from hand like this:
#TestPropertySource(properties = [
"mybean.secret=xyz"
])
class MyBeanTest extends Specification {
It works. The property is read. But it's not my goal, cause there's much more properties in the project and it would become cumbersone to define them everywhere from hand.
Can you spot the problem I am missing in this code?
The missing puzzle was YamlPropertySourceFactory.
public class YamlPropertySourceFactory implements PropertySourceFactory {
#Override
public PropertySource<?> createPropertySource(String name, EncodedResource encodedResource)
throws IOException {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(encodedResource.getResource());
Properties properties = factory.getObject();
return new PropertiesPropertySource(encodedResource.getResource().getFilename(), properties);
}
}
I was using yml properties files with nested properties:
mybean:
secret: xyz
And Spring was not loading them correctly.
I had to update #PropertySource annotation as well as follow:
#Configuration
#PropertySource(
value = ["classpath:context/default-properties.yml"],
factory = YamlPropertySourceFactory.class
)
class PropertiesConfig {
}
Now it works like a charm 😏.
I learned this on Baeldung website => https://www.baeldung.com/spring-yaml-propertysource
I am new to Spring Boot and I'm getting the following error when writing a file upload API:
Error:Description:
Field fileStorageService in com.primesolutions.fileupload.controller.FileController required a bean of type 'com.primesolutions.fileupload.service.FileStorageService' that could not be found.
The injection point has the following annotations:
- #org.springframework.beans.factory.annotation.Autowired(required=true)
Action:
Consider defining a bean of type 'com.primesolutions.fileupload.service.FileStorageService' in your configuration.*
Controller class:
public class FileController
{
private static final Logger logger = LoggerFactory.getLogger(FileController.class);
#Autowired
private FileStorageService fileStorageService;
#PostMapping("/uploadFile")
public UploadFileResponse uploadFile(#RequestParam("file") MultipartFile file) {
String fileName = fileStorageService.storeFile(file);
String fileDownloadUri = ServletUriComponentsBuilder.fromCurrentContextPath()
.path("/downloadFile/")
.path(fileName)
.toUriString();
return new UploadFileResponse(fileName, fileDownloadUri,
file.getContentType(), file.getSize());
}
#PostMapping("/uploadMultipleFiles")
public List<UploadFileResponse> uploadMultipleFiles(#RequestParam("files") MultipartFile[] files) {
return Arrays.asList(files)
.stream()
.map(file -> uploadFile(file))
.collect(Collectors.toList());
}
}
Service class:
private final Path fileStorageLocation;
#Autowired
public FileStorageService(FileStorageProperties fileStorageProperties) {
this.fileStorageLocation = Paths.get(fileStorageProperties.getUploadDir())
.toAbsolutePath().normalize();
try {
Files.createDirectories(this.fileStorageLocation);
} catch (Exception ex) {
throw new FileStorageException("Could not create the directory where the uploaded files will be stored.", ex);
}
}
public String storeFile(MultipartFile file) {
// Normalize file name
String fileName = StringUtils.cleanPath(file.getOriginalFilename());
try {
// Check if the file's name contains invalid characters
if(fileName.contains("..")) {
throw new FileStorageException("Sorry! Filename contains invalid path sequence " + fileName);
}
// Copy file to the target location (Replacing existing file with the same name)
Path targetLocation = this.fileStorageLocation.resolve(fileName);
Files.copy(file.getInputStream(), targetLocation, StandardCopyOption.REPLACE_EXISTING);
return fileName;
} catch (IOException ex) {
throw new FileStorageException("Could not store file " + fileName + ". Please try again!", ex);
}
}
Configuration class:
#ConfigurationProperties(prefix = "file")
public class FileStorageProperties {
private String uploadDir;
public String getUploadDir()
{
return uploadDir;
}
public void setUploadDir(String uploadDir) {
this.uploadDir = uploadDir;
}
}
Main:
#SpringBootApplication
#EnableConfigurationProperties({
FileStorageProperties.class
})
public class FileApplication {
public static void main(String[] args) {
SpringApplication.run(FileApplication.class, args);
}
}
properties file
## MULTIPART (MultipartProperties)
# Enable multipart uploads
spring.servlet.multipart.enabled=true
# Threshold after which files are written to disk.
spring.servlet.multipart.file-size-threshold=2KB
# Max file size.
spring.servlet.multipart.max-file-size=200MB
# Max Request Size
spring.servlet.multipart.max-request-size=215MB
## File Storage Properties
# All files uploaded through the REST API will be stored in this directory
file.upload-dir=C:/Projects/SpringBootProject/Primesolutions/PrimeSolutions/FileUpload
I'm trying to read the file upload property and pass it to the controller class.
The error seems to indicate that Spring does not know any bean of type com.primesolutions.fileupload.service.FileStorageService.
As said in the comment, make sure you class FileStorageServiceis annotated by #Service or #Component:
#Service
public class FileStorageService {
...
}
Make also sure that this class is located in a sub-package of your class FileApplication. For example, if your FileApplication class is located in a package com.my.package, make sure your FileStorageService is located in the package com.my.package.** (same package or any sub package).
Few notes to improve your code by the way :
When your class has only one not default constructor, the use of #Autowired on the constructor is optional.
Do not put too much code in your constructor. Use instead the #PostConstruct annotation.
#Service
public class FileStorageService {
private FileStorageProperties props;
// #Autowired is optional in this case
public FileStorageService (FileStorageProperties fileStorageProperties) {
this.props = fileStorageProperties;
this.fileStorageLocation = Paths.get(fileStorageProperties.getUploadDir())
.toAbsolutePath().normalize();
}
#PostConstruct
public void init() {
try {
Files.createDirectories(this.fileStorageLocation);
} catch (Exception ex) {
throw new FileStorageException("Could not create the directory where the uploaded files will be stored.", ex);
}
}
}
It is better to avoid the #Autowired on a field. Use the constructor instead. It is better for your tests, and more maintainable:
public class FileController {
private FileStorageService service;
public FileController(FileStorageService service) {
this.service = service;
}
}
I solved this problem using where i use #Autowired annotation just replace with this`
#Autowired(required = false)
`
When #Autowired doesn’t work
There are several reasons #Autowired might not work.
When a new instance is created not by Spring but by for example manually calling a constructor, the instance of the class will not be registered in the Spring context and thus not available for dependency injection. Also when you use #Autowired in the class of which you created a new instance, the Spring context will not be known to it and thus most likely this will also fail.
Another reason can be that the class you want to use #Autowired in, is not picked up by the ComponentScan. This can basically be because of two reasons.
The package is outside the ComponentScan search path. Move the
package to a scanned location or configure the ComponentScan to
fix this.
The class in which you want to use #Autowired does not have a
Spring annotation. Add one of the following annotatons to the class:
#Component, #Repository, #Service, #Controller,
#Configuration. They have different behaviors so choose carefully!
Read more here.
I solved this problem using :
#ComponentScan({ "com.yourpkg.*" })
Make sure you #ComponentScan cover all classes contains annotatons : #Component, #Repository, #Service, #Controller, #Configuration.
Reference : https://technology.amis.nl/2018/02/22/java-how-to-fix-spring-autowired-annotation-not-working-issues/
Tried with removing the (exclude = {DataSourceAutoConfiguration.class }) parameter with #SpringBootApplication:
Before:
#SpringBootApplication(exclude = {DataSourceAutoConfiguration.class })
public class SpringBootMain { ...
After:
#SpringBootApplication
public class SpringBootMain { ...
Worked for me.
The class which is going to be Autowired should be marked with #Service or #Component. Also if the class is in different package then need to add the #ComponentScan annotation in the main class as follows.
#ComponentScan({"com.beta.replyservice", "com.beta.ruleService"})
#SpringBootApplication
Solution is
#Autowired(required = false)
private FileStorageService fileStorageService;
I had the same issue. It was solved for me when I added a dependency on "spring-webmvc".
When I had the same problem, I just added a default constructor in my service class and it started working.
=> Error should look like this:
***************************
APPLICATION FAILED TO START
***************************
Description:
Field accountPresentation in june14th.account.TestSpringBoot required a bean of type 'june14th.controller.AccountPresentation' that could not be found.
The injection point has the following annotations:
- #org.springframework.beans.factory.annotation.Autowired(required=true)
Action:
Consider defining a bean of type 'june14th.controller.AccountPresentation' in your configuration.
Solution to the problem:-
First and most important step is look whether your code has the needed dependencies.
Check your Folder structure (this error is mainly due to this problem, always check your package structure) i.e, check wheather the main package and its sub packages are well structured
eg:
package X.account; //consider this is my main file package
package X.controller; //consider this as my presentation file package
when you run this program this will cause our "APPLICATION FAILED TO START" error, because of our package structure..look
package X.account; //main file package [the next fie should inside this package i.e as a sub package]
package.X.account.controller // this is the right way
I think this should solve your problem.
Put #Autowired(required=true) //do only if above will not work
Make sure to have respective annotations for classes. The same issue got solved for me when I add #Service annotation for interfaces and implemented service classes.
I use #Service on the service class which has to be Autowired. It solves my error. or you can use #Autowired(required = false) to disable the auto wiring for a particular instance.
I want to implement the following use case - my Spring Boot application should start only if a certain property in application.yaml is set:
myapp:
active: true
If the property is not set, the context initialization should fail with a message that the property is missing.
I found in this topic how to achieve it: Spring Boot - Detect and terminate if property not set? but the problem why I can't follow this approach is that it is possible that the context initialization fails before the bean, that checks this property, is loaded.
For example, if some other bean fails to load because another property is missing, the context initialization will fail at that point and my bean, that checks the desired property, won't be loaded. This is not OK for me because I want the myapp.active property to be checked first, before any other beans get loaded.
The reason why I want to have it that way is that a certain profile should be set when running the app - the application-[profile].yaml contains both myapp.active: true and some other mandatory properties that are required to load the context.
I want my app always to fail because of myapp.active not being true so that I can output a meaningful message telling that the profile is missing and that the app must be run with one of the profiles (from given list of profiles). Some guys that aren't developers are running the app so I want them to know why the app didn't run, otherwise they will think there is some bug in the app.
How can I achieve this? Is it possible somehow to read the property before the beans are being loaded? I would like to avoid setting #DependsOn on all beans (or doing the same through a BeanPostProcesser) and am seeking for a more elegant solution.
The application won't start if you use a condition-on-property. Fast enough?
#SpringBootApplication
#ConditionalOnProperty(name = "myapp.active")
public class FastFailWhenPropertyNotPresentApplication {
public static void main(String[] args) {
SpringApplication.run(FastFailWhenPropertyNotPresentApplication.class, args);
}
}
Basically #SpringBootApplication is just a #Configuration class.
You have an option matchIfMissing that you can use to specify if the condition should match if the property is not set. Defaults to false.
EDIT:
A better solution is to configure your property via a #ConfigurationProperties combined with #Validated, so you can use the javax.validation.constraints annotations.
package stackoverflow.demo;
import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.NotNull;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
#Component
#ConfigurationProperties(prefix = "myapp")
#Validated
public class MyAppProperties {
#AssertTrue
#NotNull
private Boolean active;
public Boolean getActive() {
return active;
}
public void setActive(Boolean active) {
this.active = active;
}
}
note: you can leave out #ConditionalOnProperty(name = "myapp.active")
use #AssertTrue in combination with #NotNull because of #AssertTrueconsiders null elements as valid.
and spring-boot generates a nice error-message for free:
***************************
APPLICATION FAILED TO START
***************************
Description:
Binding to target org.springframework.boot.context.properties.bind.BindException: Failed to bind properties under 'myapp' to stackoverflow.demo.MyAppProperties failed:
Property: myapp.active
Value: false
Origin: class path resource [application.properties]:1:16
Reason: must be true
Action:
Update your application's configuration
EDIT (after updated question)
A faster way: your application won't start, nor an application-context will be loaded
package stackoverflow.demo;
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.core.io.ClassPathResource;
#SpringBootApplication
public class FastFailWhenPropertyNotPresentApplication {
static Boolean active;
static {
YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean();
yaml.setResources(new ClassPathResource("application.yaml"));
active = (Boolean) yaml.getObject().getOrDefault("myapp.active", false);
}
public static void main(String[] args) {
if (!active) {
System.err.println("your fail message");
} else {
SpringApplication.run(FastFailWhenPropertyNotPresentApplication.class, args);
}
}
}
EDIT
another solution that probably fits your needs best...
By listening to the ApplicationEnvironmentPreparedEvent
Event published when a {#link SpringApplication} is starting up and the
* {#link Environment} is first available for inspection and modification.
*
note: you cannot use a #EventListener, but you have add the Listener to the SpringApplication
package stackoverflow.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.context.ApplicationListener;
#SpringBootApplication
public class FastFailWhenPropertyNotPresentApplication {
static class EnvironmentPrepared implements ApplicationListener<ApplicationEnvironmentPreparedEvent>{
#Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
Boolean active = event.getEnvironment().getProperty("myapp.active",Boolean.class,Boolean.FALSE);
if(!active) {
throw new RuntimeException("APPLICATION FAILED TO START: ACTIVE SHOULD BE TRUE ");
}
}
};
public static void main(String[] args) throws Exception {
SpringApplication springApplication = new SpringApplication(FastFailWhenPropertyNotPresentApplication.class);
springApplication.addListeners(new FastFailWhenPropertyNotPresentApplication.EnvironmentPrepared());
springApplication.run(args);
}
}
One option would be to create a bean which checks for the presence of the property, and throw an exception during bean creation if the property is not set.
#Component
public static EnsureApplicationActive {
#Value("${myapp.active}")
private Boolean active;
#PostConstruct
public void ensureApplicationActive() {
if (!Boolean.TRUE.equals(active)) {
throw new IllegalStateException("TODO: Meaningful message goes here");
}
}
}
Is there any way to load a class marked with #ConfigurationProperties without using a Spring Context directly? Basically I want to reuse all the smart logic that Spring does but for a bean I manually instantiate outside of the Spring lifecycle.
I have a bean that loads happily in Spring (Boot) and I can inject this into my other Service beans:
#ConfigurationProperties(prefix="my")
public class MySettings {
String property1;
File property2;
}
See the spring docco for more info http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#boot-features-external-config-command-line-args
But now I need to access this bean from a class that is created outside of Spring (by Hibernate). The class is created so early in the app startup process that Spring Boot has not yet made the application context available through the classic lookup helper methods or roll-my-own static references.
So I instead want to do something like:
MySettings mySettings = new MySettings();
SpringPropertyLoadingMagicClass loader = new SpringPropertyLoadingMagicClass();
loader.populatePropertyValues(mySettings);
And have MySettings end up with all its values loaded, from the command line, system properties, app.properties, etc. Is there some class in Spring that does something like this or is it all too interwoven with the application context?
Obviously I could just load the Properties file myself, but I really want to keep Spring Boot's logic around using command line variables (e.g. --my.property1=xxx), or system variables, or application.properties or even a yaml file, as well as its logic around relaxed binding and type conversion (e.g. property2 is a File) so that it all works exactly the same as when used in the Spring context.
Possible or pipe dream?
Thanks for your help!
I had the same "issue".
Here is how I solved it in SpringBoot version 1.3.xxx and 1.4.1.
Let's say we have the following yaml configuration file:
foo:
apis:
-
name: Happy Api
path: /happyApi.json?v=bar
-
name: Grumpy Api
path: /grumpyApi.json?v=grrr
and we have the following ConfigurationProperties:
#ConfigurationProperties(prefix = "foo")
public class ApisProperties {
private List<ApiPath> apis = Lists.newArrayList();
public ApisProperties() {
}
public List<ApiPath> getApis() {
return apis;
}
public static class ApiPath {
private String name;
private String path;
public String getName() {
return name;
}
public void setName(final String aName) {
name = aName;
}
public String getPath() {
return path;
}
public void setPath(final String aPath) {
path = aPath;
}
}
}
Then, to do the "magic" things of Spring Boot programmatically (e.g. loading some properties in a static method), you can do:
private static ApisProperties apiProperties() {
try {
ClassPathResource resource;
resource = new ClassPathResource("/config/application.yml");
YamlPropertiesFactoryBean factoryBean;
factoryBean = new YamlPropertiesFactoryBean();
factoryBean.setSingleton(true); // optional depends on your use-case
factoryBean.setResources(resource);
Properties properties;
properties = factoryBean.getObject();
MutablePropertySources propertySources;
propertySources = new MutablePropertySources();
propertySources.addLast(new PropertiesPropertySource("apis", properties));
ApisProperties apisProperties;
apisProperties = new ApisProperties();
PropertiesConfigurationFactory<ApisProperties> configurationFactory;
configurationFactory = new PropertiesConfigurationFactory<>(apisProperties);
configurationFactory.setPropertySources(propertySources);
configurationFactory.setTargetName("foo"); // it's the same prefix as the one defined in the #ConfigurationProperties
configurationFactory.bindPropertiesToTarget();
return apisProperties; // apiProperties are fed with the values defined in the application.yaml
} catch (BindException e) {
throw new IllegalArgumentException(e);
}
}
Here's an update to ctranxuan's answer for Spring Boot 2.x. In our situation, we avoid spinning up a Spring context for unit tests, but do like to test our configuration classes (which is called AppConfig in this example, and its settings are prefixed by app):
public class AppConfigTest {
private static AppConfig config;
#BeforeClass
public static void init() {
YamlPropertiesFactoryBean factoryBean = new YamlPropertiesFactoryBean();
factoryBean.setResources(new ClassPathResource("application.yaml"));
Properties properties = factoryBean.getObject();
ConfigurationPropertySource propertySource = new MapConfigurationPropertySource(properties);
Binder binder = new Binder(propertySource);
config = binder.bind("app", AppConfig.class).get(); // same prefix as #ConfigurationProperties
}
}
The "magic" class you are looking for is PropertiesConfigurationFactory. But I would question your need for it - if you only need to bind once, then Spring should be able to do it for you, and if you have lifecycle issues it would be better to address those (in case they break something else).
This post is going into similar direction but extends the last answer with also validation and property placeholder resolutions.
Spring Boot Binder API support for #Value Annotations
#Value annotations in ConfigurationPropertys don't seem to bind properly though (at least if the referenced values are not part of the ConfigurationProperty's prefix namespace).
import org.springframework.boot.context.properties.bind.Binder
val binder = Binder.get(environment)
binder.bind(prefix, MySettings.class).get
I would like to have a properties setup which can, on certain environments, override specific properties. For example, our default JDBC properties for dev are:
db.driverClassName=com.mysql.jdbc.Driver
db.url=jdbc:mysql://localhost:3306/ourdb
db.username=root
db.password=
The problem is that some of our devs would like to have a different username/password on the db, or possibly even a non locally hosted db. The same is true for our rabbitMQ configuration, which currently uses a similar localhost, guest/guest setup. Being able to override the properties of certain elements of this configuration on a per-developer basis would allow us to move much of the infrastructure/installation requirements for building the software off the local machine and onto dedicated servers.
I have set-up a simple project to wrap my head around the configuration required to achieve what I want, and this is my first foray into the world of spring property configuration, since up till now, property loading and management is done with some custom code. Here is my setup:
class Main_PropertyTest {
public static void main(String[] args) {
String environment = System.getenv("APPLICATION_ENVIRONMENT"); // Environment, for example: "dev"
String subEnvironment = System.getenv("APPLICATION_SUB_ENVIRONMENT"); // Developer name, for example: "joe.bloggs"
System.setProperty("spring.profiles.active", environment);
System.setProperty("spring.profiles.sub", subEnvironment);
try(AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(PropertyTestConfiguration.class)) {
Main_PropertyTest main = context.getBean(Main_PropertyTest.class);
main.printProperty();
}
}
private final String property;
public Main_PropertyTest(String property) {
this.property = property;
}
public void printProperty() {
System.out.println("And the property is: '" + property + "'.");
}
}
And my configuration:
#Configuration
public class PropertyTestConfiguration {
#Bean
public static PropertySourcesPlaceholderConfigurer primaryPlaceholderConfigurer() {
PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
propertySourcesPlaceholderConfigurer.setLocation(new ClassPathResource(System.getProperty("spring.profiles.active") + ".main.properties"));
return propertySourcesPlaceholderConfigurer;
}
#Bean
public static PropertySourcesPlaceholderConfigurer secondaryPlaceholderConfigurer() {
PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
propertySourcesPlaceholderConfigurer.setLocation(new ClassPathResource(System.getProperty("spring.profiles.sub") + ".main.properties"));
propertySourcesPlaceholderConfigurer.setIgnoreResourceNotFound(true);
propertySourcesPlaceholderConfigurer.setIgnoreResourceNotFound(true);
propertySourcesPlaceholderConfigurer.setOrder(-1);
return propertySourcesPlaceholderConfigurer;
}
#Bean
public Main_PropertyTest main_PropertyTest(#Value("${main.property}") String property) {
Main_PropertyTest main_PropertyTest = new Main_PropertyTest(property);
return main_PropertyTest;
}
}
And for completeness, my dev.main.properties and test.main.properties:
main.property=dev
main.property=test
The main problem is that I get an illegal argument exception. As far as I can tell, what I have written should be the javaconfig equivalent of this method: http://taidevcouk.wordpress.com/2013/07/04/overriding-a-packaged-spring-application-properties-file-via-an-external-file/
Unfortunately I get the following error: java.lang.IllegalArgumentException: Could not resolve placeholder 'main.property' in string value "${main.property}". Note that I also need to take care of the case where there is no sub-environment, and this is the case I have started with (although I get the same error even if both files exist). If I remove the bean which sets up the second propertysourcesplaceholderconfigurer, then it all works fine (by which I mean dev.main.properties is loaded and "And the property is: 'dev'." is printed out).
A secondary problem is that the code doesn't look great, and each layer of the system will need two PSPC's set-up so that they can access these properties. Furthermore, it requires a lot of manual calls to System.getProperty(), since I couldn't pass ${spring.profiles.active} to PSPC.setLocation();
Note: I have tried #PropertySources({primaryproperties, secondaryProperties}), but this fails because secondaryProperties does not exist. I have also tried #Autowired Environment environment; and getting the properties from that, but the secondary PSPC causes the environment to not be autowired...
So following this lengthy explanation, my questions are:
Is this the right way of solving this problem?
If so, what is wrong with my configuration?
How can I simplify the configuration (if at all)?
Is there an alternative mechanism available which would solve my problem?
Thank you for your time! :)
Your configuration is flawed when configuring BeanFactoryPostProcessor with java config the methods should be static. However it can be even easier, instead of registering your own PropertySourcesPlaceholderConfigurer utilize the default #PropertySource support.
Rewerite your jav config to the following
#Configuration
#PropertySource(name="main", value= "${spring.profiles.active}.main.properties")
public class PropertyTestConfiguration {
#Autowired
private Environment env;
#PostConstruct
public void initialize() {
String resource = env.getProperty("spring.profiles.sub") +".main.properties";
Resource props = new ClassPathResource(resource);
if (env instanceof ConfigurableEnvironment && props.exists()) {
MutablePropertySources sources = ((ConfigurableEnvironment) env).getPropertySources();
sources.addBefore("main", new ResourcePropertySource(props));
}
}
#Bean
public Main_PropertyTest main_PropertyTest(#Value("${main.property}") String property) {
Main_PropertyTest main_PropertyTest = new Main_PropertyTest(property);
return main_PropertyTest;
}
}
This should first load the dev.main.properties and additionally the test.main.properties which will override the earlier loaded properties (when filled ofcourse).
I had a similar issue with overwriting already existing properties in integration tests
I came up with this solution:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = {
SomeProdConfig.class,
MyWebTest.TestConfig.class
})
#WebIntegrationTest
public class MyWebTest {
#Configuration
public static class TestConfig {
#Inject
private Environment env;
#PostConstruct
public void overwriteProperties() throws Exception {
final Map<String,Object> systemProperties = ((ConfigurableEnvironment) env)
.getSystemProperties();
systemProperties.put("some.prop", "test.value");
}
}