【问题标题】:How do include a property at runtime in a spring context?如何在运行时在 Spring 上下文中包含属性?
【发布时间】:2016-03-14 17:11:26
【问题描述】:

如何在运行时添加属性并使用它们在我的上下文文件中填充占位符?

首先,一点背景知识:我有一个批处理数据加载器,它加载一个数据库。我要求该程序的数据库密码不存储在磁盘上,因此我允许用户以交互方式输入它。 我正在使用休眠,并且我的数据源在上下文文件中配置,实际参数在另一个属性文件中。

我正在寻找的一些属性

  • 该属性仅在运行时可用(用户输入密码)
  • 可以在上下文加载之前提供
  • 不需要动态改变
  • 其他相关属性仍通过普通属性文件加载

db.xml:

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
    <property name="driverClassName" value="${hibernate.connection.driver_class}" />
    <property name="url" value="${hibernate.connection.url}" />
    <property name="username" value="${hibernate.connection.username}" />
    <property name="password" value="${hibernate.connection.password}" />
    and so on...
</bean>

database.properties:

hibernate.connection.username=Username
### hibernate.connection.password= #don't want the password stored
hibernate.connection.url=<the url>
hibernate.connection.driver_class=oracle.jdbc.driver.OracleDriver

还有我的主要context.xml:

<import resource="classpath:db.xml" />
<context:property-placeholder location="classpath:database.properties" />
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations" value="classpath:database.properties" />
</bean>
plus more...

我想做的是在运行时刷新之前向上下文添加一个新属性 (hibernate.connection.password),以便在 db.xml 中替换相应的值。

我目前的尝试是这样的

Properties prop = new Properties();             
prop.setProperty("hibernate.connection.password", thePassword);
PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer();
ppc.setProperties(prop);

ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext(new String[] {"my-context.xml"}, false); // Don't refresh after loading
ctx.addBeanFactoryPostProcessor(ppc);
ctx.refresh();

但是,我一定是做错了什么,因为我得到一个异常,告诉我我的 database.properties 文件中的属性没有被使用。

2016-03-14 14:58:41 WARN  ClassPathXmlApplicationContext:546 - Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanDefinitionStoreException: Invalid bean definition with name 'dataSource' defined in class path resource [db.xml]: Could not resolve placeholder 'hibernate.connection.driver_class' in string value "${hibernate.connection.driver_class}"; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'hibernate.connection.driver_class' in string value "${hibernate.connection.driver_class}"

为了确保我对 database.properties 文件的设置正确,如果我删除对 ctx.addBeanFactoryPostProcessor(ppc) 的调用,我会收到一个异常,抱怨缺少密码

2016-03-14 16:52:10 WARN  ClassPathXmlApplicationContext:546 - Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanDefinitionStoreException: Invalid bean definition with name 'dataSource' defined in class path resource [db.xml]: Could not resolve placeholder 'hibernate.connection.password' in string value "${hibernate.connection.password}"; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'hibernate.connection.password' in string value "${hibernate.connection.password}"
Error: Invalid bean definition with name 'dataSource' defined in class path resource [db.xml]: Could not resolve placeholder 'hibernate.connection.password' in string value "${hibernate.connection.password}"; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'hibernate.connection.password' in string value "${hibernate.connection.password}"

如果密码存在于属性文件中,则一切正常。

然后我的问题是如何使 database.properties 和运行时定义的属性都对我的上下文文件进行后处理?

编辑

我找到的一个解决方案是将属性文件手动加载到 PropertyPlaceholderConfigurer。

ppc.setLocation(new ClassPathResource("database.properties"));

这可行,但是以编程方式设置的属性将被属性文件中的任何匹配属性覆盖,这让人感觉像是一种解决方法。另外,我仍然不明白为什么两个 PropertyPlaceholderConfigurers 都没有被使用(上下文文件中定义的一个和 java 中定义的一个)

【问题讨论】:

  • 运行时是什么意思,在应用程序启动期间或之后?
  • 是的,围绕应用程序启动
  • 这是网络应用程序吗?
  • 这是一个批量数据加载器,所以,不是。

标签: java spring


【解决方案1】:

为了让 Spring 从database.properties 读取属性,您必须定义一个属性占位符的 bean,如下所示:

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="location">
        <value>database.properties</value>
    </property>
</bean>

然后你可以在你的dataSource bean 中使用它,如下所示:

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
    <property name="driverClassName" value="${hibernate.connection.driver_class}" />
    <property name="url" value="${hibernate.connection.url}" />
    <property name="username" value="${hibernate.connection.username}" />
    <property name="password" value="${hibernate.connection.password}" />
    and so on...
</bean>

如果您想在运行时传递密码,则可以将其作为参数传递,如下所示:

-Dhibernate.connection.password=<password>

【讨论】:

  • 好吧,也许我的回复编辑得太紧了。我的上下文文件中已经有一个 PropertyPlaceholderConfigurer。我会将其编辑到我的问题中
  • @benf 这可能是因为稍后您通过执行PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer();ctx.addBeanFactoryPostProcessor(ppc); 来覆盖定义。
  • 在 CLI 上传递它会很方便,但希望它也不会出现在 /proc//cmdline 字符串中。
  • 是的,这就是我怀疑正在发生的事情。我想知道的是如何合并而不是覆盖
  • @benf 在我们的项目中,我们传递一个加密字符串作为参数,并在使用它之前在代码中执行解密。我不确定这是否可行,但您也可以查看 Kerberos,您可以在其中以 keyTab 文件的形式传递登录凭据。
猜你喜欢
  • 2012-02-08
  • 1970-01-01
  • 2020-12-03
  • 2012-03-08
  • 2018-10-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-07-01
相关资源
最近更新 更多