Журнал LinuxFormat - перейти на главную

LXF89:Java EE

Материал из Linuxformat
Перейти к: навигация, поиск

Новая серия! Учимся писать серверные приложения на Java

Содержание

Адресная книга

ЧАСТЬ 1: Театр, как известно, начинается с вешалки, а приложения уровня предприятий – с адресной книги: надо же где-то хранить сведения о клиентах. Александр Бабаев готов познакомить вас с азбукой Java EE.


Один из самых распространенных языков программирования для Интернета на настоящий момент – это PHP. Этот язык изначально задумывался для создания домашних страничек, небольших сайтов – и там работает замечательно.

В отличие от PHP, Java (та ее часть, которая помогает разрабатывать серверные приложения) сразу создавалась для крупных приложений уровня предприятия (так называемых Enterprise Applications). Поэтому в Java есть множество механизмов, которые помогают быстро строить очень крупные приложения. Очень крупные – это десятки тысяч транзакций в минуту. Сложных транзакций.

Обо всех возможностях этой технологии не рассказать ни в рамках одной статьи, ни даже в десятке. Но основные блоки JEE (Java Enterprise Edition), которые полезны не только в крупных приложениях, но и в средних и небольших, мы изучим обязательно. А на основании этого опыта можно будет двигаться дальше, читать книги, разрабатывать сложные системы.

Сервлеты

(thumbnail)
Рис. 1. Процесс обработки запроса

Для начала решим, что же такое сервлет (servlet)? Этим термином принято называть серверное приложение, но чем отличается серверное приложение от клиентского? Клиентское ПО формирует запросы и отсылает их серверному, а задача серверной части – обработать запрос и вернуть на него ответ (рис. 1).

На стороне клиента присутствуют только действия, которые выполняются браузером (показ формы на HTML-странице, формирование стандартизованного запроса).

Серверная сторона в Java обычно состоит из контейнера, который содержит один или несколько сервлетов. Контейнер получает запрос, решает, какому сервлету он предназначен, и запускает на выполнение этот сервлет. Формально происходит примерно следующее:

  • Пришедший от клиента запрос анализируется сервлет-контейнером, который создает на его основе специальный объект javax.servlet.ServletRequest.
  • Контейнер решает (как – обычно описывается в специальном конфигурационном файле), какому сервлету предназначен запрос, и передает ему созданный на предыдущем шаге объект javax.servlet.ServletRequest, а также объект javax.servlet.ServletResponse – шаблон ответа, в который сервлет пишет все, что нужно возвратить клиенту.
  • Сервлет обрабатывает запрос, заполняет объект javax.servlet.ServletResponse и после этого завершает работу.
  • Контейнер получает заполненный объект javax.servlet.ServletResponse, после чего преобразует его в стандартизованное сообщение и передает клиенту.

Что нам потребуется?

Мы предполагаем, что читатель знаком с тем, что такое HTML, и пробовал делать «странички на PHP» или чем-то аналогичном. Если словосочетания «GET-запрос», «POST-запрос» являются для вас китайской грамотой – прочтите врезку «Коротко об HTTP» (и запаситесь терпением). Подробнее про протокол HTTP можно прочитать в RFC2616, например тут: http://www.w3.org/Protocols/rfc2616/rfc2616.html.

Для работы нам понадобятся следующие инструменты:

  • JDK 5 (или JDK 6) – если вы выполняли все задания предыдущей серии уроков Java, нужный дистрибутив, скорее всего, уже имеется в вашей системе (вы ведь выполняли задания, не так ли?). JDK можно бесплатно загрузить с сайта http://java.sun.com или установить из репозиториев вашего дистрибутива Linux. После установки JDK необходимо убедиться, что пути к каталогу $JDK/bin прописаны в переменной окружения PATH, а каталоги, в которых располагаются библиотеки классов, перечислены в CLASSPATH.
  • Ваш любимый текстовый редактор.
  • Библиотека Jetty (http://www.mortbay.org/). В принципе, это полноценный web-сервер, который может очень и очень многое. Нам будет полезна его особенность, позволяющая встроить web-сервер в наше приложение.

Все необходимые библиотеки и исходный код примера есть на прилагаемом компакт-диске.

Что мы будем делать?

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

Создадим иерархию каталогов для проекта, например, такую:

  • ~/Programming/AddressBook/.
  • build – каталог для скомпилированных классов.
  • src – исходные тексты.
  • libs – каталог для библиотек.

Заполним каталоги. Для начала возьмите Jetty, распакуйте архив, найдите в нем файлы jetty-6.1.0rc3.jar; jetty-util-6.1.0rc3.jar; servlet-api-2.5.jar и поместите их в каталог libs (можно взять и другую версию, правда, с известной долей осторожности – иногда меняются API, тогда наша программа просто не скомпилируется).

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

  • Главная – на ней будут ссылки помещены на остальные страницы приложения.
  • Страница добавления нового контакта.
  • Страница редактирования контакта.
  • Страница поиска/просмотра контактов.


Классов в нашем приложении будет всего три:

  • AddressBook. Главный класс, который запускается и «висит» в памяти, обрабатывая запросы пользователей. Обычно эту роль выполняет уже упомянутый сервлет-контейнер (роль которого может выполнять, например, Apache Tomcat), но мы избавились от него, использовав Jetty.
  • AddressBookHandler. Класс-обработчик запросов пользователей. Именно этот класс и называется сервлетом. Он обычно подключается к сервлет-контейнеру через специфический конфигурационный файл, но мы избавились и от этого – спасибо, Jetty!
  • Contact. Класс, который содержит поля контакта. Эти поля будут выводиться на странице и редактироваться. Его код очень простой:


Листинг 1. Контакт

import java.io.*;
public class Contact implements Serializable {
    private String _name = "";
    private String _number = "";
    private String _comment = "";
    public String getName() { return _name; }
    public String getNumber() { return _number; }
    public String getComment() { return _comment; }
    public void setData(String aName, String aNumber, String aComment) {
        _name = aName;
        _number = aNumber;
        _comment = aComment;
    }
}


Основной класс/класс запуска приложения (AddressBook)

Этот класс содержит метод main, инициализирует Jetty и подключает сервлет (AddressBookHandler, листинг 2).


Листинг 2. Класс запуска приложения

import org.mortbay.jetty.*;
import org.mortbay.jetty.nio.*;
 
import java.util.*;
import java.io.*;
 
public class AddressBook {
    private void start() throws Exception {
        try {
            _contactsByName = (SortedMap<String, Contact>) new XStream().
                fromXML(new FileReader("contacts.xml"));
        } catch (Exception e) { }
 
        Server _jettyServer = new Server();
        Connector connector = new SelectChannelConnector();
        connector.setPort(8081);
        _jettyServer.setConnectors(new Connector[]{connector});
        _jettyServer.setStopAtShutdown(true);
        Handler handler = new AddressBookHandler(this);
        _jettyServer.setHandler(handler);
        _jettyServer.start();
    }
    public static void main(String[] args) throws Exception {
        new AddressBook().start();
    }
}

Также этот класс содержит саму адресную книгу:


Листинг 3. Адресная книга

private SortedMap<String, Contact> _contactsByName = new TreeMap<String,Contact>();
 
public SortedMap<String, Contact> getContactsByName() {
    return _contactsByName;
}
 
public Contact getContactByNumber(String aNumber) {
    for (Map.Entry<String, Contact> entry : _contactsByName.entrySet()) {
        if (entry.getValue().getNumber().equals(aNumber)) {
            return entry.getValue();
        }
    }
    return null;
}

и методы, которые позволяют добавлять/удалять/редактировать контакты:


Листинг 4. Работа с контактами

public void addContact(String aName, String aNumber, String aComment)
throws IOException {
    Contact contact = new Contact();
    contact.setData(aName, aNumber, aComment);
    _contactsByName.put(aName, contact);
    saveContactsInformation();
}
 
public void removeContactByNumber(String aNumber) throws IOException {
    Contact contact = getContactByNumber(aNumber);
    if (contact != null) {
        _contactsByName.remove(contact.getName());
        saveContactsInformation();
    }
}
 
public void editContact(String aEditNumber, String aName, String aNumber, String aComment)
throws IOException {
    Contact contact = getContactByNumber(aEditNumber);
    if (contact != null) {
        contact.setData(aName, aNumber, aComment);
        saveContactsInformation();
    }
}
 
private void saveContactsInformation() throws IOException {
    new XStream().toXML(_contactsByName, new FileWriter("contacts.xml"));
}


Класс-обработчик (AddressBookHandler)

Обработка событий пользователя концентрируется в одном методе (public void handle(…)) класса AddressBookHandler. Самые главные параметры этого метода – второй aHttpServletRequest и третий aHttpServletResponse. Request – это запрос. В нем хранятся все данные, которые передал нам браузер пользователя. А Response – это объект, в который нужно записать все, что требуется передать пользователю. Например, на любой запрос пользователя можно отвечать страницей с гордым заголовком «адресная книга» (листинг 5):


Листинг 5. Код обработчика

import org.mortbay.jetty.handler.*;
import org.mortbay.jetty.*;
 
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
 
public class AddressBookHandler extends AbstractHandler {
    private AddressBook _addressBook = null;
 
    public AddressBookHandler(AddressBook aAddressBook) {
        _addressBook = aAddressBook;
    }
    public void handle(String aTarget, HttpServletRequest aRequest,
            HttpServletResponse aResponse, int aDispatchMode)
            throws IOException, ServletException {
 
        ((Request) aRequest).setHandled(true);
        aRequest.setCharacterEncoding("utf-8");
        aResponse.setCharacterEncoding("utf-8");
        aResponse.addHeader("Content-type", "text/html; charset=utf-8");
        PrintWriter writer = aResponse.getWriter();
        writer.write("<html><head>" +
            "<title>Адресная книга</title>" +
            "</head><body><h1>Адресная книга</h1>");
    }
}

Откомпилируйте полученный код:

cd ~/Programming/AddressBook/src
javac –encoding utf-8 -cp ../libs/jetty-6.1.0rc3.jar:../libs/jetty-util-6.1.0rc3.
jar:../libs/servlet-api-2.5.jar -d ../build *.java

И запустите (не забыв подключить все требуемые библиотеки):

cd ~/Programming/AddressBook/build
java -cp ../libs/jetty-6.1.0rc3.jar:../libs/jetty-util-6.1.0rc3.jar:../libs/servletapi-
2.5.jar:. AddressBook

Терминал выводит несколько строчек и «зависает». Все правильно, сервер запустился. Запускать наше приложение будем пока именно так, чтобы его можно было легко выключить (Ctrl+C). Теперь откройте браузер и наберите в адресной строке: http://localhost:8081. Вот он, наш заголовок!

Вернемся к обработчику и попробуем уяснить, как различать запросы пользователей. Для этого в обработчике есть две возможности. Самая простая – посмотреть на первый параметр (aTarget), который содержит «файл» запроса (например в URL’е «http://www.linuxformat.ru/index.html» aTarget будет «/index.html»); посложнее – анализировать сам объект запроса. Воспользуемся первым вариантом, дописав в конец метода handle (прямо перед закрывающей скобкой) следующее:


Листинг 6. Более сложный обработчик с разбором адреса

if ("/".equals(aTarget)) {
    writer.write("<a href=\"/add\">Добавить запись</a><br/>");
    writer.write("<a href=\"/view\">Просмотреть записи</a><br/>");
} else {
    writer.write("<a href=\"/\">На главную</a><br/>");
 
    if ("/add".equals(aTarget)) {
        writer.write("<h2>Добавление записи</h2>");
    } else if ("/view".equals(aTarget)) {
        writer.write("<h2>Просмотр записей</h2>");
    } else if ("/edit".equals(aTarget)) {
        writer.write("<h2>Редактирование записи</h2>");
    } else if ("/remove".equals(aTarget)) {
        writer.write("<h2>Удаление записи</h2>");
    }
}
 
writer.write("</body></html>");

Попробуйте снова откомпилировать и запустить приложение, предварительно остановив предыдущую версию сервера. Обновите страницу в браузере – и побегайте по только что созданным страницам нашего сайта (корневая, она же «/», «/add», «/edit», «/remove»).

Теперь в соответствующих ветках if можно написать обработчики страничек. Для упрощения вынесем эти обработчики в отдельные методы (листинг 7):


Листинг 7. Добавляем обработчики отдельных действий

    if ("/add".equals(aTarget)) {
        writer.write("<h2>Добавление записи</h2>");
        handleAdd(aRequest, writer);
    } else if ("/view".equals(aTarget)) {
        writer.write("<h2>Просмотр записей</h2>");
        handleView(writer);
    } else if ("/edit".equals(aTarget)) {
        writer.write("<h2>Редактирование записи</h2>");
        handleEdit(aRequest, writer);
    } else if ("/remove".equals(aTarget)) {
        writer.write("<h2>Удаление записи</h2>");
        handleRemove(aRequest, writer);
    }

Осталось понять, как именно обрабатывать соответствующие страницы. Рассмотрим, для примера, метод handleAdd() – остальные можно написать по аналогии:


Листинг 8. Обработчик добавления записи

    private void handleAdd(HttpServletRequest aRequest, PrintWriter aWriter)
    throws IOException {
        if (aRequest.getParameter("name") != null) {
            _addressBook.addContact(aRequest.getParameter("name"),
                aRequest.getParameter("number"), aRequest.getParameter("comment"));
            outputMessage(aWriter, "Контакт добавлен");
        }
        outputForm(aWriter, aRequest, "", "", "");
    }

В приведенном обработчике есть вызов метода aRequest.getParameter("name"). Этот метод выдает значение параметра, который передает браузер из формы (<input type="text" name="name"/>). Причем сервлету все равно, каким методом (GET/POST) был передан параметр. Конкретно для обработки добавления контакта в коде проверяется, равен ли результат вызова null. Если так – значит, форма «не выполнялась», и добавлять нечего. В противном случае мы считываем три параметра (имя, телефон и комментарий), сохраняем контакт и выводим форму (вдруг пользователь захочет добавить еще один контакт?).

Методы outputForm и outputMessage (листинг 9) выводят форму (либо пустую, либо заполненную данными, которые нужно редактировать) и сообщение (чтобы пользователь понимал, что действие произошло, и как оно завершилось):


Листинг 9. Обработчик добавления записи

private void outputForm(PrintWriter aWriter, HttpServletRequest aRequest,
        String aName, String aNumber, String aComment) {
    aWriter.write(
        "<form action=\"/edit\" method=\"post\">" +
        "<input type=\"hidden\" name=\"edited\" value=\"" +
            aRequest.getParameter("number") + "\"/>" + "<table>" +
        "<tr><td>Имя: </td><td><input type=\"text\" name=\"name\" value=\"" +
            aName + "\"/></td></tr>" +
        "<tr><td>Телефон: </td><td><input type=\"text\" name=\"number\" value=\"" +
            aNumber + "\"/></td></tr>" +
        "<tr><td>Примечания: </td><td><input type=\"text\" name=\"comment\" value=\"" +
            aComment + "\"/></td></tr>" +
        "<tr><td colspan=\"2\" align=\"center\">" +
            "<input type=\"submit\" name=\"Отправить\"/></td></tr>" +
        "</table></form>");
    }
 
    private void outputMessage(PrintWriter aWriter, String aMessage) {
        aWriter.write("<span style=\"color: green;\">" + aMessage + "</span>");
    }

Для того, чтобы проверить работу нового обработчика, закомментируйте вызовы методов, которые еще не написаны (или напишите «заглушки» в виде пустых методов). Теперь можно снова остановить сервер, скомпилировать программу и запустить ее на выполнение. Если все набрано правильно, ошибок не последует, и можно будет просматривать табличку с контактами, добавлять их, редактировать и удалять. Наша адресная книга готова к работе!

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

Персональные инструменты
купить
подписаться
Яндекс.Метрика