Reputation: 84068
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?
Upvotes: 9
Views: 20293
Reputation: 131
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
Upvotes: 13
Reputation: 13860
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();
Upvotes: 0
Reputation: 68318
Update:
Based on the question update, my suggestion is:
ServiceResolver
bean which handles whatever you need to handle based on client input;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:
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.
Upvotes: 1
Reputation: 84068
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;
}
Upvotes: 2
Reputation: 403581
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".
Upvotes: 1