Load spring boot app properties from database

后端 未结 3 2059
傲寒
傲寒 2020-12-24 15:17

I need you to advice me with this issue, in a spring boot application I load some properties from database like (cron periods, email data), I need to export these properties

相关标签:
3条回答
  • 2020-12-24 15:45

    I think it’s a good idea to use BeanPostProcessor and Binder so that you don’t need to list all the attributes you want to read. The following code refers to ConfigurationPropertiesBindingPostProcessor.

    public class PropertiesInsideDatabaseInitializer implements BeanPostProcessor, InitializingBean, ApplicationContextAware {
    
        private JdbcTemplate jdbcTemplate;
        private ApplicationContext applicationContext;
        private BeanDefinitionRegistry registry;
        private Map<String, Object> systemConfigMap = new HashMap<>();
    
        private final String propertySourceName = "propertiesInsideDatabase";
    
        public PropertiesInsideDatabaseInitializer(JdbcTemplate jdbcTemplate){
            this.jdbcTemplate = jdbcTemplate;
        }
    
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
            bind(ConfigurationPropertiesBean.get(this.applicationContext, bean, beanName));
            return bean;
        }
    
        private void bind(ConfigurationPropertiesBean propertiesBean) {
            if (propertiesBean == null || hasBoundValueObject(propertiesBean.getName())) {
                return;
            }
            Assert.state(propertiesBean.getBindMethod() == ConfigurationPropertiesBean.BindMethod.JAVA_BEAN, "Cannot bind @ConfigurationProperties for bean '"
                    + propertiesBean.getName() + "'. Ensure that @ConstructorBinding has not been applied to regular bean");
            try {
                Bindable<?> target = propertiesBean.asBindTarget();
                ConfigurationProperties annotation = propertiesBean.getAnnotation();
                BindHandler bindHandler = new IgnoreTopLevelConverterNotFoundBindHandler();
                MutablePropertySources mutablePropertySources = new MutablePropertySources();
                mutablePropertySources.addLast(new MapPropertySource(propertySourceName, systemConfigMap));
                Binder binder = new Binder(ConfigurationPropertySources.from(mutablePropertySources), new PropertySourcesPlaceholdersResolver(mutablePropertySources),
                        ApplicationConversionService.getSharedInstance(), getPropertyEditorInitializer(), null);
                binder.bind(annotation.prefix(), target, bindHandler);
            }
            catch (Exception ex) {
                throw new BeanCreationException("", ex);
            }
        }
    
        private Consumer<PropertyEditorRegistry> getPropertyEditorInitializer() {
            if (this.applicationContext instanceof ConfigurableApplicationContext) {
                return ((ConfigurableApplicationContext) this.applicationContext).getBeanFactory()::copyRegisteredEditorsTo;
            }
            return null;
        }
    
        private boolean hasBoundValueObject(String beanName) {
            return this.registry.containsBeanDefinition(beanName) && this.registry
                    .getBeanDefinition(beanName).getClass().getName().contains("ConfigurationPropertiesValueObjectBeanDefinition");
        }
    
        @Override
        public void afterPropertiesSet() {
            String sql = "SELECT key, value from system_config";
            List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
            for (Map<String, Object> map : maps) {
                String key = String.valueOf(map.get("key"));
                Object value = map.get("value");
                systemConfigMap.put(key, value);
            }
            this.registry = (BeanDefinitionRegistry) this.applicationContext.getAutowireCapableBeanFactory();
        }
    
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            this.applicationContext = applicationContext;
        }
    }
    

    Modifying the PropertySources in Environment can also be achieved. The BeanPostProcessor interface is implemented to initialize it before creating the Bean

    public class PropertiesInsideDatabaseInitializer implements BeanPostProcessor, InitializingBean, EnvironmentAware {
    
        private JdbcTemplate jdbcTemplate;
        private ConfigurableEnvironment environment;
    
        private final String propertySourceName = "propertiesInsideDatabase";
    
    
        public PropertiesInsideDatabaseInitializer(JdbcTemplate jdbcTemplate){
            this.jdbcTemplate = jdbcTemplate;
        }
    
        @Override
        public void afterPropertiesSet() {
            if(environment != null){
                Map<String, Object> systemConfigMap = new HashMap<>();
                String sql = "SELECT key, value from system_config";
                List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
                for (Map<String, Object> map : maps) {
                    String key = String.valueOf(map.get("key"));
                    Object value = map.get("value");
                    systemConfigMap.put(key, value);
                }
                environment.getPropertySources().addFirst(new MapPropertySource(propertySourceName, systemConfigMap));
            }
        }
    
        @Override
        public void setEnvironment(Environment environment) {
            if(environment instanceof ConfigurableEnvironment){
                this.environment = (ConfigurableEnvironment) environment;
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-24 15:46

    You could configure the beans with the database values manually depending on what your need is (this way you can take advantage of Spring CDI and boot database configs).

    Take setting the session timeout for example:

    @SpringBootApplication
    public class MySpringBootApplication extends SpringBootServletInitializer {           
        public static void main(String[] args) {
            SpringApplication.run(MySpringBootApplication.class, args);
        }
    
        @Bean
        public HttpSessionListener httpSessionListener(){
            return new MyHttpSessionListener();
        }
    }
    

    Then a bean definition for configuring the bean:

    import javax.servlet.http.HttpSessionEvent;
    import javax.servlet.http.HttpSessionListener;
    
    public class MyHttpSessionListener implements HttpSessionListener {   
        @Autowired
        private MyRepository myRepository;
    
        @Override
        public void sessionCreated(HttpSessionEvent se) {
            se.getSession().setMaxInactiveInterval(this.myRepository.getSessionTimeoutSeconds()); 
        }
    
        @Override
        public void sessionDestroyed(HttpSessionEvent se) {
            // Noop
        }
    
    }
    

    Note: you could move the database call to a @PostConstruct method to avoid making it for each session.

    0 讨论(0)
  • 2020-12-24 15:59

    For those who need load properties from database before application starts, and make those props accesible by @Value anywhere in your project, just add this processor.

    public class ReadDbPropertiesPostProcessor implements EnvironmentPostProcessor {
    /**
     * Name of the custom property source added by this post processor class
     */
    private static final String PROPERTY_SOURCE_NAME = "databaseProperties";
    
    private String[] KEYS = {
            "excel.threads",
            "cronDelay",
            "cronDelayEmail",
            "spring.mail.username",
            "spring.mail.password",
            "spring.mail.host",
            "spring.mail.port",
            "spring.mail.properties.mail.transport.protocol",
            "spring.mail.properties.mail.smtp.auth",
            "spring.mail.properties.mail.smtp.starttls.enabled",
            "spring.mail.properties.mail.debug",
            "spring.mail.properties.mail.smtp.starttls.required",
            "spring.mail.properties.mail.socketFactory.port",
            "spring.mail.properties.mail.socketFactory.class",
            "spring.mail.properties.mail.socketFactory.fallback",
            "white.executor.threads",
            "white.search.threads",
            "lot.sync.threads",
            "lot.async.threads",
            "lot.soap.threads",
            "excel.async.threads",
            "kpi.threads",
            "upload.threads"
    };
    
    /**
     * Adds Spring Environment custom logic. This custom logic fetch properties from database and setting highest precedence
     */
    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
    
        Map<String, Object> propertySource = new HashMap<>();
    
        try {
    
            // Build manually datasource to ServiceConfig
            DataSource ds = DataSourceBuilder
                    .create()
                    .username(environment.getProperty("spring.datasource.username"))
                    .password(environment.getProperty("spring.mail.password"))
                    .url(environment.getProperty("spring.datasource.url"))
                    .driverClassName("com.mysql.jdbc.Driver")
                    .build();
    
            // Fetch all properties
    
            Connection connection = ds.getConnection();
    
            JTrace.genLog(LogSeverity.informational, "cargando configuracion de la base de datos");
    
            PreparedStatement preparedStatement = connection.prepareStatement("SELECT value FROM config WHERE id = ?");
    
            for (int i = 0; i < KEYS.length; i++) {
    
                String key = KEYS[i];
    
                preparedStatement.setString(1, key);
    
                ResultSet rs = preparedStatement.executeQuery();
    
                // Populate all properties into the property source
                while (rs.next()) {
                    propertySource.put(key, rs.getString("value"));
                }
    
                rs.close();
                preparedStatement.clearParameters();
    
            }
    
            preparedStatement.close();
            connection.close();
    
            // Create a custom property source with the highest precedence and add it to Spring Environment
            environment.getPropertySources().addFirst(new MapPropertySource(PROPERTY_SOURCE_NAME, propertySource));
    
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }
    } // class ReadDbPropertiesPostProcessor end
    

    In application.properties must exist datasource data in order to be able to connect to database.

    Then in folder META-INF create a file named spring.factories an there put the following line:

    org.springframework.boot.env.EnvironmentPostProcessor=test.config.ReadDbPropertiesPostProcessor
    

    And that's it, retreived properties will be accessible anywhere.

    0 讨论(0)
提交回复
热议问题