Gunter Winkler
München, 2014-07-28
DataSource
^1^external.service.url = https://service.example.com/api^^
^2^external.service.timeout_sec = 60^^
^3^external.service.user = TheUser^^
^4^external.service.pass = *********^^
In software engineering, inversion of control (IoC) is a programming technique [...] in which object coupling is bound at run time by an assembler object and is typically not known at compile time [...].
see "The Dependency Inversion principle" by Robert C. Martin
and "Inversion of Control Containers and the Dependency Injection Pattern" by Martin Fowler
Java-based metadata (seit 3.0)
^1^@Configuration^^
public class DaoConfig {
^2^@Bean^^(^3^name="runtimeDataSource"^^)
public DataSource dataSource() {
return new JndiObjectFactoryBean()...getObject();
}
@Bean
public EntityService entityService(
^4^@Qualifier("runtimeDataSource")^^ DataSource ds) {
return new BasicEntityService(ds);
}
}
@Test
public void verifyThatSpringContextLoads() {
try (ConfigurableApplicationContext context =
^1^new AnnotationConfigApplicationContext(DaoConfig.class))^^ {
EntityService service = ^2^context.getBean(EntityService.class)^^;
assertThat(service, is(notNullValue()));
}
}
^1^@Component^^ // or @Service, @Repository, @Controller
public class SomeComponent {
^2^@Autowire^^ // Spring
private EntityService entityService;
^3^@Inject^^ // JSR 330
private Environment env;
}
WeatherClient.java
public class WeatherClient implements WeatherProvider {
public WeatherClient(^1^String baseUrl^^, ^2^String userName^^, RestOperations restTemplate) {
this.baseUrl = baseUrl;
this.userName = userName;
this.restTemplate = restTemplate;
}
// ...
}
weather/default.properties
weather.baseUrl = http://api.geonames.org/weatherJSON
weather.username = demo
connect.timeout = 10000
WeatherConfig.java
@Configuration
^1^@PropertySource({ "classpath:weather/default.properties" })^^
public class WeatherConfig {
@Bean
public WeatherProvider client(^2^Environment env^^, RestOperations restOps) {
String baseUrl = ^3^env.getRequiredProperty^^("weather.baseUrl");
String userName = env.getRequiredProperty("weather.username");
return new WeatherClient(baseUrl, userName, restOps);
}
@Bean
public RestOperations restTemplate(Environment env) {
Integer connectTimeout =
^4^env.getProperty^^("connect.timeout", Integer.class, ^5^30000^^);
// ...
}
}
WeatherClientTest
staging?
DEV ⇒ CI ⇒ TEST ⇒ LIVE
WeatherConfig.java
@Configuration
@PropertySource({
"classpath:weather/default.properties",
^1^"classpath:weather/${stage}.properties"^^
})
public class WeatherConfig {
@Bean
public WeatherProvider client(Environment env, RestOperations restOps) {
String baseUrl = env.getRequiredProperty("weather.baseUrl");
String userName = env.getRequiredProperty("weather.username");
return new WeatherClient(baseUrl, userName, restOps);
}
// ...
}
o.s.core.env.StandardEnvironment
* smart matching: FOO_BAR = FOO.BAR = foo_bar = foo.bar
o.s.web.context.support.StandardServletEnvironment
best practice: "fail fast"
best practice: "alles an eine Stelle"
best practice: "Sonderbehandlung für Passwörter"
WeatherConfig.java
@Configuration
@PropertySource({
"classpath:weather/default.properties",
"classpath:weather/${stage}.properties",
"file://${user.home}/config/common-${stage}.properties",
"file://${user.home}/config/secret.properties"
})
public class WeatherConfig {
// ...
}
WeatherConfig.java (v1)
@Configuration
@PropertySource({
"classpath:weather-default.properties",
"classpath:weather-${stage}.properties",
"classpath:weather-${user.name}.properties",
"classpath:weather-${hostname}.properties",
"file://${user.home}/weather-default.properties",
"file://${user.home}/weather-${stage}.properties",
"file://${user.home}/weather-${user.name}.properties",
"file://${user.home}/weather-${hostname}.properties"
})
public class WeatherConfig {
// ...
}
java.lang.IllegalArgumentException: Could not resolve placeholder 'stage' in string value "classpath:weather-${stage}.properties"
java.io.FileNotFoundException: class path resource [weather-dev.properties] cannot be opened because it does not exist
WeatherConfig.java (Spring 4)
@Configuration
@PropertySource(value={
"classpath:weather-default.properties",
"classpath:weather-${stage}.properties",
"classpath:weather-${user.name}.properties",
"classpath:weather-${hostname}.properties",
"file://${user.home}/weather-default.properties",
"file://${user.home}/weather-${stage}.properties",
"file://${user.home}/weather-${user.name}.properties",
"file://${user.home}/weather-${hostname}.properties"
}, ^1^ignoreResourceNotFound = true^^)
public class WeatherConfig {
// ...
}
Spring container life cycle
public class SomeApplicationContextInitializer implements
ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
// ... do something with applicationContext
}
}
public class HostnameProvidingApplicationContextInitializer implements
ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
^1^ConfigurableEnvironment env = applicationContext.getEnvironment();^^
^2^MutablePropertySources ps = env.getPropertySources();^^
ps.^3^addLast^^(new MapPropertySource("hostNamePropertySource",
ImmutableMap.of("hostname", tryHardToFindHostname())));
}
// ...
}
11:42:53.614 [main] DEBUG o.s.c.e.PropertySourcesPropertyResolver - Found key 'hostname' in [hostNamePropertySource] with type [String] and value 'TNG-WINKLER'
<context-param>
<param-name>contextClass</param-name>
<param-value>
o.s.web.context.support.AnnotationConfigWebApplicationContext
</param-value>
</context-param>
<context-param>
<param-name>contextInitializerClasses</param-name>
<param-value>
some.package.HostnameProvidingApplicationContextInitializer
</param-value>
</context-param>
@RunWith(^1^SpringJUnit4ClassRunner.class^^)
^2^@ContextConfiguration^^(
classes = { WeatherConfig.class },
^3^ initializers = {
HostnameProvidingApplicationContextInitializer.class
})^^
public class WeatherClientTest {
// ...
}
private ApplicationContext bootSpringContext() {
AnnotationConfigApplicationContext ac
= ^1^new AnnotationConfigApplicationContext()^^;
ac.^2^register(WeatherConfig.class)^^;
^3^new HostnameProvidingApplicationContextInitializer().initialize(ac)^^;
^4^ac.refresh()^^;
return ac;
}
@Configuration
public class EmbeddedDataSourceConfig {
// for development
@Bean
public DataSource embeddedDataSource(Environment env) {
EmbeddedDataSource ds = new org.apache.derby.jdbc.EmbeddedXADataSource40();
ds.setCreateDatabase("create");
ds.setDatabaseName(env.getRequiredProperty("db.name"));
ds.setUser(env.getRequiredProperty("db.user"));
ds.setPassword(env.getRequiredProperty("db.password"));
return ds;
}
}
@Configuration
public class PostgresDataSourceConfig {
// for component tests
@Bean
public DataSource embeddedDataSource(Environment env) {
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName("org.postgresql.Driver");
ds.setUrl(env.getRequiredProperty("db.jdbc.url"));
ds.setUsername(env.getRequiredProperty("db.user"));
ds.setPassword(env.getRequiredProperty("db.password"));
return ds;
}
}
@Configuration
public class JndiDataSourceConfig {
// for application container
@Bean
public DataSource jndiDataSource(Environment env) throws NamingException {
JndiLocatorDelegate locator =
JndiLocatorDelegate.createDefaultResourceRefLocator();
return locator.lookup(
env.getRequiredProperty("datasource.jndiName"),
DataSource.class);
}
}
Wie kann man umschalten?
@Profile
@Configuration
public class DataSourceConfig {
@Bean
^1^@Profile("embedded-ds")^^ // since Spring 4
public DataSource embeddedDataSource(Environment env) {
// ...
}
@Bean
@Profile("postgres-ds")
public DataSource directDataSource(Environment env) {
// ...
}
@Bean
@Profile({"jndi-ds", ^2^"default"^^})
public DataSource jndiDataSource(Environment env) throws NamingException {
// ...
}
}
weitere Beispiele
Beispiel: Atomikos "on-demand"
Spring container life cycle
@Configuration
public class DataSourceConfig {
// beans: embeddedDataSource, directDataSource, jndiDataSource
@Bean
@Profile("atomikos-jta")
public ^1^static^^ BeanPostProcessor wrapConnections() {
return new WrapConnectionWithAtomikosBpp();
}
}
public class WrapConnectionWithAtomikosBpp
implements BeanPostProcessor {
@Override
public Object ^1^postProcessAfterInitialization^^(Object bean, String beanName) throws BeansException {
if (bean instanceof XADataSource) {
AtomikosDataSourceBean wrapped = new AtomikosDataSourceBean();
wrapped.setUniqueResourceName(beanName);
wrapped.setXaDataSource((XADataSource) bean);
return wrapped;
} else {
return bean;
}
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}