Как стать автором
Обновить

Первое приложение (Avalanche — application framework for Java)

Время на прочтение14 мин
Количество просмотров3.8K

Первое приложение (Avalanche — application framework for Java)


"Avalanche — application framework for Java" — реализация технологии стирающей различия между
вызовами локального и удаленного кода. Отказоустойчивость, масштабируемость,
модифицируемость, непрерывная доступность идут в комплекте приятными бонусами.


Все языки программирования предлагают для начала написать простую программу вывода
сообщения "Hello world". Этот пример не подходит для демонстрации функциональных
возможностей "Avalanche — application framework for Java", так как вызов этого примера всегда
возвращает одну и ту же строку и не позволяет идентифицировать источник, возвративший этот
результат.


Для первого приложения потребуется реализовать два класса (функцию и приложение), один
интерфейс (адаптер функции) и одну JSP страницу для отображения результата. Разработанное
приложение будет выполняться под управлением Tomcat.


Реализация класса функции — DemoFunction


В классе реализации функции нет ничего особенного, это обычный класс Java. В качестве
класса функции может быть использован любой класс. Пусть демонстрационный класс функции
возвращает следующую информацию из операционной системы: PID процесса и имя сервера;
версия операционной системы; название операционной системы.


Код класса DemoFunction.java


package ru.funsys.demo.avalanche;

import java.lang.management.ManagementFactory;
import java.util.Hashtable;

public class DemoFunction {

    /**
     * Получить информацию из операционной системе.<br>
     * <br>
     *  Из системных свойств выбираются значения<br>
     * <b>os.name</b> - имя ОС,<br>
     * <b>os.version</b> - версия ОС<br>
     * и <b>PID@name</b> - идентификатор процесса и имя сервера 
     * 
     * @return именованный список с параметрами ОС
     */
    public Hashtable<String, String> getInfo() {
        Hashtable<String, String> result = new Hashtable<String, String>();
        result.put("os.name", System.getProperty("os.name"));
        result.put("os.version", System.getProperty("os.version"));
        result.put("PID@name", ManagementFactory.getRuntimeMXBean().getName());
        return result;
    }

}

Реализация адаптера — DemoAdapter


С точки зрения языка программирования Java, адаптер — это интерфейс, в котором
декларируются методы класса функции, которые планируются вызывать с помощью этого
адаптера. Для класса DemoFunction требуется написать следующий код интерфейса


Код интерфейса DemoAdapter.java


package ru.funsys.demo.avalanche;

import java.util.Hashtable;

import ru.funsys.avalanche.AvalancheRemote;

public interface DemoAdapter {

    public Hashtable<String, String> getInfo() throws AvalancheRemote;

}

Следуют отметить, что все методы созданного интерфейса DemoAdapter должны
обязательно содержать декларирование исключения AvalancheRemote. Данная
реализация чем то напоминает реализацию RMI в языке программирования Java, но есть и
отличия:


  • Класс функции (в приведенном примере — DemoFunction) не реализует интерфейс адаптера
    (в приведенном примере — DemoAdapter)
  • В интерфейсе (пример — DemoAdapter) не обязательно нужно определять все публичные
    методы класса функции (например, в DemoFunction), достаточно определить только методы,
    которого будут вызываться через данный интерфейс.

Реализация адаптера позволяет вызывать методы функции, где бы она не была опубликована.


Реализация класса приложения — DemoApplication


Класс DemoApplication — выполняет вызов метода getInfo() класса DemoFunction
при помощи адаптера DemoAdapter. Пусть класс DemoApplication преобразует
полученный результат вызова getInfo() в формат TXT или HTML.


Код интерфейса DemoApplication.java


package ru.funsys.demo.avalanche;

import java.util.Enumeration;
import java.util.Hashtable;

import ru.funsys.avalanche.Application;
import ru.funsys.avalanche.AvalancheRemote;

public class DemoApplication extends Application {

    /**
     * Определение поля для хранения экземпляра адаптера
     */
    private DemoAdapter info;

    /**
     * Метод вызова метода адаптера и форматирования полученного результата
     * в текстовый формат или формат HTML в зависимости от параметра метода.
     * 
     * @param html true, если необходим формат HTML, иначе false
     * 
     * @return текс или таблицу HTML с параметрами ОС
     */
    public String getInfo(boolean html) {
        StringBuilder builder = new StringBuilder();
        if (html) {
            builder.append("<table border=\"1\">");
            builder.append("<tr><th>key</th><th>value</th></tr>");
        }
        try {
            // Вызов метода адаптера
            Hashtable<String, String> result = info.getInfo();

            for (Enumeration<String> enumeration = result.keys(); enumeration.hasMoreElements(); ) {
                String key = enumeration.nextElement();
                String value = result.get(key);
                if (html) {
                    builder.append("<tr><td>").append(key).append("</td><td>").append(value).append("</td></tr>");
                } else {
                    builder.append(key).append(": ").append(value).append("\r\n");
                }
            }
        } catch (AvalancheRemote e) {
            if (html) {
                builder.append("<tr><td>").append("error").append("</td><td>").append(e.getLocalizedMessage()).append("</td></tr>");
            } else {
                builder.append("error").append(": ").append(e.getLocalizedMessage()).append("\r\n");
            }
        }
        if (html) builder.append("</table>");
        return builder.toString();
    }

}

К классу приложения предъявляются следующие требование: он обязательно должен наследоваться от класса ru.funsys.avalanche.Application.


Поле info определять не обязательно, ссылку на адаптер можно получить по имени, вызвав наследуемый метод getAdapter(String name) класса ru.funsys.avalanche.Application. Имя адаптера задается в конфигурационном файле приложения. Определение поля позволяет сократить объем кодирования.


Реализация JSP страницы — first.jsp


JSP страница отображает результат вызова метода функции в браузере. Сперва получается ссылка на класс DemoApplication и далее вызывается его метод getInfo c параметром true.


<%@ page import="ru.funsys.demo.avalanche.DemoApplication"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Демонстрационное приложение</title>
</head>
<body>
<h1>Демонстрационное приложение</h1>
<jsp:useBean id='DemoApp' scope='application' class='ru.funsys.demo.avalanche.DemoApplication'/>

<%= DemoApp.getInfo(true) %>
<br>

</body>
</html>

Реализация приложения завершена. Теперь нужно определить конфигурацию приложения.


Файл web.xml


В файле web.xml необходимо определить секцию запуска сервлета AvalancheServlet, указав в его параметрах имена конфигурационных файлов приложения и системы логгирования log4j. Сервлет AvalancheServlet инициирует все объекты приложения на основании файла конфигурации avalanche-config.xml.


<?xml version="1.0" encoding="UTF-8"?>
<!--
web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0" 
-->
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">

    <display-name>Demo Avalanche</display-name>
    <description>
        Демонстрационное приложение с использованием framework Avalanche
    </description>

    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
        <welcome-file>index.htm</welcome-file>
        <welcome-file>index.jsp</welcome-file>
        <welcome-file>default.html</welcome-file>
        <welcome-file>default.htm</welcome-file>
        <welcome-file>default.jsp</welcome-file>
    </welcome-file-list>

    <servlet>
        <display-name>AvalancheServlet</display-name>
        <servlet-name>AvalancheServlet</servlet-name>
        <servlet-class>ru.funsys.servlet.http.AvalancheServlet</servlet-class>
        <init-param>
            <param-name>avalanche.config</param-name>
            <param-value>${catalina.base}/conf/avalanche-config.xml</param-value>
        </init-param>
        <init-param>
            <param-name>avalanche.log4j</param-name>
            <param-value>${catalina.base}/conf/avalanche-log4j.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

</web-app>

Файл конфигурации приложения avalanche-config.xml


Файл конфигурации приложения с локальным вызовом функции имеет следующий вид


<?xml version="1.0" encoding="UTF-8"?>
<avalanche name="Demo Application">

    <function class="ru.funsys.demo.avalanche.DemoFunction" name="info-function" description="Сведения об узле системы" />

    <application class="ru.funsys.demo.avalanche.DemoApplication" name="DemoApp" >
        <adapter class="ru.funsys.demo.avalanche.DemoAdapter" name="info" uri="info-function" />
    </application>

</avalanche>

В этом файле определены две секции <function> и <application>. Секция <application> имеет вложенный элемент <adapter>, в котором в атрибуте uri указано имя локальной функции (см. значение атрибута name секции <function>).


Обращаю внимание на значение атрибута name секции <application>, значение этого атрибута DemoApp указывается в атрибуте id для получения ссылки на экземпляр класса DemoApplication в JSP странице.


Удаленный вызов функции DemoFunction


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


"Avalanche — application framework for Java" позволяет избежать дополнительного кодирования для вызова методов удаленной функции DemoFunction. Для этого достаточно изменить конфигурацию приложения и опубликовать копию приложения на удаленном сервере.


Необходимо добавить в конфигурационный файл avalanche-config.xml секции:


  • <interface>, обеспечивает вызов метод удаленных экземпляров объектов по указанному протоколу
  • <connector>, обеспечивает обращение к методам локальных функций с удаленных узлов приложения.

Удаленный вызов по протоколу RMI


Для обеспечения доступа к удаленным объектам по протоколу RMI в файл конфигурации необходимо добавить следующую секцию


<interface name="rmi-interface" uri="rmi://localhost:23000/rmi-connector" />

где:


  • localhost определяет адрес удаленного узла
  • 23000 определяет порт RMI Remote Server коннектора удаленного узла
  • rmi-connector имя коннектора на удаленном узле

На удаленном узле требуется в конфигурационном файле добавить секцию <connector>


<connector class="RMIConnector" name="rmi-connector" port="23000" >
    <publish name="info" function="info-function" />
</connector>

где:


  • rmi-connector имя коннектора
  • 23000 порт RMI Remote Server
  • <publish> публикует локальную функцию info-function в коннекторе, опубликованная функция info-function будет известна удаленным узлам под именем info

Удаленный вызов по протоколу HTTP


Для обеспечения доступа к удаленным объектам по протоколу HTTP в файл конфигурации необходимо добавить следующую секцию


<interface name="http-interface" uri="http://localhost:8080/demo/connector/http-connector" />

где:


  • localhost определяет адрес удаленного узла
  • 8080 определяет порт HTTP Server коннектора удаленного узла
  • demo имя контекста удаленного экземпляра приложения
  • connector имя сервлета AvalancheServlet
  • http-connector имя коннектора на удаленном узле

На удаленном узле требуется в конфигурационном файле добавить секцию <connector>


<connector class="HTTPConnector" name="http-connector" >
    <publish name="info" function="info-function" />
</connector>

где:


  • http-connector имя коннектора
  • <publish> публикует локальную функцию info-function в коннекторе, опубликованная функция info-function будет известна удаленным узлам под именем info

Примечание! Значение HTTP порта не указывается, используется коннектор HTTP сервера (Tomcat).


Дополнительно нужно добавить секции <multipart-config> в конфигурацию сервлета AvalancheServlet и <servlet-mapping> для определения URI этого сервлета в файле web.xml.


Новая редакция файла web.xml


<?xml version="1.0" encoding="UTF-8"?>
<!--
web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0" 
-->
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">

    <display-name>Demo Avalanche</display-name>
    <description>
        Демонстрационное приложение с использованием framework Avalanche
    </description>

    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
        <welcome-file>index.htm</welcome-file>
        <welcome-file>index.jsp</welcome-file>
        <welcome-file>default.html</welcome-file>
        <welcome-file>default.htm</welcome-file>
        <welcome-file>default.jsp</welcome-file>
    </welcome-file-list>

    <servlet>
        <display-name>AvalancheServlet</display-name>
        <servlet-name>AvalancheServlet</servlet-name>
        <servlet-class>ru.funsys.servlet.http.AvalancheServlet</servlet-class>
        <init-param>
            <param-name>avalanche.config</param-name>
            <param-value>${catalina.base}/conf/avalanche-config.xml</param-value>
        </init-param>
        <init-param>
            <param-name>avalanche.log4j</param-name>
            <param-value>${catalina.base}/conf/avalanche-log4j.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
        <multipart-config>
            <!-- 50MB max -->
            <max-file-size>52428800</max-file-size>
            <max-request-size>52428800</max-request-size>
            <file-size-threshold>0</file-size-threshold>
        </multipart-config>
    </servlet>

    <servlet-mapping>
        <servlet-name>AvalancheServlet</servlet-name>
        <url-pattern>/connector/*</url-pattern>
    </servlet-mapping>

</web-app>

Чтобы сохранить работоспособность примера локального вызова функции, добавим две копии секции <application> с именами RMIApp и HTTPApp. Атрибуты uri вложенных элементов <adapter> этих секций примут составные значения имя-интерфейса/имя-удаленной функции, т.е. rmi-interface/info и http-interface/info.


Объединенная редакция конфигурационного файла avalanche-config.xml для локального и удаленного вызовов по протоколам RMI и HTTP


<?xml version="1.0" encoding="UTF-8"?>
<avalanche name="Demo Application">

    <interface name="rmi-interface" uri="rmi://localhost:23000/rmi-connector" />

    <interface name="http-interface" uri="http://localhost:8080/demo/connector/http-connector" />

    <function class="ru.funsys.demo.avalanche.DemoFunction" name="info-function" description="Сведения об узле системы" />

    <application class="ru.funsys.demo.avalanche.DemoApplication" name="DemoApp" >
        <adapter class="ru.funsys.demo.avalanche.DemoAdapter" name="info" uri="info-function" />
    </application>

    <application class="ru.funsys.demo.avalanche.DemoApplication" name="RMIApp" >
        <adapter class="ru.funsys.demo.avalanche.DemoAdapter" name="info" uri="rmi-interface/info" />
    </application>

    <application class="ru.funsys.demo.avalanche.DemoApplication" name="HTTPApp" >
        <adapter class="ru.funsys.demo.avalanche.DemoAdapter" name="info" uri="http-interface/info" />
    </application>

    <connector class="RMIConnector" name="rmi-connector" port="23000" >
        <publish name="info" function="info-function" />
    </connector>

    <connector class="HTTPConnector" name="http-connector" >
        <publish name="info" function="info-function" />
    </connector>

</avalanche>

Для сохранения работоспособности JSP страницы first.jsp можно создать ее копии для вызова удаленной функции по протоколу RMI и HTTP, указав значения в атрибуте id RMIApp и HTTPApp соответственно.


Код rmi.jsp


<%@ page import="ru.funsys.demo.avalanche.DemoApplication"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Демонстрационное приложение - RMI</title>
</head>
<body>
<h1>Демонстрационное приложение - RMI</h1>
<p>
Удаленный вызов - протокол RMI
</p>
<jsp:useBean id='RMIApp' scope='application' class='ru.funsys.demo.avalanche.DemoApplication'/>

<%= RMIApp.getInfo(true) %>

</body>
</html>

Код http.jsp


<%@ page import="ru.funsys.demo.avalanche.DemoApplication"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Демонстрационное приложение - HTTP</title>
</head>
<body>
<h1>Демонстрационное приложение - HTTP</h1>
<p>
Удаленный вызов - протокол HTTP
</p>
<jsp:useBean id='HTTPApp' scope='application' class='ru.funsys.demo.avalanche.DemoApplication'/>

<%= HTTPApp.getInfo(true) %>

</body>
</html>

Теперь достаточно скопировать разработанное приложение на другой узел, при необходимости заменить значение localhost на адрес удаленного узла в секциях <interface> файла avalanche-config.xml, и проверить работоспособность приложения.


Как видно из приведенного примера, для обеспечения удаленного вызова функции не потребовалось вносить каких либо корректировок в программный код локального вызова.


Исходные коды, рассмотренного здесь примера приложения, опубликованы на GitHub


Дополнено


Приведу пример удаленного вызова с использованием общего файлового сервиса. Оба узла (вызывающий и вызываемый) должны иметь доступ к общему сетевому файловому сервису. Эта разновидность удаленного вызова не такая быстрая, сказывается наличие временных задержек при сканировании файловой структуры для обнаружения файлов запросов и ответов. Но если нельзя по каким либо соображениям общаться напрямую по протоколам на основе TCP/IP, то можно организовать обмен через файловую систему (при наличии общего файлового сетевого ресурса).


Удаленный вызов с использованием файлового сервиса


Для обеспечения доступа к удаленным объектам с использованием файлового сервиса в файл конфигурации необходимо добавить следующую секцию


<interface name="file-interface" uri="file://path/file-connector" />

где:


  • path путь к каталогу файлового коннектора
  • file-connector имя файлового коннектора, это имя соответствует имени каталога в файловой системе

На удаленном узле требуется в конфигурационном файле добавить секцию <connector>


<connector class="FileConnector" name="file-connector" uri="file://path" >
    <publish name="info" function="info-function" />
</connector>

где:


  • file-connector имя коннектора, создается каталог с этим именем в файловой системе
  • uri определяет файловый путь к каталогу файлового коннектора
  • <publish> публикует локальную функцию info-function в коннекторе, опубликованная функция info-function будет известна удаленным узлам под именем info

Для каждой опубликованной функции в файловом коннекторе в каталоге коннектора создается свой подкаталог с именем функции (в приведенном примере: info-function)


Как и для других протоколов взаимодействия файловый обмен не требует какой-то специфической реализации кода приложения. В любой момент можно переключиться на другой протокол только скорректировав конфигурационные файлы узлов приложения.

Теги:
Хабы:
Всего голосов 15: ↑12 и ↓3+9
Комментарии333

Публикации