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
Related
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 consumer.properties file with the following contents in src/main/resources, and an accompanying Configuration class that loads and stores the file's contents into class member variables:
//consumer.properties file in src/main/resources:
com.training.consumer.hostname=myhost
com.training.consumer.username=myusername
com.training.consumer.password=mypassword
//ConsumerConfig.java
#Configuration
#PropertySource(
value= {"classpath:consumer.properties"}
)
#ConfigurationProperties(prefix="com.training.consumer")
public class ConsumerConfig {
private String hostname;
private String username;
private String password;
public ConsumerConfig() { }
public String getHostname() {
return hostname;
}
public void setHostname(String hostname) {
this.hostname = hostname;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
#Override
public String toString() {
return "ConsumerConfig [hostname=" + hostname + ", username=" + username + ", password=" + password + "]";
}
}
I also have a ConfigsService class that autowires the ConsumerConfig class to retrieve the individual properties:
#Component
public class ConfigsService {
#Autowired
ConsumerConfig consumerConfig;
public ConsumerConfig getConsumerConfig() {
return consumerConfig;
}
public void showConfig() {
consumerConfig.toString();
}
public ConsumerConfig getConfig() {
return consumerConfig;
}
}
The properties are loaded up just fine when running the ConfigsService's methods. The problem is in the unit tests, where invoking configService.getConfig().getHostname() returns a null value -- even after having created a src/test/resources directory, and adding my consumer.properties file in it:
#TestPropertySource("classpath:consumer.properties")
public class ConfigsServiceTest {
#Mock
ConsumerConfig consumerConfig;
#InjectMocks
ConfigsService configService;
#Before
public void beforeEach() {
MockitoAnnotations.initMocks(this);
}
#Test
public void someTest() {
System.out.println(configService.getConfig().getHostname()); //outputs null here -- wth!
Assert.assertTrue(true);
}
}
you are getting null values because of the mock object. I will suggest using spring runner with context configuration which will load properties in the config class and create the bean.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {ConsumerConfig.class, ConfigsService.class})
#TestPropertySource(locations = "classpath:consumer.properties")
public class ConfigsServiceTest {
#Autowired
private ConfigsService configsService;
#Test
public void someTest() {
Assert.assertNotNull(configService.getConfig().getHostname());
}
}
The error may be because in the ConfigsServiceTest class you are establishing that your ConsumerConfig is a Mock and that is why it is not loading your configuration if you remove that code it should work
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")
I'm using Jackson with Spring. I have a few methods like this:
#RequestMapping(value = "/myURL", method = RequestMethod.GET)
public #ResponseBody Foo getFoo() {
// get foo
return foo;
}
The class Foo that's serialized is quite big and has many members. The serialization is ok, using annotation or custom serializer.
The only thing I can't figure out is how to define the naming convention. I would like to use snake_case for all the serializations.
So how do I define globally the naming convention for the serialization?
If it's not possible, then a local solution will have to do then.
Not sure how to do this globally but here's a way to do it at the JSON object level and not per each individual property:
#JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
public class Foo {
private String myBeanName;
//...
}
would yield json:
{
"my_bean_name": "Sth"
//...
}
Actually, there was a really simple answer:
#Bean
public Jackson2ObjectMapperBuilder jacksonBuilder() {
Jackson2ObjectMapperBuilder b = new Jackson2ObjectMapperBuilder();
b.propertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
return b;
}
I added it in my main like so:
#SpringBootApplication
public class Application {
public static void main(String [] args) {
SpringApplication.run(Application.class, args);
}
#Bean
public Jackson2ObjectMapperBuilder jacksonBuilder() {
Jackson2ObjectMapperBuilder b = new Jackson2ObjectMapperBuilder();
b.propertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
return b;
}
}
The mapper has a setter for PropertyNamingStrategy (Method for setting custom property naming strategy to use.)
Look how it works in the tes example:
#Test
public void namingStrategy() throws Exception {
final ObjectMapper mapper = new ObjectMapper();
mapper.setPropertyNamingStrategy(new PropertyNamingStrategy.PropertyNamingStrategyBase() {
#Override
public String translate(String s) {
return s.toUpperCase();
}
});
final String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(new SomePojo("uuid_1", "user_1", "Bruce", "W.", 51));
System.out.println(json);
}
public static class SomePojo {
private String someIdAttachedToIt;
private String username;
private String fistName;
private String lastName;
private int age;
public SomePojo(String someIdAttachedToIt, String username, String fistName, String lastName, int age) {
this.someIdAttachedToIt = someIdAttachedToIt;
this.username = username;
this.fistName = fistName;
this.lastName = lastName;
this.age = age;
}
public String getSomeIdAttachedToIt() {
return someIdAttachedToIt;
}
public String getUsername() {
return username;
}
public String getFistName() {
return fistName;
}
public String getLastName() {
return lastName;
}
public int getAge() {
return age;
}
}
Output:
{
"SOMEIDATTACHEDTOIT" : "uuid_1",
"USERNAME" : "user_1",
"FISTNAME" : "Bruce",
"LASTNAME" : "W.",
"AGE" : 51
}
Provided strategies (I use LOWERCASE for the examples)
PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES
PropertyNamingStrategy.SNAKE_CASE
To add your strategy globally in Spring, you can do it at least in 2 ways:
with a mapper module declared as a bean containing the naming strategy
with a custom object mapper configured as you want
Short version:
#Configuration
public static class Config {
#Bean
public Module module() {
return new SimpleModule() {
#Override
protected SimpleModule setNamingStrategy(PropertyNamingStrategy naming) {
super.setNamingStrategy(new PropertyNamingStrategy.PropertyNamingStrategyBase() {
#Override
public String translate(String propertyName) {
// example: "propertyName" -> "PROPERTYNAME"
return propertyName.toUpperCase();
}
});
return this;
}
};
}
}
Long version:
To declare the bean for the jackson module:
// config auto scan by spring
#Configuration
public static class ConfigurationClass {
// declare the module as a bean
#Bean
public Module myJsonModule() {
return new MySimpleModule();
}
}
// jackson mapper module to customize mapping
private static class MySimpleModule extends SimpleModule {
#Override
protected SimpleModule setNamingStrategy(PropertyNamingStrategy naming) {
return super.setNamingStrategy(new MyNameStrategy());
}
}
// your naming strategy
private static class MyNameStrategy extends PropertyNamingStrategy.PropertyNamingStrategyBase {
#Override
public String translate(String propertyName) {
return propertyName.toUpperCase();
}
}
You can declare the bean in xml as well.
It won't override #JsonProperty that define the prop name explicitly.
I'm developing a web application with spring. I've had no problem autowiring and using database #Service classes. Now I'm trying to read a global property file and provide the values to all classes that need them. The solution I've come up with so far seem to be overly complicated (too many classes - AppConfig, ServerConfig iface, ElasticServerConfig) for such a trivial task but I could live with it if it worked.
my applicationContext.xml contains
<context:component-scan base-package="my.package" />
AppConfig.java:
package my.package.configuration;
#Configuration
#PropertySource("classpath:application.properties")
public class AppConfig {
}
ServerConfig.java:
public interface ServerConfig {
String getUrl();
String getUser();
String getPassword();
}
ElasticSearchConfig.java:
package my.package.configuration;
#Component(value = "elasticServerConfig")
public class ElasticServerConfig implements ServerConfig {
private static final Logger LOGGER = LogManager.getLogger(ElasticServerConfig.class);
private String url;
private String user;
private String password;
#Autowired
public ElasticServerConfig(final Environment env) {
this.url = env.getProperty("elastic_server.url");
this.user = env.getProperty("elastic_server.user");
this.password = env.getProperty("elastic_server.password");
LOGGER.debug("url=" + url + "; user=" + user + "; password=" + password); // this works!
}
#Override
public final String getUrl() {
return url;
}
#Override
public final String getUser() {
return user;
}
#Override
public final String getPassword() {
return password;
}
}
When the web application boots, the ElasticServerConfig constructor prints out the correct url/user/pwd as read from application.properties. However an instance of ElasticServerConfig is not injected into a Search object:
package my.package.util;
public class Search {
#Autowired
#Qualifier("elasticServerConfig")
private ServerConfig elasticServerConfig;
public final List<Foobar> findByPatternAndLocation() {
if (elasticServerConfig == null) {
LOGGER.error("elasticServerConfig is null!");
}
// and i get a NullPointerException further on
// snip
}
}
You have to register the Search class as a Spring Bean and take it from the Spring context when you want to use it. It's important to get the bean from the spring context. If you create an object of that class with new, Spring has no way to know about that class and mange it's dependencies.
You can get get a bean from the Spring context by #Autowire it somewhere or by accessing an instance of the context and use the getBean method:
#Configuration
#PropertySource("classpath:application.properties")
public class AppConfig {
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(AppConfig.class, args);
ctx.getBean...
}
}
Either use #Component annotation on the class and make sure that the class is in package thats under my.package
or register it in the configuration class
#Configuration
#PropertySource("classpath:application.properties")
public class AppConfig {
#Bean
public Search search(){
return new Search();
}
}