Архитектура API сервлета основывается на том, что классический провайдер сервиса использует метод service( ), через который все клиентские запросы будут посылаться программным обеспечением контейнера сервлетов, и методы жизненного цикла init( ) и destroy( ), которые вызываются только в то время, когда сервлет загружается и выгружается (это случается редко).

public interface Servlet {
  
public void init(ServletConfig config) throws ServletException;
  
  
public ServletConfig getServletConfig();
  
  
public void service(ServletRequest req, ServletResponse res)
        
throws ServletException, IOException;
  
  
public String getServletInfo();
  
  
public void destroy();
}

Основное назначение getServletConfig( ) состоит в возвращении объекта ServletConfig, который содержит параметры инициализации и запуска для этого сервлета. getServletInfo( ) возвращает строку, содержащую информацию о сервлете, такую, как автор, версия и авторские права.

Класс GenericServlet является оболочечной реализацией этого интерфейса и обычно не используется. Класс HttpServlet является расширением GenericServlet и специально предназначен для обработки HTTP протокола - HttpServelt является одним из тех классов, которые вы будете использовать чаще всего.

Наибольшее удобство атрибутов сервлетного API состоит во внешних объектах, которые вводятся вместе с классом HttpServlet для его поддержки. Если вы взглянене на метод service( ) в интерфейсе Servlet, вы увидите, что он имеет два параметра: ServeltRequest и ServletResponse. Вместе с классом HttpServlet, эти два объекта предназначены для HTTP: HttpServletRequest и HttpServletResponse. Вот простейший пример, который показывает использование HttpServelResponse:

//: c15:servlets:ServletsRule.java
// {Depends: j2ee.jar}
import javax.servlet.*;

import javax.servlet.http.*;

import java.io.*;

public class ServletsRule extends HttpServlet {
  
int i = 0; // "постоянство" сервлета
  
  
public void service(HttpServletRequest req, HttpServletResponse res)
        
throws IOException {
     
res.setContentType("text/html");
      PrintWriter out = res.getWriter
();
      out.print
("<HEAD><TITLE>");
      out.print
("A server-side strategy");
      out.print
("</TITLE></HEAD><BODY>");
      out.print
("<h1>Servlets Rule! " + i++);
      out.print
("</h1></BODY>");
      out.close
();
  
}
}
// /:~

Программа ServletsRule настолько проста, насколько может быть прост сервлет. Сервлет инициализируется только один раз путем вызова его метода init( ), при загрузке сервлета после того, как контейнер сервлетов будет загружен в первый раз. Когда клиент создает запрос к URL, который представлен сервлетом, контейнер сервлетов перехварывает этот запрос и совершает вызов метода service( ) после установки объектов HttpServletRequest и HttpServletResponse.

Основная ответственность метода service( ) состоит во взаимодействии с HTTP запросом, который посылает клиент, и в построении HTTP ответа, основываясь на атрибутах, содержащихся в запросе. ServletsRule манипулирует только объектом ответа, не обращая внимания на то, что посылает клиент.

После установки типа содержимого клиента (которое должно всегда выполнятся прежде, чем будет получен Writer или OutputStream), метод getWriter( ) объекта ответа производит объект PrintWriter, который используется для записи символьных данных ответа (другой вариант: getOutputStream( ) производит OutputStream, используемый для бинарного ответа, который применим для более специализированных решений).

Оставшаяся часть программы просто посылает HTML клиенту (предполагается, что вы понимаете HTML, так что эта часть не объясняется), как последовательность строк. Однако, обратите внимание на включение "счетчика показов", представленного переменной i. Он автоматически конвертируется в строку в инструкции print( ).

Когда вы запустите программу, вы увидите, что значение i сохраняется между запросами к сервлету. Это важное свойство сервлетов: так как только один сервлет определенного класса загружается в контейнер, и он никогда не выгружается (до тех пор, пока контейнер не завершит свою работу, что обычно случается только при перезагрузке серверного компьютера), любые поля сервлета этого класса действительно становятся постоянными объектами. Это значит, что вы можете без усилий сохранять значения между запросами к сервлету, в то время, как в CGI вы должны записывать значения на диск, чтобы сохранить их, что требует большого количества дурацкого окружения для правильного их получения, а в результате получаем не кроссплатформенное решение.

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

Есть другая особенность при использовании HttpServlet. Этот класс обеспечивает методы doGet( ) и doPost( ), которые приспособлены для CGI "GET" пересылки от клиента и CGI "POST". GET и POST отличаются только деталями в способе пересылки данных, которые лично я предпочитаю игнорировать. Однако, большинство доступной информации, которую я видел, поддерживает создание раздельных методов doGet( ) и doPost( ), вместо единого общего метода service( ), который обрабатывает оба случая. Такое предпочтение кажется достаточно общим, но я никогда не видел объяснения, способного заставить меня поверить в то, что это не является наследием от CGI программистов, которые должны были учитывать имеют ли они дело с методом GET или POST. Так что действуя в духе "делать самые простые вещи, которые только могут работать", [5] я просто буду использовать метод service( ) в этом примере, и позволю этому методу заботится о выборе GET vs. POST. Однако, имейте в виду, что я могу что-то упустить, и что фактически может быть отличная причина для использования методов doGet( ) и doPost( ).

Когда бы форма не отсылалась сервлету, HttpServletRequest посупает со всеми предварительно загруженными данными формы, хранящимися как пары ключ-значение. Если вы знаете имя поля, вы можете просто использовать его напрямую в методе getParameter( ), чтобы найти значение. Вы можете также получить Enumiraton (старая форма Итератора) из имен полей, как показано в следующем примере. Этот пример также демонстрирует как один сервлет может быть использован для воспроизведения страницы, содержащей форму, и для страницы ответа (более хорошее решение вы увидите позже, в разделе о JSP). Если Enumiration пустое, значит нет полей; это означает, что форма не была отправлена. В этом случае производится форма, а кнопка подтверждения будет повторно вызывать тот же самый сервлет. Однако если поля существуют, они будут отображены.

//: c15:servlets:EchoForm.java
// Группа пар имя-значение любой HTML формы
// {Depends: j2ee.jar}
import javax.servlet.*;

import javax.servlet.http.*;

import java.io.*;

import java.util.*;

public class EchoForm extends HttpServlet {
  
public void service(HttpServletRequest req, HttpServletResponse res)
        
throws IOException {
     
res.setContentType("text/html");
      PrintWriter out = res.getWriter
();
      Enumeration flds = req.getParameterNames
();
     
if (!flds.hasMoreElements()) {
        
// Нет отосланной формы -- создаем:
        
out.print("<html>");
         out.print
("<form method="POST"" + "action="EchoForm">");
        
for (int i = 0; i < 10; i++)
           
out.print("<b>Field" + i + "</b> " + "<input type="text""
                 
+ " size="20" name="Field" + i + "" value="Value"
                 
+ i + ""><br>");
         out.print
("<INPUT TYPE=submit name=submit"
              
+ " Value="Submit"></form></html>");
     
}
     
else {
        
out.print("<h1>Your form contained:</h1>");
        
while (flds.hasMoreElements()) {
           
String field = (String) flds.nextElement();
            String value = req.getParameter
(field);
            out.print
(field + " = " + value + "<br>");
        
}
      }
     
out.close();
  
}
}
// /:~

Вы можете заметить одно препятствие, из-за которого Java не выглядит предназначенной для обработки строк в памяти - форматирование возвращаемой страницы достаточно тягостно из-за символов завершения строки, эскейп-символов и знаков "+", необходимых для построения объектов String. Для огромных HTML страницы становится неразумным помещение кода прямо в Java. Одно из решений состоит в хранении страницы, как отдельного текстового файла, который потом открывается и передается Web серверу. Если вы выполните замену любого вида для содержимого этой страницы, это будет нехорошо, так как Java плохо обрабатывает строки. В этом случае вам, вероятно, лучше использовать более подходящее решение (Python может быть моим выбором; существует версия, которая сама встраивается в Java и называется JPython) для генерации страницы ответа.