Okay, it works great!
I ended up extending PropertyPlaceholderConfigurer to override resolvePlaceholder. My class keeps its own static Properties and checks there first. If it doesn't find what it wants, then it calls super.resolvePlaceholder. This lets me set properties that will stick around even after refreshing the BeanFactory. You would get similar behavior if you looked for the properties in a database instead of a static variable. Here is the class:
Code:
package com.company;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.apache.log4j.Logger;
import java.util.*;
public class StaticPlaceholderConfigurer extends PropertyPlaceholderConfigurer {
private static final Logger log = Logger.getLogger(StaticPlaceholderConfigurer.class);
private static final Map overrides = new HashMap();
private String _beanName;
private BeanFactory _factory;
public StaticPlaceholderConfigurer() {
log.debug("overrides at new instance: " + overrides);
}
protected String resolvePlaceholder(String placeholder, Properties fallback) {
Properties props;
String ret;
synchronized (overrides) {
props = (Properties)overrides.get(getKey());
log.debug("props for " + placeholder + ": " + props);
if (props == null) {
return super.resolvePlaceholder(placeholder, fallback);
} else {
ret = props.getProperty(placeholder);
if (ret != null) return ret;
else return super.resolvePlaceholder(placeholder, fallback);
}
}
}
public String setOverride(String name, String value) {
synchronized (overrides) {
Properties props;
OverrideKey key;
key = getKey();
props = (Properties)overrides.get(key);
if (props == null) {
props = new Properties();
overrides.put(key, props);
}
return (String)props.setProperty(name, value);
}
}
public void setOverrides(Properties props) {
synchronized (overrides) {
overrides.put(getKey(), new Properties(props));
}
}
public void clearOverrides() {
synchronized (overrides) {
overrides.remove(getKey());
}
}
private OverrideKey getKey() {
return new OverrideKey(_factory, _beanName);
}
public void setBeanName(String name) {
super.setBeanName(name);
_beanName = name;
}
public void setBeanFactory(BeanFactory f) {
super.setBeanFactory(f);
_factory = f;
}
}
class OverrideKey {
private final BeanFactory _f;
private final String _beanName;
public OverrideKey(BeanFactory f, String beanName) {
_f = f;
_beanName = beanName;
}
public int hashCode() {
// return _f.hashCode() + _beanName.hashCode();
// The factory is a different instance after refresh.
return _beanName.hashCode();
}
public boolean equals(Object o) {
if (o instanceof OverrideKey) {
OverrideKey k = (OverrideKey)o;
// return _f.equals(k._f) && _beanName.equals(k._beanName);
// The factory is a different instance after refresh.
return _beanName.equals(k._beanName);
} else {
return false;
}
}
}
I then use an applicationContext.xml file like this one:
Code:
<beans>
<bean id="myService" class="com.company.MyService">
<property name="emailManager"> <ref bean="${emailManager}"/> </property>
</bean>
<bean id="override" class="com.company.StaticPlaceholderConfigurer">
<property name="systemPropertiesModeName"><value>SYSTEM_PROPERTIES_MODE_NEVER</value></property>
<property name="properties">
<props>
<prop key="emailManager">emailManager</prop>
</props>
</property>
</bean>
<bean id="emailManager" class="com.company.EmailManager"/>
<bean id="mockEmailManager" class="com.company.MockEmailManager"/>
</beans>
When we begin, MyService has an EmailManager. But if I want to change to a MockEmailManager, I just get the override bean, call setOverride, and then refresh the ApplicationContext. Future clients of MyService will use a MockEmailManager. This is great for test code. I plan to use the same patten for all sorts of hot-swappable interfaces.
Thanks for your help,
Paul