вівторок, 10 червня 2008 р.

Сервлеты действия

Введение

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

Это быстро стало неудобным, поэтому программисты начали включать в свои сервлеты условную логику, которая делала их более адаптируемыми, обрабатывающими несколько типов запросов. Со временем это тоже стало приводить к плохому коду. Существует лучший способ, называемый сервлетом действия (action servlet), который реализует концепцию под названием Model 2. Насколько я знаю, эта идея впервые была описана Дэвидом М. Гиари (David M. Geary) (см. раздел "Ресурсы"), а эффективно использована в популярных библиотеках сервлетов, например в проекте Jakarta Struts.

Сервлет действия не имеет условной логики, выбирающей поведение сервлета. Вместо этого у вас имеются действия (определенные программистом классы), которым сервлет передает полномочия для обработки запросов различного типа. В большинстве случаев это намного лучший объектно-ориентированный подход, чем использование нескольких сервлетов или нескольких условий if в одном сервлете.

Что делает наш пример сервлета действия

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


Настройка проекта

Создайте новый Tomcat-проект в Eclipse точно также, как создавали для HelloWorld. Обратите внимание на то, что название вашего проекта является контекстным значением для сервлета по умолчанию, поэтому вы будете использовать его при вводе URL для обращения к сервлету. Если вы настроили Tomcat на использование контекстных файлов, он должен автоматически создать их для этого проекта.

Eclipse также должен создать проект, имеющий корректную структуру, со следующими важными каталогами:

  • WEB-INF
  • WEB-INF/src
  • work

В первом каталоге (WEB-INF) хранятся важные конфигурационные файлы, в частности, файл web.xml, который мы рассмотрим позже. В нем также хранится наш откомпилированный код в каталоге classes. Во втором каталоге (WEB-INF/src) хранится исходный код наших Java-классов. В третьем каталоге (work) хранится откомпилированный код наших файлов JavaServer Pages (JSP), который Tomcat создает для нас автоматически каждый раз, когда мы вызываем JSP-страницу первый раз после изменения кода (мы рассмотрим JSP-технологию более подробно в следующем разделе). Корневой каталог проекта содержит все наши исходные JSP-файлы, а также наш файл базы данных.

Обратите внимание на то, что всю эту структуру можно увидеть в перспективе Resource в Eclipse, но в перспективе Java Browsing вы увидите только каталоги WEB-INF/src и work.

Все эти файлы содержатся в архиве contacts.jar, поставляемом с данным руководством (ссылка приведена разделе "Ресурсы"). Для их импортирования просто создайте новый Tomcat-проект, затем импортируйте contacts.jar (используя Import>Zip file). При этом все файлы разместятся в нужном месте, за исключением исходного кода. Исходный код помещается в подкаталог src корневого каталога проекта. Переместите содержимое этого каталога в WEB-INF/src, и настройка будет завершена.

Представление

В конце концов, это руководство посвящено сервлетам, которые почти не имеют ничего общего с представлением. Но без просмотра каких-либо результатов на экране мы рассказали бы вам только часть истории. Конечно же, вы можете писать сервлеты, которые совсем не участвуют в представлении, но большинство Web-приложений отображают информацию в браузере. Это означает, что вы должны выбрать используемый механизм представления. Технология Java Server Pages является одной из обычных альтернатив и широко применяется.

Используя технологию JSP, вы можете создавать динамические Web-страницы. Они поддерживают статический HTML (или другой язык разметки, например XML) и динамические элементы, которые, исходя из их названия, могут создавать содержимое динамически. JSP-страницы компилируются в сервлеты (то есть, в Java-код) контейнером Tomcat. Однако вы почти никогда не будете об этом беспокоиться. Просто знайте, что происходит следующее:

  • Пользователь вводит URL, который J2EE-контейнер сервлетов направляет в сервлет.
  • Сервлет делает свою работу и помещает информацию в сессию, или в компонент, и направляет его в JSP-страницу.
  • JSP-код преобразовывает информацию в компоненте и/или сессии, и передает ответ в браузер.

Вы легко можете создавать простые JSP-страницы и запускать их в Tomcat лишь с небольшими изменениями в конфигурации нашего Web-приложения и без загрузки дополнительных библиотек кода, поэтому мы будем их использовать (ссылки на дополнительную информацию по JSP-технологии приведены в разделе "Ресурсы").

Наше приложение Contacts будет иметь одну главную JSP-страницу со списком существующих контактов и формой для добавления новых. Позже мы добавим страницы для регистрации и выхода из приложения.

Важно помнить, что JSP-технология является только одним из вариантов представления. Существуют и другие. Одним из вариантов, становящихся очень популярными, является пакет шаблонов Jakarta Velocity (см. "Ресурсы"). JSP-технология имеет один главный недостаток, который заключается в том, что если вы хотите отделить вашу логику от представления, то сложные, функциональные приложения потребуют очень сложных JSP-страниц и некоторой дополнительной работы сервера для создания пользовательских тегов. Другим недостатком JSP-технологии является то, что она часто вызывает непреодолимое желание смешать бизнес-логику и представление, что приводит к созданию хрупких систем, которые очень трудно обслуживать.

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

Файл web.xml

Для того чтобы мы могли использовать JSP-страницу, которую собираемся создать, необходимо указать Tomcat, как ее обрабатывать. Для этого мы должны создать файл web.xml в нашем каталоге WEB-INF. Он должен выглядеть примерно так:


<!DOCTYPE web-app PUBLIC '-//Sun Microsystems, Inc.//DTD
Web Application 2.3//EN' 'http://java.sun.com/dtd/web-app_2_3.dtd'>
<web-app>
<servlet>

<servlet-name>contacts</servlet-name>
<servlet-class>
com.roywmiller.contacts.model2.ContactsServlet</servlet-class>
</servlet>

<servlet-mapping>

<servlet-name>contacts</servlet-name>
<url-pattern>/index.htm</url-pattern>
</servlet-mapping>

<servlet-mapping>
<servlet-name>contacts</servlet-name>

<url-pattern>*.perform</url-pattern>
</servlet-mapping>

<servlet>
<servlet-name>jspAssign</servlet-name>

<servlet-class>
org.apache.jasper.servlet.JspServlet</servlet-class>
<init-param>
<param-name>logVerbosityLevel</param-name>
<param-value>WARNING</param-value>

</init-param>
<init-param>
<param-name>fork</param-name>
<param-value>false</param-value>
</init-param>

<load-on-startup>3</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>jspAssign</servlet-name>
<url-pattern>/*.jsp</url-pattern>

</servlet-mapping>
</web-app>
Мы создали основной файл web.xml для нашего HelloWorldServlet, но он является минимальным. По мере усложнения приложения файл web.xml становится более хитроумным. Давайте кратко его проанализируем.

Тег указывает имя псевдонима для нашего сервлета, который мы будем использовать в файле. Он также указывает Tomcat, экземпляр какого класса создавать при создании сервлета в оперативной памяти. В моем рабочем пространстве Eclipse я создал пакет com.roywmiller.contacts.model2 для хранения класса сервлета. Вы можете вызвать любой пакет по желанию, но путь к вашему сервлету должен соответствовать элементу . Второй определяемый нами сервлет поставляется с Tomcat, и нам не нужно его менять. Это просто сервлет JSP-обработки.

указывает Tomcat, какой сервлет выполнять при поступлении на сервер определенных URL. У нас имеется три отображения. Первое отображает страницу по умолчанию, которую Web-сервер ищет () для нашего сервлета. Второе отображение указывает Tomcat отображать любой URL, заканчивающийся .perform, в наш сервлет. Адреса URL этой формы будут указывать нашему сервлету, какое действие реализовать (позже мы рассмотрим, как это работает, более подробно). Третье отображение указывает Tomcat использовать JSP-сервлет для обработки JSP-страниц.

Кодирование JSP-страницы

Вот код нашей JSP-страницы:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<%@ page import="java.util.*" %>
<%@ page import="com.roywmiller.contacts.model.*" %>
<html>

<head>
<title>Contacts List 1.0</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">

<style type="text/css">
body, table, hr {
color: black;
background: silver;
font-family: Verdana, sans-serif;
font-size: x-small;
}
</style>

</head>

<body>
<jsp:useBean id="contacts" scope="session"
class="com.roywmiller.contacts.model.ContactList"/>

<h2>Contact List 1.0</h2>
<hr size="2"/>
<table frame="below" width="100%">

<tr>
<th align="left"></th>
<th align="left">Name</th>
<th align="left">Street</th>

<th align="left">City</th>
<th align="left">State</th>
<th align="left">Zip</th>
<th align="left">Type</th>

</tr>
<%
List list = contacts.getContacts();
for (Iterator i = list.iterator(); i.hasNext();) {
Contact contact = (Contact)i.next();
%>
<tr>
<td width="100"><
a href="removeContactAction.perform?id=<%= contact.getId()%>"
>Delete</a></td>
<td width="200"><%
=contact.getFirstname()%> <%=contact.getLastname()%></td>

<td width="150"><%=contact.getStreet()%></td>
<td width="100"><%=contact.getCity()%></td>
<td width="100"><%=contact.getState()%></td>
<td width="100"><%=contact.getZip()%></td>

<td width="100"><%=contact.getType()%></td>
</tr>
<%
}
%>
</table>
<br/>
<br/>

<br/>
<fieldset>
<legend><b>Add Contact</b></legend>
<form method="post" action="addContactAction.perform">
<table>

<tr>
<td>First Name:<td>
<td><input type="text" size="30"
name="firstname"></td>
</tr>
<tr>

<td>Last Name:<td>
<td><input type="text" size="30"
name="lastname"></td>
</tr>
<tr>
<td>Street:<td>

<td><input type="text" size="30"
name="street"></td>
</tr>
<tr>
<td>City:<td>
<td><input type="text" size="30"
name="city"></td>

</tr>
<tr>
<td>State:<td>
<td><input type="text" size="30"
name="state"></td>
</tr>

<tr>
<td>Zip:<td>
<td><input type="text" size="30"
name="zip"></td>
</tr>
<tr>

<td>Type:<td>
<td><input type="radio" size="30"
name="type" value="family">
Family <input type="radio" size="30"
name="type" value="acquaintance"
checked> Acquaintance</td>
</tr>

</table>
<br/>
<input type="submit" name="addContact" value=" Add ">
</form>
</fieldset>

</body>

</html>

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

Анатомия простой JSP-страницы

В данной JSP-странице HTML - это HTML. Java-код, встроенный в таблицу, выглядит следующим образом:

<% Java code %>
Для того чтобы встроить Java-код в страницу, вы должны указать, где находятся классы, также как делаете это в Java-коде. Например:

<%@ page import="java.util.*" %>
Наша страница отображает список контактов, передаваемый из экземпляра ContactList, о котором JSP-страница знает благодаря следующей строке:


<jsp:useBean id="contacts" scope="session" class="com.roywmiller.contacts.model.ContactList"/>
Эта строка указывает JSP-странице использовать компонент, называемый contacts. Это экземпляр com.roywmiller.contacts.model.ContactList, имеющий область видимости для сессии.

Обратите внимание на то, что мы используем Java-цикл for в теле страницы:


List list = contacts.getContacts();
for (Iterator i = list.iterator(); i.hasNext();) {
Contact contact = (Contact)i.next();
%>
<tr>
<td width="100">
<a href="removeContactAction.perform?id=<%=
contact.getId()%>" >Delete</a></td>
<td width="200"><%=contact.getFirstname()%> <%=
contact.getLastname()%></td>

<td width="150"><%=contact.getStreet()%></td>
<td width="100"><%=contact.getCity()%></td>
<td width="100"><%=contact.getState()%></td>
<td width="100"><%=contact.getZip()%></td>

<td width="100"><%=contact.getType()%></td>
</tr>
<%
}

Это демонстрирует, как JSP-технология позволяет смешивать HTML и Java-код. Здесь мы выполняем цикл по списку контактов нашего объекта contact. В каждом цикле мы добавляем элемент к нашей HTML-таблице. В каждой строке таблицы мы вызываем getter-метод экземпляра Contact для заполнения ячеек таблицы. В первой ячейке мы должны создать ссылку Delete для каждой строки. Атрибут href мы устанавливаем в следующую строку:

removeContactAction.perform?id=<%= contact.getId()%>
Когда пользователь нажимает эту ссылку, данная строка добавляется в конец URL (после прямого слеша (/)), который посылается на сервер. Знак вопроса является разделителем для параметров запроса, которые следуют парами name=value. В данном случае мы передаем ID контакта.

То же самое происходит по всей странице, например, в форме для добавления новых контактов. Обратите внимание на тег <form>:


<form method="post" action="addContactAction.perform">
Когда пользователь нажимает кнопку Add (кнопка подтверждения внизу страницы), к URL добавляется addContactAction.perform.

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

Введение в технологию Java Servlet

Что делает сервлет?

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

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

Альтернативы сервлетам

Сервлеты не являются единственным методом работы с Web-страницами. Одной из самых ранних технологий для этой цели являлся CGI (common gateway interface), но он инициализировал отдельный процесс для каждого запроса, что не было очень эффективно. Существовали также патентованные серверные расширения, например Netscape Server API (NSAPI), но они были, да-да, патентованными. В мире Microsoft существует стандарт ASP (active server pages). Сервлеты являются альтернативой всем этим технологиям и предлагают несколько преимуществ:

  • Они также платформо-независимы, как язык Java.

  • Они предоставляют полный доступ ко всем API языка Java, включая библиотеки для доступа к данным (например, JDBC).

  • Они (в большинстве случаев) в своей основе более эффективны, чем CGI, поскольку сервлеты порождают новые субпроцессы (thread) для запросов, а не отдельные процессы.

  • Существует распространенная отраслевая поддержка сервлетов, включая контейнеры для наиболее популярных Web-серверов и серверов приложений.

Сервлеты являются мощным дополнением к инструментальным средствам профессионального программиста.

Но что такое сервлет?

Большинство Java-сервлетов, с которыми вы столкнетесь как профессиональный программист, предназначены для ответов на HTTP-запросы в контексте Web-приложения. Следовательно, вы должны знать HTTP-классы из пакетов javax.servlet и javax.servlet.http.

При создании Java-сервлета обычно создается подкласс HttpServlet. Этот класс имеет методы, предоставляющие доступ к конвертам запроса и ответа для обработки запросов и создания ответов.

HTTP-протокол, естественно, не связан с Java. Это просто спецификация, определяющая, какими должны быть запросы и ответы. Классы Java-сервлетов заключают эти низко-уровневые конструкции в Java-классы, имеющие удобные методы, которые облегчают работу с этими конструкциями в контексте языка Java. Когда пользователь инициирует запрос через URL, классы Java-сервлетов преобразуют их в HttpServletRequest и передают адресату, указанному в URL, как определено в конфигурационных файлах конкретного контейнера сервлетов, который вы используете. Когда сервер завершает свою работу с запросом, Java Runtime Environment упаковывает результаты в HttpServletResponse и затем передает HTTP-ответ обратно клиенту, пославшему запрос. Взаимодействуя с Web-приложением, вы обычно посылаете несколько запросов и получаете несколько ответов. Все они работают в контексте сессии, которая в языке Java заключается в объект HttpSession. Вы можете обратиться к этому объекту при обработке запросов и добавить что-нибудь к нему при создании ответов. Он обеспечивает некоторого рода контекст кросс-запросов.

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

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

  • Пользователь вводит URL в браузере. Конфигурационный файл вашего Web-сервера указывает, что этот URL предназначен для сервлета, управляемого контейнером сервлетов на вашем сервере.

  • Если экземпляр сервлета еще не был создан (существует только один экземпляр сервлета для приложения), контейнер загружает класс и создает экземпляр объекта.

  • Контейнер вызывает метод init() сервлета.

  • Контейнер вызывает метод service() сервлета и передает HttpServletRequest и HttpServletResponse.

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

  • При необходимости, когда сервлет выполнил полезную работу, контейнер вызывает метод destroy() сервлета для его финализации.

Как "запустить" сервлет?

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

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

Объявление класса

Сервлет - это класс, поэтому давайте создадим его. В Eclipse создайте класс с именем HelloWorldServlet в вашем проекте HelloWorld. Он должен выглядеть примерно так:

public class HelloWorldServlet extends HttpServlet {

public void service(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
PrintWriter writer = response.getWriter();
writer.println("Hello, World!");
writer.close();
}
}

Введите этот код, затем нажмите Ctrl+Shift+O для ввода ваших выражений import. Eclipse должен выбрать импорт следующих классов:

  • java.io.IOException
  • java.io.PrintWriter
  • javax.servlet.ServletException
  • javax.servlet.HttpServlet
  • javax.servlet.HttpServletRequest
  • javax.servlet.HttpServletResponse

Обратите внимание на то, что мы создали подкласс HttpServlet и переопределили метод service(). Метод service() является самым главным методом обработки, который механизм сервлетов будет вызывать в цикле жизни нашего сервлета. Он принимает конверт запроса и конверт ответа, к которым мы можем обратиться в нашем методе. Однако в данном случае нам не нужно этого делать, поскольку мы делаем только самое основное для того, чтобы сервлет заработал. Мы могли бы переопределить метод doGet(), но service() дает все, что нам надо.

В нашем методе service() мы вызвали getWriter() нашего конверта ответа, для того чтобы разрешить вывод строкового литерала в поток. Затем мы закрыли поток. Это типично для сервлетов, выполняющих вывод информации: вы выполняете необходимую логику, а затем пишете результаты в выходной поток.

Настройка web-приложения

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

Нажмите правой кнопкой мыши на проекте HelloWorld и выберите Properties. Выберите категорию Tomcat для свойств. Вы должны увидеть контекст проекта, который выглядит примерно так:

/HelloWorld

Теперь посмотрите на файловую систему в вашем домашнем каталоге Tomcat. Прокрутите список вниз до каталога conf/Catalina/localhost. В нем вы должны увидеть набор XML-файлов. В частности, вы должны увидеть файл HelloWorld.xml. Откройте его. Этот файл определяет контекст Web-приложения для Tomcat.



<Context path="/HelloWorld" reloadable="true"
docBase="path to your project\HelloWorld"
workDir="path to your project\HelloWorld\work" />

Во время запуска Tomcat читает эти файлы контекста, для того чтобы проинформировать контейнер сервлетов, где искать ваши классы (которые содержат ваши сервлеты). Если вы снова посмотрите на выражения INFO, которые отображает Tomcat в консоли при загрузке, то увидите информацию о контексте вашего Web-приложения в списке.

Последним шагом для настройки вашего Web-приложения в Tomcat является создание файла web.xml, который должен размещаться в каталоге WEB-INF вашего проекта. Примечание: Не помещайте его в каталог WEB-INF/src - он служит для других целей. Вот как примерно должен выглядеть файл для данного простого примера:




<!DOCTYPE web-app PUBLIC '-//Sun Microsystems, Inc.//DTD
Web Application 2.3//EN' 'http://java.sun.com/dtd/web-app_2_3.dtd'>
<web-app>
<servlet>
<servlet-name>hello</servlet-name>
<servlet-class>HelloWorldServlet</servlet-class>

</servlet>

<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>

</web-app>


Этот файл определяет ваше Web-приложение для Tomcat. Элемент servlet-name называет ваш сервлет для использования в этом файле. Элемент servlet-class отображает это имя в конкретный класс, определяющий сервлет - HelloWorldServlet в нашем примере. Элемент servlet-mapping указывает Tomcat, что URL (в нашем случае) /hello отображается в наш сервлет, определенный отображенным классом сервлета.

Если у нас имеется этот файл в нужном месте, то мы можем активизировать Tomcat и увидеть загрузку нашего сервлета.

Выполнение сервлета

Как я уже упоминал, "выполнение сервлета" заключается в запуске Tomcat и указании в Web-браузере URL для его активизации. Запустите Tomcat , используя соответствующую кнопку на панели инструментов (если Tomcat уже работает, необходимо остановить и запустить его повторно). После завершения загрузки Tomcat запустите Web-браузер и введите следующий URL:

http://localhost:8080/HelloWorld/hello
Вы должны увидеть приветствие в вашем браузере.