Spring Boot - making configuration final - java

My Spring Boot (1.3.5) application uses externalized configuration using an application.properties file. Alongside, I currently have a configuration class as such:
#Configuration
MyConfig {
#Value("${prop}")
private String prop;
// getters
}
I'm wondering if there is a way to make prop final. Ideally I'd like to access my configuration properties publicly; for example myConfig.prop. I'd like to make all properties public and final; however I believe configuration classes are first instantiated via an empty constructor before properties are loaded. Is there an alternative approach?

You can inject your config values in the constructor and assign to a final field.
#Configuration
class MyConfig {
private String final prop;
public MyConfig(#Value("${prop}") String prop){
this.prop = prop;
}
}

For all the people landing here and asking themself the same question, please have a look at https://stackoverflow.com/a/57191291/11770752
In addition you can use this with lombok if desired, which reduces the class' boilerplate code.

Related

field annotated with `#value` is not initialized in mongock configuration

I need to assure data migration using mongock.
The #ChangeUnit class holds the logic for migration. It has a field annotated with #Value which is always null, even though I properly initialized in application.properties:
mongock.migration-scan-package=my.package
login-secret=test
Then the MigrationConfiguration looks as follows:
#ChangeUnit(id = "test", order = "001", author = "test")
#RequiredArgsConstructor
#Configuration
public class InitUsersChangeLog {
private final MyService service;
private final MongoTemplate template;
#Value("${login-secret}")
private String LOGIN;
#Execution
public void initUser() {
service.create(User.builder().login(LOGIN).build());
}
}
Main class:
#EnableMongock
#SpringBootApplication
public class MailServiceApplication {...}
My assumption is that this value is not injected properly into the MongockConfiguration bean. I tried to configure the bean manually (without using mongock.migration-scan-package=my.package) in the properties, but with no success.
As Mongock currently doesn't support #Value annotation you can try to use getProperty method from Environment bean. Environment bean can be injected same as other beans using constructor or Lombok annotations.
You want to change this:
#Value("your.key.property")
to that:
private final Environment env;
public void method(){
env.getProperty("your.key.property")
}
Mongock currently no supports #value injection via field o method parameter. We will provide that in a future minor release within version 5, but we can't give you dates, yet.
Extending MichalJ's answer, which is absolutely valid. I would like to add that the changeUnits are not retrieved by Mongock via Springboot, they are processed by Mongock independently. So the annotation #Configuration, #Component, etc. won't be taken into account and they could even be damaging.
Related to that, this code won't work, at least not in a near future:
#Value("${login-secret}")
private String LOGIN;
First, as said, Mongock doesn't support value currently, but the first approach will require the constructor parameter to have that #Value("${login-secret}"), not at the field level.

Springboot - #Autowired null with injection

I have a Spring-boot project and I need to get values from application.properties file because of profiles.
But when injecting a config file in the class, the object return null.
Application.properties:
server.ip=000.000.000.000
server.port=0000
Config Class:
#Component
#ConfigurationProperties(prefix = "server")
public class AppProperties {
private String ip;
private Integer port;
... getters and setters
the class where I need the values:
#Component
public class Teste {
#Autowired
private AppProperties properties;
...
socket = new Socket(properties.getIp(), properties.getPort());
On debugging, at the start of the application, the values in AppProperties get a correct value.
You can use #Value annotation to access the property values from application.properties file like this
#Value("${<propertname>}")
private String userBucketPath;
Your code looks like correct but I would like to suggest you try with below steps also:
Use #PropertySource annotation to define properties location. (I knew default location is classpath).
If you really need to add a profile to your project. Create a new application property file like application-{profile-name}.properties and while starting an application pass JVM argument -Dspring.profiles.active={profile-name}.
I don't know it's exact need or answer for you. I'm just trying to share an additional step.
I get null values for properties because I instantiate a Teste class with new Teste().
The Solution is use Teste class with Intection.
#Autowired
private Teste teste;

What's the Spring way of accessing properties from an entity?

I'm new to Spring and I'm building an application where some entities (JPA/Hibernate) need access to a property from application.properties. I do have a configuration class in which this is trivial:
#Configuration
public class FactoryBeanAppConfig {
#Value("${aws.accessKeyId}")
private String awsAccessKeyId;
#Value("${aws.secretKey}")
private String awsSecretKey;
}
but since entities do not have and I think they should not have the annotations such as #Configuration or #Component, what's the Spring way for them to access the property?
Now, I know I can create my own class, my own bean, and make it as a simple wrapper around the properties; but is that the Spring way to do it or is there another way?
specify Property file location using #PropertySource
Something like below
#PropertySource("classpath:/application.proerties")
You also need to add below bean in your config
#Bean
public static PropertySourcesPlaceholderConfigurer propertyConfigIn() {
return new PropertySourcesPlaceholderConfigurer();
}
There is no "Spring way", since JPA entities and Spring have nothing to do with each other. Most importantly, JPA entities are not Spring beans, so Spring doesn't know or care about them as they're not managed by Spring.
You can try to hack around, trying in vain to access Spring's configuration from code that should not be trying to access it, or you can accept the truth that your design is broken and you're trying to do something that's not meant to be done.
As was proposed several times, use a service class for this. It's managed by Spring, so it can access the Spring config, and it can handle entities, so there's no crossing boundaries.
First create a public static variable in some bean managed by Spring, then make the following use of the #Value annotation.
public static String variable;
#Value("${variable}")
private void setVariable(String value) {
variable = value;
}
It will be set at runtime on startup, now you can access it from entities and everywhere else because it is just a public static var.
You can use #PropertySource to load the properties file as follows
#Configuration
#PropertySource("classpath:/com/organization/config/application.proerties")
public class FactoryBeanAppConfig {
...
}
Entities should not acces environment properties. If you are using your entity through a service, then the service can access the properties to act on the entity.

Spring data multiple datasources from multiple files

I have 2 (or more) different configuration properties file located in the project and I want to load them for different datasources.
I tried to do as following:
#Bean
#ConfigurationProperties(locations = {"#{myconfigroot.getRootFolder()}/datasource1.properties"}
public static DataSource getFirstDatasource() {
return DataSourceBuilder.create().build();
}
But obviously this won't work as the ConfigurationProperties annotation locations property doesn't go through the spEL. (Or may be I write it wrong?) myconfigroot.getRootFolder() is a static method which returns the path to the datasource1.properties.
Please advice. Thanks.
===== Edited =======
I believe this is a common problem when somebody want their application want to load different configuration files. Due to some reasons the file location and name can't be put in the startup script or command line, or, the path can only be determined in runtime, that would require spring to load them during the bean creation.
I tried once using PropertySourcePlaceHolderConfigurer but seems not work either.
Anybody can share some lights?
Latest Spring boot (version 1.3.5) doesn’t support SpEL in this case.
See JavaDoc of annotation #ConfigurationProperties
Note that contrary to {#code #Value}, SpEL expressions are not
evaluated since property values are externalized.
I found a way to customize Spring boot default behavior as follows:
For example, I have database.properties file in somewhere, for some reason I cannot get the location before runtime.
username=mike
password=password
Accordingly, define POJO mapping to properties:
#Component
#ConfigurationProperties(locations = "myConfiguration")// myConfiguration is customized placeholder
public class MyProperties{
String username;
String password;
//Getters, Setters…
}
Then, to extend default StandardEnvironment:
public class MyEnvironment extends StandardEnvironment {
#Override
public String resolvePlaceholders(String location) {
if (location.equals("myConfiguration")) {
//Whatever you can do, SpEL, method call...
//Return database.properties path at runtime in this case
return getRootFolder() + "datasource.properties";
} else {
return super.resolvePlaceholders(text);
}
}
}
Last, apply it in Spring boot main method entry:
#SpringBootApplication
public class MyApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
new SpeedRestApplication()
.configure(new SpringApplicationBuilder(SpeedRestApplication.class).environment(new MyEnvironment()))//Replace default StandardEnvironment
.run(args);
}
}
Once Spring boot starts up, the MyProperties bean name and password fields are injected from database.properties. Then you could wire the MyProperties bean to other beans as configuration.
Hope it helps!
I finally got it work by using the following mechanism:
public class DatasourcePostProcessor implements EnvironmentPostProcessor {
#Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
Properties p = new Properties();
p.load(new FileInputStream(new File(getRootFolder() + "/datasource1.properties")));
Map<String, Object> propMap = new HashMap<>();
for (Map.Entry<Object, Object> entry : p.entrySet()) {
propMap.put(entry.getKey().toString(), entry.getValue());
}
MapPropertySource source = new MapPropertySource("datasource1", propMap);
environment.getPropertySources().addLast(source);
}
}
and register the environment post processor into the spring.factories:
org.springframework.boot.env.EnvironmentPostProcessor=com.myorg.test.DatasourcePostProcessor
Anyway, hope this helps people and accept the first anwer as it enlight me. Also post the following references from the google search that found during research:
Where I found how to wire the property source with the environment: https://github.com/spring-projects/spring-boot/issues/4595
Where I found how to load the customized properties file: How to configure a custom source to feed Spring Boot's #ConfigurationProperties

How to test Classes with #ConfigurationProperties and #Autowired

I want to test small parts of the application that rely on properties loaded with #Autowired and #ConfigurationProperties. I am looking for a solution loading only the required properties and not always the whole ApplicationContext.
Here as reduced example:
#TestPropertySource(locations = "/SettingsTest.properties")
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {TestSettings.class, TestConfiguration.class})
public class SettingsTest {
#Autowired
TestConfiguration config;
#Test
public void testConfig(){
Assert.assertEquals("TEST_PROPERTY", config.settings().getProperty());
}
}
Configuration Class:
public class TestConfiguration {
#Bean
#ConfigurationProperties(prefix = "test")
public TestSettings settings (){
return new TestSettings();
}
}
Settings Class:
public class TestSettings {
private String property;
public String getProperty() {
return property;
}
public void setProperty(String property) {
this.property = property;
}
}
The properties file in the resource folder contains the entry:
test.property=TEST_PROPERTY
In my current setup config is not null, but no fields are available.
The reason the fields are not field should have something to do with the fact that I am not using Springboot but Spring.
So what would be the Springboot way to get this running?
edit:
The reason why I want to do this is: I have a parser that parses Textfiles, the regular expressions used are stored in a properties file.
To test this I would like to load only the properties needed for this parser which are in the exaple above the TestSettings.
While reading the comments I already noticed that this are no Unit tests anymore. However using the full Spring boot configuration for this small test seems a bit too much to me. That's why I asked if there is a posibilty to load only the one class with properties.
You need to annotate your TestConfiguraion with #EnableConfigurationProperties as follows:
#EnableConfigurationProperties
public class TestConfiguration {
#Bean
#ConfigurationProperties(prefix = "test")
public TestSettings settings (){
return new TestSettings();
}
}
Also you only need to include TestConfiguration.class in #ContextConfiguration of you SettingsTest class:
#TestPropertySource(locations = "/SettingsTest.properties")
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = TestConfiguration.class)
public class SettingsTest {
...
A couple points:
You don't need a "TestConfiguration" class in your main package, because all it's doing is configuring the "TestSettings" bean. You can do this simply by annotating the TestSettings class itself.
Normally you would load the context you need for the test using the #SpringApplicationConfiguration annotation, passing the name of your Application class. However, you said you don't want to load the whole ApplicationContext (though it's not clear why), so you need to create a special configuration class to do the loading only for tests. Below I call it "TestConfigurationNew" to avoid confusion with the TestConfiguration class that you had originally.
In the Spring Boot world, all properties are generally kept in the "application.properties" file; but it is possible to store them elsewhere. Below, I have specified the "SettingsTest.properties" file that you proposed. Note that you can have two copies of this file, the one in the main/resources folder, and the one in the test/resources folder for testing.
Change the code as follows:
TestSettings.java (in main package)
#Configuration
#ConfigurationProperties(prefix="test", locations = "classpath:SettingsTest.properties")
public class TestSettings {
private String property;
public String getProperty() {
return property;
}
public void setProperty(String property) {
this.property = property;
}
}
SettingsTest.java (in test package)
#TestPropertySource(locations="classpath:SettingsTest.properties")
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = TestConfigurationNew.class)
public class SettingsTest {
#Autowired
TestSettings settings;
#Test
public void testConfig(){
Assert.assertEquals("TEST_PROPERTY", settings.getProperty());
}
}
TestConfigurationNew.java (in test package):
#EnableAutoConfiguration
#ComponentScan(basePackages = { "my.package.main" })
#Configuration
public class TestConfigurationNew {
}
This should now work the way you wanted.
you can actually just add #EnableConfigurationProperties to your #SpringBootTest directly.
eg:
#ActiveProfiles("test")
#RunWith(SpringRunner.class)
#SpringBootTest(classes = TestConfiguration.class)
#EnableConfigurationProperties
...
If you use Spring Boot, now you only need:
#RunWith(SpringRunner.class)
#SpringBootTest
No extra #ContextConfiguration, no extra class only for tests to EnableAutoConfiguration and EnableConfigurationProperties. You don't have to specify the configuration class to load, they will all be loaded.
But, ensure the properties entries you want to read in main/resources/application.yml is also present in test/resources/application.yml. Repetition is unavoidable.
Another way is:
Define a class of configuration only for tests, along with MyApplicationTest.java, at the same level. This class can be empty.
Like:
#EnableAutoConfiguration
#EnableConfigurationProperties(value = {
ConnectionPoolConfig.class
})
public class MyApplicationTestConfiguration {
}
And, in the class to load the autowired configuration.
Like:
#RunWith(SpringRunner.class)
//#SpringBootTest // the first, easy way
#ContextConfiguration(classes = MyApplicationTestConfiguration.class,
initializers = ConfigFileApplicationContextInitializer.class)
public class ConnectionPoolConfigTest {
#Autowired
private ConnectionPoolConfig config;
Basically, you:
use a specific configuration to #EnableConfigurationProperties and #EnableAutoConfiguration, listing all the #ConfigurationProperties files you want to load
in the test class, you load this configuration file of tests, with an initializer class defined by Spring to load application.yml file.
And, put the values to load in test/resources/application.yml. Repetition is unavoidable. If you need load another file, use #TestProperties() with a location. Note: #TestProperties only supports .properties files.
Both way works for configuration class loading values
either from application.yml/application.properties
or from another properties file, specified by PropertySource, like #PropertySource(value = "classpath:threadpool.properties")
Important
Last notes from Spring doc, as per here
Some people use Project Lombok to add getters and setters automatically. Make sure that Lombok does not generate any particular constructor for such a type, as it is used automatically by the container to instantiate the object.
Finally, only standard Java Bean properties are considered and binding on static properties is not supported.
That means, if you have lombok.#Builder without #NoArgsConstructor nor #AllArgsConstructor, properties injection will not happen because it only sees the invisible constructor created by #Builder. So, be sure to use none, or all of these annotations!
Unit test
To avoid having to load a Spring context, we can use the Binder class, which is also used internally by Spring anyway.
// A map of my properties.
Map<String, String> properties = new HashMap<>();
properties.put("my-prefix.first-property", "foo");
properties.put("my-prefix.second-property", "bar");
// Creates a source backed by my map, you can chose another type of source as needed.
ConfigurationPropertySource source = new MapConfigurationPropertySource(properties)
// Binds my properties to a class that maps them.
Binder binder = new Binder(source);
BindResult<MyConfiguration> result = binder.bind("my-prefix", MyConfiguration.class);
// Should return true if bound successfully.
Assertions.assertTrue(result.isBound);
// Asserts configuration values.
MyConfiguration config = result.get();
Assertions.assertEquals("foo", config.getFirstProperty());
Assertions.assertEquals("bar", config.getSecondProperty());

Categories