Swapping out Spring Bean Configuration at Runtime
I recently came across a problem that I hadn’t hit before: introducing the ability to rewire a bean’s internals based on configuration introduced at runtime. This is valuable for simple configuration changes or perhaps swapping out something like a Strategy or Factory class, rather than rebuilding of a complex part of the application context.
I was able to find some notes about how to do this, but I thought that some might find my notes and code samples useful, especially since I can confirm this technique works on versions of Spring back to 1.2.6. Unfortunately, not all of us are lucky enough to be on the latest and greatest of every library.
Scope of the Problem
The approach I’m going to outline is meant primarily to target changes to a single bean, though this code could easily be extended to change multiple beans. It could be invoked through JMX or some other UI exposed to administrators.
One thing it does not cover is rewiring a singleton all across an application – this could conceivably be done via some reflection and inspection of the current application context, but is likely to be unsafe in most applications unless they have some way of temporarily shutting down or blocking all processing for a period while the changes are made all over the application.
The Code
Here’s the sample code. It will take a list of Strings which contains bean definitions, and wire them into a new temporary Spring context. You’ll see a parent context can be provided, which is useful in case your new bean definitions need to refer to beans already configured in the application.
public static <T> Map<String, T> extractBeans(Class<T> beanType, List<String> contextXmls, ApplicationContext parentContext) throws Exception { List<String> paths = new ArrayList<String>(); try { for (String xml : contextXmls) { File file = File.createTempFile("spring", "xml"); // ... write the file using a utility method FileUtils.writeStringToFile(file, xml, "UTF-8"); paths.add(file.getAbsolutePath()); } String[] pathArray = paths.toArray(new String[0]); return buildContextAndGetBeans(beanType, pathArray, parentContext); } finally { // ... clean up temp files immediately if desired } } private static <T> Map<String, T> buildContextAndGetBeans(Class<T> beanType, String[] paths, ApplicationContext parentContext) throws Exception { FileSystemXmlApplicationContext context = new FileSystemXmlApplicationContext(paths, false, parentContext) { @Override // suppress refresh events bubbling to parent context public void publishEvent(ApplicationEvent event) { } }; try { // avoid classloader errors in some environments context.setClassLoader(beanType.getClassLoader()); context.refresh(); // parse and load context Map<String, T> beanMap = context.getBeansOfType(beanType); return beanMap; } finally { try { context.close(); } catch (Exception e) { // ... log this } } }
If you look at buildContextAndGetBeans(), you’ll see it does the bulk of the work by building up a Spring context with the supplied XML bean definition files. It then returns a map of the constructed beans of the type requested.
Note: Since the temporary Spring context is destroyed, ensure your beans do not have lifecycle methods that cause them to be put into an invalid state when stopped or destroyed.
Here’s an example of a Spring context that might be used to rewire a component. Imagine we have an e-commerce system that does fraud checks, but various strategies for checking for fraud. We may wish to swap these from our service class without having to stop and reconfigure the application, since we lose business when we do so. Perhaps we are finding a specific abuse of the system that would be better dealt with by changing the strategy used to locate fraudulent orders.
Here’s a sample XML definition that could be used to rewire our FraudService.
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <bean id="fraudStrategy" class="com.example.SomeFraudStategory"> <!-- example of a bean defined in the parent application context that we can reference --> <property name="fraudRuleFactory" ref="fraudRuleFactory"/> </bean> </beans>
And here is the code you could use to rewire your bean with a reference to the defined fraudStrategy, assuming you have it in a utility class called SpringUtils:
public class FraudService implements ApplicationContextAware { private ApplicationContext context; // volatile for thread safety (in Java 1.5 and up only) private volatile FraudStrategy fraudStrategy; @Override // get a handle on the the parent context public void setApplicationContext(ApplicationContext context) { this.context = context; } public void swapFraudStategy(String xmlDefinition) throws Exception { List<Sting> definitions = Arrays.asList(xmlDefinition); Map<String, FraudStrategy> beans = SpringUtils.extractBeans(FraudStrategy.class, definitions, context); if (beans.size() != 1) { throw new RuntimeException("Invalid number of beans: " + beans .size()); } this.fraudStrategy = beans.values().iterator().next(); } }
And there you have it! This example could be extended a fair bit to meet your needs, but I think it shows the fundamentals of how to create a Spring context on the fly, and use its beans to reconfigure your application without any need for downtime.
Reference: Swapping out Spring Bean Configuration at Runtime from our JCG partners at the Carfey Software blog.