60

Configuration:
Spring 2.5, Junit 4, Log4j
The log4j file location is specified from a system property

${log.location}

At runtime, system property set with -D java option. All is well.

Problem / What I Need:
At unit test time, system property not set, and file location not resolved.
App uses Spring, would like to simply configure Spring to set the system property.

More Info:
Requirement is for configuration only. Can't introduce new Java code, or entries into IDE. Ideally, one of Spring's property configuration implementations could handle this--I just haven't been able to find the right combination.

This idea is close, but needs to add Java code:
Spring SystemPropertyInitializingBean

Any help out there? Any ideas are appreciated.

n00begon
  • 3,503
  • 3
  • 29
  • 42
Steve
  • 655
  • 1
  • 8
  • 8
  • Related question with additional answers: http://stackoverflow.com/questions/11306951/how-to-set-environment-variable-or-system-property-in-spring-tests/41305482 – anre Dec 23 '16 at 17:51

4 Answers4

96

There was a request in the comments for a Spring 3 example on how to do this.

<bean id="systemPrereqs"
    class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
    <property name="targetObject" value="#{@systemProperties}" />
    <property name="targetMethod" value="putAll" />
    <property name="arguments">
        <!-- The new Properties -->
        <util:properties>
            <prop key="java.security.auth.login.config">/super/secret/jaas.conf</prop>
        </util:properties>
    </property>
</bean>
Betlista
  • 10,327
  • 13
  • 69
  • 110
Patrick
  • 3,901
  • 1
  • 25
  • 30
  • +1 Excellent. When I think of how much code would need to be written to do this. Sometimes Spring is magic. :-) – David Victor Nov 09 '11 at 19:57
  • How would you the load this? Do i just declare an @Autowired variable of type MethodInvokingFactoryBean – ziggy Jan 06 '13 at 17:27
  • 3
    @ziggy If I have a bean `exampleBean` that requires those system properties to be set, then I do `` to make sure they are set before exampleBean is created. – Patrick Jan 08 '13 at 21:43
  • @Patrick Still not set in the log4j system prop example. Works if done with -Dcatalina.base=target, anything special to make this init before log4j tooling when running with SpringJUnit4ClassRunner? Thx. – Joseph Lust Apr 10 '13 at 21:47
  • @JosephLust To process this file Spring must start, and it logs things as it starts which initializes log4j, so your settings are never seen by log4j. You can try to re-initialize log4j as Spring processes the file - possibly with a call to `PropertyConfigurator.configure(URL configURL)` – Patrick Apr 12 '13 at 22:20
  • Was helpful, thanks. Especialy, I wanted to set some system properties using a properties file, and the above solution worked with – Géza Jul 17 '15 at 13:29
  • Now with Spring 4, you can use a [MethodInvokingBean](http://docs.spring.io/spring/docs/4.2.x/javadoc-api/org/springframework/beans/factory/config/MethodInvokingBean.html) instead of `MethodInvokingFactoryBean` to avoid the type determination and lifecycle limitations the latter comes with. – Kolargol00 Aug 05 '15 at 10:51
57

You can achieve that with the combination of two MethodInvokingFactoryBeans

Create an inner bean that accesses System.getProperties and an outer bean that invokes putAll on the properties acquired by the inner bean:

<bean
    class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
    <property
        name="targetObject">
        <!-- System.getProperties() -->
        <bean
            class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
            <property name="targetClass" value="java.lang.System" />
            <property name="targetMethod" value="getProperties" />
        </bean>
    </property>
    <property
        name="targetMethod"
        value="putAll" />
    <property
        name="arguments">
        <!-- The new Properties -->
        <util:properties>
            <prop
                key="my.key">myvalue</prop>
            <prop
                key="my.key2">myvalue2</prop>
            <prop
                key="my.key3">myvalue3</prop>

        </util:properties>
    </property>
</bean>

(You could of course use just one bean and target System.setProperties(), but then you'd be replacing existing properties which is not a good idea.

Anyway, here's my little test method:

public static void main(final String[] args) {

    new ClassPathXmlApplicationContext("classpath:beans.xml");

    System.out.println("my.key: "+System.getProperty("my.key"));
    System.out.println("my.key2: "+System.getProperty("my.key2"));
    System.out.println("my.key3: "+System.getProperty("my.key3"));

    // to test that we're not overwriting existing properties
    System.out.println("java.io.tmpdir: "+System.getProperty("java.io.tmpdir"));
}

And here's the output:

my.key: myvalue
my.key2: myvalue2
my.key3: myvalue3
java.io.tmpdir: C:\DOKUME~1\SEANFL~1\LOKALE~1\Temp\
Betlista
  • 10,327
  • 13
  • 69
  • 110
Sean Patrick Floyd
  • 292,901
  • 67
  • 465
  • 588
  • Thank you. This works. Remaining problem: Seems log4j is initialized inside another bean that loads before these new system properties are initialized. Now I get to figure out how to force an order to the bean initialization. – Steve Jul 27 '10 at 18:52
  • 1
    Use Spring's depends-on attribute. Many thanks for this answer...it is solid gold... – Dave Nov 20 '10 at 05:32
  • 8
    Spring 3 simplifies this by eliminating the need for the 2nd MethodInvokingFactoryBean. You use SpEL and the target object line becomes – Patrick Jan 11 '11 at 00:00
  • @Patrick - I'd very much appreciate an example for Spring 3. I've never quite figured out from the example above and your comment how to get this working. – Tom Sep 12 '11 at 13:52
  • @Tom - I've posted a Spring 3 example as an additional answer. – Patrick Sep 12 '11 at 16:38
  • 1
    This requires the util namespace. Can't that be done instead with a `...` which is more portable? – Gray Sep 30 '15 at 12:39
  • @Gray I don't see the problem with adding the namespace, but yes, you can also use your way. But mainly: everybody who is still using Spring with XML should start using JavaConfig. It's so much more natural. – Sean Patrick Floyd Sep 30 '15 at 13:58
  • There's not a problem with adding the namespace but your answer doesn't mention it. The `` is included by default. – Gray Oct 01 '15 at 17:57
11

Spring Batch has a SystemPropertyInitializer class which can be used to set a system property slightly more concisely, e.g. to force JBoss logging to use slf4j (with Spring JPA):

<bean id="setupJBossLoggingProperty"
    class="org.springframework.batch.support.SystemPropertyInitializer"
    p:keyName="org.jboss.logging.provider" p:defaultValue="slf4j"/>

<bean id="entityManagerFactory"
    class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
    depends-on="setupJBossLoggingProperty"

Remember to add the "depends-on" attribute to force the system property to be set first.

Betlista
  • 10,327
  • 13
  • 69
  • 110
paulcm
  • 1,704
  • 1
  • 17
  • 17
  • I couldn't get this to work in the end. `SystemPropertyInitializer` uses `afterPropertiesSet() ` which apparently is not called until after the `` is called. – Stewart Mar 21 '17 at 03:17
4

For a more terse approach try:

<beans ... xmlns:p="http://www.springframework.org/schema/p" ...    
    <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean" 
        p:targetObject="#{@systemProperties}" p:targetMethod="setProperty"
        p:arguments="#{{'org.jboss.logging.provider','slf4j'}}"/>
Betlista
  • 10,327
  • 13
  • 69
  • 110
Paul Rooney
  • 111
  • 1
  • 5