Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing properties to a Spring context

Tags:

java

spring

rmi

I'm using Spring to handle RMI calls to some remote server. It is straightforward to construct an application context and obtain the bean for remote invocations from within the client:

ApplicationContext context = new ApplicationContext("classpath:context.xml");

MyService myService = (MyService ) context.getBean( "myService " );

However I don't see a simple way to pass properties into the configuration. For example if I want to determine the host name for the remote server at runtime within the client.

I'd ideally have an entry in the Spring context like this:

<bean id="myService" class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
  <property name="serviceUrl" value="rmi://${webServer.host}:80/MyService"/>
  <property name="serviceInterface" value="com.foo.MyService"/>
</bean>

and pass the properties to the context from the client as a parameter.

I can use a PropertyPlaceholderConfigurer in the context to substitute for these properties, but as far as I can tell this only works for properties read from a file.

I have an implementation that addresses this (added as an answer) but I'm looking for a standard Spring implementation to avoid rolling my own. Is there another Spring configurer (or anything else) to help initialise the configuration or am I better off looking at java config to achieve this?

like image 845
Rich Seller Avatar asked Jul 06 '09 09:07

Rich Seller


3 Answers

See http://forum.springsource.org/showthread.php?t=71815

TestClass.java

package com.spring.ioc;

public class TestClass {

    private String first;
    private String second;

    public String getFirst() {
        return first;
    }

    public void setFirst(String first) {
        this.first = first;
    }

    public String getSecond() {
        return second;
    }

    public void setSecond(String second) {
        this.second = second;
    }
}

SpringStart.java

package com.spring;

import java.util.Properties;

import com.spring.ioc.TestClass;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;

public class SpringStart {
    public static void main(String[] args) throws Exception {
    PropertyPlaceholderConfigurer configurer = new PropertyPlaceholderConfigurer();
    Properties properties = new Properties();
    properties.setProperty("first.prop", "first value");
    properties.setProperty("second.prop", "second value");
    configurer.setProperties(properties);

    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext();
    context.addBeanFactoryPostProcessor(configurer);

    context.setConfigLocation("spring-config.xml");
    context.refresh();

    TestClass testClass = (TestClass)context.getBean("testBean");
    System.out.println(testClass.getFirst());
    System.out.println(testClass.getSecond());
    }
}

spring-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

    <bean id="testBean" class="com.spring.ioc.TestClass">
        <property name="first" value="${first.prop}"/>
        <property name="second" value="${second.prop}"/>
    </bean>

</beans>

Output:

first value
second value
like image 181
Bert van Brakel Avatar answered Nov 04 '22 20:11

Bert van Brakel


My existing solution involves defining a new MapAwareApplicationContext that takes a Map as an additional constructor argument.

public MapAwareApplicationContext(final URL[] configURLs,
    final String[] newConfigLocations,
    final Map<String, String> additionalProperties) {
    super(null);

    //standard constructor content here

    this.map = new HashMap<String, String>(additionalProperties);

    refresh();
}

It overrides postProcessBeanFactory() to add in a MapAwareProcessor:

protected void postProcessBeanFactory(
    final ConfigurableListableBeanFactory beanFactory) {
    beanFactory.addBeanPostProcessor(new MapAwareProcessor(this.map));
    beanFactory.ignoreDependencyInterface(MapAware.class);
}

The MapAwareProcessor implements postProcessBeforeInitialization() to inject the map into any type that implements the MapAware interface:

public Object postProcessBeforeInitialization(final Object bean, 
        final String beanName) {
    if (this.map != null && bean instanceof MapAware) {
        ((MapAware) bean).setMap(this.map);
    }

    return bean;
}

I then add a new bean to my config to declare a MapAwarePropertyPlaceholderConfigurer:

<bean id="propertyConfigurer"
  class="com.hsbc.r2ds.spring.MapAwarePropertyPlaceholderConfigurer"/>

The configurer implements MapAware, so it will be injected with the Map as above. It then implements resolvePlaceholder() to resolve properties from the map, or delegate to the parent configurer:

protected String resolvePlaceholder(final String placeholder, 
        final Properties props, final int systemPropertiesMode) {
    String propVal = null;
    if (this.map != null) {
        propVal = this.map.get(placeholder);
    }
    if (propVal == null) {
        propVal = super.resolvePlaceholder(placeholder, props);
    }
    return propVal;
}
like image 33
Rich Seller Avatar answered Nov 04 '22 21:11

Rich Seller


Update:

Based on the question update, my suggestion is:

  1. Create a ServiceResolver bean which handles whatever you need to handle based on client input;
  2. Declare this bean as a dependency to the relevant services;
  3. At runtime, you may update / use this bean however you see fit.

The ServiceResolver can then, either on the init-method or on each invocation determine the values to return to the client, based on e.g. JNDI lookups or enviroment variables.

But before doing that, you might want to take a look at the configuration options available. You can either:

  • add property files which don't have to be present at compile-time;
  • look up values from JNDI;
  • get values from the System.properties.

If you need to lookup properties from a custom location, take a look at org.springframework.beans.factory.config.BeanFactoryPostProcessor and how the org.springframework.beans.factory.config.PropertyPlaceholderConfigurer is implemented.

The basic idea is that you get the beans with the 'raw' properties, e.g. ${jdbcDriverClassName} and then you get to resolve them and replace them with the desired values.

like image 1
Robert Munteanu Avatar answered Nov 04 '22 21:11

Robert Munteanu