tutorial starter start mvc example application java spring spring-boot

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; } }