【问题标题】:Spring Environment Property Source ConfigurationSpring环境属性源配置
【发布时间】:2013-01-19 15:49:09
【问题描述】:

我正在开发一个应用程序库,其中包含一个名为“Config”的实用程序类,它由 Spring Environment 对象支持,并为所有应用程序配置值提供强类型 getter。

配置的属性源可能会因环境(DEV/PROD)和使用情况(独立/测试/webapp)而异,范围可以从默认属性(系统和环境属性)到自定义数据库和 JNDI 源。

我正在苦苦挣扎的是如何让使用这个库的应用程序轻松配置Environment 使用的属性源,以便这些属性可用于我们的Config 类和通过@ 987654325@.

我们仍在使用 XML 配置,因此理想情况下,这可以在 XML 中进行配置。

<bean id="propertySources" class="...">
    <property name="sources">
        <list>
            <ref local="jndiPropertySource"/>
            <ref local="databasePropertySource"/>
        </list>
    </property>
</bean>

...然后以某种方式注入到 Environment 的属性源集合中。

我了解到,由于应用上下文生命周期的时间安排,这样的事情可能是不可能的,这可能需要使用应用初始化程序类来完成。

有什么想法吗?

【问题讨论】:

    标签: spring


    【解决方案1】:

    这取决于您要如何使用属性,如果是使用${propertyname} 语法注入属性,那么是的,只需使用 PropertySourcesPlaceHolderConfigurer 即可,它在内部可以访问在环境中注册的 PropertySources。

    如果您打算直接使用 Environment,例如使用 env.getProperty(),那么您是对的 - 使用 PropertySourcesPlaceHolderConfigurer 的属性在此处不可见。唯一的方法是使用 Java 代码注入它,我知道有两种方法:

    一个。使用 Java 配置:

    @Configuration
    @PropertySource("classpath:/app.properties")
    public class SpringConfig{
    
    }
    

    b.使用自定义ApplicationContextInitializer,其描述方式为here

    【讨论】:

    • 我们需要这两种情况(环境直接和占位符配置器)。但是,进一步研究一下,看起来这有点统一,如果没有指定其他来源,占位符配置将遵循环境。如果这是真的,我们只需要在 Environment 上设置源,我们会“免费”在占位符配置器中获得它。所以,看起来我们需要像 @PropertySource 注解这样的东西,但可以通过 XML 而不是 Java 进行配置。有这样的吗?
    • 还有一个问题。我是ApplicationContextInitializer 示例,如果添加到环境中的PropertySource 需要是配置的bean(例如需要数据库连接信息)怎么办。由于正在初始化应用程序上下文,因此 bean 尚不可用。似乎是鸡/蛋的问题。
    • 是的,我同意,感觉不是很干净 - 个人使用 @PropertySource 对我来说效果很好,但看起来没有 xml 等效项。
    • 我尝试实现方法 b,但我无法让我的数据库使用占位符属性:我收到错误“无法从底层数据库获取连接!”。但是,通过代码配置(使用@Configuration)时一切正常。
    【解决方案2】:

    我想出了以下似乎可行的方法,但我对 Spring 还很陌生,所以我不太确定它在不同的用例下会如何。

    基本上,该方法是扩展PropertySourcesPlaceholderConfigurer 并添加一个setter 以允许用户轻松配置XML 中的PropertySource 对象列表。创建后,将属性源复制到当前的Environment

    这基本上允许在一个地方配置属性源,但同时被占位符配置和 Environment.getProperty 场景使用。

    扩展PropertySourcesPlaceholderConfigurer

    public class ConfigSourcesConfigurer 
            extends PropertySourcesPlaceholderConfigurer
            implements EnvironmentAware, InitializingBean {
    
        private Environment environment;
        private List<PropertySource> sourceList;
    
        // Allow setting property sources as a List for easier XML configuration
        public void setPropertySources(List<PropertySource> propertySources) {
    
            this.sourceList = propertySources;
            MutablePropertySources sources = new MutablePropertySources();
            copyListToPropertySources(this.sourceList, sources);        
            super.setPropertySources(sources);
        }
    
        @Override
        public void setEnvironment(Environment environment) {
            // save off Environment for later use
            this.environment = environment;
            super.setEnvironment(environment);
        }
    
        @Override
        public void afterPropertiesSet() throws Exception {
    
            // Copy property sources to Environment
            MutablePropertySources envPropSources = ((ConfigurableEnvironment)environment).getPropertySources();
            copyListToPropertySources(this.sourceList, envPropSources);
        }
    
        private void copyListToPropertySources(List<PropertySource> list, MutablePropertySources sources) {
    
            // iterate in reverse order to insure ordering in property sources object
            for(int i = list.size() - 1; i >= 0; i--) {
                sources.addFirst(list.get(i));
            }
        }
    }
    

    beans.xml 文件显示基本配置

    <beans>
        <context:annotation-config/>
        <context:component-scan base-package="com.mycompany" />
    
        <bean class="com.mycompany.ConfigSourcesConfigurer">
            <property name="propertySources">
                <list>
                    <bean class="org.mycompany.CustomPropertySource" />
                    <bean class="org.springframework.core.io.support.ResourcePropertySource">
                        <constructor-arg value="classpath:default-config.properties" />
                    </bean>
                </list>
            </property>
        </bean>
        <bean class="com.mycompany.TestBean">
            <property name="stringValue" value="${placeholder}" />
        </bean>
    </beans>
    

    【讨论】:

    • 您能举例说明您的 CustomPropertySource 的样子吗?
    【解决方案3】:

    以下内容适用于 Spring 3.2.4。

    PropertySourcesPlaceholderConfigurer 必须静态注册才能处理占位符。

    自定义属性源在init 方法中注册,并且由于默认属性源已经注册,它本身可以使用占位符进行参数化。

    JavaConfig 类:

    @Configuration
    @PropertySource("classpath:propertiesTest2.properties")
    public class TestConfig {
    
        @Autowired
        private ConfigurableEnvironment env;
    
        @Value("${param:NOVALUE}")
        private String param;
    
        @PostConstruct
        public void init() {
            env.getPropertySources().addFirst(new CustomPropertySource(param));
        }
    
        @Bean
        public static PropertySourcesPlaceholderConfigurer placeHolderConfigurer() {
            return new PropertySourcesPlaceholderConfigurer();
        }
    
        @Bean
        public TestBean1 testBean1() {
            return new TestBean1();
        }
     }
    

    自定义属性来源:

        public class CustomPropertySource extends PropertySource<Object> {
    
        public CustomPropertySource(String param) {
            super("custom");
            System.out.println("Custom property source initialized with param " + param + ".");
        }
    
        @Override
        public Object getProperty(String name) {
            return "IT WORKS";
        }
    
    }
    

    测试bean(getValue() 将输出"IT WORKS"):

    public class TestBean1 {
    
       @Value("${value:NOVALUE}")
       private String value;
    
       public String getValue() {
          return value;
       }
    }
    

    【讨论】:

      【解决方案4】:

      我遇到了类似的问题,在我的情况下,我在独立应用程序中使用Spring,加载默认配置后,我可能需要应用配置目录中存在的另一个属性文件(延迟加载配置)。我的解决方案受到 Spring Boot documentation 的启发,但不依赖于 Spring Boot。源码见下:

      @PropertySources(@PropertySource(value = "classpath:myapp-default.properties"))
      public class PersistenceConfiguration {
      
          private final Logger log = LoggerFactory.getLogger(getClass());
      
          private ConfigurableEnvironment env;
      
          @Bean
          public static PropertySourcesPlaceholderConfigurer placeholderConfigurerDev(ConfigurableEnvironment env) {
              return new PropertySourcesPlaceholderConfigurer();
          }
      
      
          @Autowired
          public void setConfigurableEnvironment(ConfigurableEnvironment env) {
              for(String profile: env.getActiveProfiles()) {
                  final String fileName = "myapp-" + profile + ".properties";
                  final Resource resource = new ClassPathResource(fileName);
                  if (resource.exists()) {
                      try {
                          MutablePropertySources sources = env.getPropertySources();
                          sources.addFirst(new PropertiesPropertySource(fileName,PropertiesLoaderUtils.loadProperties(resource)));
                      } catch (Exception ex) {
                          log.error(ex.getMessage(), ex);
                          throw new RuntimeException(ex.getMessage(), ex);
                      }
                  }
              }
              this.env = env;
          }
      
          ...
      
      }
      

      【讨论】:

        【解决方案5】:

        我最近遇到了如何在环境中注册自定义属性源的问题。我的具体问题是我有一个带有 Spring 配置的库,我想将它导入到 Spring 应用程序上下文中,并且它需要自定义属性源。但是,我不一定可以控制创建应用程序上下文的所有位置。因此,我不想使用推荐的 ApplicationContextInitializer 或 register-before-refresh 机制来注册自定义属性源。

        我发现真正令人沮丧的是,使用旧的 PropertyPlaceholderConfigurer,很容易在 Spring 配置中完全继承和自定义配置器。相反,要自定义属性源,我们被告知我们必须在 Spring 配置本身中而不是在初始化应用程序上下文之前进行。

        经过一些研究和反复试验,我发现 可以从 Spring 配置内部注册自定义属性源,但您必须小心如何操作。在上下文中执行任何 PropertySourcesPlaceholderConfigurers 之前,需要注册源。您可以通过将源注册设置为具有 PriorityOrdered 的 BeanFactoryPostProcessor 和比使用源的 PropertySourcesPlaceholderConfigurer 更高优先级的顺序来做到这一点。

        我写了这个类,它完成了这项工作:

        package example;
        
        import java.io.IOException;
        import java.util.Properties;
        
        import org.springframework.beans.BeansException;
        import org.springframework.beans.factory.BeanInitializationException;
        import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
        import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
        import org.springframework.context.ApplicationContext;
        import org.springframework.context.ApplicationContextAware;
        import org.springframework.core.Ordered;
        import org.springframework.core.PriorityOrdered;
        import org.springframework.core.env.ConfigurableEnvironment;
        import org.springframework.core.env.Environment;
        import org.springframework.core.env.MutablePropertySources;
        import org.springframework.core.env.PropertiesPropertySource;
        import org.springframework.core.env.PropertySource;
        import org.springframework.core.io.support.PropertiesLoaderSupport;
        
        /**
         * This is an abstract base class that can be extended by any class that wishes
         * to become a custom property source in the Spring context.
         * <p>
         * This extends from the standard Spring class PropertiesLoaderSupport, which
         * contains properties that specify property resource locations, plus methods
         * for loading properties from specified resources. These are all available to
         * be used from the Spring configuration, and by subclasses of this class.
         * <p>
         * This also implements a number of Spring flag interfaces, all of which are
         * required to maneuver instances of this class into a position where they can
         * register their property sources BEFORE PropertySourcesPlaceholderConfigurer
         * executes to substitute variables in the Spring configuration:
         * <ul>
         * <li>BeanFactoryPostProcessor - Guarantees that this bean will be instantiated
         * before other beans in the context. It also puts it in the same phase as
         * PropertySourcesPlaceholderConfigurer, which is also a BFPP. The
         * postProcessBeanFactory method is used to register the property source.</li>
         * <li>PriorityOrdered - Allows the bean priority to be specified relative to
         * PropertySourcesPlaceholderConfigurer so that this bean can be executed first.
         * </li>
         * <li>ApplicationContextAware - Provides access to the application context and
         * its environment so that the created property source can be registered.</li>
         * </ul>
         * <p>
         * The Spring configuration for subclasses should contain the following
         * properties:
         * <ul>
         * <li>propertySourceName - The name of the property source this will register.</li>
         * <li>location(s) - The location from which properties will be loaded.</li>
         * <li>addBeforeSourceName (optional) - If specified, the resulting property
         * source will be added before the given property source name, and will
         * therefore take precedence.</li>
         * <li>order (optional) - The order in which this source should be executed
         * relative to other BeanFactoryPostProcessors. This should be used in
         * conjunction with addBeforeName so that if property source factory "psfa"
         * needs to register its property source before the one from "psfb", "psfa"
         * executes AFTER "psfb".
         * </ul>
         * 
         * @author rjsmith2
         *
         */
        public abstract class AbstractPropertySourceFactory extends
                PropertiesLoaderSupport implements ApplicationContextAware,
                PriorityOrdered, BeanFactoryPostProcessor {
        
            // Default order will be barely higher than the default for
            // PropertySourcesPlaceholderConfigurer.
            private int order = Ordered.LOWEST_PRECEDENCE - 1;
        
            private String propertySourceName;
        
            private String addBeforeSourceName;
        
            private ApplicationContext applicationContext;
        
            private MutablePropertySources getPropertySources() {
                final Environment env = applicationContext.getEnvironment();
                if (!(env instanceof ConfigurableEnvironment)) {
                    throw new IllegalStateException(
                            "Cannot get environment for Spring application context");
                }
                return ((ConfigurableEnvironment) env).getPropertySources();
            }
        
            public int getOrder() {
                return order;
            }
        
            public void setOrder(int order) {
                this.order = order;
            }
        
            public String getPropertySourceName() {
                return propertySourceName;
            }
        
            public void setPropertySourceName(String propertySourceName) {
                this.propertySourceName = propertySourceName;
            }
        
            public String getAddBeforeSourceName() {
                return addBeforeSourceName;
            }
        
            public void setAddBeforeSourceName(String addBeforeSourceName) {
                this.addBeforeSourceName = addBeforeSourceName;
            }
        
            public void setApplicationContext(ApplicationContext applicationContext) {
                this.applicationContext = applicationContext;
            }
        
            /**
             * Subclasses can override this method to perform adjustments on the
             * properties after they are read.
             * <p>
             * This should be done by getting, adding, removing, and updating properties
             * as needed.
             * 
             * @param props
             *            properties to adjust
             */
            protected void convertProperties(Properties props) {
                // Override in subclass to perform conversions.
            }
        
            /**
             * Creates a property source from the specified locations.
             * 
             * @return PropertiesPropertySource instance containing the read properties
             * @throws IOException
             *             if properties cannot be read
             */
            protected PropertySource<?> createPropertySource() throws IOException {
                if (propertySourceName == null) {
                    throw new IllegalStateException("No property source name specified");
                }
        
                // Load the properties file (or files) from specified locations.
                final Properties props = new Properties();
                loadProperties(props);
        
                // Convert properties as required.
                convertProperties(props);
        
                // Convert to property source.
                final PropertiesPropertySource source = new PropertiesPropertySource(
                        propertySourceName, props);
        
                return source;
            }
        
            @Override
            public void postProcessBeanFactory(
                    ConfigurableListableBeanFactory beanFactory) throws BeansException {
                try {
                    // Create the property source, and get its desired position in
                    // the list of sources.
                    if (logger.isDebugEnabled()) {
                        logger.debug("Creating property source [" + propertySourceName
                                + "]");
                    }
                    final PropertySource<?> source = createPropertySource();
        
                    // Register the property source.
                    final MutablePropertySources sources = getPropertySources();
                    if (addBeforeSourceName != null) {
                        if (sources.contains(addBeforeSourceName)) {
                            if (logger.isDebugEnabled()) {
                                logger.debug("Adding property source ["
                                        + propertySourceName + "] before ["
                                        + addBeforeSourceName + "]");
                            }
                            sources.addBefore(addBeforeSourceName, source);
                        } else {
                            logger.warn("Property source [" + propertySourceName
                                    + "] cannot be added before non-existent source ["
                                    + addBeforeSourceName + "] - adding at the end");
                            sources.addLast(source);
                        }
                    } else {
                        if (logger.isDebugEnabled()) {
                            logger.debug("Adding property source ["
                                    + propertySourceName + "] at the end");
                        }
                        sources.addLast(source);
                    }
                } catch (Exception e) {
                    throw new BeanInitializationException(
                            "Failed to register property source", e);
                }
            }
        
        }
        

        这里需要注意的是,这个属性源工厂类的默认顺序比PropertySourcesPlaceholderConfigurer的默认顺序要高。

        此外,属性源的注册发生在 postProcessBeanFactory 中,这意味着它将以相对于 PropertySourcesPlaceholderConfigurer 的正确顺序执行。我发现 InitializingBean 和 afterPropertiesSet 不尊重 order 参数的困难方式,我放弃了这种方法,因为它是错误的和多余的。

        最后,因为这是一个 BeanFactoryPostProcessor,所以尝试以依赖关系的方式连接很多是一个坏主意。因此,该类直接通过应用程序上下文访问环境,应用程序上下文是使用 ApplicationContextAware 获得的。

        在我的例子中,我需要属性源来解密密码属性,我使用以下子类来实现:

        package example;
        
        import java.util.Properties;
        
        /**
         * This is a property source factory that creates a property source that can
         * process properties for substituting into a Spring configuration.
         * <p>
         * The only thing that distinguishes this from a normal Spring property source
         * is that it decrypts encrypted passwords.
         * 
         * @author rjsmith2
         *
         */
        public class PasswordPropertySourceFactory extends
                AbstractPropertySourceFactory {
        
            private static final PasswordHelper passwordHelper = new PasswordHelper();
        
            private String[] passwordProperties;
        
            public String[] getPasswordProperties() {
                return passwordProperties;
            }
        
            public void setPasswordProperties(String[] passwordProperties) {
                this.passwordProperties = passwordProperties;
            }
        
            public void setPasswordProperty(String passwordProperty) {
                this.passwordProperties = new String[] { passwordProperty };
            }
        
            @Override
            protected void convertProperties(Properties props) {
                // Adjust password fields by decrypting them.
                if (passwordProperties != null) {
                    for (String propName : passwordProperties) {
                        final String propValue = props.getProperty(propName);
                        if (propValue != null) {
                            final String plaintext = passwordHelper
                                    .decryptString(propValue);
                            props.setProperty(propName, plaintext);
                        }
                    }
                }
            }
        
        }
        

        最后,我在我的 Spring 配置中指定了属性源工厂:

        <!-- Enable property resolution via PropertySourcesPlaceholderConfigurer. 
            The order has to be larger than the ones used by custom property sources 
            so that those property sources are registered before any placeholders
            are substituted. -->
        <context:property-placeholder order="1000" ignore-unresolvable="true" />
        
        <!-- Register a custom property source that reads DB properties, and
             decrypts the database password. -->
        <bean class="example.PasswordPropertySourceFactory">
            <property name="propertySourceName" value="DBPropertySource" />
            <property name="location" value="classpath:db.properties" />
            <property name="passwordProperty" value="db.password" />
            <property name="ignoreResourceNotFound" value="true" />
        
            <!-- Order must be lower than on property-placeholder element. -->
            <property name="order" value="100" />
        </bean>
        

        说实话,使用 PropertySourcesPlaceholderConfigurer 和 AbstractPropertySourceFactory 中的默认顺序,可能甚至不需要在 Spring 配置中指定顺序。

        尽管如此,这是可行的,并且它不需要对应用程序上下文初始化进行任何摆弄。

        【讨论】:

          猜你喜欢
          • 2015-02-18
          • 2014-12-01
          • 2015-11-22
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-09-02
          • 2013-08-16
          • 1970-01-01
          相关资源
          最近更新 更多