[an error occurred while processing this directive] IT • archiv :: Print

IT • archiv


[an error occurred while processing this directive]

[an error occurred while processing this directive]

Эффективная обработка JSP с помощью JavaBeans

[an error occurred while processing this directive](none) [an error occurred while processing this directive](none)[an error occurred while processing this directive] ::
[an error occurred while processing this directive](none)
[an error occurred while processing this directive]([an error occurred while processing this directive] Милан Адамовик [an error occurred while processing this directive])

[an error occurred while processing this directive](none)

Переносим логику обработки из JSP в JavaBeans с помощью Шаблонного Метода паттерна проектирования (Template Method design pattern).

JSP Framework
PDF versionPDF версия
Обзор
Концепция II-ой модели Java Server Page хорошо известна всем разработчикам JSP. Основная идея в том, что представление (HTML) должно быть отделено от обработки. Эта статья предлагает эффективную схему многократного использования для перемещения динамического содержания, функций обработки и контроля из Java Server Page в соответствующий JavaBean. Предлагаемая конструкция приложения, используя Шаблонный метод паттерна проектирования, улучшает структуру JSP и дает возможность многократного использования общих для всего Web приложения функций JSP. Вдобавок, это простой механизм взаимосвязи между bean-ами внутри одной HTTP сессии. (В оригинальной версии на английском языке 2,500 слов)

Технология Java Server Pages (JSP) дает много возможностей для легкой и быстрой разработки Web приложений. Однако, если вы используете эти возможности бессистемно, ваш JSP код может быстро стать мешаниной HTML тэгов, JSP тэгов, и кода Java, трудным для понимания, отладки, и поддержки.

Идея заключается в том, чтобы код JSP как можно больше был похож на HTML, благодаря перемещению процесса обработки в JavaBeans. Выгода такого подхода в том, что программист HTML и дизайнер графики могут вести разработку представления, используя любой HTML редактор, пока вы, программист на Java , будете программировать логику приложения. Кроме того, этот подход облегчает вам создание Web приложения, меняющего свой внешний вид и логику в зависимости от ситуации.

Конструкция приложения, описываемая в статье, использует паттерн Шаблонный Метод (Template Method pattern) для усиления общей структуры всего приложения и реализации стандартных действий, характерных для любого JSP. Эти действия включают в себя (но не ограничиваются этим), управление состоянием страницы, обычную обработку страницы, ошибок и механизм совместного использования информации между страницами. Все это определяется единожды, а вам остается иметь дело только с тонкостями конкретной страницы.

Я описываю простое приложение для голосования, как пример использования предлагаемой модели. Вы должны владеть основами JSP и знаниями Java. Некоторые знания UML также желательны , хотя и не обязательны.

Обзор статической структуры

Этот раздел описывает основные части предлагаемой модели, и пример приложения для голосования. На Рисунке 1 изображена диаграмма UML предлагаемой конструкции приложения:

Диаграмма классов в UML.
Рисунок 1. Диаграмма классов в UML.

Центральная часть схемы — два общих включаемых JSP файла, и два класса описанных ниже. Их роль — обеспечивать стандартные действия конкретного JSP.

Цель Web-страницы JSP --исключительно представление. Каждый JSP должен иметь соответствующий JavaBean, выполняющий специфическую для страницы логику. Каждая JSP страница должна статически включать includeheader.jsp и includefooter.jsp. Каждый JavaBean должен быть унаследован от AbstractJSPBean, который содержит шаблонные методы, реализующие общие для всех JSP действия.

Приложение для голосования состоит из следующих JSP и соответствующих им JavaBeans:

Я не буду рассматривать классы, эмулирующие базу данных и бизнес логику — Voter, Candidate, и VoteDB)- в тонкостях, но они необходимы для правильной работы примера.

Итак, пройдя по верхнему уровню, мы переключаемся на рассмотрение JSP-страницы от самого истока.

Пример JSP

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

Листинг 1. login.jsp

<%@ page import = "lbm.jsputil.*" %>
 <jsp:useBean id="_loginJSPBean"
  class="lbm.examples.LoginJSPBean"
 scope="session"/ >
 <jsp:setProperty name="_loginJSPBean" property="*"/>
 <% AbstractJSPBean _abstractJSPBean = _loginJSPBean; %>
 <%@ include file="includeheader.jsp" %>

 <html>
 <head><title>Vote Login</title></head>
 <body bgcolor="white">

 <font size=4>
   Please enter your Voter ID and Password

 </font>

 <font size="3" color="Red">
   <jsp:getProperty name=
  "_loginJSPBean" property="errorMsg"/>

 </font>

 <font size=3>
   <form method=post>
     Voter ID < input type=text name=voterId value=<jsp:getProperty
        name="_loginJSPBean" property="voterId"/>>

     Password <input type=password name=password value= <jsp:getProperty
         name="_loginJSPBean" property="password"/>>

     

     <input type=submit value="Login">
   </form>
 </font>
 </body>
 </html>

 <%@ include file="includefooter.jsp" %>

Структура JSP следующая: Страница начинается с нескольких JSP операторов. Сопровождающий код HTML не имеет JSP директив, операторов, скриптлетов, и т.д. Единственное исключение — директивы <JSP:GETPROPERTY>, типичное использование которых — получение динамического содержания из bean. В конце страница заканчивается с одной директивой include.

Давайте рассмотрим наиболее важные из этих JSP операторов.

<jsp:useBean id="_loginJSPBean" 
 class="lbm.examples.LoginJSPBean" scope="session"/ >
 <jsp:setProperty name="_loginJSPBean" property="*"/ >

Первый оператор устанавливает связь между JSP и соответствующим bean. Второй оператор неявно передает значения всех полей формы, сохраненных как параметры HTTP запроса, в соответствующие атрибуты bean-а, используя методы set bean-а. За дополнительной информацией по этому механизму, обратитесь к статье Govind Seshadri "Создание продвинутых форм с помощью JSP" .

<% AbstractJSPBean _abstractJSPBean = _loginJSPBean; %>
 <%@ include file="includeheader.jsp" %>

Первый оператор делает возможным использование includeheader.jsp включаемого статически во втором операторе. Заметьте , что _loginJSPBean и _abstractJSPBean теперь ссылаются на один и тот же объект, но с различными интерфейсами.

Листинг 2. includeheader.jsp

<%-- Определение SharedSessionBean --%>
 <jsp:useBean id="_sharedSessionBean"
  class="lbm.jsputil.SharedSessionBean"
  scope="session"/>
 <% _abstractJSPBean.setSharedSessionBean(_sharedSessionBean); %>

 <%-- Определение скрытого объекта Servlet --%>
 <% _abstractJSPBean.setRequest(request); %>
 <% _abstractJSPBean.setResponse(response); %>
 <% _abstractJSPBean.setServlet(this); %>

 <%-- Выполнение обработки связанной с JSP --%>
 <% _abstractJSPBean.process(); %>

 <%-- Если getSkipPageOutput равно false, не выводить страницу JSP --% >
 <% if (! _abstractJSPBean.getSkipPageOutput()) { % >

includeheader.jsp — один из ключевых элементов модели. Все JSP используют этот общий элемент.

Два первых оператора в Листинге 2 позволяют JSP bean-ам с различных страниц связываться друг с другом внутри одной и той же сессии. По существу, каждый JSP будет иметь два Java Bean-а, связанных с ним: специфический для приложения JSP JavaBean, например, LoginJSPBean) и общий SharedSessionBean. Таким образом, SharedSessionBean используется как общий элемент, чтобы связать все страницы. Позже будет объяснено как.

Следующие три оператора includeheader.jsp относятся к скрытому Servlet объекту.

<% _abstractJSPBean.setRequest(request); %>
 <% _abstractJSPBean.setResponse(response); %>
 <% _abstractJSPBean.setServlet(this); %>

Спецификация JSP обеспечивает доступ к скрытым объектам, которые являются частью спецификации Java Servlet. Такие объекты как request, response, и servlet очень часто бывают полезны при обработке страниц. Поэтому они передаются в JSP bean.

<% _abstractJSPBean.process(); %>

Оператор приведенный выше, в конечном итоге, запускает обработку JSP страницы. Как видите, при этом вызывается метод абстрактного JSP bean-а, а не конкретного LoginJSPBean. Почему? Объясняется в следующем разделе.

Применение Шаблонного метода паттерна проектирования

AbstractJSPBean главное звено в паттерне проектирования Шаблонного Метода. Каждый конкретный JSP JavaBean должен наследовать этот класс.

Листинг 3. AbstractJSPBean.java

package lbm.jsputil;

 import java.util.*;
 import javax.servlet.http.*;
 import javax.servlet.*;

 public abstract class AbstractJSPBean {

   /* константы используемые для _state */
   public static final int NEW = 0;
   public static final int FIRSTPASS = 1;
   public static final int PROC = 2;
   public static final int ERR = -1;

   private int _state; // текущий state
 // current message that is being appended during validation
   private String _errorMsg;
 //должен ли быть пропущен вывод страницы
   private boolean _skipPageOutput;

 // использован для связи JSP Bean с HTTP Session
   private SharedSessionBean _sharedSessionBean;

   /* стандартные объекты Servlet
      необходимые для каждого JSP Bean */
   protected HttpServletRequest _request;
   protected HttpServletResponse _response;
   protected Servlet _servlet;

   public AbstractJSPBean () {
     setState(NEW);
   }

   protected abstract void beanProcess() 
 	throws java.io.IOException;

   protected abstract void beanFirstPassProcess() 
 	throws java.io.IOException;

   protected abstract void beanFooterProcess() 
 	throws java.io.IOException;

   protected abstract String getJSPCode();

   public void process() throws java.io.IOException {
 // по умолчанию вывод страницы не пропускается.
 // Методы специфического bean процесса
     setSkipPageOutput(false);
 // могут переопределять его.
     if (getState() == NEW) {
       setState(FIRSTPASS);
       beanFirstPassProcess();
     } else {
       resetErrorMsg();
       setState(PROC);
       beanProcess();
     }

      // проверка правильности заполнения полей приложением
      // в действительности это проверка правильности написанного кода
     String l_err = "";
      if (_sharedSessionBean == null) l_err =
 	l_err + "; SharedSessionBean must be set";
      if (_request == null) l_err =
 	l_err + "; Request must be set";
      if (_response == null) l_err =
 	l_err + "; Response must be set";
      if (_servlet == null) l_err =
 	l_err + "; Servlet must be set";
      if (l_err != "")
 	throw new IllegalStateException(l_err);
   }

   public void footerProcess() throws java.io.IOException {
     beanFooterProcess();
   }

   protected void addErrorMsg (String addErrorMsg) {
      if (_errorMsg == null) _errorMsg = addErrorMsg;
      else _errorMsg =
 	_errorMsg + " <br>\n" + addErrorMsg;

     setState(ERR);
   }
   protected void resetErrorMsg () {
     _errorMsg = null;
   }
   public String getErrorMsg () {
    if (_errorMsg == null) return "";
    else return _errorMsg;
   }

   protected void setState (int newState) {
     _state = newState;
   }
   public int getState () {
     return _state;
   }

   public void setSharedSessionBean
 	(SharedSessionBean newSharedSessionBean) {
      if (_sharedSessionBean == null) {
        _sharedSessionBean = newSharedSessionBean;
        _sharedSessionBean.putJSPBean(getJSPCode(), this);
     } else {
        if (_sharedSessionBean != newSharedSessionBean) {
          throw new IllegalStateException(
 	 "SharedSessionBean is not set properly.
 	 SharedSessionBean must be the same
 	 for all PageBeans within the session");
       }
     }
   }
   public SharedSessionBean getSharedSessionBean () {
     return _sharedSessionBean;
   }

   public void setSkipPageOutput (boolean newSipPageOutput) {
     _skipPageOutput = newSipPageOutput;
   }
   public boolean getSkipPageOutput () {
     return _skipPageOutput;
   }

   protected void redirect (String redirectURL)
 	throws java.io.IOException {
     //пропустить вывод страницы после перенаправления
     setSkipPageOutput(true);
     _response.sendRedirect(redirectURL);
   }

   public void setRequest (HttpServletRequest newRequest) {
     _request = newRequest;
   }
   public void setResponse (HttpServletResponse newResponse) {
     _response = newResponse;
   }
   public void setServlet (Servlet newServlet) {
     _servlet = newServlet;
   }
 }

AbstractJSPBean содержит следующие абстрактные методы: beanFirstPassProcess(), beanProcess(), и beanFooterProcess(). Такие методы называются примитивными. Они — заглушки, которые должны реализоваться в конкретном подклассе JSP JavaBean . Каждый выполняется в момент одной из фаз обработки JSP.

Теперь посмотрим на метод process(), представленный ниже:

  public void process() throws java.io.IOException {
 // по умолчанию вывод страницы не
 // пропускается и конкретные bean-ы,
 // могут переопределять его методы
     setSkipPageOutput(false);
      if (getState() == NEW) {
       setState(FIRSTPASS);
       beanFirstPassProcess();
     } else {
       resetErrorMsg();
       setState(PROC);
       beanProcess();
     }
     ....

Метод process() сначала проверяет состояние JSP, затем в зависимости от состояния, вызывает нужный примитивный метод. Он также устанавливает нужное состояние JSP.

Методы process() и footerProcess() называются шаблонными методами. Они вызываются из JSP в includeheader.jsp и includefooter.jsp. Bean-ы конкретного приложения не должны переопределять их. Шаблонные методы содержат скелетный алгоритм. Типичная задача шаблонного метода — выполнить общую обработку и, в определенный момент времени, вызвать примитивные (абстрактные) методы (beanFirstPassProcess(), beanProcess(), и beanFooterProcess()), чьи реализации отличаются для конкретных JSP JavaBeans. Кроме примитивных, шаблоные методы также вызывают и другие методы, реализованные в AbstractJSPBean. Описанные выше принципы представляют суть Шаблонного Метода паттерна проектирования.

Выгоды от этого подхода следующие:

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

Обработка форм, динамическое содержание и связь bean-ов

Листинг 4 показывает реализацию одного из JSP JavaBean, LoginJSPBean, который производит обработку страницы.

Листинг 4. LoginJSPBean

package lbm.examples;

 import lbm.jsputil.*;
 import java.util.*;

 public class LoginJSPBean extends AbstractJSPBean {

    public static final String PAGE_CODE =
     "login";

   private String _voterId;
   private String _password;
   private Voter _voter = null;

   public LoginJSPBean() {
   }

   public void setVoterId (String newVoterId) {
     _voterId = newVoterId;
   }

   public String getVoterId() {
        if (_voterId == null) return "";
        else return _voterId;
   }

   public void setPassword (String newPassword) {
              _password = newPassword;
   }

   public String getPassword() {
        if (_password == null) return "";
        else return _password;
   }

   public Voter getVoter () {
     return _voter;
   }

        protected void beanProcess () throws java.io.IOException {
      if (_voterId == null || _voterId.equals("")) {
              addErrorMsg("Voter must be entered");
     }

      if (_password == null || _password.equals("")) {
              addErrorMsg("Password must be entered");
     }

      if (getState() != ERR) {
        //Если все поля введены, пытаться соединиться с voter
        Voter voter = VoteDB.login(_voterId, _password);
        if (voter == null) {
              addErrorMsg("Unable to authenticate
		 the Voter. Please try again.");
       }
       else {
          _voter = voter;

          if (_voter.getVotedForCandidate() != null) {
            // Если избиратель проголосовал,
            // отправить избирателя на последнюю страницу
            redirect("confirmation.jsp");
         }
          else {
            // идти на страницу Избирателя
            redirect("vote.jsp");
         }
       }
     }
   }

    protected void beanFirstPassProcess()
 	throws java.io.IOException {
    }

    protected void beanFooterProcess()
 	throws java.io.IOException {
    }

    protected String getJSPCode() {
      return PAGE_CODE;
   }
 }

Исследуем набор set и get методов класса LoginJSPBean. Как было замечено, они используются для динамического соответствия, и для передачи значений между полями формы (параметрами запроса) и атрибутами bean.

Метод beanProcess(), показанный выше в Листинге 4 , иллюстрирует основной процесс обработки формы. Этот метод выполняется в течение второго и всех последующих вызовов страницы, перед тем как начался вывод страницы. Это значит, что он будет выполняться только после нажатия пользователем клавиши Login.

Вы сперва проверяете, что обязательные поля voterId и password введены. На любые, возникающие ошибки вызывается метод addErrorMsg. Этот метод устанавливает атрибут errorMsg класса AbstractJSPBean. Атрибут используется JSP для показа ошибок пользователя:

<jsp:getProperty name="_loginJSPBean" property="errorMsg"/>

При проверке успешного ввода данных, метод beanProcess() обращается к базе данных для аутентификации пользователя. В случае успеха, он перенаправляет ответ в соответствующую страницу, вызывая метод redirect(), который реализуется в классе AbstractJSPBean.

Давайте рассмотрим несколько методов класса VoteJSPBean. Они показывают некоторые другие аспекты нашей конструкции приложения, такие как связь между JSP JavaBeans и контролем потока приложения.

Листинг 5. Метод beanFirstPassProcess() класса VoteJSPBean

   protected void beanFirstPassProcess() throws java.io.IOException {
     // получить Voter из страницы Login
     _voter = null;

     LoginJSPBean loginJSPBean =
          (LoginJSPBean) getSharedSessionBean().
 	getJSPBean (LoginJSPBean.PAGE_CODE);

      if (loginJSPBean != null) {
        _voter = loginJSPBean.getVoter();
      }

      if (_voter == null) {
        // избиратель еще не соединился
        // отправить его на страницу Login
        setState(NEW);
        redirect("login.jsp");
      }
    }

Показанный выше метод использует объект _sharedSessionBean класса AbstractJSPBean.Класс SharedSessionBean использует простой метод делающий возможным связь между всеми объектами JSP JavaBean внутри одной сессии HTTP. Он хранит объект Map всех JSP JavaBeans внутри одной сессии. Map — интерфейс Java Collections, который был введен в Java 1.2.Для тех, кто знаком с Java 1.1, он очень похож на Hashtable. Ключ к JSP JavaBean — его PAGE_CODE, который хранится как константа в каждом классе JSP JavaBean.

В этом примере, метод beanFirstPassProcess() сначала ищет объект LoginJSPBean,потом вызывает объект Voter из объекта LoginJSPBean и сохраняет ссылку на него для последующего использования. Если Voter — null, это значит, что пользователь обратился к странице Voter без аутентификации, поэтому он перенаправляется на страницу Login. Это простой пример контроля потока вашего приложения. Вы можете разрабатывать более сложные методы, используя интеллектуальный диспетчер, например. Однако это не входит в рамки этой статьи.

Листинг 6. Метод getCandidateList() класса VoteJSPBean

   public String getCandidateList () {
      StringBuffer candidateList = new StringBuffer();
      Candidate candidate;

      Iterator candidates = VoteDB.getCandidates();
      while (candidates.hasNext()) {
        candidate = (Candidate) candidates.next();

        candidateList.append( "<input type=radio
 		name=\"candidateName\"
 		value=\"");
        candidateList.append(candidate.getName());
        candidateList.append("\"> ");
        candidateList.append(candidate.getName());
        candidateList.append("<br>\n");
      }

      return candidateList.toString();
    }

Метод getCandidateList(), приведенный выше вызывается из vote.jsp следующим образом:

   <jsp:getProperty name="_voteJSPBean" property="candidateList"/>

Фактически, этот метод обеспечивает динамическое содержание HTML, основанное на данных из базы данных. Он без сомнения требует от Java программиста, который разрабатывает JavaBean некоторого знания HTML.

В качестве альтернативы, вы можете иметь отдельные библиотеки HTML утилит, которые могут форматировать HTML. Утилиты могут брать предопределенный тип входных данных, такой как Iterator, и создавать HTML вывод в одном из нескольких предопределенных форматов. Другой выбор — использование библиотек тэгов (Смотрите Ресурсы).

Заключительное замечание: Конструкция приложения

Разделяя представление от логики, эта конструкция приложения позволяет вам изменять представление (JSP) и логику (bean) независимо. Это в действительности означает, что вы можете изменять логику в bean-е без необходимости затрагивать JSP, до тех пор, пока вы храните атрибуты bean (сигнатуры их родительских методов) неизмененными. Тоже самое применимо в обратном направлении. Вы можете взять свой JSP код и дать его разработчикам HTML и графическим дизайнерам для полного изменения внешнего вида сайта без воздействия на код Java.

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

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

Предлагаемая конструкция не предназначена для многозвенных проектов, характерных для Web приложений. Она нацелена главным образом на представление JSP . Для построения истинно трехзвенных или многозвенных систем, JSP JavaBeans должны вызывать Enterprise JavaBeans или какую-либо иную реализацию бизнес логики.

Пример показывает использование этого подхода для приложений, отслеживающих сессии HTTP. Однако он будет работать также хорошо, когда вы не будете иметь дело с сессиями. Поток приложения и страницы будут проектироваться по-разному. Возможно вам не понадобится SharedSessionBean. Возможно, ваши приложения будут состоять из страниц предназначенных только для представления и отдельных страниц, выполняющих обработку и проверку, без какого либо HTML вывода. Тогда вы главным образом будете использовать метод beanFirstPassProcess().

Заключительное замечание: Пример

Пример устанавливался и тестировался под Tomcat 3.1. Он совместим со спецификацией JSP 1.1 и Servlet 2.2. Эта статья не описывает особенности установки JSP приложений на Tomcat (Смотрите Ресурсы).

Посмотрите исходные тексты класса VoteDB для получения Voter ID, которые вы можете использовать для соединения при тестировании примера (пароль такой же как ID).

Для успешного запуска примера, включите cookies в браузере . Если вам необходимо чтобы ваше приложение работало при отключенной поддержке cookies, вам необходимо переписать URL-и (используйте метод encodeURL() класса javax.servlet.http.HttpServletResponse). В этом случае вам надо переписать все URL-и в приложении, включая ссылки на ваши JSP, действия в тэгах форм, и URL-и , которые используются для перенаправления ответов HTTP в JSP JavaBeans.

Заключение

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

Об авторе

Милан Адамовик (Milan Adamovic) — независимый консультант, живет и работает в Vancouver, B.C. Его экспертная оценка включает системный анализ и проектирование (объектно-ориентированный анализ и проектирование, UML, Oracle CASE, проектирование баз данных), серверное программирование на Java (servlets, JSP, EJB), Oracle, другие языки программирования. Особенно его интересуют Internet технологии, архитектура Web приложений, объектно-ориентированный анализ и проектирование, методологии разработки программного обеспечения.

Ресурсы

Reprinted with permission from the January 2001 edition of JavaWorld magazine. Copyright © ITworld.com, Inc., an IDG Communications company.
View the original article at: http://www.javaworld.com/ jw-01-2001/jw-0119-jspframe.html

[an error occurred while processing this directive]
[an error occurred while processing this directive] Перевод на русский © Дмитрий Зацеляпин, 2001
< Вернуться на caйт :: Copyright © 1999 — 2010, IT • archiv.