0

创建在运行时加载属性集的服务的最佳方法是什么(bean 传递“xyz”并加载 xyz.properties)?在输入命令以启动服务之前,需要能够将这些属性文件放入类路径之外的文件夹中(编辑:这可能在程序运行时的任何时候发生)。

我已经有一个系统可以做到这一点,我们已经使用了一年多,但我正在迁移到 spring 以使代码更加模块化(通过 DI 更容易地定制服务)并且更容易维护。从 IoC 的角度来看,我当前创建环境然后将其与“this”一起传递给依赖项的方法似乎是颠倒的。

有没有办法使用 PropertyPlaceholderConfigurer 而无需硬编码属性文件的名称?也许只是对我传递给它的依赖项可以加载的服务的构造函数的变量的引用?到目前为止,看起来我必须创建一个服务并在没有任何配置的情况下注入它的依赖项,然后为每个服务调用一个单独的加载方法来传递属性,但这似乎我并没有真正使用 spring。

用例:该应用程序将客户端连接到各种服务器,并将来自其他应用程序的请求转发到这些服务器。非程序员必须能够在不关闭或重新启动应用程序的情况下添加新的配置文件。配置文件将包括主机、端口和登录信息等基本信息,但也包括更复杂的信息,例如是否使用 tcp/http、ssl/https(这将确定要使用的客户端类型)以及超时和池 min/max/等(这将需要默认值)。

4

3 回答 3

1

我试过了PropertyPlaceholderConfigurer,坦率地说,不知何故,我无法绕过它。当您使用现有选项时,它很容易使用,但我无法扩展框架。

所以我的方法要简单得多:

  1. 创建一个@InjectConfig以配置键为参数的注释。

  2. 在您的 bean/服务中,使用此注释注释字段或公共设置器。

  3. 编写一个BeanPostProcessor从“配置提供者”获取选项并将它们注入到字段/设置器中。

  4. 现在您只需要一个配置提供程序。将其注入后处理器,您就完成了。

注意:我更喜欢注释设置器,因为这意味着您可以轻松地通过测试配置服务(只需调用设置器),而无需为 238576 配置文件提供智能名称。

编辑如果您有很多配置,那么配置工厂可能是更好的选择:

  1. 创建一个描述配置包的键(我通常在这里使用枚举或新类型来防止拼写错误)
  2. 创建时将此密钥放入服务中(手动或通过 Spring)
  3. 编写一个可以返回的配置工厂Properties或一个Map用于配置键的配置工厂。
  4. 将此工厂注入您的服务

在您的服务的初始化代码中,使用密钥通过工厂查找您的配置。

使用这种方法,您可以拥有一个在测试中总是返回相同内容的虚拟工厂和一个更复杂的生产工厂。

然后可以通过 spring 配置真正的工厂,这样它就知道在哪里寻找配置文件。一种方法是注册java.io.File每个配置键。现在您的关注点(配置服务和加载配置)完全分开了。

于 2013-08-19T13:41:26.983 回答
0

PropertyPlaceholderConfigurer 在应用程序上下文初始化时读取和初始化文件,并且只读取一次。所以很可能你不能在运行时配置它。但是你可以有变量。例如,就我而言,我有默认属性和用户特定属性。因此 PropertyPlaceholderConfigurer 首先从类路径加载属性,然后尝试在定义的位置(用户主文件夹)查找其他属性。我用户的属性文件存在,因此配置器加载它并覆盖属性。

这是我的例子:

<bean id="config" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="ignoreResourceNotFound" value="true"/> <!-- do not throw exception if file not found -->
    <property name="locations">
        <list>
            <value>classpath:server.properties</value>
            <value>file:${user.home}/user.properties</value>
        </list>
    </property>
</bean>

我不确定这个答案是否正是您所需要的。但我试图猜测你的实际任务是什么。因此,如果您每次访问它们时都需要重新读取属性运行时,您必须像以前一样手动执行,因为 Spring 应用程序上下文可以帮助您配置应用程序初始配置。

于 2013-08-19T13:43:52.527 回答
0

似乎最好的方法可能是使用包含主 ApplicationContext 的 ServiceManager ,然后让每个 Service 初始化自己的 FileSystemXmlApplicationContext ,并将主上下文作为父级,如下所示:

public class ServiceManager {

ApplicationContext appContext;
String APP_HOME = System.getProperty("user.home") + File.separator;

public void init() {
    //set main spring context
    appContext = new AnnotationConfigApplicationContext(AppConfig.class);
}

public void start(String serviceName) throws Exception {
    ApplicationContext serviceContext = new FileSystemXmlApplicationContext(
            new String[]{APP_HOME + serviceName + ".xml"}, //path to child ctx
            appContext); //reference to parent ctx to build hierarchy
    Service service = (Service) serviceContext.getBean("service");
    service.start();
}

}

复制 ApplicationContext 有点繁重,但如今内存非常便宜,这提供了关注点的完全分离。我仍然拥有由父上下文管理的共享日志记录和事件系统,并且每个服务现在都在其自己的配置中进行了简化。我使用两个服务构建了一个概念验证,到目前为止它似乎运行良好。完成其他服务并完成测试后,我将添加另一条评论。

参考: http ://techo-ecco.com/blog/spring-application-context-hierarchy-and-contextsingletonbeanfactorylocator/

于 2013-08-20T16:27:52.487 回答