Первое приложение (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)
Как и для других протоколов взаимодействия файловый обмен не требует какой-то специфической реализации кода приложения. В любой момент можно переключиться на другой протокол только скорректировав конфигурационные файлы узлов приложения.