setting properties value by api call on application startup - java

If I have some global config properties value that want to set on application start up, one of the ways to do is by setting it in application.properties and then using #Value to inject those values. However, if I want to set the values by making an API call to get those properties value on application start up and then set the values (but want to use similar way as #Value), rather than getting and setting it via properties files, how should it be achieved ?
#Configuration
public class config {
#Value("${properties1}")
private String properties1;
#Value("${properties2}")
private String properties2;
}
I have done some web search on custom property source (https://projects.spring.io/spring-cloud/spring-cloud.html#customizing-bootstrap-property-sources), and tried to follow the example, but encountered the error that the placeholder could not be resolved. How to get back the value ?
Could not resolve placeholder 'property.from.sample.custom.source' in value "${property.from.sample.custom.source}"
#Configuration
public class CustomPropertySourceLocator implements PropertySourceLocator {
#Override
public PropertySource<?> locate(Environment environment) {
return new MapPropertySource("customProperty",
Collections.<String, Object>singletonMap("property.from.sample.custom.source", "worked as intended"));
}
}
#Service
public class MainService {
#Value("${property.from.sample.custom.source}")
private String value;
public void printValue() {
System.out.println("value - " + value);
}
}

Assuming you use Spring Boot, you can run the Spring Boot application and pass the arguments using the following Maven command (depends on the Spring Boot version):
Spring Boot 1.x: using -Drun.arguments:
spring-boot:run -Drun.arguments=--properties1=One,--properties2=Two
Spring Boot 2.x: using -Dspring-boot.run.arguments:
spring-boot:run -Dspring-boot.run.arguments=--properties1=One,--properties2=Two
Now you can access the values using the #Value annotation:
#Value("${properties1}")
private String properties1;
#Value("${properties2}")
private String properties2;
Note: Once the properties are defined in the properties files (ex. application.properties and/or application-dev.yml etc..), defined in the command line like above, they can be accessed through the #Value annotation.
Baeldung's website offers a nice article: Command-Line Arguments in Spring Boot.

I am not sure if we can set values to properties file after application is up. Because properties file will be injected to #Bean when application is running. But we can hack this.
First, Create a file that contains all the configuration file which will be load from properties file. This file will be our template and initial / default values of the configuration.
Let say we have below configuration as application.yml
config:
name: Anna
age: 18
Then create configuration file
#ConfigurationProperties(prefix = "config")
public class ConfigProperties {
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
Don't forget to modify our Application class
#SpringBootApplication
#EnableConfigurationProperties(ConfigProperties::class)
public class Application { ... }
Through here, we can just #Autowire the config class to get the value we want, rather than using a #Value.
Through here we already have the properties value to our config class. Then if you want to update the properties value (now already on config file) through an API, do a simple setName(...).
But, we have to be aware that this way is only work on a single run. When we restart the application, the value will use the default from properties file.

Related

Getting value for a variable using external file in springboot

How can i get the value "TestValue" from application.properties file in springboot project. The messageVarible in not static final.
#Component
public class testMapper {
public String TestMessage() {
String message-variable = "TestValue";
// ...
}
}
you can add the property to application.properties or any other properties resource file loadable by spring boot. then you can read it inside your application like that
#Value("${messageVariable}")
private String messageVariable;
where you defined in your file like that
messageVariable=any message
or if you are using yaml file
messageVariable:any message

Unable to resolve variable from properties file when tried to access as function parameter using #Value annotation

This may be silly question to ask but i'm unable to find any satisfactory solution to my problem. In java we don't have the concept of default variables so i am trying to give default value from properties file to my function parameters/arguments using #Value annotation, but i'm always getting null and i'm unable to figure why is this happening. Please help me to solve the issue or provide me some appropriate link/reference which may solve my issue.
MainApplication.java
#SpringBootApplication
public class Application
{
public static void main(String[] args)
{
ApplicationContext context = SpringApplication.run(NetappApplication.class, args);
Sample sample = context.getBean(Sample.class);
System.out.println(sample.check(null));
}
}
Sample.java
public interface Sample
{
public String check(String message);
}
SampleImpl.java
#Service
#PropertySource("classpath:app.properties")
public class SampleImpl implements Sample
{
#Value("${test}")
String message1;
#Override
public String check(#Value("${test}") String message)
{
return message;
}
}
app.properties
test=anand
But you are passing null to your method...
Perhaps what you want to do is to assign default value to test in case it's not defined in property file:
#Value("${test:default}");
Then, when properties are autowired by Spring if placeholder resolver doesn't get the value from props file, it will use what is after :.
The best use case for this (that I can think of) is when you create Spring configuration.
Let's say you have a configuration class: for DB access. Simply put:
#Configuration
public class DbConfig {
#Value("${url:localhost}")
String dbUrl;
// rest for driver, user, pass etc
public DataSource createDatasource() {
// here you use some DataSourceBuilder to configure connection
}
}
Now, when Spring application starts up, properties' values are resolved, and as I wrote above you can switch between value from property and a default value. But it is done once, when app starts and Spring creates your beans.
If you want to check incoming argument on runtime, simple null check will be enough.
#Value("${test}")
String message1;
#Override
public String check(String message) {
if (message == null) {
return message1;
}
}

Configuration Properties are not read by Spring boot when setting properties like this "export my_variable=something"

edit: please pay close attention to the question. I want to make changes without having to rebuild and redeploy the application. I want to make changes on the fly.
I have a simple Spring boot application where I am trying to test if an application can read environment variable without having to rebuild and redeploy the application.
I have a simple main class which is also a #RestController
#SpringBootApplication
#RestController
#EnableScheduling
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
#Value("${taco.orders.pageSize}")
private String pageSize;
#GetMapping("/myName")
public String myName() {
return pageSize;
}
int i = 0;
#Scheduled(fixedRate = 2000L)
public void scheduled() {
System.err.println(++i + "-" + pageSize);
}
}
This is what I have in my application.yml file:
taco:
orders:
pageSize: fifty
This print fine "fifty". But when I go to terminal and set a different value for the key, that new value is not reflected.
export TACO_ORDERS_PAGESIZE=NINETY
I have also created a git repo if somebody wants to retry it.
You need to substitute the environment variable in your yaml file.
taco:
orders:
pageSize: {TACO_ORDERS_PAGESIZE : fifty}
The default value for your taco.orders.pageSize is fifty it will be automatically override by your env variable TACO_ORDERS_PAGESIZE NINETY.

How to set tableName dynamically using environment variable in spring boot?

I am using AWS ECS to host my application and using DynamoDB for all database operations. So I'll have same database with different table names for different environments. Such as "dev_users" (for Dev env), "test_users" (for Test env), etc.. (This is how our company uses same Dynamo account for different environments)
So I would like to change the "tableName" of the model class using the environment variable passed through "AWS ECS task definition" environment parameters.
For Example.
My Model Class is:
#DynamoDBTable(tableName = "dev_users")
public class User {
Now I need to replace the "dev" with "test" when I deploy my container in test environment. I know I can use
#Value("${DOCKER_ENV:dev}")
to access environment variables. But I'm not sure how to use variables outside the class. Is there any way that I can use the docker env variable to select my table prefix?
My Intent is to use like this:
I know this not possible like this. But is there any other way or work around for this?
Edit 1:
I am working on the Rahul's answer and facing some issues. Before writing the issues, I'll explain the process I followed.
Process:
I have created the beans in my config class (com.myapp.users.config).
As I don't have repositories, I have given my Model class package name as "basePackage" path. (Please check the image)
For 1) I have replaced the "table name over-rider bean injection" to avoid the error.
For 2) I printed the name that is passing on to this method. But it is Null. So checking all the possible ways to pass the value here.
Check the image for error:
I haven't changed anything in my user model class as beans will replace the name of the DynamoDBTable when the beans got executed. But the table name over riding is happening. Data is pulling from the table name given at the Model Class level only.
What I am missing here?
The table names can be altered via an altered DynamoDBMapperConfig bean.
For your case where you have to Prefix each table with a literal, you can add the bean as such. Here the prefix can be the environment name in your case.
#Bean
public TableNameOverride tableNameOverrider() {
String prefix = ... // Use #Value to inject values via Spring or use any logic to define the table prefix
return TableNameOverride.withTableNamePrefix(prefix);
}
For more details check out the complete details here:
https://github.com/derjust/spring-data-dynamodb/wiki/Alter-table-name-during-runtime
I am able to achieve table names prefixed with active profile name.
First added TableNameResolver class as below,
#Component
public class TableNameResolver extends DynamoDBMapperConfig.DefaultTableNameResolver {
private String envProfile;
public TableNameResolver() {}
public TableNameResolver(String envProfile) {
this.envProfile=envProfile;
}
#Override
public String getTableName(Class<?> clazz, DynamoDBMapperConfig config) {
String stageName = envProfile.concat("_");
String rawTableName = super.getTableName(clazz, config);
return stageName.concat(rawTableName);
}
}
Then i setup DynamoDBMapper bean as below,
#Bean
#Primary
public DynamoDBMapper dynamoDBMapper(AmazonDynamoDB amazonDynamoDB) {
DynamoDBMapper mapper = new DynamoDBMapper(amazonDynamoDB,new DynamoDBMapperConfig.Builder().withTableNameResolver(new TableNameResolver(envProfile)).build());
return mapper;
}
Added variable envProfile which is an active profile property value accessed from application.properties file.
#Value("${spring.profiles.active}")
private String envProfile;
We have the same issue with regards to the need to change table names during runtime. We are using Spring-data-dynamodb 5.0.2 and the following configuration seems to provide the solutions that we need.
First I annotated my bean accessor
#EnableDynamoDBRepositories(dynamoDBMapperConfigRef = "getDynamoDBMapperConfig", basePackages = "my.company.base.package")
I also setup an environment variable called ENV_PREFIX which is Spring wired via SpEL.
#Value("#{systemProperties['ENV_PREFIX']}")
private String envPrefix;
Then I setup a TableNameOverride bean:
#Bean
public DynamoDBMapperConfig.TableNameOverride getTableNameOverride() {
return DynamoDBMapperConfig.TableNameOverride.withTableNamePrefix(envPrefix);
}
Finally, I setup the DynamoDBMapperConfig bean using TableNameOverride injection. In 5.0.2, we had to setup a standard DynamoDBTypeConverterFactory in the DynamoDBMapperConfig builder to avoid NPE.:
#Bean
public DynamoDBMapperConfig getDynamoDBMapperConfig(DynamoDBMapperConfig.TableNameOverride tableNameOverride) {
DynamoDBMapperConfig.Builder builder = new DynamoDBMapperConfig.Builder();
builder.setTableNameOverride(tableNameOverride);
builder.setTypeConverterFactory(DynamoDBTypeConverterFactory.standard());
return builder.build();
}
In hind sight, I could have setup a DynamoDBTypeConverterFactory bean that returns a standard DynamoDBTypeConverterFactory and inject that into the getDynamoDBMapperConfig() method using the DynamoDBMapperConfig builder. But this will also do the job.
I up voted the other answer but here is an idea:
Create a base class with all your user details:
#MappedSuperclass
public abstract class AbstractUser {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String firstName;
private String lastName;
Create 2 implentations with different table names and spirng profiles:
#Profile(value= {"dev","default"})
#Entity(name = "dev_user")
public class DevUser extends AbstractUser {
}
#Profile(value= {"prod"})
#Entity(name = "prod_user")
public class ProdUser extends AbstractUser {
}
Create a single JPA respository that uses the mapped super classs
public interface UserRepository extends CrudRepository<AbstractUser, Long> {
}
Then switch the implentation with the spring profile
#RunWith(SpringJUnit4ClassRunner.class)
#DataJpaTest
#Transactional
public class UserRepositoryTest {
#Autowired
protected DataSource dataSource;
#BeforeClass
public static void setUp() {
System.setProperty("spring.profiles.active", "prod");
}
#Test
public void test1() throws Exception {
DatabaseMetaData metaData = dataSource.getConnection().getMetaData();
ResultSet tables = metaData.getTables(null, null, "PROD_USER", new String[] { "TABLE" });
tables.next();
assertEquals("PROD_USER", tables.getString("TABLE_NAME"));
}
}

Springboot not replacing environment variables in application.properties file

I am trying to run Quartz Scheduler using SpringBoot. Using Quartz Jdbc Data Store. Due to security reasons , we wish to pick the Db credentials from the properties file. From what I understand from here(Using env variable in Spring Boot's application.properties) and springBoot docs, SpringBoot automatically replaces environment variables in application.properties but I am not seeing this . Here is my system environment file which i am sourcing before running the application
export DB_HOST=localhost
export DB_PORT=11111
export DB_USER=root
export DB_PASSWORD=root
export QUARTZ_DB_NAME=quartz
And here is my application.properties
org.quartz.dataSource.quartzDataSource.URL =jdbc:mysql://${DB_HOST}:${DB_PORT}/${QUARTZ_DB_NAME}
org.quartz.dataSource.quartzDataSource.user = ${DB_USER}
org.quartz.dataSource.quartzDataSource.password = ${DB_PASSWORD}
And my configuration class
#Configuration
public class ConfigureQuartz {
#Autowired
private ApplicationContext applicationContext;
#Bean
public SchedulerFactoryBean schedulerFactoryBean() throws IOException
{
final SchedulerFactoryBean quartzScheduler = new SchedulerFactoryBean();
quartzScheduler.setSchedulerName("mdsScheduler");
quartzScheduler.setQuartzProperties(quartzProperties());
final AutoWiringSpringBeanJobFactory jobFactory = new AutoWiringSpringBeanJobFactory();
jobFactory.setApplicationContext(applicationContext);
quartzScheduler.setJobFactory(jobFactory);
return quartzScheduler;
}
#Bean
public Properties quartzProperties() throws IOException {
final PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
propertiesFactoryBean.setLocation(new ClassPathResource("/application.properties"));
propertiesFactoryBean.afterPropertiesSet();
return propertiesFactoryBean.getObject();
}
But when i run my spring application using java -jar <>.java , I dont see the values substitued .
I can workaround by reading the values using System.getEnv() but would be great if the values can be substitued . Not sure why its not working :(
Spring-boot allows us several methods to provide externalized configurations , you can try using application.yml or yaml files instead of the property file and provide different property files setup according to different environments.We can separate out the properties for each environment into separate yml files under separate spring profiles.Then during deployment you can use :
java -jar -Drun.profiles=SpringProfileName
to specify which spring profile to use.Note that the yml files should be name like
application-{environmentName}.yml
for them to be automatically taken up by springboot.
Reference : https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html#boot-features-external-config-profile-specific-properties
To read from the application.yml or property file :
The easiest way to read a value from the property file or yml is to use the spring #value annotation.Spring automatically loads all values from the yml to the spring environment , so we can directly use those values from the environment like :
#Component
public class MyBean {
#Value("${name}")
private String name;
// ...
}
Or another method that spring provides to read strongly typed beans is as follows:
YML
acme:
remote-address: 192.168.1.1
security:
username: admin
roles:
- USER
- ADMIN
Corresponding POJO to read the yml :
#ConfigurationProperties("acme")
public class AcmeProperties {
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 above method works well with yml files.
Reference:
https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html
To read from the environment :
If you are running the application on linux set the env as below :
export DB_HOST=localhost
export DB_PORT=11111
export DB_USER=root
export DB_PASSWORD=root
export QUARTZ_DB_NAME=quartz
if you are on windows set it like :
SET DB_HOST=localhost
SET DB_PORT=11111
SET DB_USER=root
SET DB_PASSWORD=root
SET QUARTZ_DB_NAME=quartz
In your application.properties if you keep the keys as below , it should automatically resolve the value from the environment :
spring.datasource.url = ${DB_HOST}/"nameofDB"
spring.datasource.username = ${DB_USER}
spring.datasource.password = ${DB_PASSWORD}

Categories