java - starter - ¿Cómo creo frijoles programáticamente en Spring Boot?
spring boot wikipedia (2)
¿Qué le parece crear sus frijoles y pedirle a Boot que le inyecte valores?
Algo como
@Bean
@ConfigurationProperties("ds.client1")
public DataSource dataSource() {
DataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties("ds.client2")
public DataSource dataSource() {
DataSourceBuilder.create().build();
}
Entonces, cualquier ajuste en el espacio de nombres ds.client1
pertenece a la primera fuente de datos (es decir, ds.client1.password
es la contraseña de fuente de datos para ese DataSource
).
¿Pero tal vez no sabe cuántas fuentes de datos tendrá? Esto se está volviendo más complicado, especialmente si necesita inyectar esas fuentes de datos dinámicas en otros objetos. Si solo necesita buscarlos por su nombre, puede registrarlos usted mismo como solteros. Aquí hay un ejemplo que funciona
@ConfigurationProperties(prefix = "ds")
public class DataSourceSettings implements BeanFactoryAware {
private List<String> clients = new ArrayList<>();
private BeanFactory beanFactory;
public List<String> getClients() {
return clients;
}
public void setClients(List<String> clients) {
this.clients = clients;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
@PostConstruct
public void configure() {
Map<String, String> clientDataSources = new HashMap<String, String>();
for (String client : clients) {
// extract client name
String[] parts = client.split("//|");
String clientName = parts[0];
String url = parts[1];
// client to datasource mapping
String dsName = url.substring(url.lastIndexOf("/") + 1);
if (clientName.contains(",")) {
// multiple clients with same datasource
String[] clientList = clientName.split(",");
for (String c : clientList) {
clientDataSources.put(c, url);
}
}
else {
clientDataSources.put(clientName, url);
}
}
Assert.state(beanFactory instanceof ConfigurableBeanFactory, "wrong bean factory type");
ConfigurableBeanFactory configurableBeanFactory = (ConfigurableBeanFactory) beanFactory;
for (Map.Entry<String, String> entry : clientDataSources.entrySet()) {
DataSource dataSource = createDataSource(entry.getValue());
configurableBeanFactory.registerSingleton(entry.getKey(), dataSource);
}
}
private DataSource createDataSource(String url) {
return DataSourceBuilder.create().url(url).build();
}
}
Tenga en cuenta que esos beans solo están disponibles por búsqueda de nombre de bean. Avíseme si eso funciona para usted.
Tengo una aplicación que tiene una cantidad de configuraciones de fuente de datos listadas en application.properties. Tengo una clase @ConfigurationProperties
que carga estas configuraciones. Ahora quiero tomar los valores de esta clase ConfigurationProperties
y usarlos para crear beans DataSource sobre la marcha. Intenté usar @PostConstruct
e implementar BeanFactoryPostProcessor
. Sin embargo, con BeanFactoryPostProcessor
, el procesamiento parece ocurrir temprano, antes de que mi clase ConfigurationProperties
haya sido poblada. ¿Cómo puedo leer propiedades y crear beans DataSource
sobre la marcha con Spring Boot?
Así es como se ve mi application.properties:
ds.clients[0]=client1|jdbc:db2://server/client1
ds.clients[1]=client2,client3|jdbc:db2://server/client2
ds.clients[2]=client4|jdbc:db2://server/client4
ds.clients[3]=client5|jdbc:db2://server/client5
Y mi clase ConfigurationProperties:
@Component
@ConfigurationProperties(prefix = "ds")
public class DataSourceSettings {
public static Map<String, String> CLIENT_DATASOURCES = new LinkedHashMap<>();
private List<String> clients = new ArrayList<>();
public List<String> getClients() {
return clients;
}
public void setClients(List<String> clients) {
this.clients = clients;
}
@PostConstruct
public void configure() {
for (String client : clients) {
// extract client name
String[] parts = client.split("//|");
String clientName = parts[0];
String url = parts[1];
// client to datasource mapping
String dsName = url.substring(url.lastIndexOf("/") + 1);
if (clientName.contains(",")) {
// multiple clients with same datasource
String[] clientList = clientName.split(",");
for (String c : clientList) {
CLIENT_DATASOURCES.put(c, dsName);
}
} else {
CLIENT_DATASOURCES.put(clientName, dsName);
}
}
}
Al final de este método @PostConstruct
, me gustaría crear un BasicDataSource
con estas configuraciones y agregarlo a ApplicationContext. Sin embargo, si intento hacer esto implementando BeanFactoryPostProcessor
e implementando postProcessBeanFactory
, la propiedad de los clients
es nula, al CLIENT_DATASOURCES
que CLIENT_DATASOURCES
que he @PostConstruct
con @PostConstruct
.
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
System.out.println("clients: " + CLIENT_DATASOURCES);
}
¿Cuál es la mejor manera de crear fuentes de datos sobre la marcha con Spring Boot?
Creé un proyecto de ejemplo en github para demostrar tu uso.
https://github.com/lhotari/dynamic-datasources
Implementé un ImportBeanDefinitionRegistrar para agregar los beans. Puede obtener la configuración mediante la implementación de EnvironmentAware . Puede haber otras maneras de lograr su objetivo, pero esta fue la forma en que usé en GspAutoConfiguration para registrar beans dinámicamente. GspAutoConfiguration hace que Grails GSP esté disponible en las aplicaciones Spring Boot.
Aquí está la clase de configuración relevante en el ejemplo de la fuente de datos dinámica: https://github.com/lhotari/dynamic-datasources/blob/master/src/main/groovy/sample/DynamicDataSourcesConfiguration.java
package sample;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import org.springframework.beans.FatalBeanException;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.boot.bind.PropertiesConfigurationFactory;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.jdbc.datasource.SingleConnectionDataSource;
import org.springframework.validation.BindException;
@Configuration
public class DynamicDataSourcesConfiguration implements ImportBeanDefinitionRegistrar, EnvironmentAware {
private ConfigurableEnvironment environment;
private static Map<String, Object> defaultDsProperties = new HashMap<String, Object>() {
{
put("suppressClose", true);
put("username", "sa");
put("password", "");
put("driverClassName", "org.h2.Driver");
}
};
@Override
public void setEnvironment(Environment environment) {
this.environment = (ConfigurableEnvironment)environment;
}
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
DataSourceSettings settings = resolveSettings();
for (Entry<String, String> entry : settings.clientDataSources().entrySet()) {
createDsBean(registry, entry.getKey(), entry.getValue());
}
}
private void createDsBean(BeanDefinitionRegistry registry, String beanName, String jdbcUrl) {
GenericBeanDefinition beanDefinition = createBeanDefinition(SingleConnectionDataSource.class);
beanDefinition.getPropertyValues().addPropertyValues(defaultDsProperties).addPropertyValue("url", jdbcUrl);
registry.registerBeanDefinition(beanName, beanDefinition);
}
private GenericBeanDefinition createBeanDefinition(Class<?> beanClass) {
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(beanClass);
beanDefinition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_NO);
return beanDefinition;
}
private DataSourceSettings resolveSettings() {
DataSourceSettings settings = new DataSourceSettings();
PropertiesConfigurationFactory<Object> factory = new PropertiesConfigurationFactory<Object>(settings);
factory.setTargetName("ds");
factory.setPropertySources(environment.getPropertySources());
factory.setConversionService(environment.getConversionService());
try {
factory.bindPropertiesToTarget();
}
catch (BindException ex) {
throw new FatalBeanException("Could not bind DataSourceSettings properties", ex);
}
return settings;
}
}