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.
Related
I am working on the toggle module where I have to define toggle status in application.yml file but the surprising part is boolean value defined in yaml file when fetched in spring-boot code is always showing the default value as false however value in yaml file is true.
But we I rename is-enable to enable things work fine, is there any naming conversion restrictions in yaml files?
application.yml file values
name: test-YAML
environment: testing
is-enabled: true
Config File goes as below:-
#Configuration
#EnableConfigurationProperties
#ConfigurationProperties
#Data
public class YAMLConfig {
private String name;
private String environment;
private boolean isEnabled;
private List<String> servers = new ArrayList<String>();
private List<String> external = new ArrayList<String>();
private Map<String, String> map = new HashMap<String, String>();
private Component component = new Component();
}
I am running my main application like this
#SpringBootApplication
public class MyApplication implements CommandLineRunner {
#Autowired
private YAMLConfig myConfig;
public static void main(String[] args) {
SpringApplication app = new SpringApplication(MyApplication.class);
app.run();
}
public void run(String... args) throws Exception {
System.out.println("name:" + myConfig.getName());
System.out.println("enabled:" + myConfig.isEnabled());
}
Let me know if more details required. I using sample project from project-from-git
This is beacause your properties will be bind using setters if you are using default constructor.
See docs
So, when you have class like this:
#Configuration
#EnableConfigurationProperties
#ConfigurationProperties
#Data
public class YAMLConfig {
private String name;
private String environment;
private boolean isEnabled;
...
}
As you know, Lombok will generate getters and setters for you, so it will look like this:
#Configuration
#EnableConfigurationProperties
#ConfigurationProperties
#Data
public class YAMLConfig {
private String name;
private String environment;
private boolean isEnabled;
private List<String> servers = new ArrayList<String>();
private List<String> external = new ArrayList<String>();
private Map<String, String> map = new HashMap<String, String>();
...
public boolean isEnabled() {
return isEnabled;
}
public void setEnabled(boolean enabled) {
isEnabled = enabled;
}
}
If you have property is-enabled in your yml file, since this is boolean value, spring will look for setter setIsEnabled in your class, and since that setter doesn't exist it won't bind value for you, soisEnabled property will be set to false by default.
if you add manually setter like this:
public void setIsEnabled(boolean enabled) {
isEnabled = enabled;
}
Your property should be bind.
BTW, you shoud avoid using #ConfigurationProperties withot specifying prefix.
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 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 yaml-property file:
myPrefix:
value: Hello
myPrefix2:
value: World
And two classes
#PropertySource("classpath:permission-config.yml")
#ConfigurationProperties(prefix = "myPrefix")
#Component
#Getter
#Setter
public class ViewUsers {
private String value;
}
and
#PropertySource("classpath:permission-config.yml")
#ConfigurationProperties(prefix = "myPrefix2")
#Component
#Getter
#Setter
public class ManageUsers {
private String value;
}
Then null is injected.
Or, if I try to use #Value then ONLY latest value is retrieved, which is the last one (World), the preceding ones are always ignored.
Try the following approach:
Remove #Component from configuration properties (ViewUsers and ManageUsers)
Instead, use the following construction:
#PropertySource("classpath:permission-config.yml")
#ConfigurationProperties(prefix = "myPrefix")
public class ViewUsers {
private String value; // getter, setter
}
#PropertySource("classpath:permission-config.yml")
#ConfigurationProperties(prefix = "myPrefix2")
public class ManageUsers {
private String value; // getter, setter
}
#Configuration
#EnableConfigurationProperties({ViewUsers.class, ManageUsers.class}
public class MySampleConfiguration {
... beans here...
}
Also make sure that Lombok annotations are working as expected (Try to use without lombok just for the POC).
Update 1
As #M. Deinum has kindly stated, PropertySource like this doesn't work with yaml files.
You can try the following workaround:
#Configuration
#PropertySource(name = "someName", value = "classpath:foo.yaml", factory = YamlPropertySourceFactory.class)
public class MyConfig {
}
import org.springframework.boot.env.YamlPropertySourceLoader;
public class YamlPropertySourceFactory implements PropertySourceFactory {
#Override
public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
final List<PropertySource<?>> load = new YamlPropertySourceLoader().load(name, resource.getResource());
return load.get(0);
}
}
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")