Hi,
In our current project we need to provide a simple (stand alone POJO) service
with high availability guarantees. The simplest way (for us) is to install it
on multiple servers and build a failover mechanism into the clients.
I was pleasantly surprised by how easy it is to achieve a transparent failover
using Spring, and I thought that this could be useful for others too.
Our first version below, is very basic: it always tries the first service
provider in the list, and if an exception is thrown, the next one is tried,
etc.
Possible improvements are:
- first try the provider that previously worked
- use round robin for basic load balancing
All comments are welcome,
Ivo
Code:<bean id="highAvailabilityBean" class="org.springframework.remoting.support.FailoverProxyFactoryBean"> <property name="serviceInterface" value="com.x.MyInterface" /> <property name="serviceProviders"> <list> <!-- service beans are typically proxies to remote services --> <ref bean="primaryServiceProvider"/> <ref bean="fallBackServiceProvider"/> </list> </property> </bean>Code:package org.springframework.remoting.support; import java.lang.reflect.InvocationTargetException; import java.util.Iterator; import java.util.List; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.springframework.aop.framework.ProxyFactory; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.remoting.support.RemoteAccessor; /** * Factory bean for proxies to redundant services. Behaves like the proxied * service when used as bean reference, exposing the specified service * interface, but with transparent fail-over. All call to the proxy are * delegated to the first object in the list of service providers (@see * #setServiceProviders(List)). When this delegated call fails, the next * provider in the list will be called, etc. * * @author be324288 * */ public class FailoverProxyFactoryBean extends RemoteAccessor implements MethodInterceptor, InitializingBean, FactoryBean { private Object serviceProxy; private List serviceProviders; /** * The list of (redundant) objects that implement the interface specified * with {@link RemoteAccessor#setServiceInterface(java.lang.Class)} * * @return */ public List getServiceProviders() { return serviceProviders; } /** * @param serviceProviders */ public void setServiceProviders(List serviceProviders) { this.serviceProviders = serviceProviders; } public void afterPropertiesSet() throws Exception { if (getServiceInterface() == null) { throw new IllegalArgumentException("serviceInterface is required"); } if (serviceProviders == null || serviceProviders.isEmpty()) { throw new IllegalArgumentException("serviceBeans is required"); } for (Object o : serviceProviders) { if (!getServiceInterface().isInstance(o)) { throw new IllegalArgumentException(o.getClass() + " does not implement the serviceInterface: " + getServiceInterface()); } } this.serviceProxy = ProxyFactory.getProxy(getServiceInterface(), this); } public Object getObject() { return this.serviceProxy; } public Class getObjectType() { return getServiceInterface(); } public boolean isSingleton() { return true; } public Object invoke(MethodInvocation mi) throws Throwable { Iterator iter = serviceProviders.iterator(); while (iter.hasNext()) { Object bean = iter.next(); try { return bean.getClass().getMethod(mi.getMethod().getName(), (Class[]) mi.getMethod().getParameterTypes()).invoke(bean, mi.getArguments()); } catch (InvocationTargetException e) { // try next bean or throw exception if this is the last bean if (!iter.hasNext()) { throw e.getCause(); } } } return null; } }


Reply With Quote