views:

1709

answers:

5

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?

+1  A: 

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.

Robert Munteanu
Thanks that's how my existing implementation does it, I'll update my question to reflect that. I was hoping that there is a standard Spring implementation for this so I can avoid rolling my own.
Rich Seller
+1  A: 

PropertyPlaceholderConfigurer can fetch properties from a file, that's true, but if it can't find them, it falls back to using system properties. This sounds like a viable option for your client application, just pass the system property in using -D when you launch the client.

From the javadoc

A configurer will also check against system properties (e.g. "user.dir") if it cannot resolve a placeholder with any of the specified properties. This can be customized via "systemPropertiesMode".

skaffman
+2  A: 

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;
}
Rich Seller
Good lord, that's a complicated solution to a simple problem...
skaffman
That's kind of my point, it seems like something that should be achievable without too much effort, certainly less effort than this
Rich Seller
A: 

Create an RmiProxyFactoryBean instance and configure the serviceUrl property directly in your code:

String serverHost = "www.example.com";

RmiProxyFactoryBean factory = new RmiProxyFactoryBean();
factory.setServiceUrl("rmi://" + serverHost + ":80/MyService");
factory.setServiceInterface(MyService.class);
try {
    factory.afterPropertiesSet();
} catch (Exception e) {
    throw new RuntimeException(
            "Problem initializing myService factory", e);
}
MyService myService = (MyService) factory.getObject();
Jim Huang