복붙노트

[SPRING] 스프링 부트에서 빈을 프로그래밍 방식으로 만드는 방법은 무엇입니까?

SPRING

스프링 부트에서 빈을 프로그래밍 방식으로 만드는 방법은 무엇입니까?

application.properties에 나열된 많은 데이터 소스 설정이있는 응용 프로그램이 있습니다. 이 설정을로드하는 @ConfigurationProperties 클래스가 있습니다. 이제이 ConfigurationProperties 클래스의 값을 가져 와서 DataSource Bean을 직접 작성하는 데 사용하려고합니다. @PostConstruct를 사용하고 BeanFactoryPostProcessor를 구현하려했습니다. 그러나 BeanFactoryPostProcessor를 사용하면 처리가 일찍 진행된 것 같습니다. 즉, ConfigurationProperties 클래스가 채워지기 전입니다. Spring Boot로 속성을 읽고 DataSource 빈을 생성하는 방법은 무엇입니까?

내 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

그리고 내 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);
            }
        }
    }

이 @PostConstruct 메서드가 끝나면이 설정으로 BasicDataSource를 만들고 ApplicationContext에 추가하고 싶습니다. 그러나 BeanFactoryPostProcessor를 구현하고 postProcessBeanFactory를 구현하여이 작업을 수행하려고하면 클라이언트 속성이 null이고 @PostConstruct로 채워진 CLIENT_DATASOURCES도 마찬가지입니다.

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    System.out.println("clients: " + CLIENT_DATASOURCES);
}

Spring Boot로 즉석에서 데이터 소스를 생성하는 가장 좋은 방법은 무엇입니까?

해결법

  1. ==============================

    1.Bean을 생성하고 Boot에 값을 주입하도록 요청하는 것은 어떻습니까?

    Bean을 생성하고 Boot에 값을 주입하도록 요청하는 것은 어떻습니까?

    좋아하는 것

    @Bean
    @ConfigurationProperties("ds.client1")
    public DataSource dataSource() {
        DataSourceBuilder.create().build();
    }
    
    @Bean
    @ConfigurationProperties("ds.client2")
    public DataSource dataSource() {
        DataSourceBuilder.create().build();
    }
    

    그런 다음 ds.client1 네임 스페이스의 모든 설정이 첫 번째 데이터 소스에 속합니다 (즉, ds.client1.password는 해당 DataSource의 데이터 소스 비밀번호 임).

    하지만 얼마나 많은 데이터 소스를 보유하고 있는지 알 수 없을 수도 있습니다. 특히 다른 동적 데이터 소스를 다른 객체에 삽입해야하는 경우 더욱 복잡해지고 있습니다. 이름으로 검색하기 만하면 싱글 톤으로 등록 할 수 있습니다. 다음은 작동하는 예입니다.

    @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();
        }
    }
    

    이 빈은 빈 이름 검색에서만 사용할 수 있습니다. 그게 당신을 위해 작동하는지 알려주세요.

  2. ==============================

    2.당신의 유스 케이스를 보여주기 위해 github에 예제 프로젝트를 만들었습니다.

    당신의 유스 케이스를 보여주기 위해 github에 예제 프로젝트를 만들었습니다.

    https://github.com/lhotari/dynamic-datasources

    나는 콩을 추가하기 위해 ImportBeanDefinitionRegistrar를 구현했다. EnvironmentAware를 구현하여 구성을 유지할 수 있습니다. 목표를 달성하는 다른 방법이있을 수도 있지만, GspAutoConfiguration에서 bean을 동적으로 등록하는 방법이었습니다. GspAutoConfiguration은 Spring 부트 애플리케이션에서 Grails GSP를 사용할 수 있도록합니다.

    다음은 동적 데이터 소스 샘플의 관련 구성 클래스입니다. 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;
        }
    
    }
    
  3. from https://stackoverflow.com/questions/25160221/how-do-i-create-beans-programmatically-in-spring-boot by cc-by-sa and MIT license