When handling setting values in Spring Framework, setting values can be specified in property files, system properties (values specified by -D), OS environment variables, JNDI (Web application environment only), etc., and on the application side It can be obtained via @ Value
or ʻEnvironment`.
For example, it looks like the following.
Setting value specification example(properties file)
services.user.url=http://user.service.com/{id}
services.company.url=http://company.service.com/{id}
Reference example of setting value
@Service
public class MyService {
private final RestTemplate restTemplate = new RestTemplate();
private final Environment environment;
@Value("${services.user.url}") //Injection of setting value at bean generation
private String userServiceUrl;
public MyService(Environment environment) {
this.environment = environment;
}
public User getUser(String id) {
User user = restTemplate.getForObject(userServiceUrl, User.class, id);
return user;
}
public Company getCompany(String id) {
String companyServiceUrl = environment.getProperty("services.company.url"); //Get the set value at runtime
Company company = restTemplate.getForObject(companyServiceUrl, User.class);
return company;
}
}
In Spring Boot, instead of "@Value", "[Type-safe Configuration Properties](https://docs.spring.io/spring-boot/docs/2.1.7.RELEASE/reference/htmlsingle/#" I think it is common to use "boot-features-external-config-typesafe-configuration-properties)", but here ** intentionally ** "
@ Value"is used as a property placeholder (" Specifies to use
$ {...}` ”). (Although I will not explain it, the method introduced here is also applicable to "Type-safe Configuration Properties")
Spring Framwork does not provide a class to get the setting value from the database (unfortunately?), But the setting value is saved from the database because it is abstracted by a class called PropertySource
. If you create a PropertySource
to get the and apply it to ʻEnvironment`, you can refer to it from your application.
PropertySource
to get the settings from the databaseLet's create a class that gets the setting value from the database at startup, caches it, and updates the cache at any time (though thread safety seems suspicious ...).
package com.example.demo;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.sql.DataSource;
import java.util.Collections;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class JdbcPropertySource extends EnumerablePropertySource<DataSource> implements InitializingBean {
private final JdbcTemplate jdbcTemplate;
private final String[] tableNames;
private Map<String, Object> properties = Collections.emptyMap();
public JdbcPropertySource(String name, DataSource dataSource, String... tableNames) {
super(name, dataSource);
this.jdbcTemplate = new JdbcTemplate(dataSource);
this.tableNames = tableNames.length == 0 ? new String[]{"t_properties"} : tableNames;
}
@Override
public String[] getPropertyNames() {
return properties.keySet().toArray(new String[0]);
}
@Override
public Object getProperty(String name) {
Map<String, Object> currentProperties = properties;
return currentProperties.get(name);
}
@Override
public void afterPropertiesSet() {
load();
}
public void load() {
Map<String, Object> loadedProperties = Stream.of(tableNames)
.flatMap(tableName -> jdbcTemplate.queryForList("SELECT name, value FROM " + tableName).stream())
.collect(Collectors.toMap(e -> (String) e.get("name"), e -> e.get("value")));
this.properties = loadedProperties;
}
}
package com.example.actuatordemo;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import javax.sql.DataSource;
@Configuration
public class MyConfiguration {
//DataSource settings
@Bean
DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.setName("demo")
.addScript("classpath:init-db.sql")
.build();
}
//Setting of JdbcPropertySource created this time
@Bean
JdbcPropertySource jdbcPropertySource(DataSource dataSource) {
return new JdbcPropertySource("jdbcProperties", dataSource);
}
//Bean definition to apply the JdbcPropertySource created this time to Environment
@Bean
static BeanFactoryPostProcessor environmentPropertySourcesCustomizer() {
return bf -> {
ConfigurableEnvironment environment = bf.getBean(ConfigurableEnvironment.class);
JdbcPropertySource propertySource = bf.getBean(JdbcPropertySource.class);
//Apply according to the following priority of OS environment variables (priority is determined according to requirements)
environment.getPropertySources()
.addAfter(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, propertySource);
};
}
//Bean definition to enable property placeholders
@Bean
static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
This time, I applied it to ʻEnvironment using the mechanism of
BeanFactoryPostProcessor`. Is this the best method? Is not very confident. I would appreciate it if you could comment if there is a better way.
Since this entry uses the embedded database, prepare the SQL to input the table and data.
src/main/resources/init-db.sql
drop table t_properties if exists;
create table t_properties (
name varchar(512) not null primary key,
value text
);
insert into t_properties (name, value) values ('services.user.url','http://dev01/services/user/{id}');
insert into t_properties (name, value) values ('services.company.url','http://dev01/services/company/{id}');
In the project I'm currently working on (non-Spring Boot project ...), it may be required (possibly) to have some setting values in the database (from a system operation point of view), so what can I do technically? I thought about it and made a prototype. There seems to be some room for improvement such as guaranteeing thread safety, but I am relieved to know that it can be realized by using the extension points of Spring Framework.
Recommended Posts