Создание распределённого приложения при помощи RMI

Для обеспечения отказоустойчивости приложения и целей распределения нагрузки часто возникает необходимость разнесения функциональной части на физически раздельные машины. Для реализации данной задачи целесообразно использовать стандартный протокол удалённого вызова RMI. В составе Spring Framework существует набор функциональных модулей для упрощения реализации удалённого вызова по RMI. В данной статье я наглядно продемонстрирую как при помощи Spring Framework можно реализовать распределённую архитектуру.

Опубликовано 21-12-2012
Эксперементы
Теги java, network, rmi

Для обеспечения отказоустойчивости приложения и целей распределения нагрузки часто возникает необходимость разнесения функциональной части на физически раздельные машины. Для реализации данной задачи целесообразно использовать стандартный протокол удалённого вызова RMI. В составе Spring Framework существует набор функциональных модулей для упрощения реализации удалённого вызова по RMI. В данной статье я наглядно продемонстрирую как при помощи Spring Framework можно реализовать распределённую архитектуру.

* [Создаём начальное приложение.](#a1) * [Создание удалённого объекта.](#a2) * [Использование удалённого объекта.](#a3) * [Обработка обрыва связи.](#a4) * [Балансировка нагрузки.](#a5) * [Исходный код.](https://github.com/sarjsheff/dataandview) Мы возьмём наглядный случай. Существует две системы frontend (для отображения данных) и backend (для хранения и обработки данных). Создадим простейшее приложение состоящее из двух классов: App (отображение данных) и DataDAO (хранение и обработка данных). Все операции описанные в данной статье проделываются мной при помощи NetBeans 7.2.1 . [Исходный код.](https://github.com/sarjsheff/dataandview) ### Создаём начальное приложение. Создаём проект: * Maven -> Приложение Java * Имя проекта: `dataandview` * Идентификатор группы: `ru.sarjsheff` * Пакет: `ru.sarjsheff.dataandview` Нажимаем Готово . Наш проект создан. Теперь наполним его функциональной составляющей. Начнём с класса предоставляющего доступ к данным, создадим для него интерфейс `ru.sarjsheff.dataandview.DAO` : ``` java package ru.sarjsheff.dataandview; import java.util.List; import java.util.Map; public interface DAO { List getListData(); Map getMapData(); String transformData(String str); } ``` И сам класс `ru.sarjsheff.dataandview.data.DataDAO` : ``` java package ru.sarjsheff.dataandview.data; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import ru.sarjsheff.dataandview.DAO; public class DataDAO implements DAO { @Override public List getListData() { List ret = new LinkedList(); for (int i = 0; i < 4; i++) { ret.add("Data Item "+i); } return ret; } @Override public Map getMapData() { Map ret = new HashMap(); for (int i = 0; i < 4; i++) { ret.put("item" + i, i); } return ret; } @Override public String transformData(String str) { return str.toUpperCase(); } } ```

Теперь создадим класс для отображения ru.sarjsheff.dataandview.view.View :

package ru.sarjsheff.dataandview.view;

import java.util.Iterator;
import java.util.List;
import java.util.Map;
import ru.sarjsheff.dataandview.DAO;

public class View {

    private DAO data;

    public DAO getData() {
        return data;
    }

    public void setData(DAO data) {
        this.data = data;
    }

    public void printListData() {
        System.out.println("== ListData ==");
        List lst = data.getListData();
        Iterator it = lst.iterator();
        while (it.hasNext()) {
            System.out.println("[" + it.next() + "]");
        }
        System.out.println("== ListData ==");
    }

    public void printMapData() {
        System.out.println("== MapData ==");
        Map map = data.getMapData();
        Iterator it = map.keySet().iterator();
        while (it.hasNext()) {
            String key = (String) it.next();
            System.out.println("[" + key + " :: " + map.get(key) + "]");
        }
        System.out.println("== MapData ==");
    }

    public void printTransformData() {
        System.out.println("== TransformData ==");
        List lst = data.getListData();
        Iterator it = lst.iterator();
        while (it.hasNext()) {
            System.out.println("[" + data.transformData((String) it.next()) + "]");
        }
        System.out.println("== TransformData ==");
    }
}

Свяжем все вместе при помощи Spring Framework. Создаём файл src/main/resources/ac.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-3.1.xsd">
    
    <bean id="dao" class="ru.sarjsheff.dataandview.data.DataDAO">
    </bean>
    
    <bean id="view" class="ru.sarjsheff.dataandview.view.View">
        <property name="data" ref="dao"/>
    </bean>
</beans>

Добавляем зависимости в pom.xml для Spring Framework и log4j :

        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.16</version>
            <type>jar</type>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>3.1.3.RELEASE</version>
            <type>jar</type>
        </dependency>

Обязательно файл конфигурации src/main/resources/log4j.properties :

log4j.rootLogger=INFO, stdout

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.conversionPattern=%d{ABSOLUTE} %5p %t %c{1}:%M:%L - %m%n

В файле ac.xml мы видем создание объекта класса DataDAO , создание объекта класса View и привязка DataDAO к View . Теперь попробуем запустить наше приложение и вызвать все методы из отображения (View). Редактируем ru.sarjsheff.dataandview.App :

package ru.sarjsheff.dataandview;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import ru.sarjsheff.dataandview.view.View;

public class App 
{
    public static void main( String[] args )
    {
        ApplicationContext ac = new ClassPathXmlApplicationContext("classpath:ac.xml");
        View view = (View) ac.getBean("view");
        view.printMapData();
        view.printListData();
        view.printTransformData();
    }
}

Запускаем ru.sarjsheff.dataandview.App и видим:

10:38:16,056  INFO main ClassPathXmlApplicationContext:prepareRefresh:503 - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@75d837b6: startup date [10:38:16]; root of context hierarchy
10:38:16,128  INFO main XmlBeanDefinitionReader:loadBeanDefinitions:315 - Loading XML bean definitions from class path resource [ac.xml]
10:38:16,396  INFO main DefaultListableBeanFactory:preInstantiateSingletons:577 - Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@3fd76922: defining beans [dao,view]; root of factory hierarchy
== MapData ==
[item2 :: 2]
[item3 :: 3]
[item1 :: 1]
[item0 :: 0]
== MapData ==
== ListData ==
[Data Item 0]
[Data Item 1]
[Data Item 2]
[Data Item 3]
== ListData ==
== TransformData ==
[DATA ITEM 0]
[DATA ITEM 1]
[DATA ITEM 2]
[DATA ITEM 3]
== TransformData ==

Создание удалённого объекта.

Нам нужно вынести класс работы с данными в отдельное приложение и связать его с основным приложением. Для этого нам потребуется создать ещё 2 приложения dataandview_shared и dataandview_remote. В приложение dataandview_dhared мы вынесем наш интерфейс ru.sarjsheff.dataandview.DAO , данное приложение будет связующим звеном между dataandview и dataandview_rmi. Создаём проект:

  • Maven -> Приложение Java
  • Имя проекта: dataandview_shared
  • Идентификатор группы: ru.sarjsheff
  • Пакет: ru.sarjsheff.dataandview

Нажимаем Готово . Проект создан, переносим в него интерфейс ru.sarjsheff.dataandview.DAO . Собираем jar и инсталлируем его в репозиторий maven. Правой кнопкой по проекту Очистить и собрать . Приложение dataandview должно видеть dataandview_shared добавим для этого зависимость в pom.xml проекта dataandview :

        <dependency>
            <groupId>ru.sarjsheff</groupId>
            <artifactId>dataandview_shared</artifactId>
            <version>1.0-SNAPSHOT</version>
            <type>jar</type>
        </dependency>

Теперь можно смело удалить ru.sarjsheff.dataandview.DAO из проекта dataandview если вы не удалили его ранее. Создадим отдельный проект dataandview_rmi и перенесём в него наш класс для работы с данными ru.sarjsheff.dataandview.data.DataDAO . Создаём проект:

  • Maven -> Приложение Java
  • Имя проекта: dataandview_rmi
  • Идентификатор группы: ru.sarjsheff
  • Пакет: ru.sarjsheff.dataandview

Нажимаем Готово . Добавляем Spring Framework, log4j и наш интерфейс в pom.xml :

        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.16</version>
            <type>jar</type>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>3.1.3.RELEASE</version>
            <type>jar</type>
        </dependency>
        <dependency>
            <groupId>ru.sarjsheff</groupId>
            <artifactId>dataandview_shared</artifactId>
            <version>1.0-SNAPSHOT</version>
            <type>jar</type>
        </dependency>

Добавляем конфигурационный файл src/main/resources/log4j.properties :

log4j.rootLogger=INFO, stdout

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.conversionPattern=%d{ABSOLUTE} %5p %t %c{1}:%M:%L - %m%n

Переносим класс ru.sarjsheff.dataandview.data.DataDAO из проекта dataandview. После переноса давайте изменим передаваемые данные в классе ru.sarjsheff.dataandview.data.DataDAO для того что бы убедится в дальнейшем в корректности наших действий :

package ru.sarjsheff.dataandview.data;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import ru.sarjsheff.dataandview.DAO;

public class DataDAO implements DAO {

    @Override
    public List getListData() {
        List ret = new LinkedList();

        for (int i = 0; i < 4; i++) {
            ret.add("Remote Data Item "+i);
        }

        return ret;
    }

    @Override
    public Map getMapData() {
        Map ret = new HashMap();

        for (int i = 0; i < 4; i++) {
            ret.put("remote item" + i, i);
        }

        return ret;
    }

    @Override
    public String transformData(String str) {
        return str.toUpperCase();
    }
}

Теперь самое интересное, создадим конфигурацию Spring Frmaework и предоставим доступ к объекту ru.sarjsheff.dataandview.data.DataDAO по протоколу RMI. Файл src/main/resources/ac.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-3.1.xsd">


    <bean id="dataDAOService" class="ru.sarjsheff.dataandview.data.DataDAO">
    </bean>
    
    <bean class="org.springframework.remoting.rmi.RmiServiceExporter">
        <property name="serviceName" value="DataDAOService"/>
        <property name="service" ref="dataDAOService"/>
        <property name="serviceInterface" value="ru.sarjsheff.dataandview.DAO"/>
        <property name="registryPort" value="1199"/>
    </bean>
    
</beans>

Запускаем наш удаленный объект, для этого нужно инициализировать Spring Framework контекст из класса ru.sarjsheff.dataandview.App проекта dataandview_rmi :

package ru.sarjsheff.dataandview;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App 
{
    public static void main( String[] args )
    {
        ApplicationContext ac = new ClassPathXmlApplicationContext("classpath:ac.xml");
    }
}

Запускаем ru.sarjsheff.dataandview.App проекта dataandview_rmi в консоле мы увидим RMI запущен на 1199 порту и наш сервис DataDAOService зарегистрирован и доступен:

10:46:20,385  INFO main ClassPathXmlApplicationContext:prepareRefresh:503 - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@52f61f70: startup date [10:46:20]; root of context hierarchy
10:46:20,459  INFO main XmlBeanDefinitionReader:loadBeanDefinitions:315 - Loading XML bean definitions from class path resource [ac.xml]
10:46:20,734  INFO main DefaultListableBeanFactory:preInstantiateSingletons:577 - Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@31f61519: defining beans [dataDAOService,org.springframework.remoting.rmi.RmiServiceExporter#0]; root of factory hierarchy
10:46:20,838  INFO main RmiServiceExporter:getRegistry:393 - Looking for RMI registry at port '1199'
10:46:20,915  INFO main RmiServiceExporter:getRegistry:404 - Could not detect RMI registry - creating new one
10:46:20,955  INFO main RmiServiceExporter:prepare:276 - Binding service 'DataDAOService' to RMI registry: RegistryImpl[UnicastServerRef [liveRef: [endpoint:[192.168.1.24:1199](local),objID:[0:0:0, 0]]]]

Использование удалённого объекта.

Отредактируем ac.xml проекта dataandview , укажем связь с удалённым объектом DataDAO :

<?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-3.1.xsd">

    <bean id="dao" class="org.springframework.remoting.rmi.RmiProxyFactoryBean" >
        <property name="serviceUrl" value="rmi://127.0.0.1:1199/DataDAOService"/>
        <property name="serviceInterface" value="ru.sarjsheff.dataandview.DAO"/>
    </bean>

    <bean id="view" class="ru.sarjsheff.dataandview.view.View">
        <property name="data" ref="dao"/>
    </bean>
    
</beans>

Теперь проверим взаимодействие. Если у вас не запущен ru.sarjsheff.dataandview.App проекта dataandview_rmi запустите его, после этого запускаем ru.sarjsheff.dataandview.App проекта dataandview и видим:

11:06:21,022  INFO main ClassPathXmlApplicationContext:prepareRefresh:503 - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@75d837b6: startup date [11:06:21]; root of context hierarchy
11:06:21,102  INFO main XmlBeanDefinitionReader:loadBeanDefinitions:315 - Loading XML bean definitions from class path resource [ac.xml]
11:06:21,376  INFO main DefaultListableBeanFactory:preInstantiateSingletons:577 - Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@6e6be2c6: defining beans [dao,view]; root of factory hierarchy
== MapData ==
[remote item2 :: 2]
[remote item1 :: 1]
[remote item3 :: 3]
[remote item0 :: 0]
== MapData ==
== ListData ==
[Remote Data Item 0]
[Remote Data Item 1]
[Remote Data Item 2]
[Remote Data Item 3]
== ListData ==
== TransformData ==
[REMOTE DATA ITEM 0]
[REMOTE DATA ITEM 1]
[REMOTE DATA ITEM 2]
[REMOTE DATA ITEM 3]
== TransformData ==

В выводе должно быть видно слово remote которое мы добавили ранее в DataDAO.

Обработка обрыва связи.

В текущей конфигруации при обрыве связи с RMI сервером клиентское приложение не сможет восстановить соединение с удалённым объектом. Для автоматического восстановления связи требуется выставить две пропорции cacheStub и refreshStubOnConnectFailure бина dao нашего клиентского приложения dataandview в файле ac.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-3.1.xsd">

    <bean id="dao" class="org.springframework.remoting.rmi.RmiProxyFactoryBean" >
        <property name="serviceUrl" value="rmi://127.0.0.1:1199/DataDAOService"/>
        <property name="serviceInterface" value="ru.sarjsheff.dataandview.DAO"/>
        <property name="cacheStub" value="false" />
        <property name="refreshStubOnConnectFailure" value="true" />
    </bean>

    <bean id="view" class="ru.sarjsheff.dataandview.view.View">
        <property name="data" ref="dao"/>
    </bean>
    
</beans>

Данные изменения решат проблему с обрывом связи во время выполнения приложения, но остаётся проблема с восстановлением соединения после неудачной попытки соединится во время запуска приложения. Для решения это проблемы нужно будет создать свой унаследованный от org.springframework.remoting.rmi.RmiProxyFactoryBean класс ru.sarjsheff.rmi.RmiWOExceptionProxyFactoryBean ( в проекте dataandview ) в котором мы перекроем сброс Exception выше при инициализации объекта :

package ru.sarjsheff.rmi.utils;

import org.springframework.aop.framework.ProxyFactory;
import org.springframework.remoting.rmi.RmiProxyFactoryBean;

public class RmiWOExceptionProxyFactoryBean extends RmiProxyFactoryBean {

    private Object serviceProxy;

    @Override
    public void afterPropertiesSet() {
                try {
                    super.afterPropertiesSet();
                } catch (Exception ex) {
                }
        if (getServiceInterface() == null) {
            throw new IllegalArgumentException("Property 'serviceInterface' is required");
        }
        this.serviceProxy = new ProxyFactory(getServiceInterface(), this).getProxy(getBeanClassLoader());
    }


    public Object getObject() {
        return this.serviceProxy;
    }

    public Class<?> getObjectType() {
        return getServiceInterface();
    }

    public boolean isSingleton() {
        return true;
    }

}

Отличие от стандартного только в блоке try catch вокруг вызова afterPropertiesSet() . Теперь меняем ac.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-3.1.xsd">

    <bean id="dao" class="ru.sarjsheff.rmi.utils.RmiWOExceptionProxyFactoryBean" >
        <property name="serviceUrl" value="rmi://127.0.0.1:1199/DataDAOService"/>
        <property name="serviceInterface" value="ru.sarjsheff.dataandview.DAO"/>
        <property name="cacheStub" value="false" />
        <property name="refreshStubOnConnectFailure" value="true" />
    </bean>

    <bean id="view" class="ru.sarjsheff.dataandview.view.View">
        <property name="data" ref="dao"/>
    </bean>
    
</beans>

Балансировка нагрузки.

Для обеспечения отказоустойчивости и увеличения нагрузочной способности системы целесообразно использовать распределение нагрузки. Распределять будем методом перебора. Создадим ещё один сервер RMI, правой кнопкой на проекте Копировать... -> Имя проекта: dataandview_rmi_2 . После копирования смените имя нового проекта, правой кнопкой на проекте Переименовать... -> Изменить отображаемое имя: dataandview_rmi_2 . Проект dataandview_rmi_2 в файле ac.xml сменим порт для RMI :

<?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-3.1.xsd">


    <bean id="dataDAOService" class="ru.sarjsheff.dataandview.data.DataDAO">
    </bean>
    
    <bean class="org.springframework.remoting.rmi.RmiServiceExporter">
        <property name="serviceName" value="DataDAOService"/>
        <property name="service" ref="dataDAOService"/>
        <property name="serviceInterface" value="ru.sarjsheff.dataandview.DAO"/>
        <property name="registryPort" value="1299"/>
    </bean>
    
</beans>

Теперь у нас есть два сервера ( dataandview_rmi и dataandview_rmi_2 ) запускающихся на разных портах. Добавим в клиентское приложение dataandview второй сервер:

<?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-3.1.xsd">

    <bean id="dao" class="ru.sarjsheff.rmi.utils.RmiWOExceptionProxyFactoryBean" >
        <property name="serviceUrl" value="rmi://127.0.0.1:1199/DataDAOService"/>
        <property name="serviceInterface" value="ru.sarjsheff.dataandview.DAO"/>
        <property name="cacheStub" value="false" />
        <property name="refreshStubOnConnectFailure" value="true" />
    </bean>

    <bean id="dao1" class="ru.sarjsheff.rmi.utils.RmiWOExceptionProxyFactoryBean" >
        <property name="serviceUrl" value="rmi://127.0.0.1:1299/DataDAOService"/>
        <property name="serviceInterface" value="ru.sarjsheff.dataandview.DAO"/>
        <property name="cacheStub" value="false" />
        <property name="refreshStubOnConnectFailure" value="true" />
    </bean>

    <bean id="view" class="ru.sarjsheff.dataandview.view.View">
        <property name="data" ref="dao"/>
    </bean>
    
</beans>

Объект view ссылается на один объект dao нам требуется вызывать каждый dao по очереди. Опишем proxy класс ru.sarjsheff.rmi.utils.RmiFailoverProxyFactoryBean распределяющий вызовы между dao методом перебора :

package ru.sarjsheff.rmi.utils;

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.BeanClassLoaderAware;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.ReflectionUtils;

public class RmiFailoverProxyFactoryBean implements FactoryBean<Object>, BeanClassLoaderAware, MethodInterceptor, InitializingBean {

    private Object serviceProxy;
    private ClassLoader beanClassLoader;
    private List targets;
    private Class serviceInterface;
    private int num = 0;
    private boolean balancer = false;

    public void afterPropertiesSet() {
        this.serviceProxy = new ProxyFactory(getServiceInterface(), this).getProxy(getBeanClassLoader());
    }

    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("Balancer.Invoking [" + num + "] " + invocation.getMethod().getName());
        Object retVal = null;

        num++;
        if (num >= targets.size()) {
            num = 0;
        }

        boolean OK = false;

        if (balancer) {
            Object trgt = targets.get(num);
            try {
                retVal = ReflectionUtils.invokeMethod(invocation.getMethod(), trgt, invocation.getArguments());
                OK = true;
            } catch (Exception ex) {
                System.err.println(ex.getMessage());
            }
        }

        if (!OK) {
            Iterator it = targets.iterator();
            while (it.hasNext()) {
                Object trg = it.next();
                try {
                    retVal = ReflectionUtils.invokeMethod(invocation.getMethod(), trg, invocation.getArguments());
                    break;
                } catch (Exception ex) {
                    System.err.println(ex.getMessage());
                }
            }

        }
        System.out.println("Balancer.Done");
        return retVal;
    }

    public Object getObject() {
        return this.serviceProxy;
    }

    public Class<?> getObjectType() {
        return getServiceInterface();
    }

    public boolean isSingleton() {
        return true;
    }

    public void setBeanClassLoader(ClassLoader cl) {
        this.beanClassLoader = cl;
    }

    public ClassLoader getBeanClassLoader() {
        return beanClassLoader;
    }

    public void setServiceInterface(Class serviceInterface) {
        if (serviceInterface != null && !serviceInterface.isInterface()) {
            throw new IllegalArgumentException("'serviceInterface' must be an interface");
        }
        this.serviceInterface = serviceInterface;
    }

    public Class getServiceInterface() {
        return this.serviceInterface;
    }

    public List getTargets() {
        return targets;
    }

    public void setTargets(List targets) {
        this.targets = targets;
    }

    public boolean isBalancer() {
        return balancer;
    }

    public void setBalancer(boolean balancer) {
        this.balancer = balancer;
    }
}

Добавим его в нашу конфигурацию ac.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-3.1.xsd">

    <bean id="dao" class="ru.sarjsheff.rmi.utils.RmiFailoverProxyFactoryBean">
        <property name="serviceInterface" value="ru.sarjsheff.dataandview.DAO"/>
        <property name="targets">
            <list>
                <ref bean="dao1" />
                <ref bean="dao2" />                
            </list>
        </property>
        <property name="balancer" value="true"/>
    </bean>

    <bean id="dao1" class="ru.sarjsheff.rmi.utils.RmiWOExceptionProxyFactoryBean" >
        <property name="serviceUrl" value="rmi://127.0.0.1:1199/DataDAOService"/>
        <property name="serviceInterface" value="ru.sarjsheff.dataandview.DAO"/>
        <property name="cacheStub" value="false" />
        <property name="refreshStubOnConnectFailure" value="true" />
    </bean>

    <bean id="dao2" class="ru.sarjsheff.rmi.utils.RmiWOExceptionProxyFactoryBean" >
        <property name="serviceUrl" value="rmi://127.0.0.1:1299/DataDAOService"/>
        <property name="serviceInterface" value="ru.sarjsheff.dataandview.DAO"/>
        <property name="cacheStub" value="false" />
        <property name="refreshStubOnConnectFailure" value="true" />
    </bean>

    <bean id="view" class="ru.sarjsheff.dataandview.view.View">
        <property name="data" ref="dao"/>
    </bean>
    
</beans>

Запустим и увидим что сервера вызываются по очереди ( Balancer.Invoking 0 и Balancer.Invoking 1 ):

11:49:54,361  INFO main ClassPathXmlApplicationContext:prepareRefresh:503 - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@21800bc: startup date [11:49:54]; root of context hierarchy
11:49:54,437  INFO main XmlBeanDefinitionReader:loadBeanDefinitions:315 - Loading XML bean definitions from class path resource [ac.xml]
11:49:54,723  INFO main DefaultListableBeanFactory:preInstantiateSingletons:577 - Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@19e10522: defining beans [dao,dao1,dao2,view]; root of factory hierarchy
== MapData ==
Balancer.Invoking [0] getMapData
Balancer.Done
[remote item2 :: 2]
[remote item1 :: 1]
[remote item3 :: 3]
[remote item0 :: 0]
== MapData ==
== ListData ==
Balancer.Invoking [1] getListData
Balancer.Done
[Remote Data Item 0]
[Remote Data Item 1]
[Remote Data Item 2]
[Remote Data Item 3]
== ListData ==
== TransformData ==
Balancer.Invoking [0] getListData
Balancer.Done
Balancer.Invoking [1] transformData
Balancer.Done
[REMOTE DATA ITEM 0]
Balancer.Invoking [0] transformData
Balancer.Done
[REMOTE DATA ITEM 1]
Balancer.Invoking [1] transformData
Balancer.Done
[REMOTE DATA ITEM 2]
Balancer.Invoking [0] transformData
Balancer.Done
[REMOTE DATA ITEM 3]
== TransformData ==

Отключение любого сервера не скажутся на работу клиентского приложения.