LXF90:JavaEE

Материал из Linuxformat.

Перейти к: навигация, поиск
Java EE

Телефонная книга: переход на JSP

ЧАСТЬ 2 Встречают по одежке – и Большой Босс не был сильно впечатлен созданной нами в прошлый раз адресной книгой. Александр Бабаев исправляет замеченные недочеты.

Содержание

В прошлый раз мы создали простейшую электронную записную книжку. Она работает в браузере и показывает несколько простых страничек, на которых можно просмотреть список контактов, добавить новый контакт, удалить его или отредактировать. А сейчас давайте попробуем сделать все это более правильно.

Почему было плохо?

Действительно, почему? Работает, и хорошо. Достаточно быстро и не слишком сложно. Но вдруг захочется поменять дизайн страничек? А захочется через десять минут работы. Или после того, как страничку посмотрит начальник.

Чтобы сделать это, можно изменить код проекта, потом перекомпилировать его, остановить сервер (А? Кто-то работал? Извините...), установить новый код и повторно запустить сервер. Метод, мягко говоря, неудобный. А можно изменить сам проект так, чтобы выполнение таких пожеланий не требовало столь сложных действий. Второй путь зовется рефакторингом и гораздо более корректен. Если разделить дизайн и логику работы приложения (бизнес-логику), то в дальнейшем можно будет, например, разделить и работу по их поддержанию. Хороший программист не всегда создает хорошие пользовательские интерфейсы, поэтому данный аспект тоже важен.

Как сделать хорошо?

Ну, вкратце уже понятно. Нужно вынести в отдельные файлы ту часть, которая меняется часто (в нашем случае, это интерфейс) и как-то подключить эти файлы из нашей программы. Плюс, желательно сделать это так, чтобы формат файлов «дизайна» был стандартным, чтобы каждый раз не переучиваться.

Решений для данной проблемы существует множество. Рассмотрим самые распространенные:

  • Шаблоны. Одна из самых распространенных библиотек работы с шаблонами – Velocity. При использовании шаблонных движков можно добавлять в текст специальные вставки, которые говорят: «Тут вставить значение переменной Name». Иногда можно делать более сложные операции (вставка подшаблонов, вычисления, условные вставки).
  • JSP (Java Server Pages). По времени появления, пожалуй, первая технология для отделения дизайна от бизнес-логики. Но я ее поставил второй, так как она сложнее, чем просто шаблонная библиотека. JSP позволяет внедрить код на (по задумке) любом языке программирования внутрь специальным образом созданной странички. Впрочем, обычно используется Java. Теоретически, можно написать серверное приложение, используя исключительно JSP. Этот подход похож на PHP, с тем отличием, что JSP-страницы – это полноценные сервлеты, они компилируются при обновлении исходного текста и обрабатываются как таковые.
  • JSF (Java Server Faces). В некотором роде эта технология объединяет подходы, которые используются при создании «обычных» и «сетевых» программ. Интерфейс (как дизайн интерфейса, так и его логика) программы описывается специальным образом, а после этого пишутся JSP-странички, в которых указывается «тут вставить таблицу с именем таким-то». JSF обрабатывает эти спецвставки и «рисует» функциональные элементы интерфейса (обрабатывая события от них и так далее), позволяя дизайнеру сосредоточиться на остальном.
  • Google Web Toolkit. Не могу не остановиться на этом средстве. При его использовании на выходе получается полноценное AJAX-приложение (что это такое – тема отдельной статьи, пример – Google Mail), а на входе – все тот же Java-код. Решение интересное, не лишенное своих достоинств и недостатков.

Мы же в рамках данной статьи рассмотрим «средненькое» решение – Java Server Pages. В основном – из-за его стандартности, хотя для данного конкретного случая можно выбрать какой-нибудь шаблонный движок, например, тот же Velocity (http://velocity.apache.org).

Общая схема работы приложения

Поняв, что нужно отделить логику от дизайна, давайте подумаем, каким образом это можно сделать. Предлагаю остановиться на следующей схеме - (Рис. 1).

Сервлет выдает данные, абсолютно не заботясь о том, как они будут отображаться. Но выдает он их не в «сыром» виде, а в полностью обработанном, готовом для отображения на экране (например, если нужно полное имя человека, а в данных – его ФИО по отдельности, то сервлет должен преобразовать второе в первое перед передачей в JSP).

Возникает вопрос: как же передаются данные от сервлета в JSP? Через уже известный нам объект request. К нему «прикручен» специальный ассоциативный массив «String – Object», который называется атрибутами и который живет, пока жив запрос. К нему имеет доступ и сервлет, и JSP-страница, поэтому его можно (и это правильно) использовать для передачи данных.

Переходим на Tomcat

Но сначала нужно переписать наш сервлет «по-взрослому». Встроенный сервер – это замечательно для кустарных проектов, но обычно контейнер сервлетов уже стоит, и подключаться следует к нему.

Мы будем использовать Tomcat 5.5. Это классический, можно даже сказать, стандартный открытый сервлет-контейнер. Для установки Tomcat достаточно просто скачать его с http://tomcat.apache.org (или взять с нашего DVD), распаковать и запустить bin/startup.sh (или соответсвующий .bat). Tomcat работает с файлами специального типа Web Archive (WAR). Обнаружив такой файл в определенном каталоге, Tomcat разворачивает его и запускает содержащееся в нем приложение. Чтобы перезапустить или обновить программу, достаточно просто заменить один WAR-файл другим.

Предыдущий код не готов для работы с Tomcat, поэтому его нужно немного переписать. Вот что будет сделано:

  • AddressBook потеряет методы start и main и превратится в простое хранилище записей.
  • AddressBookHandler превратится в AddressBookServlet, и в него будет добавлено примерно следующее (Листинг 1):

Листинг 1. Новый AddressBook

private AddressBook _addressBook = null;

public void init(ServletConfig aServletConfig) throws ServletException {
 super.init(aServletConfig);
 _addressBook = new AddressBook();
}

protected void doGet(HttpServletRequest aRequest, HttpServletResponse aResponse)
     throws ServletException, IOException
 handle(aRequest, aResponse);
}

protected void doPost(HttpServletRequest aRequest, HttpServletResponse aResponse)
     throws ServletException, IOException                                
 handle(aRequest, aResponse);                                            
}

Сам метод handle тоже слегка преобразуется (Листинг 2):

Листинг 2. Новый метод handle

 private void handle(HttpServletRequest aRequest, HttpServletResponse aResponse)
       throws ServletException, IOException {
  aRequest.setCharacterEncoding("utf-8");

  String target = aRequest.getRequestURI().substring(
    aRequest.getContextPath().length());

  if (target.equals("/")) {
  _drawer.outputPage("index.jsp", aRequest, aResponse);
  } else if ("/add".equals(target)) {
  handleAdd(aRequest, aResponse);
  } else if ("/view".equals(target)) {
  handleView(aRequest, aResponse);
  } else if ("/edit".equals(target)) {
  handleEdit(aRequest, aResponse);
  } else if ("/remove".equals(target)) {
  handleRemove(aRequest, aResponse);
  }
 }
  • Для того, чтобы Tomcat «понял», что ему положили сервлет, и знал, как его обрабатывать, нужно написать специальный файл, который называется «дескриптор». Несмотря на то, что слово страшное, это просто XML-документ с описанием сервлета. Если перевести с языка написания дескрипторов на русский, то получится примерно следующая информация:
    • Наш сервлет называется «ABServlet» и запускается классом AddressBookServlet. Теоретически можно назвать сервлет так же, как и класс, но мы не будем так делать, чтобы было меньше путаницы.
    • Для всех URL, которые начинаются с «/», нужно вызывать сервлет, который называется ABServlet.

А вот как он выглядит (Листинг 3):

Листинг 3. Дескриптор для сервлета

 <?xml version="1.0" encoding="UTF-8"?>
 <web-app version="2.4"
    xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
    http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" >
  <servlet>
  <display-name>AddressBook</display-name>  
  <servlet-name>Servlet</servlet-name>
  <servlet-class>AddressBookServlet</servlet-class>
  <load-on-startup>0</load-on-startup>
  </servlet>
  <servlet-mapping>
  <servlet-name>Servlet</servlet-name>
  <url-pattern>/</url-pattern>
  </servlet-mapping>
 </web-app>
  • Дескриптор будет называться web.xml и храниться в специальном каталоге. Где именно – обсудим, когда будем собирать сервлет в WAR

. Сделайте указанные изменения самостоятельно или возьмите гото-вый код с DVD. Все в порядке? Тогда движемся дальше.

Новый метод

Если присмотреться более внимательно к коду нового handle, можно заметить, что там появился вызов метода outputPage. Раньше его, в отличие от разных handle... не было. Это метод, который выбирает JSP-файл и передает ему управление для вывода страничек. Выглядит метод следующим образом (Листинг 4):

Листинг 4. Метод outputPage

public void outputPage(String aJSPName, HttpServletRequest aRequest, HttpServletResponse aResponse) throws IOException, ServletException
{
 RequestDispatcher dispatcher = aRequest.getRequestDispatcher("/jsps/" + aJSPName);
 dispatcher.forward(aRequest, aResponse);
}

В этом методе мы берем нужный JSP-файл и говорим сервлет-контейнеру: «Обработай, пожалуйста». Остальное берет на себя контейнер. Он ищет JSP-файл, загружает его, компилирует (если это нужно), выполняет получившийся сервлет, а результат записывает в aResponse.

JSP-страницы

Для начала создадим каталог, в котором будем собирать наше интернет-приложение. Назвать можно как угодно, например, WebApp (Web Application). В нем создадим специальный каталог WEB-INF, где должен находиться дескриптор web.xml, и каталог jsps, в котором будут храниться JSP-странички.

Создадим три JSP-файла: для индексной странички, для редактирования (или добавления) записей и для просмотра, и назовем их, соответственно, index.jsp, edit.jsp, view.jsp. Не забудьте – их нужно сохранить в в WebApp/jsps.

Сам JSP достаточно прост. Рассмотрим index.jsp (Листинг 5):

Листинг 5. index.jsp

 <%@ page contentType="text/html; charset=UTF-8" %>
 <html>
  <head>
   <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
   <title>Адресная книга</title>
  </head>
  <body><h1>Адресная книга</h1>
   <a href="<%=request.getContextPath()%>/add">Добавить запись</a><br/>
   <a href="<%=request.getContextPath()%>/view">Просмотреть записи</a><br/>
  </body>
 </html>
 

Первая строчка добавляет поле «Content-type» к HTTP-заголовку ответа. Это прямой аналог строки

aRequest.setContentType("text/html; charset=utf-8")

из «старого» метода handle. А дальше, кроме странных вставок <%...%>, идет обычный HTML-код. И это хорошо! Это понятно! Теперь разберемся с непонятным.

В JSP можно вставлять «инородный» для HTML код, который специальным образом интерпретируется сервером и может быть использован для вставки различных данных. Есть несколько типов таких вставок.

  • <%@...%> – обозначает специальную вставку, которая определяет параметры страницы, в нашем случае – ContentType. Можно задавать, например, язык, на котором написана страница. Он же используется для секций import (см. view.jsp ниже).
  • <%=...%> – это простой вывод переменной. Действие вставки <%=что-нибудь%> аналогично вызову request.getWriter().write(что-нибудь).
  • <%...%> – самый общий вариант вставки, внутри может быть любой код. В нашем случае, на Java.

index.jsp – простой файл, посмотрим на нечто более сложное. Например, view.jsp (Листинг 6).

Листинг 6. view.jsp

 <%@ page contentType="text/html; charset=UTF-8" %>
 <%@ page import="java.util.*" %>
 <html>
 <head><title>Адресная книга</title></head>
 <body><h1>Адресная книга, список контактов</h1>
  <a href="<%=request.getContextPath()%>">На главную</a><br/>
  <span style="color: green;"><%=request.getAttribute("message")%></span>
  <table border="1">
  <tr><td width="100">Имя</td><td width="100">Номер</td><td width="100">Комментарий</td><td> - </td></tr>
  <% Map numbers = (Map) request.getAttribute("numbers");
   Map comments = (Map) request.getAttribute("comments");
   for (Object entry : numbers.entrySet()) {
   String name = (String) ((Map.Entry) entry).getKey();
   String number = (String) numbers.get(name);
   String comment = (String) comments.get(name); %>
  <tr>
   <td class="name"><%=name%></td>
   <td class="number"><%=number%></td>
   <td class="comment"><%=comment%></td>
   <td class="name">
 <a href="<%=request.getContextPath()%>/remove?number=<%=number%>">Удалить</a>
 <a href="<%=request.getContextPath()%>/edit?number=<%=number%>">Редактировать</a>
   </td>
  </tr>
  <% } %>
  </table>
 </body>
 </html>
 

Как можно заметить, здесь есть и импорт (о чем я говорил чуть выше), и вставка Java-кода. Данный файл отлично показывает, как, например (не самый лучший способ, конечно), сделать вывод в цикле.

А как это обрабатывается-то?

Естественно, и методы handle... после такого изменения стали другими. Весь вывод HTML-кода исчез, осталась подготовка данных, и вызов метода outputPage. Вот, например, метод handleEdit(...) (Листинг 7):

Листинг 7. Метод handleEdit, обработка редактирования записи

if (aRequest.getParameter("number") == null) {
 _addressBook.removeContactByNumber(aRequest.getParameter("number"));
 aRequest.setAttribute("message", "Не определено, что редактировать");
 handleView(aRequest, aResponse);
} else if (aRequest.getParameter("edited") != null) {
 _addressBook.editContact(aRequest.getParameter("edited"),           
  aRequest.getParameter("name"),                                     
  aRequest.getParameter("number"),
  aRequest.getParameter("comment"));
  aRequest.setAttribute("message", "Контакт \"" +
  aRequest.getParameter("name") + "\" отредактирован");
 handleView(aRequest, aResponse);                                    
} else {
 Contact contact = _addressBook.getContactByNumber(aRequest.getParameter("number"));
 aRequest.setAttribute("action", "edit");
 aRequest.setAttribute("edit.name", contact.getName());
 aRequest.setAttribute("edit.number", contact.getNumber());
 aRequest.setAttribute("edit.comment", contact.getComment());
 outputPage("edit.jsp", aRequest, aResponse);
}

Остальные методы меняются аналогично – их полный код можно найти на диске.

И как все это вставить в Tomcat?

Теперь у нас есть:

  • Классы Contact, AddressBook, AddressBookServlet.
  • Файл web.xml.
  • Каталог jsps с файлами edit.jsp, index.jsp, view.jsp.

Для того, чтобы Tomcat понял, что ему дали полноценное приложение, нужно выполнить всего три шага:

  • Скомпилировать все, что компилируется, и создать правильную иерархию файлов и каталогов, которая представлена на рис. 2.
  • Создать специальный файл-описание архива («манифест»).
  • Заархивировать созданную структуру при помоци утилиты jar, входящей в комплект JDK.

Скомпилируем файлы. Тут ничего нового не появилось, разве что изменилась сама команда (обратите внимание на ключ -cp, задающий библиотеки classpath):

cd ~/Programming/AddressBook/src
javac -encoding utf-8 -cp ~/bin/tomcat/common/lib/servlet-api.jar -d ../build/WEB-INF/classes/ *.java

Переходим к созданию манифеста. Он должен называться MANIFEST.MF и располагаться в каталоге META-INF. К счастью, за этим следит сам jar, поэтому нам достаточно просто сохранить где-то файл и указать его jar'у как манифест. В нашем случае он предельно прост и не содержит интересной информации, но в принципе здесь могут располагаться всякие настройки для запуска вашего приложения. Вот его текст (Листинг 8):

Листинг 8. Манифест для war-файла

Manifest-Version: 1.0
Created-By: Hands of programmer

Теперь соберем все в war (Web Archive). Манифест для приведенной ниже команды должен быть назван MANIFEST.MF и располагаться рядом с каталогом build. Результирующий архив называется address.war и располагается там же, рядом с манифестом.

jar -cfm ../address.war ../MANIFEST.MF *

А сейчас наступает самый волшебный момент! Возьмите address.war и положите его в каталог webapps Tomcat'а. Подождите несколько секунд. Увидев новое приложение, Tomcat развернет его (появляется каталог с именем вашего war'а) и подключит к системе. После этого можно просто зайти в браузер и набрать:

http://localhost:8080/address/

Вуаля, получите ваше приложение.

И что теперь?

А теперь можно менять JSP-файлы «на лету» в распакованном каталоге webapps/address/jsps. При этом будет автоматически происходить несколько действий, в результате которых файлы подхватятся приложением. Так меняется дизайн без перекомпиляции, без рестарта серверного приложения, как это у нас было до сих пор.

Я считаю, что на данном этапе приложение «Адресная книга» работает хорошо. Оно выполняет свои несложные функции и умеет изменяться «на лету» по запросу пользователя. Оно простое – и это чуть ли не самое главное. Но есть еще достаточно аспектов, о которых стоит знать при разработке более сложных интернет-приложений. Мы рассмотрим их в следующих статьях данной серии. LXF

Личные инструменты
  • Купить электронную версию
  • Подписаться на бумажную версию