I'm using Spring Boot 2.0 with default application.yml properties file. I would like to split it to separate property files because it becomes huge.
Also I would like to write tests to check properties correctness: values that will present on production application context (not the test one).
Here is my property file: src/main/resources/config/custom.yml
my-property:
value: 'test'
Property class:
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
#Data
#Configuration
#ConfigurationProperties(prefix = "my-property")
#PropertySource("classpath:config/custom.yml")
public class MyProperty {
private String value;
}
Test:
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
#RunWith(SpringRunner.class)
#SpringBootTest(classes = MyProperty.class)
#EnableConfigurationProperties
public class MyPropertyTest {
#Autowired
private MyProperty property;
#Test
public void test() {
assertEquals("test", property.getValue());
}
}
But test fails with error:
java.lang.AssertionError:
Expected :test
Actual :null
Also I see that property value is null when running the application by printing it in ApplicationRunner.
When I used application.yml for all properties it were well with the same configuration.
How to put correct configuration for properties and tests for make it work?
Link to Github repo
Finely I found the right way for having custom yaml properties in my app.
The issue is that Spring doesn't support yaml files as #PropertySource (link to issue). And here is a workaround how to deal with that described in spring documentation.
So, to be able to load properties from yaml files you need:
* To implement EnvironmentPostProcessor
* To register it in spring.factories
Please visit this github repo for complete example.
Also, thanks a lot for your support, guys!
#TestPropertySource can solve your problem.
#RunWith(SpringRunner.class)
#SpringBootTest(classes = MyProperty.class)
#TestPropertySource(locations="classpath:test.properties")
public class MyPropertyTest {
#Autowired
private MyProperty property;
#Test
public void test() {
assertEquals("test", property.getValue());
}
}
Hope it helps.
I am a little late to the party, but this might also help. The solution provided as answer is the best approach so far, but here is an alternative that I used
Make use of profiles and modify the PropertySoucesPlaceHolderConfiguration bean to do load the necessary property files based on the profiles. It loads the application.properties as the default one but the other propertyfiles -oauth_DEV and oauth_QA are loaded based on the profiles set
#Bean
public PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurerconfigurer() {
System.out.println("Inside Placeholder bean");
PropertySourcesPlaceholderConfigurer cfg = new PropertySourcesPlaceholderConfigurer();
ClassPathResource cls1= new ClassPathResource("application.properties");
ClassPathResource cls2 = null;
Map<String, Object> propMap = ((ConfigurableEnvironment) ctx.getEnvironment()).getSystemProperties();
for(Map.Entry<String, Object> entrySet: propMap.entrySet()) {
System.out.println("Map.Key:"+entrySet.getKey()+" Map.valiue:"+entrySet.getValue());
}
List<String> profiles= Arrays.asList(ctx.getEnvironment().getActiveProfiles());
if(profiles == null || profiles.isEmpty()) {
if(!propMap.containsKey("spring.profiles.active")) {
cls2 = new ClassPathResource("oauth-default.properties");
} else {
cls2 = new ClassPathResource("oauth-"+propMap.get("spring.profiles.active")+".properties");
}
}else {
for(String profile:profiles) {
if(profile.equalsIgnoreCase("DEV")) {
cls2 = new ClassPathResource("oauth-DEV.properties");
}else if(profile.equalsIgnoreCase("QA")) {
cls2 = new ClassPathResource("oauth-QA.properties");
}else if (profile.equalsIgnoreCase("UAT")) {
cls2 = new ClassPathResource("oauth-UAT.properties");
}else if(profile.equalsIgnoreCase("PROD")){
cls2 = new ClassPathResource("oauth-PROD.properties");
}else {
cls2 = new ClassPathResource("oauth-default.properties");
}
}
}
cfg.setLocations(cls1,cls2);
//cfg.setPlaceholderPrefix("#{");
return cfg;
}
Then create another bean that reads the properties based on the prefix - "security.oauth2.client"
#Configuration
#ConfigurationProperties(prefix="security.oauth2.client")
public class OauthSecurityConfigurationDto {
private String clientId;
private String clientSecret;
private String scope;
private String accessTokenUri;
private String userAuthorizationUri;
private String grantType;
private String resourceIds;
private String registeredRedirectUri;
private String preEstablishedRedirectUri;
private String useCurrentUri;
private String userInfoUri;
public String getClientId() {
return clientId;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public String getClientSecret() {
return clientSecret;
}
public void setClientSecret(String clientSecret) {
this.clientSecret = clientSecret;
}
public String getScope() {
return scope;
}
public void setScope(String scope) {
this.scope = scope;
}
public String getAccessTokenUri() {
return accessTokenUri;
}
public void setAccessTokenUri(String accessTokenUri) {
this.accessTokenUri = accessTokenUri;
}
public String getUserAuthorizationUri() {
return userAuthorizationUri;
}
public void setUserAuthorizationUri(String userAuthorizationUri) {
this.userAuthorizationUri = userAuthorizationUri;
}
public String getGrantType() {
return grantType;
}
public void setGrantType(String grantType) {
this.grantType = grantType;
}
public String getResourceIds() {
return resourceIds;
}
public void setResourceIds(String resourceIds) {
this.resourceIds = resourceIds;
}
public String getRegisteredRedirectUri() {
return registeredRedirectUri;
}
public void setRegisteredRedirectUri(String registeredRedirectUri) {
this.registeredRedirectUri = registeredRedirectUri;
}
public String getPreEstablishedRedirectUri() {
return preEstablishedRedirectUri;
}
public void setPreEstablishedRedirectUri(String preEstablishedRedirectUri) {
this.preEstablishedRedirectUri = preEstablishedRedirectUri;
}
public String getUseCurrentUri() {
return useCurrentUri;
}
public void setUseCurrentUri(String useCurrentUri) {
this.useCurrentUri = useCurrentUri;
}
public String getUserInfoUri() {
return userInfoUri;
}
public void setUserInfoUri(String userInfoUri) {
this.userInfoUri = userInfoUri;
}
}
Remember the setters are important because the ConfigurationProperties load the values into the properties of the class only when getters and setters are defined
Now we can autowire the dependency wherever needed and use the property.
If This is your exact code that means you are reading your property from a wrong property file.
replace your property resource to this line.
#PropertySource("classpath:config/services.yml")
Related
I am unable to load property file in resources directory,
#Configuration
#PropertySource(value = "classpath:/test.properties", ignoreResourceNotFound = true)
public class ArgConfig {
#Autowired
private Environment env;
#Value("${store.name}")
private String name;
public String getName() {
return name;
}
}
test.properties contains -->
store.name=nike
Property Source from Spring Documentation
Followed the same from the documentation still unable to load the properties file.
Is it mandatory for you to use it as test.properties? if so, use the #PropertySource annotation. Otherwise, use it like application-test.properties and set the profile as test using #ActiveProfiles("test")
Another way is, place your application.properties under src/test/resources if you want to override the value.
Refer https://www.baeldung.com/spring-tests-override-properties for more information
Apologies for wasting precious time.
I found the answer, it is just to place the property file under resource directory(which I did even before but not sure why it thrown error).
Here is the entire code,
Project structure:
#RestController
#RequestMapping("/")
public class SampleRestController {
#Autowired
private Store storeDetails;
#GetMapping("/names")
public List<String> getNames(){
String storeName = storeDetails.getName();
System.out.println("Store Name = " + storeName);
return storeName!=null ? Arrays.asList(storeName) : Arrays.asList("store1","store2","store3");
}
}
#Configuration
#PropertySource("classpath:/store.properties")
public class StoreConfig {
#Autowired
Environment env;
#Bean
public Store storeDetails() {
Store store = new Store();
store.setName(env.getProperty("store.name"));
return store;
}
}
#Component
public class Store {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
#SpringBootApplication
public class SpringbootApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootApplication.class);
}
}
Thanks everyone!!!
I'm trying to read the values in the property file. Why I get values: [] in the output?
abc.env[0].envname=test
abc.env[0].grant_type=password
abc.env[1].envname=dev
abc.env[1].grant_type=password
Below is the class I'm trying to execute
#SpringBootApplication
public class AbcApplication implements CommandLineRunner{
private static Logger LOG = LoggerFactory.getLogger(AbcApplication.class);
#Autowired
ReadAbcApplicationProperties readAbcApplicationProperties;
public static void main(String[] args) {
SpringApplication.run(AbcApplication.class, args);
}
#Override
public void run(String... args) {
LOG.info("values: {}", readAbcApplicationProperties.getAbcSources());
}
}
#Component
#ConfigurationProperties(prefix = "abc")
public class ReadAbcApplicationProperties {
private List<AbcProperties> abcSources = new ArrayList<AbcProperties>();
#Autowired
ReadAbcApplicationProperties readAbcApplicationProperties;
public List<AbcProperties> getAbcSources() {
return abcSources;
}
public void setAbcSources(List<AbcProperties> abcSources) {
this.abcSources = abcSources;
}
}
public class AbcProperties {
private String envname;
private String tokenGrantType;
public String getEnvname() {
return envname;
}
public void setEnvname(String envname) {
this.envname = envname;
}
public String getTokenGrantType() {
return tokenGrantType;
}
}
Can someone help me with the missing part?
There are some inconsistencies in your code..
ReadAbcApplicationProperties - Why you inject itself in ?
#Autowired
ReadAbcApplicationProperties readAbcApplicationProperties;
This line must be removed.
field private String tokenGrantType is not consistent with abc.env[0].grant_type in properties file
private List<AbcProperties> abcSources is not consistent with abc.env[0] and abc.env[1] and ... abc.env[X] in properties file
The classes should be:
AbcProperties
public class AbcProperties {
private String envname;
private String grantType; //to match `grant_type` in .properties file
//getters setters
}
ReadAbcApplicationProperties
#ConfigurationProperties(prefix = "abc")
public class ReadAbcApplicationProperties {
private List<AbcProperties> env = new ArrayList<>(); //to match abc.env[X] in properties file
// getters setters
}
I tried the above in a spring-boot sample project and it works
You have different property names in property file and corresponding bean class AbcProperties. see tokenGrantType and grant_type.
An ideal configuration would be:
Apologies for using yml file instead of properties, but it all behaves the same. It's just yml would gives us more readability.
myapp: # this can be your prefix
abc:
-
envname: test
grant_type: password
-
envname: dev
grant_type: password
Having "myapp" as prefix(top-level key), woudl enable you to add any number of properties inside a single class like below 'ConfigPropertiesBinder'.
#Data // If not, include getter and setters, with cons.
#RefreshScope
#Configuration
#ConfigurationProperties(prefix = "myapp")
public class ConfigPropertiesBinder {
private List<AbcProperties> abc; // Should match with myapp.abc
// Other properties
}
#Data
public class AbcProperties{
private String envname; // Match with myapp.abc[x].envname
private String grantType; // '-' and '_' converts to camelcase
}
See if it helps.
i have a question here, please give some ideas.
I have two beans. FaceComparisonServerImpl depends on FaceServer.
When i want to test. I want to change the String in my 'FaceServer' bean.
#Service
public class FaceComparisonServerImpl implements FaceComparisonServer {
#Autowired
private FaceServer faceServer;
#Override
public FaceComparsionInfo getServerInfo() {
String serverInfo = faceServer.getServerInfo();
...
}
}
#Component
public class FaceServer {
#Autowired
private RestTemplate restTemplate;
//Not final, just to test.
private String version = "1.0";
private static final String CODE = "code";
private static final String MESSAGE = "message";
//Final
private static final String SERVER_URL = "http://127.0.0.1:8066/api/ZKComparison";
}
Bellow is my test code.
#ExtendWith(SpringExtension.class)
#ContextConfiguration(classes = TestConfig.class)
public class FaceServerTestByTyler {
#Autowired
private FaceComparisonServer faceComparisonServer;
#Test
public void getServerInfo(){
//How can i modify the value of SERVER_URL in faceServer?
FaceComparsionInfo serverInfo = faceComparisonServer.getServerInfo();
System.out.println(serverInfo);
}
}
My question is:
How can i modified the value of 'version' and 'SERVER_URL' in #Bean(faceServer)?
Thanks you!
You need create FaceServer mock bean for test configuration.
And override required methods
#Configuration
Class TestConfig{
#Bean
#Primary
public FaceServer faceServer() {
return new FaceServer() {
#override
public String getServerInfo(){
return "required info";
}
};
}
}
The easiest way to customize the values is to make them Spring properties:
#Component
public class FaceServer {
#Value("${faceServer.version}")
private String version;
#Value("${faceServer.url}")
private String serverUrl;
// ...
}
You can either have default values for the #Value annotations or use some default property values in application.yml.
Now just override those properties in your test with the values you want:
#ExtendWith(SpringExtension.class)
#ContextConfiguration(classes = TestConfig.class)
#TestPropertySource(properties = {
"faceServer.version=1.0",
"faceServer.url=http://127.0.0.1:8066/api/ZKComparison"
})
public class FaceServerTestByTyler {
#Autowired
private FaceComparisonServer faceComparisonServer;
// ...
}
However...
The second option is to make your classes more unit-testable. Prefer construction injection over field injection, and you can test your classes more independently.
#Service
public class FaceComparisonServerImpl implements FaceComparisonServer {
private final FaceServer faceServer;
public FaceComparisonServerImpl(FaceServer faceServer) {
this.faceServer = faceServer;
}
#Override
public FaceComparsionInfo getServerInfo() {
String serverInfo = faceServer.getServerInfo();
// ...
}
}
This now becomes unit-testable:
public class FaceServerTestByTyler {
private FaceComparisonServer faceComparisonServer;
private FaceServer faceServer;
#BeforeEach
public setup() {
faceServer = mock(FaceServer.class);
faceComparisonServer = new FaceComparisonServer(faceServer);
}
#Test
public void getServerInfo() {
when(faceServer.getServerInfo()).thenReturn(xxx);
// ...
}
}
The second option ends up with a test that runs much faster than any solutions that suggest to create a mock bean through a test configuration.
I have the following code in a sample Spring Boot Application
#Configuration
#PropertySource("classpath:second.properties")
public class PropertyConfig {
#Value("#{guru.username}")
String user;
#Value("#{guru.password}")
String password;
#Value("#{guru.url}")
String url;
#Bean
FakeDataSource getFakeDataSource() {
FakeDataSource fk = new FakeDataSource();
fk.setName(user);
fk.setPassword(password);
fk.setUrl(url);
return fk;
}
#Bean
PropertySourcesPlaceholderConfigurer getPropertySourcesPlaceholderConfigurer() {
PropertySourcesPlaceholderConfigurer placeholderConfigurer= new PropertySourcesPlaceholderConfigurer();
//placeholderConfigurer.setLocation(new ClassPathResource("second.properties"));
return placeholderConfigurer;
}
}
And FakeDataSource is a simple pojo with the name, passowrd, url properties.
Then my main application
#SpringBootApplication
public class SpringGuru101DependencyInjectionApplication {
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(SpringGuru101DependencyInjectionApplication.class, args);
// Step 2: Make Class
FakeDataSource fakeDataSource = ctx.getBean(FakeDataSource.class);
System.out.println(fakeDataSource.getName());
}
}
but the sout statement is printing null,
my second.properties file is present in my resources directory with following content
guru.username=Saurabh
guru.password=ido
guru.url=http://example.com
There are two places should be corrected:
(1) As I said in the comment of your question, you should replace the wellhead sign (#) to dollar sign ($) for reading values from your configuration file. For example: #Value("${guru.username}").
(2) You missed public static in front of the method getPropertySourcesPlaceholderConfigurer.
And this modified method should be looked like as follows:
#Bean
public static PropertySourcesPlaceholderConfigurer getPropertySourcesPlaceholderConfigurer() {
PropertySourcesPlaceholderConfigurer placeholderConfigurer= new PropertySourcesPlaceholderConfigurer();
//placeholderConfigurer.setLocation(new ClassPathResource("second.properties"));
return placeholderConfigurer;
}
If you are using Spring-boot, you can use another method for reading configuration which is Spring boot recommended. This will help you get rid of all the #Value notations, allowing spring inject properties without additional hints.
You can potentially do something like:
#ConfigurationProperties("foo")
public class FooProperties {
private boolean enabled;
private InetAddress remoteAddress;
private final Security security = new Security();
public boolean isEnabled() { ... }
public void setEnabled(boolean enabled) { ... }
public InetAddress getRemoteAddress() { ... }
public void setRemoteAddress(InetAddress remoteAddress) { ... }
public Security getSecurity() { ... }
public static class Security {
private String username;
private String password;
private List<String> roles = new ArrayList<>(Collections.singleton("USER"));
public String getUsername() { ... }
public void setUsername(String username) { ... }
public String getPassword() { ... }
public void setPassword(String password) { ... }
public List<String> getRoles() { ... }
public void setRoles(List<String> roles) { ... }
}
}
The POJO above defines the following properties:
foo.enabled, false by default
foo.remote-address, with a type that can be coerced from String
foo.security.username, with a nested "security" whose name is determined by the name of the property. In particular the return type is not used at all there and could have been SecurityProperties
foo.security.password
foo.security.roles, with a collection of String
More details: https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html
In the property file (config.properties) I defined several properties:
my.property.first=xyz
my.property.second=12
I created a class to read these properties:
package my.package.first;
............
#Configuration
public Class MyProperties {
#Value("${my.property.first}") private String propertyFirst;
#Value ("${my.property.second}") private String propertySecond;
public String getPropertyFirst() {
return propertyFirst;
}
public int getPropertySecond() {
return propertySecond
}
#Bean
public MyProperties getInstance() {
return this;
}
}
Now I want to use these properties in a class placed in some other package:
package my.otherpackage.third;
import my.property.package.first.MyProperties;
.............................
public class GetMyProperties{
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyProperties.class);
MyProperties myProperties =context.getBean("getInstance",MyProperties.class);
//This returns ${my.property.first}
String propertyFirst = myProperties.getPropertyFirst();
// This returns ${my.property.second}
int propertySecond = context.getBeanFactory().resolveEmbeddedValue(("${my.property.first}"));
}
I try to solve this by using only Annotations.
I use Spring 4.
Thanks
MyProperties isn't really a #Configuration class, it is a bean. Give it the annotation #Component and move the #Bean declaration to another #Configuration class. It doesn't matter where the bean is defined for spring (as long as it is in a configuration class), it'll pick it up if you have the #ComponentScan or #SpringBootApplication somewhere in your app, which I'm pretty sure you do. your MyProperties class you make #Component
You'll want:
#Component
public class MyProperties {
#Value("${my.property.first}") private String propertyFirst;
#Value ("${my.property.second}") private String propertySecond;
public String getPropertyFirst() {
return propertyFirst;
}
public void setPropertyFirst(String propertyFirst){
this.propertyFirst = propertyFirst;
}
public int getPropertySecond() {
return propertySecond
}
public void setPropertySecond(String propertySecond){
this.propertySecond= propertySecond;
}
}
#Configuration
public class Config {
#Bean
public MyProperties getInstance() {
return new MyProperties();
}
}
package my.otherpackage.third;
import my.property.package.first.MyProperties;
.....
#EnableAutoConfiguration
#ComponentScan(basePackages = {"my"})
public class GetMyProperties{
public static void main(String[] args){
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(GetMyProperties.class);
MyProperties myProperties =context.getBean("getInstance",MyProperties.class);
//This returns the value of ${my.property.first}
String propertyFirst = myProperties.getPropertyFirst();
// This returns the value of ${my.property.second}
int propertySecond = myProperties.getPropertySecond();
}
}