Как избежать ловушек во время переноса web-приложений Java с Windows на AIX

Три ошибки, которые вам больше не втретятся

Прочитайте статью, чтобы не попасть в три ловушки при переносе web-приложений Java(TM) с Windows(R) на AIX(R). Если вы осуществляете разработку на платформах Windows и используете продукт на платформах типа UNIX(R), вы можете столкнуться с неявными ошибками.

Шу Фанг Ру, разработчик программного обеспечения, IBM

Шу Фанг Ру (Shu Fang Rui) работает программистом в IBM Shanghai Globalization Laboratory (SGL), Шанхай, Китай. Она интересуется беспроводными и Java EE технологиями, любит путешествовать, также увлекается несколькими видами спорта, бадминтоном и бильярдом. Вы можете написать ей на: shufangr@cn.ibm.com.



09.03.2007

Введение

Сейчас, как правило, приложения разрабатываются в среде разработки, а затем используются в производственной среде. В большинстве случаев в качестве базовой платформы очень хорошо подходит Windows® , поскольку дает возможность использовать множество мощных интегрированных средств разработки (IDE). Платформы типа UNIX® , такие как UNIX, Linux® или AIX® , благодаря своей стабильности являются хорошими производственными платформами. Язык программирования Java ™ считается независимым от платформы языком программирования со своим свойством "Единожды записанный, работает везде". В большинстве случаев, во время переноса приложения с одной платформы на другую, разработчики экономят много времени. Однако существуют ловушки или ошибки, о которых вы должны знать для того, чтобы гарантировать, что на целевой платформе ваши приложения будут работать именно так, как вы этого хотите.

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


Проблемы соединения по HTTP

Передача данных по HTTP является общей во всех видах web-приложений. Всякий раз, когда вы активизируете сервлет или JavaServer Pages (JSP), происходит передача данных по HTTP. Несмотря на то, что протокол HTTP не зависит от платформы, необходимо обратить особое внимание на случаи связи между разными платформами.

В рассматриваемом сценарии клиент отправляет специальный запрос шлюзу, шлюз обрабатывает запрос и затем отправляет ответ клиенту. Клиент использует внутренний основанный на XML протокол для связи со шлюзом, шлюз же, только обрабатывает сообщения в соответствии с протоколом. Протокол требует наличия символа переноса строки между двумя элементами XML: <Name> и <Greeting>.

Согласно коду в листинге 1, разрыв строки добавляется к телу запроса. Однако действительно ли сервер беспрепятственно обрабатывает запрос и отправляет правильный ответ? Как сказать. Это общая проблема в процессе переносая приложений Java с одной платформы на другую.

Листинг 1. Клиент отправляет запрос по HTTP
try {
  URL url = new URL("http://localhost:9081/SampleWeb/Simulator");
  URLConnection conn = url.openConnection();
  conn.setDoOutput(true);
  conn.setRequestProperty("Content-Type", "application/xml");

  OutputStream os = conn.getOutputStream();
  PrintWriter writer = new PrintWriter(os);

  writer.println("<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>");
  writer.println("<Name>");
  writer.print("<first name>");
  writer.print(“Rachel");
  writer.println("</first name>");
  writer.println("</Name>");

  //Здесь должен быть перенос строки
  writer.println();

  writer.println("<Greeting>");
  writer.println("Hello!");
  writer.println("</Greeting>");

  writer.flush();
  conn.getInputStream();
} catch (MalformedURLException mue) {
  System.err.println("error, message =" + mue);
} catch (IOException ioe) {
  System.err.println("error, message =" + ioe);
}
Ловушка
В среде разработки на Windows код работает исправно, однако когда он применяется в рабочей среде на AIX, вы сталкиваетесь с тем, что от шлюза не приходит никакого ответа. Итак, что же происходит с этим предположительно верным кодом?

Шлюз, который вы не можете контролировать, - это программа на языке С, которая запускается на платформе Windows. Это создает неправильное предположение о том, что все полученные запросы идут с Windows и \r\n - символ разрыва строки должен находиться между элементами <Name> и <Greeting>. Таким образом, он пытается проанализировать запрос с символом \r\n между элементами <Name> и <Greeting>. Однако, на AIX и на большинстве платформ типа UNIX, если значения переменной свойств среды line.separator в языке Java не установлены заранее, используется значение по умолчанию \n и, следовательно, шлюз не воспринимает формат запроса.

Решение проблемы
Если вы знаете причину этой проблемы, решить ее достаточно просто. Это можно сделать при помощи кода клиента или шлюза.
  • Если вы не можете управлять кодом шлюза, вы можете просто запрограммировать в вашем клиенте “System.setProperty(“line.separator", "\r\n")" ;.
  • Или же, сделайте так, чтобы код шлюза работал на разных платформах, как и следовало бы. Для платформ типа UNIX используйте символ \n для переноса строки. Для Mac OS используйте \r. На платформе Windows используйте \r\n.
Напоминание
Изучите программные интерфейсы приложения Java (APIs), такие как java.io.Writer, java.io.Reader и наследующие их API. Все они представляют собой текстовые API и получают значения разделителя строк по умолчанию из свойств системы, если они не установлены другим способом. Если строго определенного формата символов не требуется, для большей эффективности вам следует подумать об использовании байтовых программных интерфейсов Java (API).

В процессе переноса с одной платформы на другие, жесткое кодирование содержимого зависящего от платформы часто является одной из причин, почему ваши Java-приложения перестают быть совместимыми. Разделитель строк - это просто одна из наиболее общих констант. Возможное содержимое включает разделитель строк, разделитель пути и т.д. Когда вы хотите включить эти элементы содержимого в свой код, используйте System.getProperty("property name") для того, чтобы получить значения характеристики вместо кодирования символов.

Размещение файла

Другая распространенная проблема, с которой вы можете столкнутся, перенося приложения Java с одной платформы на другие, – это размещение файла. В разных средах существуют разные способы размещения файлов.

Предположим, вы хотите разместить DTD файл в одном из ваших сервисных Java-проектов, который используется web-проектом в проекте приложений предприятия. Для того, чтобы разместить sample.dtd в класс DtdEntityResolver в WebSphere® Studio Application Developer (Application Developer) V5.1.2, вы должны были бы записать код (см. листинге 2) и получить путь E:/workspace/UtilProj/bin/com/ibm/util/sample.dtd.

Листинг 2. Пример кода для размещения файла
 Class clazz = getClass();
URL url = clazz.getResource("."); //попытка получить ссылку на текущий каталог
String currentPath = url.getPath(); 
String filePath = currentPath + "sample.dtd";

Посмотрев на этот код, вы можете сказать, что у вас имеется лучшее решение. Более подходящее решение действительно есть, но давайте сначала просто попробуем код, который хорошо работает в WebSphere Test Environment Application Developer V5.1.2. Таким способом вы размещаете файл.

После завершения всех остальных модулей ваша команда решает применить проект приложения предприятия в рабочей среде - WebSphere Application Server (Application Server) V5.1 на AIX. На этот раз вам не очень повезло. java.lang.NullPointerException сброшен и вам не удалось загрузить файл.

Ловушка
Почему так произошло? С Windows это работает, а с AIX не получается. Возможно межплатформенная ошибка в вашем Java коде? Сначала вы можете именно так и подумать. Однако дело не в этом. Давайте еще раз посмотрим на путь полученного файла вышеприведенного кода. Это -$Workspace/$ProjectName/$bin/$packageName/sample.dtd. В вашем исходном проекте есть бинарная директория, которая используется для хранения скомпилированных двоичных классов. Сохраняется ли бинарная директория после использования архивного файла EAR на сервере приложений запускаемого с AIX? Как вы знаете, после экспорта такого проекта как EAR, вспомогательный Java-проект входит в архивный файл Java (JAR). В файле JAR невозможно разместить ресурс используя "." (указатель текущей директории), таким образом java.lang.Class.getResource(".") возвращает Null.

После того как вы все это выяснили, можете подумать о том, что с автономным сервером приложений запускаемым с платформы Windows, вышеприведенный код, скорее всего выдаст то же исключение - NullPointerException . Та же ошибка происходит, когда файл EAR используется на автономном сервере приложений, а не входящем в WebSphere Test Environment. То, что ваш код в среде тестирования отправленный с Application Developer V5.1.2 ведет себя не так как в Application Server 5.1.x, даже если они запускаются с одной платформы Windows, звучит странно. Для Java-проекта в проекте приложения предприятия WTE загружает бинарные классы, непосредственно, из бинарной директории в вашу рабочую область, в то время как, автономный сервер приложения загружает их из используемого файла JAR. Если вам интересна сравнительная характеристика двух сред, информацию вы можете получить в информационном центре Rational® Application Developer Information Center (см. Ресурсы ). Более подробную информацию вы можете найти в WebSphere Test Environment в справочнике WebSphere Application Server Test Environment Guide (см. Ресурсы ).

В Rational Application Developer V6.0 среда тестирования разработана так, чтобы представлять собой автономный сервер приложения, следовательно, различия между сервером приложений как средой тестирования и сервером приложений как автономным сервером исчезают. Выше приведенный код одинаково работает и на Rational Application Developer V6.0 и на Application Server 6, как с Windows так и AIX. NullPointerException всегда сброшен, поскольку обе среды рассматривают вспомогательный Java-проект в проекте приложения предприятия как файл JAR.

Решение проблемы
Сейчас, когда вы знаете, почему происходила ошибка, давайте перейдем к более подходящему решению: используя getClass().getResource("sample.dtd") . Здесь функция java.lang.Class.getResource(String filename) передает задачу поиска ресурса связанному с ним "загрузчику" - ClassLoader. Находится ли файл в архиве JAR или в бинарной директории, он всегда возвращает корректный путь к файлам. На рисунке 1 сравниваются разные среды исполнения на платформах Windows и AIX.

На приведенном ниже рисунке 1 нужно отметить, что java.lang.Class.getResource(String filename) работает в любой среде, и не имеет значения это встроенная среда тестирования Application Developer, встроенная среда тестирования Rational Application Developer, автономный сервер приложения, запускаемый с Windows или автономный сервер приложения, запускаемый с AIX. Можно сделать вывод, что java.lang.Class.getResource(String filename) - всегда предпочтительна для обеспечения мобильности платформы.

Рисунок 1. Результаты getResource(fileName) на Windows и AIX
Результаты getResource(fileName) на Windows и AIX
Напоминание
Файлы JAR заархивированы в ZIP-подобном формате, следовательно, вы можете работать с ними, как с ZIP-архивами (например, сжимать файлы без потери данных, архивировать, восстанавливать данные и распаковывать архивы. После этого вы размещаете ссылку на файл, используя getClass().getResource(String filename), скажем такую - $INSTALLEDAPP_HOME/SampleEAR.ear/UtilProj.jar!/com/ibm/util/sample.dtd. Следующая задача заключается в том, чтобы прочитать содержимое с файла JAR; смотрите листинг 3.
Листинг 3. Неправильный способ считывания содержимого файла JAR
 URL jarUrl = getClass().getResource(“simple.dtd");
String path = fileUrl.getPath();
FileInputStream fis = new FileInputStream(path);

Прочитать содержимое JAR-файла сложно. В листинге 3 показан интуитивный способ получения FileInputStream для файла simple.dtd, но это не работает. Мы получили исключение Java.io.FileNotFoundException. Смотрите листинг 4 и листинг 5, где показано, как это правильно сделать.

Листинг 4. Правильный способ считывания содержимого файла JAR
  URL jarUrl = getClass().getResource(“simple.dtd");
URLConnection urlConn = jarUrl.openConnection();
InputStream is = urlConn.getInputStream();
Листинг 5. Правильный способ считывания содержимого файла JAR
 InputStream is = getClass().getResourceAsInputStream("simple.dtd");

Почему новый FileInputStream(String name) не работает, тогда как URL.openConnection().getInputStream() работает? Потому что каждый экземпляр java.net.URL связан с протоколом, например HTTP, JAR, файл и т.д. Каждый протокол имеет специальный блок управления, который является экземпляром java.net.URLStreamHandler, для управления деталями соединения соответствующего протокола. URL.openConnection() вызывает URLStreamHandler.openConnection() для того, чтобы получить объект URLConnection, который играет роль соединения с удаленным объектом, на который указывает URL. Для протокола HTTP возвращается один объект HttpURLConnection; для JAR протокола возвращается один объект JarURLConnection.

Для кода в листинге 4urlConn является экземпляром JarURLConnection. При запуске getInputStream в JarURLConnection вызывает JarFile.getInputStream(JarEntry jarEntry) и jarEntry; в вашем случае это файл с именем simple.dtd. В конечном счете, возвращается экземпляр JarInputStream, который используется для считывания содержимого файла JAR.

Становится понятно, почему FileInputStream не работает - JarEntry в файле JAR использует Jar протокол. FileInputStream работает только с файлом протокола, следовательно, он не может удачно работать на sample.dtd (a JarEntry в файле JAR, который использует JAR протокол).

Для кода в листинге 5 классовая константа возвращаемая посредством getClass() вызывает ClassLoader.getResourceAsInputStream() . Последний после этого вызывает getResource(fileName).openConnection().getInputStream() . Код в листинге 5 делает то же, что и код в листинге 4.

Итак при считывании содержимого JAR-файла, используйте код листинга 4 или листинга 5 ; никогда не используйте FileInputStream , поскольку с JAR протоколом он не работает.

Отсутствие процесса прочтения в соединении сокетов

В этом разделе рассматривается проблема, которая часто встречается в соединении сокетов на платформе AIX. Проверка рабочих характеристик (performance test) очень хорошая вещь. Она помогает вам найти такие неявные ошибки как те, что обнаруживаются при проведении функциональной проверки. Этими ошибками могут быть или “утечка памяти”, или гонки сигналов в многопоточном программировании. Они как злые гномы в вашем коде, которые иногда могут заставить его вести себя очень необычно.

В сценарии проверки рабочих характеристик тестовый клиент отсылает запросы web-сервиса web-приложению, запускающемуся на сервере приложений. Web-приложение обрабатывает их и создает новое сообщение, после этого отправляет только что созданное сообщение шлюзу. Далее шлюз отправляет ответ web-приложению, а web-приложение переправляет ответ тестовому клиенту. Рисунок 2 иллюстрирует этот процесс.

Рисунок 2. Проверка рабочих характеристик на web-приложении
Проверка рабочих характеристик на web-приложении
Ловушка
Это обычный план и его достаточно просто протестировать. Однако при проверке рабочих характеристик, когда приложение предприятия обрабатывает множество транзакций в секунду (TPS), оно может выдать исключение в случае, если до этого никаких проверок рабочих характеристик не проводилось.

Исключение появляется в соединении сокетов и обычно выглядит так:

java.net.SocketException: There is no process to read data written to a pipe.

Ошибка "Процесс считывания данных вводимых в канал не происходит" - это характерное для AIX сообщение об ошибке, которая заключается в “родном” методе внедрения соответствующего Java кода. Это исключение генерирует C-код, который обеспечивает обмен через сокеты на AIX.

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

Решение проблемы
В большинстве случаев проблема вызвана возможными ошибками. Например:
  • Web-приложение устанавливает HTTP-соединение со шлюзом и пытается отправить ему запрос (шаг 2 на рисунке 2 ), как показано в листинге 6.
    Листинг 6. Отправка HTTP-запроса web-приложению
    URL url = new URL(serverAddress);
    URLConnection conn = url.openConnection();
    conn.setDoOutput(true);
    OutputStream os = conn.getOutputStream();
    PrintWriter writer = new PrintWriter(os);
    writer.println("Hello, dude");
    writer.flush();
    writer.close();
    InputStream is = conn.getInputStream();

    Без последней строки InputStream is = conn.getInputStream мы бы увидели исключение: java.net.SocketException: There is no process to read data written to a pipe. Без conn.getInputStream() сообщение с запросом вообще не будет отправлено принимающей стороне. В результате, принимающая сторона не получит никакого сообщения, и конечно не будет никакого процесса, который бы считал данные из сокета. Следовательно, это является причиной исключения.

  • Тайм-аут в процессе ожидания ответа, как показано в 4 шаге на рисунке 2.

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

  • Отвечающий канал заблокирован другим процессом, как показано в 3 шаге на рисунке 2.

    Предположим, вы управляете пулом процессов в шлюзе для того, чтобы ответить на запросы отправленные web-приложением. Когда достигается очень высокий TPS и планирование процессов неэффективно, вполне вероятно, что ни один процесс не сможет быть запланированным для обработки нового запроса. В результате, запрос не считывается и не обрабатывается ни одним процессом. Это также может быть причиной ошибки.

Напоминание
Для ввода или вывода основанных на концепции потоков API, убедитесь, что все открытые подключения (открытый InputStream(Reader) или OutputStream(Writer) ) закрыты, если они больше не нужны.

Краткие выводы

Перенос web-приложений Java с одной платформы на другую требует некоторых усилий, хотя и незначительных. Необходимо помнить три вещи:

  • Когда вы пишете OS-код, не надо все подряд кодировать. Используйте java.lang.System.getProtery(String name) - это всегда более безопасно.
  • Используйте java.lang.Class.getResource(String filename), чтобы разместить ресурсы на Application Developer V5.1.2, Rational Application Developer V6.0 и на “родственные” версии сревера приложений, как на Windows, так и на AIX.
  • Если программы подвержены сетевым ошибкам, операции считывания и записи делайте вместе. Если вы записываете данные в сокет, для их считывания должен быть какой-то процесс.

Ресурсы

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=AIX и UNIX
ArticleID=201213
ArticleTitle=Как избежать ловушек во время переноса web-приложений Java с Windows на AIX
publish-date=03092007