(495) 925-0049, ITShop интернет-магазин 229-0436, Учебный Центр 925-0049
  Главная страница Карта сайта Контакты
Поиск
Вход
Регистрация
Рассылки сайта
 
 
 
 
 

Введение в JavaServer Faces 1.2: Часть 1. Создание простых приложений

Ричард Хайтауэр

JSF для начинающих

Подобно Swing и AWT, JSF представляет собой каркас разработки приложений, предоставляющий набор стандартных графических компонентов для создания интерфейсов. Но при этом JSF ориентирована на создание Web-приложений и имеет следующие преимущества:

  • Четкое разделение бизнес-логики и интерфейса
  • Управление сохраняемостью на уровне компонент
  • Простая работа с событиями на стороне сервера
  • Использование простых и знакомых концепций, таких как графический интерфейс или слои в Web-приложении
  • Доступность нескольких реализаций от различных компаний-разработчиков
  • Широкая поддержка со стороны интегрированных средств разработки (IDE)

Как правило, приложение, созданное на основе JSF, состоит из следующих частей:

  • Объектов JavaBean, управляющих состоянием и поведением приложения
  • Компонентов GUI, с возможностью сохранения состояния
  • Классов, реализующих событийную модель, например, слушателей (listeners), аналогичных тем, что используются при традиционной разработке графических интерфейсов
  • Страниц, выступающих в роли представлений в парадигме Модель-Представление-Контроллер (Model-View-Controller - MVC). Подобные страницы могут обращаться к корневым узлам представления (view roots) через дерево компонентов JSF.

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

Компонентная архитектура

 
Компоненты и их состояния

Зачастую наибольшей трудностью при изучении JSF является осознание того, что компоненты обладают сохраняемым состоянием. Если вы когда-либо использовали Struts, повторите за мной: "JSF - это не Struts. JSF - это не Struts". Исходя из моих личных наблюдений, разработчики, имеющие опыт создания графических интерфейсов на Swing, AWT, Visual Basic или Delphi, испытывают меньше проблем при освоении JSF, чем те, которые годами использовали Struts и никогда не сталкивались с разработкой GUI. Данное руководство должно помочь вам привыкнуть к мысли о сохраняемости состояния компонент.

JSF предоставляет описываемые тегами компоненты, соответствующие всем полям ввода в стандартном HTML. Кроме этого вы можете создавать специальные компоненты для нужд вашего приложения, а также комбинировать несколько компонент HTML в один сложный объект , например, элемент Data Picker, состоящий из трех выпадающих меню. Все компоненты могут сохранять свое состояние - это поддерживается на уровне самой технологии JSF. Кроме этого компоненты используются для динамической генерации HTML-страниц. Вдобавок к стандартным JSF-компонентам доступно множество других, предоставляемых сторонними разработчиками.

JSF включает в себя:

  • Модель публикации событий
  • Простой контейнер для зависимых компонентов (inversion-of-control (IoC) container)
  • Компоненты, реализующие практически все стандартные возможности графических интерфейсов, включающие в себя:
    • Встраиваемую генерацию интерфейса
    • Валидацию на стороне сервера
    • Конвертирование данных
    • Управление переходами между страницами

Являясь компонентной архитектурой, JSF легко расширяется и конфигурируется. Большинство функций JSF, например, навигация или управление объектами JavaBean, могут быть реализованы встраиваемыми компонентами. Подобная свобода встраивания компонент обеспечивает значительную гибкость при разработке Web-интерфейсов, а также позволяет легко использовать JSF совместно с другими компонентными технологиями. Например, можно заменить стандартную поддержку IoC в JSF на Spring, предоставляющую полновесное решение для обращения к объектам JavaBean как через IoC, так и с помощью аспектно-ориентированного программирования (AOP).

JSF и JSP

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

 
JSF не привязана к JSP

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

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

Вы это осознаете, как только впервые измените некие атрибуты на JSP-странице, а обновив ее, обнаружите, что ничего не изменилось. Это объясняется тем, что теги на странице обращаются к текущему состоянию компонента. Если компонент был создан ранее, то теги не изменят его состояние. Вместо этого состояние компонент может модифицироваться контроллером (например, он может сделать текстовое поле недоступным для ввода). После этого новое состояние может быть отображено при следующем вызове представления.

Как правило, интерфейсы JSF-приложений создаются вообще без программирования на Java, и лишь слегка задействовав универсальный язык выражений (JSTL EL). Как уже отмечалось выше, существует множество IDE, облегчающих создание и сборку приложений JSF, а также множество сторонних компонент GUI. Но при этом можно работать без всякой поддержки со стороны каких-либо средств WYSIWYF, несмотря на то, что их использование изначально учитывалось при проектировании JSF. Этим мы и займемся в данной статье.

 
Можно обходиться и без отвратительной поддержки WYSIWYG в IDE

Несмотря на то, что JSF изначально проектировалась с учетом будущей поддержки со стороны интегрированных сред WYSIWYG, ее преимущества доступны и без них. Даже если все делать вручную, использовать JSF все равно значительно проще других каркасов Web-приложений на Java. Таким образом, если вы ранее использовали WYSIWYG IDE, например, создавая интерфейсы Swing, то, скорее всего, вы будете продолжать использовать эти средства и с JSF. Если же нет, то и в случае JSF можно обойтись без WYSIWYG IDE. Только чистое программирование!

Новые возможности JSP 2.1 и JSF 1.2

В JSP 2.1 появилось множество новых возможностей, облегчающих поддержку JSF, таких как API универсального языка выражений (Universal Expression Language - EL), также появившегося в JSF 1.2. Теперь, в отличие от JSF 1.1 и JSP 2.0, можно использовать стандартные теги JST для итерирования по спискам и отрисовки компонент JSF. При этом, даже несмотря на все улучшения, использовать Facelets значительно удобнее, и множество идей Facelets позднее мигрировали в JSF 2.0.

JSF и MVC

JSF проектировалась с учетом опыта накопленного в течение нескольких лет эволюции программных средств для разработки Web-приложений на Java. Все начиналось c JSP - технологии, обладающей немалыми достоинствами, но, к сожалению, позволяющей слишком легко смешивать код на Java с HTML-содержимым Web-страниц. Следующим шагом было появление так называемой "Модели 1" (Model 1), поощряющей перенос Java-кода в компоненты JavaBeans, к которым затем можно было обращаться на странице с помощью тега <jsp:useBean>. Этот подход неплохо работал в случае простых Web-приложений, но раздражал многих Java-разработчиков своим сходством с C++, например, использованием статических директив include. В результате была предложена архитектура под названием "Model 2".

Model 2 - это не что иное, как архитектура MVC, приспособленная под нужды Web-приложений. В соответствии с Model 2, контроллер представляет собой сервлеты (или Actions) а отображение страниц реализуется с помощью JSP-страниц. Примером упрощенной реализации Model 2 может служить Apache Struts - библиотека, использующая Actions в качестве сервлетов для контроллера. При этом логика поведения контроллера отделена от данных приложения, размещенных в объектах ActionForms. Struts в основном вызывал недовольство своей скорее процедурной, чем объектно-ориентированной моделью, чем заслужил прозвище "COBOL для Web". Другими примерами реализации Model 2 являются WebWork и Spring MVC, которые хотя и более объектно-ориентированны, но не получили такого широкого распространения, как Struts. При этом никто из них не содержит компонентной модели с сохраняемым состоянием, в отличие от JSF. Кстати, Struts2 был спроектирован на основе WebWork, отбросив таким образом первоначальный код Struts. Другими словами, Struts не нравится даже самим разработчикам Struts.

Но главной проблемой Model 2 является то, что событийная модель носит слишком примитивный характер, являясь по суси сильно урезанной версией MVC. Кроме этого, не поддерживаются сохраняемые компоненты, что перекладывает слишком много работы на плечи разработчиков. Более мощная компонентная и событийная модель значительно облегчила бы взаимодействие на всех уровнях приложения. Кроме того, подобно JSP, большинство реализаций Model 2 эффективно не предотвращают смешивание разметки HTML и специальных тегов для отрисовки интерфейса приложения, которые ведут себя почти как компоненты, за исключением того, что не обладают сохраняемым состоянием. Наконец, некоторые реализации, например классический Struts, делают ошибку, разделяя поведение и состояние приложения, оставляя у многих Java-разработчиков ощущение, будто они программируют на COBOL.

JSF как улучшенная реализация MVC

 
JSF - это не Struts. Забудьте о нем и начните изучение сначала
JSF - это не реализация Model 2, ее возможности значительно шире. Многие думают, что поскольку изначальный автор Struts также возглавлял работу над спецификацией JSF, то предыдущий опыт работы с Struts должен пригодиться в проекте на JSF. Даже не пытайтесь программировать на JSF в стиле Struts. Так или иначе, вам придется забыть часть того, что вы знаете о Struts, чтобы эффективно изучать JSF.
 

JSF предоставляет разработчикам компонентную модель и значительно лучшую поддержку MVC, чем реализация Model 2. Несмотря на то, что JSF спроектирована на основе протокола, не поддерживающего сохранение состояний, она значительно ближе к реальной парадигме MVC, чем архитектура Model 2. Кроме того, JSF помогает создавать более гибкие, лучше ориентированные на обработку событий графические интерфейсы, чем Model 2. В отличие от простой схемы "запрос - обработка", характерной для Model 2, JSF предлагает полноценный набор событий, таких как: выбор пункта меню, нажатие на кнопку, раскрытие узла в дереве и т.д.

Гибко настраиваемая модель событий в JSF позволяет вашему приложению меньше зависеть от деталей HTTP и, как следствие, упрощает процесс разработки. JSF улучшает архитектуру приложений по сравнению с Model 2, помогая освободить классы контроллера и JSP-страницы от кода бизнес логики и представления (кроме того, из контроллера убирается еще и код представления). По правде говоря, классы, реализующие контроллер получаются легковесными и не привязанными к JSF вообще, что позволяет их легко тестировать. В отличие от полноценного MVC, ситуация, когда слой модели в JSF генерирует события, на которые должно отреагировать более чем одно представление, встречаются крайне редко (хотя в Crank пытаются реализовать поддержку нечто подобного, используя поддержку ajax4JSF в JBoss). Как уже отмечалось ранее, поддержка подобных возможностей необязательна, т.к. в случае Web нет поддержки сохранения состояния объектов на уровне протокола взаимодействия, поэтому события, инициирующие обновление представления, практически всегда (или может просто всегда?) представляют собой запрос от пользователя.

Подробнее о реализации MVC в JSF

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

В отличие от JSP, реализация слоя представления в JSF представляет собой компонентную модель, обладающую сохраняемым состоянием. Слой состоит из двух частей : корня представления и страниц JSP. Корень - это набор компонентов, в которых хранится общее состояние интерфейса. JSF, подобно Swing и AWT, управляет деревом компонентов в соответствии с паттерном "Компоновщик" (Composite). При этом контейнер, с помощью которого осуществляется управление содержащимися в нем компонентами, сам является компонентом. JSF свзязывает компоненты интерфейса с страницами JSP, позволяет ассоциировать поля ввода со свойствами объектов JavaBean (или скорее даже свойствами свойств), а кнопки - с обработчиками событий и методами, реализующими то или иное действие.

На рисунке 1 изображена архитектура демонстрационного приложения (того самого, что мы подробно разберем ниже) с точки зрения парадигмы MVC:

Рисунок 1. Демонстрационное приложение, спроектированное в соответствие с принципами MVC
Final sample from MVC perspective

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

Разработка приложения с помощью JSF: шаг за шагом

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

  • Как лучше организовать структуру JSF-приложения для последующего разворачивания на сервере
  • Как описывать конфигурацию приложения в файле web.xml
  • Как использовать конфигурационный файл faces-config.xml
  • Как создавать управляемые объекты JavaBeans - так называемые объекты модели и контроллера
  • Как создавать слой представления, используя JSP
  • Как использовать библиотеки тегов для создания дерева компонентов, начиная от корневого узла слоя представления
  • Как производить валидацию по умолчанию для данных Web-форм

Ниже мы будем последовательно улучшать наше приложение, используя все больше возможностей JSF. На рисунке 2 показано, как примерно будет выглядеть конечный вариант калькулятора. Весь исходный код доступен в разделе для скачивания.

Рисунок 2. Окончательная версия Калькулятора
Final app

Калькулятор

 
Сборка приложения с помощью Maven 2 и Eclipse WTP
По умолчанию приложения будет собираться с помощью Apache Maven 2. При этом мы будем использовать структуру каталогов, являющуюся стандартной для Web-приложений Maven.
 

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

Страница содержит следующие элементы:

  • Форму
  • Два текстовых поля
  • Две метки
  • Два элемента для вывода сообщений об ошибках
  • Две кнопки Submit
  • Панель для вывода результата

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

Процесс создания приложения

В целом, процесс разработки калькулятора будет состоять из следующих шагов:

  • Объявление описание сервлета Faces в дескрипторе Web-приложения (файле web.xml)
  • Описание местонахождения файла faces-config.xml внутри web.xml
  • Создание класса Calculator
  • Объявление Calculator в файле faces-config.xml в качестве объекта JavaBean
  • Создание страницы index.jsp
  • Создание страницы calculator.jsp

Структура каталогов приложения будет выглядеть следующим образом:

+---src
    +---main
        +---java
        +---webapp
            +---pages
            +---WEB-INF
                +---lib

Сам исходный код на Java будет размещен в каталоге src//main/java. Файл web.xml, а также конфигурационный файл JSF будут расположены в src/main/WEB-INF. Данный пример был создан с помощью среды Eclipse IDE для разработчиков Java EE (так называемая Eclipse JEE), которая включает в себя мастер для автоматической генерации файлов web/xml и faces-config.xml с заведомо верным набором элементов. Кроме этого предполагается, что для запуска будет использовать сервер приложений, поддерживающий Java EE 5, т.е. включающий в себя JAR-файлы для JSF и JSTL.

Описание и отображение сервлета Faces

Перед использованием Faces-сервлета необходимо объявить его в файле web.xml, как показано в листинге 1:

Листинге 1. Объявление сервлета Faces в web.xml

                    
<servlet>
  <servlet-name>Faces Servlet</servlet-name>
  <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
  <load-on-startup>1</load-on-startup>
</servlet>

В этом нет ничего нового, за исключением того, что вместо написания собственного сервлета, мы будем использовать стандартный JSF-сервлет для обработки запросов. Сервлет должен вызываться на каждый запрос со страниц JSP, на которых используется тег <f:view> (в частности, мы будем применять его в Калькуляторе). Для этого необходимо добавить отображение сервлета (servlet mapping), указывающее, что через него должны загружаться только JSP-страницы, использующие JSF. Пример показан в листинге 2:

Листинг 2. Отображение сервлета Faces в web.xml

                    
<servlet-mapping>
  <servlet-name>Faces Servlet</servlet-name>
  <url-pattern>*.jsf</url-pattern>
</servlet-mapping>

<servlet-mapping>
  <servlet-name>Faces Servlet</servlet-name>
  <url-pattern>/faces/*</url-pattern>
</servlet-mapping>

Согласно отображению в web.xml, JSF контейнер будет вызывать сервлет Faces для обработки всех запросов, относительный URI которых начинается с /faces/ или заканчивается на *.jsf. Благодаря этому будет корректно инициализироваться контекст Faces и корневой элемент представления перед показом страниц JSF. Корневой элемент содержит дерево компонентов, а контекст Faces служит для взаимодействия приложения с JSF.

Таким образом, URL для загрузки Калькулятора должен быть либо http://localhost:8080/calculator/pages/calculator.jsf либо http://localhost:8080/calculator/faces/pages/calculator.jsp, но ни в коем случае не http://localhost:8080/calculator/pages/calculator.jsp. В последнем случае JSP вызывается вне контекста JSF, а, следовательно, ни контекст Faces, ни корневой элемент представления не могут быть инициализированы.

Конфигурирование Faces с помощью faces-config.xml

По умолчанию сервлет Faces будет пытаться использовать конфигурационный файл faces-config.xml, расположенный в директории WEB-INF Web-приложения. При этом можно использовать дополнительные конфигурационные файлы. Для этого предусмотрен специальный параметр в web.xml под названием javax.faces.application.CONFIG_FILES , в котором можно перечислить имена файлов, разделенные запятыми. Дополнительные файлы используются практически всегда за исключением простейших приложений. Но поскольку наш пример относится к последним, то назовем наш файл faces-config.xml и поместим его в /src/main/webapp/WEB-INF.

Создание класса Calculator

Теперь мы создадим класс Calculator в стиле POJO (plain old Java object). Заметим, что сам класс никоим образом не зависит от JSF, но может быть использован в JSF-приложении с помощью механизма привязки (binding) методов и свойств. Наш класс содержит два свойства: firstNumber и secondNumber.

Поскольку целью примера является всего лишь создание вашего первого JSF-приложения, то можно использовать максимально простую модель. В данном случае она представляет собой его один объект, показанный в листинге 3. Ниже мы разделим ее на два класса: контроллер и непосредственно класс модели.

Листинг 3. POJO-классCalculator

                    
package com.arcmind.jsfquickstart.model;

/**
 * Calculator. Simple POJO.
 *
 * @author Rick Hightower
 */
public class Calculator {

    /** Первый операнд */
    private int firstNumber = 0;

    /** Результат операции */
    private int result = 0;

    /** Второй операнд */
    private int secondNumber = 0;

    /** Сложение операндов */
    public void add() {
    result = firstNumber + secondNumber;
  }

    /** Перемножение операндов */
  public void multiply() {
    result = firstNumber * secondNumber;
  }

  /** Сброс результата */
  public void clear() {
    result = 0;
  }

  /* ---------- свойства ------------- */

  public int getFirstNumber() {
    return firstNumber;
  }

  public void setFirstNumber(int firstNumber) {
    this.firstNumber = firstNumber;
  }

  public int getResult() {
    return result;
  }

  public void setResult(int result) {
    this.result = result;
  }

  public int getSecondNumber() {
    return secondNumber;
  }

  public void setSecondNumber(int secondNumber) {
    this.secondNumber = secondNumber;
  }

}

Код в листинге 3 настолько прост, что не нуждается в пояснениях. Напомним только, что сам по себе класс Calculator может использоваться вне JSF.

Объявление объекта Calculator в файле faces-config.xml

Полностью содержимое файла faces-config.xml приведено в листинге 4. Как видите, немалая часть файла служит лишь для связывания файла с XML схемой Java EE JSF. Для объявления же управляемых объектов JavaBean служит элемент <managed-bean>.

Листинг 4. Файл faces-config.xml, содержащий объявления управляемых объектов JavaBean

                    
<?xml version="1.0" encoding="UTF-8"?>

<faces-config xmlns="http://java.sun.com/xml/ns/javaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
     http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd"
     version="1.2">
  <managed-bean>
    <managed-bean-name>calculator</managed-bean-name>
    <managed-bean-class>com.arcmind.jsfquickstart.model.Calculator</managed-bean-class>
    <managed-bean-scope>request</managed-bean-scope>
  </managed-bean>

</faces-config>

Объявление управляемого объекта состоит из двух частей: имени объекта - calculator -, задаваемого с помощью элемента <managed-bean-name>, и полного имени класса (элемент <managed-bean-class>). При этом класс управляемого объекта обязан содержать конструктор без параметров.

Кроме вышеперечисленных элементов существует еще один - <managed-bean-scope>, который определяет, где JSF будет искать объект. В данном случае это request. Если объект привязан к представлению (как будет показано ниже) и еще не существует на момент обращения, то JSF создаст его автоматически с помощью API универсального языка выражений EL. Все объекты, помещаемые в request, доступны только в течение обработки одного запроса. Как правило, туда имеет смысл помещать объекты, чье состояние не представляет интереса после окончательного отображения страницы в конце обработки запроса.

Создание страницы index.jsp

Страница index.jsp необходима, чтобы гарантировать, что calculator.jsp будет загружена в контексте JSF и благополучно найдет корневой элемент представления. Код index.jsp приведен в листинге 5:

Листинг 5. Содержимое страницы index.jsp, перенаправляющая на calculator.jsp

                    
<jsp:forward page="/faces/calculator.jsp" />

Как видите, все, что делает эта страница - это перенаправление к calculator.jsp, находящейся в каталоге faces, т.е. внутри контекста JSF. Благодаря этому, calculator.jsp получает доступ к корневому элементу представления.

Создание страницы calculator.jsp

Данная страница занимает центральное место в слое представления Калькулятора. Страница содержит элементы для ввода двух чисел, как показано на рисунке 3:

Рисунок 3. Первая версия калькулятора, запущенная из Eclipse JEE/WTP
First Calculator application

Полностью код страницы приведен в листинге 6:

Листинг 6. /src/main/webapp/calculator.jsp

                    
<?xml version="1.0" encoding="ISO-8859-1" ?>
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Calculator Application</title>
</head>
<body>
<f:view>
  <h:form id="calcForm">
    <h4>Calculator</h4>
    <table>
      <tr>
        <td><h:outputLabel value="First Number" for="firstNumber" /></td>
        <td><h:inputText id="firstNumber"
          value="#{calculator.firstNumber}" required="true" /></td>
        <td><h:message for="firstNumber" /></td>
      </tr>

      <tr>
        <td><h:outputLabel value="Second Number" for="secondNumber" />
        </td>
        <td><h:inputText id="secondNumber"
          value="#{calculator.secondNumber}" required="true" /></td>
        <td><h:message for="secondNumber" /></td>
      </tr>
    </table>
    <div>

      <h:commandButton action="#{calculator.add}"  value="Add" />
      <h:commandButton action="#{calculator.multiply}"  value="Multiply" />
      <h:commandButton action="#{calculator.clear}"  value="Clear" immediate="true"/>
    </div>

  </h:form>

  <h:panelGroup rendered="#{calculator.result != 0}">
    <h4>Results</h4>
    <table>
      <tr><td>
        First Number  ${calculator.firstNumber}
      </td></tr>
      <tr><td>
        Second Number ${calculator.secondNumber}
      </td></tr>
      <tr><td>
        Result ${calculator.result}
      </td></tr>
    </table>
  </h:panelGroup>
</f:view>

</body>
</html>

Большая часть файла представляет собой обычный HTML, а точнее XHTML. При этом можно вставлять HTML-содержимое внутрь тегов <f:view>, <h:form> и <h:panelGroup>. То, что якобы невозможно смешивать теги HTML и JSF, является распространенным заблуждением. Как правило, это можно делать, за исключением компонента <h:panelGrid>, который может содержать только другие компоненты в качестве дочерних элементов.

Далее мы продемонстрируем процесс создания этой страницы шаг за шагом, потому как она достаточно сложна.

Объявление библиотек тегов

Написание страницы начинается с объявления библиотек тегов для JSF, как показано в листинге 7:

Листинг 7: Импортирование библиотек тегов в calculator.jsp

                    
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>

Код в листинге 7 содержит директивы для JSP компилятора, объявляющие, что будут использоваться две библиотеки тегов JSF: html и core. Библиотека html включает в себя теги для работы с формами и другими элементами HTML. Все теги, реализующие логику, валидацию данных, контроллер и т.д., включены в библиотеку core.

Тег <f:view>

После того, как готова разметка страницы в виде HTML, необходимо явно указать, что для работы с элементами интерфейса будет использоваться JSF. Для этого используется тег <f:view>, информирующий JSP-контейнер о том, что компоненты будут управляться JSF.

Без этого JSF не сможет построить дерево компонентов или получить доступ к ранее созданному дереву. Пример использования <f:view> приведет в листинге 8:

Листинг 8. Тег <f:view> в calculator.jsp

                    
<f:view>
  <h:form id="calcForm">
     ...
  </h:form>
</f:view>

Объявление <f:view> делается в первой строчке листинга, после чего контейнер знает, что управление осуществляется с помощью JSF.

Дочерние элементы <f:view>: тег <h:form>

Во второй строчке листинга 8 используется тег <h:form>, указывающий JSF, что в этом месте необходима форма HTML. Во время отрисовки страницы JSF находит ранее созданные дочерние компоненты формы и вызывает их методы для генерации соответствующего HTML-кода. Компоненты формы могут располагаться на странице произвольным образом, например, так, как располагаются поля для ввода данных в Калькуляторе (см. листинг 9):

Листинг 9. Поля ввода внутри тега <h:form>

                    
<table>
  <tr>
    <td><h:outputLabel value="First Number" for="firstNumber" /></td>
    <td><h:inputText id="firstNumber"
      value="#{calculator.firstNumber}" required="true" /></td>
    <td><h:message for="firstNumber" /></td>
  </tr>

  <tr>
    <td><h:outputLabel value="Second Number" for="secondNumber" />
    </td>
    <td><h:inputText id="secondNumber"
      value="#{calculator.secondNumber}" required="true" /></td>
    <td><h:message for="secondNumber" /></td>
  </tr>
</table>

Как и ранее, заметим, что в листинге преобладает HTML. В страницах JSF можно использовать любые элементы HTML - span,div,table - JSF не накладывает ограничений на работу дизайнера. Более того, есть даже программы, позволяющие создавать страницы JSF в Dreamweaver. Если же использовать JSF совместно с Facelets, что стало возможным, начиная с JSF 1.1, то работа дизайнера еще более упростится. Напомним, что технология Facelets войдет в состав JSF 2.0.

Обратите внимание, что в листинге 9 в атрибутах values обоих тегов <h:inputText> используется JSF EL (язык выражений JSF), например, value="#{calculator.firstNumber}". На первый взгляд, эти выражения выглядят схоже с выражениями JSTL EL, однако, код JSF EL связывает поля ввода с соответствующими значениями свойств объектов JavaBean. Эта связь двунаправлена, т.е. если значение свойства firstNumber было 100, то в поле ввода будет выведено 100 при отображении страницы. Если пользователь ввел 200, то 200 будет сохранено в свойстве объекта (не учитывая возможности возникновения ошибок при конвертации и валидации данных - об этом мы поговорим позже).

Кроме полей ввода в форме calcForm еще присутствуют три элемента типа commandButtons, задающие определенные действия (см. листинг 10):

Листинг 10. Кнопки внутри тега <h:form>

                    
<div>
  <h:commandButton action="#{calculator.add}"  value="Add" />
  <h:commandButton action="#{calculator.multiply}"  value="Multiply" />
  <h:commandButton action="#{calculator.clear}"  value="Clear" immediate="true"/>
</div>

 
О таблицах стилей

Внешний вид компонентов JSF определяется соответствующими классами из таблиц стилей. Каждый компонент связывается с встроенными (inline) стилями с помощью атрибутов style, а также содержит атрибут styleClass для задания класса CSS. Элемент <h:panelGrid>, который будет рассматриваться в следующей секции, содержит атрибуты style для присвоения стилей строкам и столбцам таблицы. Далее мы рассмотрим использование каскадных таблиц стилей (CSS) в JSF.

В листинге 10 показано, как связываются кнопки с методами add(), multiply() и clear() объекта calculator. Таким образом, при нажатии на кнопку будет вызван соответствующий метод (опять же, если не возникло ошибок конвертации или валидации).

По умолчанию JSF производит валидацию данных формы перед вызовом метода, например add() или multiply(). Однако этот шаг можно пропустить, установив значение атрибута immediate в true, как сделано в листинге 10 для кнопки Clear. В этом случае JSF вызовет метод немедленно (здесь есть тонкости, но об это позже).

Отображение результата: тег <h:panelGroup>

Результаты операций сложения и умножения выводятся внутри тега <f:view> с помощью элемента <h:panelGroup>. Пример показан в листинге 11:

Листинг 11. Вывод результата

                    
<h:panelGroup rendered="#{calculator.result != 0}">
  <h4>Results</h4>
  <table>
    <tr><td>
      First Number  ${calculator.firstNumber}
    </td></tr>
    <tr><td>
      Second Number  ${calculator.secondNumber}
    </td></tr>
    <tr><td>
      Result  ${calculator.result}
    </td></tr>
  </table>
</h:panelGroup>

Как и ранее, в коде страницы преобладает HTML. Обратите внимание, что можно свободно использовать выражения JSP внутри <h:panelGroup>. Кроме того, аналогично другим компонентам JSF этот тег содержит атрибут rendered, управляющий выводом содержимого. Таким образом, секция отображения результата выводится только если значением выражения calculator.result != 0 is является true. В частности, по этой причине секция не отображается при первоначальной загрузке страницы, а выводится только после выполнения операции (если конечно результат не равен нулю). В этом решении есть определенная проблема: оно выносит логику приложения в слой представления. К тому же необходимо корректно показывать нулевой результат арифметических операций, таких как 0 + 0 или 0*7. Далее мы устраним этот недостаток.

Запуск приложения

 
Проще ли Struts чем JSF?

Если задаться целью переписать наше простое приложение на Struts, то, мне кажется, это потребует как минимум вдвое больше усилий. В этом случае понадобятся два класса Action, каждый со своим отображением (action mapping). В соответствии с рекомендацией Model 2, отображение также понадобится для перенаправления на исходную страницу при загрузке приложения. Обработка ошибок, которая делается по умолчанию в JSF, потребовала бы специального конфигурирования валидатора в Struts или же перегрузки метода validate в классе ActionForm. При этом пришлось бы выбрать один из трех вариантов: или использовать DynaValidatorForm в конфигурационном файле Struts, или создать класс-наследник ActionForm и переопределить метод validate, или класс-наследник ValidatorForm и работать с валидатором напрямую. Ну и, наконец, скорее всего бы пришлось определять переходы (по два на каждый action-класс или глобальные, используемые всеми классами). В общем, врагу не посоветуешь использовать Struts при разработке новых приложений.

Для запуска приложения перейдите по адресу, соответствующему расположению WAR-файла приложения (у меня это http://localhost:8080/calculator0/). При этом index.jsp вызовет calculator.jsp внутри контекста JSF. Если вы используете среду Eclipse WTP и правильно настроили сервер приложений, то достаточно кликнуть правой кнопкой на calculator.jsp в панели навигатора и выбрать пункт Run As > Run On Server.

Если в поле firstNumber или secondNumber ввести нечто, не являющееся числом, например, строку abc и нажать submit, то вновь будет вызвана страница calculator.jsp, а сообщение об ошибке будет выведено рядом с соответствующим полем ввода. То же самое произойдет, если одно из полей оставить пустым. Как видите, простая валидация выполняется в JSF практически автоматически, благодаря связыванию компонентов со свойствами, имеющими тип int. Поведение приложения при возникновении ошибок валидации и конвертации данных показано на рисунке 4:

Рисунок 4. Ошибки валидации и конвертации данных
Validation and data-conversion errors

Как видите, сообщения об ошибках выглядят странно. Это сообщения по умолчанию, которые не предназначены для показа пользователю:

  • calcForm:firstNumber: 'abc' must be a number between -2147483648 and 2147483647 Example: 9346. Перевод: calcForm:firstNumber: 'abc' должно быть числом в диапазоне от -2147483648 до 2147483647. Например, 9346.
  • calcForm:secondNumber: Validation Error: Value is required. Перевод: calcForm:secondNumber: Ошибка валидации: Значение является обязательным для ввода.

В следующей секции мы исправим эти сообщения.

Если же вы ввели два значения, чья сумма или произведение не равны нулю, то результат должен быть схож с показанным на рисунке 5:

Рисунок 5. Панель для вывода результата
Results panel 

Совершенствование Калькулятора

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

Использование компонента <h:panelGrid>

До этого момента нам приходилось писать много HTML-кода. С одной стороны, используя HTML можно в точности добиться желаемой разметки, но с другой - для Web-приложения это может быть не настолько критично как, например, для рекламного проспекта. Поэтому можно облегчить создание интерфейса, используя компонент <h:panelGrid>, позволяющий размещать дочерние элементы внутри ячеек таблицы.

В листинге 12 показана обновленная версия страницы для ввода данных, созданная с помощью <h:panelGrid>:

Листинг 12. Использование <h:panelGrid> внутри <h:form>

                    
<h:form id="calcForm">
  <h4>Calculator</h4>
    <h:panelGrid columns="3">
      <%-- First Number--%>
      <h:outputLabel value="First Number" for="firstNumber" />
      <h:inputText id="firstNumber"
        value="#{calculator.firstNumber}" required="true" />
      <h:message for="firstNumber" />
      <%-- Second Number--%>
      <h:outputLabel value="Second Number" for="secondNumber" />
      <h:inputText id="secondNumber"
        value="#{calculator.secondNumber}" required="true" />
      <h:message for="secondNumber" />
   </h:panelGrid>

Область для вывода результата также претерпела некоторые изменения, как показано в листинге 13:

Листинг 13. Секция для вывода результата с помощью <h:panelGrid>

                    
<h:panelGroup rendered="#{calculator.result != 0}">
  <h4>Results</h4>
  <h:panelGrid columns="1">
      <h:outputText value="First Number  #{calculator.firstNumber}"/>
      <h:outputText value="Second Number #{calculator.secondNumber}"/>
      <h:outputText value="Result  #{calculator.result}"/>
  </h:panelGrid>
</h:panelGroup>

Как видите, нам удалось избавиться практически от трети (около 20 строк) всего кода на странице и к тому же значительно улучшить читаемость. Теперь наше приложение глубже завязано на JSF, что, как правило, нравится программистам, и что крайне не любят Web-дизайнеры. Вообще, для каждого проекта важно найти оптимальный баланс между полным контролем над HTML-содержимым страниц и легкостью поддержки будущего приложения, достигающейся при помощи JSF-компонент.

Тут мы впервые встретимся с некоторыми тонкостями использования JSF. Компонент <h:panelGrid> может содержать только дочерние компоненты, в отличие от <h:form>, <f:view> и <h:panelGroup>, внутрь которых можно также помещать обычные фрагменты HTML. Первый элемент <h:panelGrid> на странице включает в себя три колонки, поэтому добавление более трех дочерних компонентов приведет к появлению новой строки. Второй элемент <h:panelGrid> включает в себя только одну колонку, поэтому каждый из дочерних компонентов отображается в отдельной строке. При выводе страницы <h:panelGrid> преобразуется в таблицу HTML, так что, с точки зрения пользователя, результат будет практически идентичен тому, что было раньше. Еще раз повторим: дочерними элементами могут быть только компоненты JSF. Обычный HTML, добавленный внутрь <h:panelGrid> будет проигнорирован при отрисовке страницы.

Оформление интерфейса с помощью CSS

Если у вас есть опыт использования HTML и CSS, то вы, наверное, представляете, как улучшить интерфейс первой версии Калькулятора. То же самое можно делать и при использовании <h:panelGrid>. Все что требуется - это импортировать таблицу CSS и можно начинать использовать стили внутри <h:panelGrid>. В данном случае мы добавим окантовку к таблице и будем чередовать белый и серебряный фон строк.

Первым делом надо объявить импортирование таблицы стилей, как показано в листинге 14:

Листинг 14. Импортирование таблицы стилей

                    
<head>
<title>Calculator Application</title>
  <link rel="stylesheet" type="text/css" 
       href="<%=request.getContextPath()%>/css/main.css" />
</head>

Сама таблица приведена в листинге 15:

Листинг 15. Таблица стилей CSS

                    
oddRow {
  background-color: white;
}

evenRow {
  background-color: silver;
}

formGrid {
  border: solid #000 3px;
  width: 400px;
}

resultGrid {
  border: solid #000 1px;
  width: 200px;
}

В листинге 15 показаны определения стилей oddRow и evenRow. Они задают белый фон нечетных строк и серебряный фон четных строк соответственно.

Теперь применим эти стили к <h:panelGrid>, как показано в листинге 16:

Листинг 16. Использование стилей в компоненте <h:panelGrid>

                    
<h:panelGrid columns="3" rowClasses="oddRow, evenRow"
      styleClass="formGrid">
      ...
</h:panelGrid>

Чтобы применить стили к строкам таблицы необходимо установить значение атрибута rowClasses в oddRow, evenRow. Стиль, определяющий внешнюю рамку для таблицы, применяется с помощью атрибута styleClass(styleClass="formGrid"). В результате <h:panelGrid> должен выглядеть как в листинге 17:

Листинг 17. Использование стилей для показа результата в компоненте <h:panelGrid>

                    
<h:panelGrid columns="1" rowClasses="oddRow, evenRow"
    styleClass="resultGrid">
    ...
</h:panelGrid>

А сам интерфейс Калькулятора должен принять вид как на рисунке 6:

Рисунок 6. Интерфейс калькулятора, оформленный с помощью CSS
Calculator with some style

<panelGrid> предоставляет гораздо больше возможностей по поддержке стилей, чем рассматривается в данной статье. За более полной информацией обратитесь к описанию API библиотеки тегов.

Приведение в порядок сообщений об ошибках

Если бы все пользователи были своего рода технофилами, то, может, их и устроили бы сообщения об ошибках, в противном же случае они малоинформативны. Но это можно исправить несколькими способами. Начнем с добавления метки, как показано в листинге 18:

Листинг 18. Добавление меток

                    
<h:inputText id="firstNumber" label="First Number" ... />
...
<h:inputText id="secondNumber" label="Second Number" .../>
...

Обратите внимание на значение "First Number" атрибута label в <h:inputText>. Теперь при возникновении ошибки сообщение будет выглядеть как на рисунке 7:

Рисунок 7. Вывод сообщений с помощью меток
Messages with labels

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

Листинг 19. Вывод кратких сообщений

                    
<h:outputLabel value="First Number" for="firstNumber" />
<h:inputText id="firstNumber" label="First Number"
  value="#{calculator.firstNumber}" required="true" />
<h:message for="firstNumber" showSummary="true" showDetail="false"/>

В листинге 19 значения атрибутов showSummary и showDetail компонента <h:message> устанавливаются в значения true и false соответственно. Теперь при ошибке валидации firstNumber сообщение будет выглядеть как: "First Number: 'aaa' must be a number consisting of one or more digits." - а в случае пустого поля secondNumber будет выведено: "Second Number: Validation Error: Value is required". Впрочем, сообщения можно еще улучшить, как будет показано ниже.

Переопределение текстов сообщений

В JSF 1.2 были добавлены атрибуты requiredMessage и conversionMessage, так что теперь можно переопределять текст сообщений индивидуально для каждого компонента (см. листинг 20):

Листинг 20. Использование атрибутов requiredMessage и converterMessge для вывода коротких сообщений об ошибках

                    
<%-- First Number--%>
<h:outputLabel value="First Number" for="firstNumber" />
<h:inputText id="firstNumber" label="First Number"
  value="#{calculator.firstNumber}" required="true"
  requiredMessage="required" converterMessage="not a valid number"
  />
<h:message for="firstNumber" />
<%-- Second Number--%>
<h:outputLabel value="Second Number" for="secondNumber" />
<h:inputText id="secondNumber" label="Second Number"
  value="#{calculator.secondNumber}" required="true"
  requiredMessage="required" converterMessage="not a valid number"
  />
<h:message for="secondNumber" />

Обратите внимание на значения атрибутов requiredMessage="required" converterMessage="not a valid number" компонентов <h:inputText>. Теперь сообщения об ошибках хорошо вписываются в <h:panelGrid>: они выводятся рядом с полями ввода, так что пользователю понятно, к чему они относятся (см. рисунок 8):

Рисунок 8. Вывод коротких сообщений
Shorter messages

К сожалению, у этого подхода есть один недостаток: приходится устанавливать значения атрибутов у каждого поля ввода. В простом примере это несложно, но грозит серьезными трудностями при поддержке крупного приложения. Очевидно, что это приводит к определенной избыточности, т.е. нарушает принцип "не повторяйся" (don"t repeat yourself или сокращенно - DRY).

Глобальное управление сообщениями

Сообщениями можно управлять глобально, для этого следует определить набор ресурсов (resource bundle) в файле faces-config.xml и использовать его для хранения текстов сообщений. Пример приведен в листинге 21:

Листинг 21. Конфигурирование сообщений в файле faces-config.xml

                    
<?xml version="1.0" encoding="UTF-8"?>

<faces-config xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
    http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd"
    version="1.2">

    <application>
        <message-bundle>messages</message-bundle>
    </application>
  ...
      

Содержимое файла message.properties показано в листинге 22:

Листинг 22. Файл ресурсов messages.properties, содержащий тексты сообщений

                    
javax.faces.component.UIInput.REQUIRED_detail=required
javax.faces.converter.IntegerConverter.INTEGER_detail=not a valid number

Теперь для изменения сообщения об ошибке конвертации или валидации для любого поля ввода достаточно просто поменять текст в глобальном файле ресурсов.

Замечание: Если вы работаете в среде Eclipse JEE, не забудьте добавить src/main/resources/messages.properties в список каталогов с исходным кодом.

В этом разделе мы улучшили логику представления интерфейса нашего приложения. Далее мы добавим класс-контроллер - CalculatorController, который будет содержать ссылку на объект Calculator, используя механизм инъекции зависимостей (далее dependency injection).

Добавление контроллера

Далее мы чуть изменим наше приложение, чтобы класс Calculator оставался в виде POJO и не был привязан к JSF. Для этого мы создадим класс-контроллер, который будет управлять объектами модели. Таким образом, от JSF будет зависеть только контроллер, а не слой модели приложения.

В этом разделе будет рассмотрено:

  • Использование механизма dependency injection в JSF
  • Работа с объектом facesContext
  • Добавление объектовFacesMessage
  • Использование элемента <h:messages>
  • Связывание компонентов с контроллером

Все эти возможности будут использованы в нашем примере. Затем мы остановимся на каждой из них в отдельности и рассмотрим более подробно.

Добавление метода divide() в класс Calculator

В листинге 23 приведен метод divide() класса Calculator. Далее мы покажем, как корректно обрабатывать ошибки деления на ноль, выводя сообщения для пользователя с помощью объекта FacesMessage:

Листинг 23. Добавление метода divide() в POJO-класс Calculator

                    
package com.arcmind.jsfquickstart.model;

/**
 * Calculator. Simple POJO.
 *
 * @author Rick Hightower
 */
public class Calculator {

    /** Первый операнд */
    private int firstNumber = 0;

    /** Результат операции */
    private int result = 0;

  ...

    /** Операция деления */
    public void divide() {
       this.result = this.firstNumber / this.secondNumber;
    }

    /** Сброс результата */
    public void clear () {
    result = 0;
    }

  ...

}

Создание класса-контроллера

Теперь мы создадим новый класс - CalculatorController, содержащий ссылку на POJO-класс Calculator.

CalculatorController также связан с тремя компонентами JSF - другими словами, он зависит от классов, являющихся частью JSF. Кроме этого, он отвечает за обработку исключений путем добавления объектов типа FacesMessage в FacesContext.

CalculatorController содержит ссылки на следующие компоненты JSF:

  • resultsPanel - компонент типа UIPanel
  • firstNumberInput - компонент типа UIInput
  • secondNumberInput - компонент типа UInput

Обработка ошибок в Калькуляторе показана на рисунке 9:

Рисунок 9. Исключение, возникающее при попытке деления на ноль
Application example

На рисунке 10 показано сообщение об успешном выполнении операции:

Рисунок 10. Результат операции вместе с сообщением об успешном ее выполнении
Results displaying with status message

Класс CalculatorController, показанный в листинге 24, позволяет объектам модели приложения оставаться независимыми от JSF. В тоже время он сам является JSF-зависимым объектом, хранит ссылки на компоненты интерфейса, а также отвечает за обработку ошибок и формирование сообщений о статусе операции.

Листинг 24. JSF-зависимый класс CalculatorController

                    
package com.arcmind.jsfquickstart.controller;

import javax.faces.application.FacesMessage;
import javax.faces.component.UIInput;
import javax.faces.component.UIPanel;
import javax.faces.context.FacesContext;
import com.arcmind.jsfquickstart.model.Calculator;

public class CalculatorController {

  private Calculator calculator;
  private UIPanel resultsPanel;  
  private UIInput firstNumberInput;
  private UIInput secondNumberInput;

  public String add() {
    FacesContext facesContext = FacesContext.getCurrentInstance();

    try {
      calculator.add();
      resultsPanel.setRendered(true);
      facesContext.addMessage(null, new FacesMessage(
          FacesMessage.SEVERITY_INFO, "Added successfully", null));

    } catch (Exception ex) {
      resultsPanel.setRendered(false);
      facesContext.addMessage(null, 
          new FacesMessage(FacesMessage.SEVERITY_ERROR, ex.getMessage(), null));
    }
    return null;
  }

  public String multiply() {
    FacesContext facesContext = FacesContext.getCurrentInstance();

    try {
      calculator.multiply();
      resultsPanel.setRendered(true);
      facesContext.addMessage(null, new FacesMessage(
          FacesMessage.SEVERITY_INFO, "Multiplied successfully", null));

    } catch (Exception ex) {
      resultsPanel.setRendered(false);
      facesContext.addMessage(null, 
          new FacesMessage(FacesMessage.SEVERITY_ERROR, ex.getMessage(), null));
    }
    return null;
  }

  public String divide() {
    FacesContext facesContext = FacesContext.getCurrentInstance();

    try {
      calculator.divide();
      resultsPanel.setRendered(true);
      facesContext.addMessage(null, new FacesMessage(
          FacesMessage.SEVERITY_INFO, "Divided successfully", null));

    } catch (Exception ex) {
      resultsPanel.setRendered(false);
      if (ex instanceof ArithmeticException) {
        secondNumberInput.setValue(Integer.valueOf(1));
      }
      facesContext.addMessage(null, 
          new FacesMessage(FacesMessage.SEVERITY_ERROR, ex.getMessage(), null));
    }
    return null;
  }

  public String clear() {
    FacesContext facesContext = FacesContext.getCurrentInstance();

    try {
      calculator.clear();
      resultsPanel.setRendered(false);
      facesContext.addMessage(null, new FacesMessage(
          FacesMessage.SEVERITY_INFO, "Results cleared", null));

    } catch (Exception ex) {
      resultsPanel.setRendered(false);      
      facesContext.addMessage(null, 
          new FacesMessage(FacesMessage.SEVERITY_ERROR, ex.getMessage(), null));
    }
    return null;
  }

  public String getFirstNumberStyleClass() {
    if (firstNumberInput.isValid()) {
      return "labelClass";
    } else {
      return "errorClass";
    }    
  }
  //remove simple props

Изменение страницы calculator.jsp

Далее мы изменим страницу calculator.jsp, чтобы она, во-первых, отображала сообщения об ошибках при выполнении операций, а во-вторых, обращалась к объекту контроллера, а не непосредственно к объекту POJO-класса Calculator. Код страницы приведен в листинге 25:

Листинг 25. Модифицированная версия calculator.jsp

                    
<?xml version="1.0" encoding="ISO-8859-1" ?>
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Calculator Application</title>
     <link rel="stylesheet" type="text/css"
          href="<%=request.getContextPath()%>/css/main.css" />
</head>
<body>
<f:view>
  <h:form id="calcForm">
    <h4>Calculator 3</h4>
    <h:messages infoClass="infoClass" errorClass="errorClass"
        layout="table" globalOnly="true"/>
    <h:panelGrid columns="3" rowClasses="oddRow, evenRow"
          styleClass="formGrid">
        <%-- First Number--%>
        <h:outputLabel value="First Number" for="firstNumber"
                styleClass="#{calculatorController.firstNumberStyleClass}"/>
        <h:inputText id="firstNumber" label="First Number"
          value="#{calculatorController.calculator.firstNumber}" required="true"
          binding="#{calculatorController.firstNumberInput}" />
        <h:message for="firstNumber" errorClass="errorClass"/>

        <%-- Second Number--%>
        <h:outputLabel id="snl" value="Second Number" for="secondNumber"
                styleClass="#{calculatorController.secondNumberStyleClass}"/>
        <h:inputText id="secondNumber" label="Second Number"
          value="#{calculatorController.calculator.secondNumber}" required="true"
          binding="#{calculatorController.secondNumberInput}"/>
        <h:message for="secondNumber" errorClass="errorClass"/>
    </h:panelGrid>
    <div>
      <h:commandButton action="#{calculatorController.add}"  value="Add" />
      <h:commandButton action="#{calculatorController.multiply}"  value="Multiply" />
      <h:commandButton action="#{calculatorController.divide}"  value="Divide" />
      <h:commandButton action="#{calculatorController.clear}"  value="Clear"
          immediate="true"/>
    </div>
  </h:form>


  <h:panelGroup binding="#{calculatorController.resultsPanel}" rendered="false">
  <h4>Results</h4>
   <h:panelGrid columns="1" rowClasses="oddRow, evenRow"
    styleClass="resultGrid">
    <h:outputText value="First Number  #{calculatorController.calculator.firstNumber}"/>
    <h:outputText value="Second Number #{calculatorController.calculator.secondNumber}"/>
    <h:outputText value="Result  #{calculatorController.calculator.result}"/>
   </h:panelGrid>
  </h:panelGroup>
</f:view>

</body>
</html>

Описание контроллера в файле faces-config.xml

Далее необходимо добавить описание контроллера в файле faces-config.xml и связать его с объектом calculator через dependency injection (см. листинг 26):

Листинг 26. Модифицированная версия faces-config.xml

                    
<?xml version="1.0" encoding="UTF-8"?>

<faces-config xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
    http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd"
    version="1.2">

    <application>
        <message-bundle>messages</message-bundle>
    </application>

    <managed-bean>
        <managed-bean-name>calculatorController</managed-bean-name>
        <managed-bean-class>
            com.arcmind.jsfquickstart.controller.CalculatorController
        </managed-bean-class>
        <managed-bean-scope>request</managed-bean-scope>
        <managed-property>
            <property-name>calculator</property-name>
            <value>#{calculator}</value>
        </managed-property>
    </managed-bean>
    <managed-bean>
        <managed-bean-name>calculator</managed-bean-name>
        <managed-bean-class>
            com.arcmind.jsfquickstart.model.Calculator
        </managed-bean-class>
        <managed-bean-scope>none</managed-bean-scope>
    </managed-bean>

</faces-config>

Таким образом, изменения затронули все слои приложения. Пришло время остановиться на них поподробнее.

Механизм dependency injection в JSF

JSF поддерживает dependency injection - механизм, с помощью которого ссылки на одни объекты JavaBean можно сохранять в свойствах других подобных объектов. Поскольку в данном случае объект calculator будет доступен только через внешний объект calculatorController, мы поместим его в область видимости none. Это означает, что объект не будет помещен ни в какую специальную область видимости после создания. Фрагмент файла faces-config.xml, описывающий calculator, приведен в листинге 27:

Листинг 27. Объявление управляемого объекта calculator в области видимости none

                    
<managed-bean>
    <managed-bean-name>calculator</managed-bean-name>
    <managed-bean-class>
        com.arcmind.jsfquickstart.model.Calculator
    </managed-bean-class>
    <managed-bean-scope>none</managed-bean-scope>
</managed-bean>

Объект calculatorController будет помещен в область видимости request. При этом ссылка на calculator будет передана в calculatorController. Это делается с помощью выражения #{calculator} в теге <managed-property>. Таким образом, JSF создаст экземпляр класса Calculator и передаст его в метод setCalculator класса CalculatorController, как показано в листинге 28:

Листинг 28. Объявление объекта calculator в области видимости request и связывание с помощью тега <managed-property>

                    
<managed-bean>
    <managed-bean-name>calculatorController</managed-bean-name>
    <managed-bean-class>
        com.arcmind.jsfquickstart.controller.CalculatorController
    </managed-bean-class>
    <managed-bean-scope>request</managed-bean-scope>
    <managed-property>
        <property-name>calculator</property-name>
        <value>#{calculator}</value>
    </managed-property>
</managed-bean>

Объект calculator используется классом CalculatorController, но при этом сам остается "чистым", т.е. никоим образом не привязанным от JSF. Классы модели всегда желательно держать независимыми от библиотек, подобных JSF, изолируя весь JSF-зависимый код внутри контроллера, в данном случае - классе CalculatorController. Это значительно облегчает тестирование и повторное использование модели.

Привязка компонентов JSF к классу CalculatorController

Класс CalculatorController тесно связан с JSF - это особенность архитектуры приложения. К нему привязаны три компонента JSF. Один из них - resultsPanel, представляющий собой секцию для вывода результатов арифметических операций, покзан в листинге 29:

Листинг 29. Компонент resultsPanel в классе CalculatorController

                    
private UIPanel resultsPanel;

...

public UIPanel getResultsPanel() {
  return resultsPanel;
}

public void setResultsPanel(UIPanel resultPanel) {
  this.resultsPanel = resultPanel;
}

За привязку resultsPanel к CalculatorController отвечает JSF. Обратите внимание на атрибут binding в листинге 30:

Листинг 30. Привязка компонентов к контроллеру

                    
<h:panelGroup binding="#{calculatorController.resultsPanel}" rendered="false">
  <h4>Results</h4>
   <h:panelGrid columns="1" rowClasses="oddRow, evenRow"
    styleClass="resultGrid">
    <h:outputText value="First Number  #{calculatorController.calculator.firstNumber}"/>
    <h:outputText value="Second Number #{calculatorController.calculator.secondNumber}"/>
    <h:outputText value="Result  #{calculatorController.calculator.result}"/>
   </h:panelGrid>
/h:panelGroup>

Значением это атрибута является выражение "#{calculatorController.resultsPanel}", связывающее свойство resultsPanel с компонентом. JSF сохраняет ссылку на компонент в этом свойстве, вызывая метод calculateController.setResultsPanel при загрузке страницы. Таким образом, JSF позволяет легко манипулировать элементами интерфейса, не требуя явного обхода или поиска в дереве компонентов.

На самом деле, сначала JSF вызывает метод calculateController.getResultsPanel. Если компонент уже создан, то он будет использован в представлении. Если же метод вернул null, то JSF создаст новый экземпляр компонента, а затем передаст его в метод calculateController.setResultPanel, привязав таким образом к объекту контроллера.

В листинге 31 показано, как компонент resultsPanel используется внутри метода CalculateController.add():

Листинг 31. Метод CalculateController.add(), скрывающий компонент resultsPanel при возникновении исключения

                    
public String add() {

   ...
   
   try {
      calculator.add();
      resultsPanel.setRendered(true);
      ...

   } catch (Exception ex) {
      ...
      resultsPanel.setRendered(false);
   }
   return null;
}

 
А как насчет поддержки Ajax? Никаких проблем! Решение - частичная загрузка страниц

Если вы используете некое решение для поддержки Ajax, например, JBoss Ajax4Jsf, то для реализации частичной загрузки страниц не потребуется больших усилий (при этом Ajax4Jsf необходим только до выхода JSF 2.0, который будет включать всю необходимую функциональность). С точки зрения пользователя, приложение будет выглядеть как апплет или созданное с помощью Flex. И на JavaScript ничего писать не придется!

Как видно из листинга, если метод calculator.add отрабатывает успешно, то CalculateController.add вызывает resultsPanel.setRendered(true), делая панель результата видимой для пользователя. В противном случае происходит вызов resultsPanel.setRendered(false) и панель не выводится.

Это очень важный момент, на нем имеет смысл остановиться подробнее. JSF - это компонентная технология, все компоненты в ней обладают сохраняемым состоянием. Таким образом, больше не требуется специальный код, управляющий состоянием компонентов, как в первоначальном примере. Достаточно один раз скрыть или деактивировать компонент, и он будет скрыт или неактивен при последующих перезагрузках представления. Подобное поведение значительно ближе к традиционному GUI, чем различные реализации Model 2. Используя JSF, вы пишете меньше кода, а соответственно , тратите меньше времени на разработку Web-приложений. JSF делает из Web-приложений настоящие приложения . Имейте это в виду.

Работа с сообщениями в CalculatorController

JSF предоставляет средства для показа сообщений пользователям о статусе той или иной операции. Для добавления сообщений используется класс FacesContext, благодаря которому они впоследствии могут быть выведены с помощью тега <h:messages>.

JSF хранит объект класса FacesContext в переменной ThreadLocal. К ней можно получить доступ, вызвав статический метод FacesContext.getCurrentInstance(). В методе add() в FacesContext добавляются сообщения, которые должны быть доступны до конца обработки запроса. Пример показан в листинге 32:

Листинг 32. Добавление JSF-сообщений в методе CalculateController.add()

                    
public String add() {

   FacesContext facesContext = FacesContext.getCurrentInstance();

   try {
      calculator.add();
      facesContext.addMessage(null, new FacesMessage(
            FacesMessage.SEVERITY_INFO, "Added successfully", null));
      ...

   } catch (Exception ex) {
      facesContext.addMessage(null, 
            new FacesMessage(FacesMessage.SEVERITY_ERROR, ex.getMessage(), null));
      //Также необходимо запротоколировать исключение
      ...
   }
   return null;
}

В данном примере сообщения представляют собой объекты класса FacesMessage, добавляемые в объект facesContext. При этом уровень важности сообщения зависит от возникновения исключений при выполнении операции суммирования. Если оно прошло успешно, то уровень будет INFO, в противном случае - ERROR.

Сообщения выводятся на странице с помощью тега <h:messages>, как показано в листинге 33:

Листинг 33. Вывод сообщений об ошибках и успешном завершении операции

                    
<h:messages infoClass="infoClass" errorClass="errorClass" 
            layout="table" globalOnly="true"/>

Для вывода только глобальных сообщений, т.е. не относящихся к конкретному компоненту, можно установить значение атрибута globalOnly в true. К таковым относятся сообщения, добавляемые в листинге 32. Обратите внимание, что стиль сообщений об ошибках отличается от стиля обычных статусных сообщений.

Решение проблемы деления на ноль в классе CalculatorController

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

Для начала необходимо привязать компонент secondNumberInput к объекту класса CalculatorController, как показано в листинге 34:

Листинг 34. Привязка компонента для ввода второго операнда: binding="#{calculatorController.secondNumberInput}"

                    
<h:inputText id="secondNumber" label="Second Number"
   value="#{calculatorController.calculator.secondNumber}" required="true"
   binding="#{calculatorController.secondNumberInput}"/>

Теперь этот компонент можно использовать по своему усмотрению. Например, присвоить ему значение 1 в случае выброса исключения при попытке деления на ноль (см. листинг 35):

Листинг 35. Новая версия метода divide()

                    
public String divide() {

   FacesContext facesContext = FacesContext.getCurrentInstance();

   try {
      calculator.divide();
      facesContext.addMessage(null, new FacesMessage(
            FacesMessage.SEVERITY_INFO, "Divided successfully", null));
      resultsPanel.setRendered(true);

   } catch (Exception ex) {
      if (ex instanceof ArithmeticException) {
         secondNumberInput.setValue(Integer.valueOf(1));
      }
      facesContext.addMessage(null, 
            new FacesMessage(FacesMessage.SEVERITY_ERROR, ex.getMessage(), null));
   }
   return null;
}

Очень важно постоянно помнить, что JSF значительно ближе к компонентной модели GUI, чем к некой запутанной реализации Model 2. Это открывает потенциально широкие возможности. В частности, можно манипулировать значением компонента secondNumberInput в листинге 35, потому что это объект, а не просто кусок HTML или JSP-кода. Его можно модифицировать и он запомнит новое значение. У него есть состояние.

Работа с атрибутами

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

Листинг 36. Изменение цвета текста метки на красный

                    
<%-- Первый операнд--%>
<h:outputLabel value="First Number" for="firstNumber" 
    styleClass="#{calculatorController.firstNumberStyleClass}"/>
...

Обратите внимание, что значением атрибута styleClass является выражение #{calculatorController.firstNumberStyleClass}, при вычислении которого будет вызван метод, приведенный в листинге 37:

Листинг 37. Выбор стиля CSS для сообщения об ошибке

                    
public String getFirstNumberStyleClass() {
   if (firstNumberInput.isValid()) {
      return "labelClass";
   } else {
      return "errorClass";
   }
}

В листинге 37 показано, как можно менять класс CSS в зависимости от того, является ли значение компонента firstNumbedInput корректным.

Навигация в JSF

JSF включает в себя механизм навигации, аналогичный Struts. Он определяет связь между логическим признаком результата и следующим представлением. Ниже мы добавим поддержку навигации в Калькулятор.

Правила перехода

Навигация осуществляется с помощью правил перехода. На рисунке 11 показаны правила, которые будет использоваться в Калькуляторе:

Рисунок 11. Правила перехода между страницами Калькулятора
Navigation rules

Как правило, диаграмму переходов Web-приложения удобнее создавать с помощью специальных средств. В частности, многие IDE предоставляют такие возможности для JSF-приложений. Eclipse JEE включает в себя Navigation Rule Layout Tool, пример использования которого показан на рисунке 12:

Рисунок 12. Редактирование правил переходов в Eclipse
Navigation rules layout in Eclipse

Рисунки 11 и 12 должны стать намного понятнее после прочтения данного раздела.

 
В некоторых случаях навигация не нужна

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

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

Добавление ссылки с домашней страницы на главную страницу Калькулятора

Ссылку можно добавить тремя различными способами:

  • С помощью commandLink и обычного правила перехода
  • С помощью commandLink и правила перехода, использующего элемент <redirect>
  • С помощью outputLink

Первый способ реализуется путем добавления правила перехода в файл faces-config.xml, как показано в листинге 38:

Листинг 38. Правило перехода, определенное в файле faces-config.xml

                    
<navigation-rule>
    <navigation-case>
        <from-outcome>CALCULATOR</from-outcome>
        <to-view-id>/pages/calculator.jsp</to-view-id>
    </navigation-case>
</navigation-rule>

Правило в листинге 38 указывает JSF, что после любого действия (action), чьим результатом является логический признак CALCULATOR, необходимо загрузить /pages/calculator.jsp в качестве представления. Данное правило является динамическим, т.е. оно применимо к любому действию, возвращающему CALCULATOR. Исключением является случай, когда можно применить более конкретное правило, например, связанное с конкретным представлением с помощью элемента <from-view-id> (на этом мы остановимся позже). Таким образом, правило приведенное в листинге 38 аналогично глобальным переходам (global forwards) в Struts.

На странице необходимо добавить элемент <h:commandLink> в качестве дочернего к <h:form>, как показано в листинге 39:

Листинг 39. Использование <h:commandLink> на домашней странице

                    
<h:form>
    <h:panelGrid columns="1">
    <h:commandLink action="CALCULATOR" value="Calculator Application"/>
        ...

Все работает, как и должно: по данной ссылке загружается главная страница Калькулятора. Однако пользователя может смутить то, что в адресной строке браузера по-прежнему фигурирует http://localhost:8080/calculator3/home.jsf. Для кого-то это может показаться нормальным, особенно для людей, часто работающих с Web-приложениями. Но это может помешать сохранить страницу Калькулятора в закладках браузера, т.к. ее подлинный адрес неизвестен.

Это можно поправить с помощью элемента <redirect> в правиле перехода, описанном в faces-config.xml (см. листинг 40):

Листинг 40. Правило перехода и элемент redirect

                    
<navigation-rule>
    <navigation-case>
        <from-outcome>CALCULATOR_REDIRECT</from-outcome>
        <to-view-id>/pages/calculator.jsp</to-view-id>
        <redirect/>            <!-- LOOK HERE -->
    </navigation-case>
</navigation-rule>

<h:commandLink> по-прежнему использует правило перехода, используя атрибут action. Теперь при нажатии на ссылку браузер будет перенаправлен на страницу Калькулятора.

Листинг 41. Правило перехода с использованием <redirect>

                    
<h:commandLink action="CALCULATOR_REDIRECT" value="Calculator Application (redirect)"/>

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

Этого дополнительного запроса можно избежать, если вы не против разместить прямую ссылку на требуемую страницу, как показано в листинге 42:

Листинг 42. Связывание с помощью прямой ссылки (элемента <h:outputLink>)

                    
<h:outputLink value="pages/calculator.jsf">
    <h:outputText value="Calculator Application (outputlink)"/>
</h:outputLink>

В листинге 42 показана прямая ссылка на следующее представление, что, как правило, считается признаком плохого стиля при использовании Model 2 и JSF. Желательно, чтобы у контроллера была возможность инициализировать классы модели для следующего представления, поэтому лучше делать не прямой вызов, а через какой-либо action-метод контроллера. Но если нужна просто ссылка и правильный адрес в браузере, то в листинге 42 показано, как это сделать.

Долгое время сохранять ссылки на JSF-приложения в закладках было достаточно проблематично. Некоторые библиотеки, например, JBoss Seam, предлагали свои решения этой проблемы. Ей также будет уделено внимание в следующей версии JSF - JSF 2.

Переход на страницу результата

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

Листинг 43. Правило для перехода из любого представления Калькулятора на страницу результата

                    
<navigation-rule>
    <display-name>Calculator View</display-name>
    <from-view-id>/pages/calculator.jsp</from-view-id>
    <navigation-case>
        <from-outcome>results</from-outcome>
        <to-view-id>/pages/results.jsp</to-view-id>
    </navigation-case>
</navigation-rule>

Согласно данному правилу, если текущим представлением является calculator.jsp, а action-метод возвращает признак results, то должен быть выполнен переход к странице результатов (results.jsp). JSF автоматически преобразовывает возвращаемые значения action-методов к строковому виду и использует их для выбора следующего представления. Методы могут возвращать значения любых типов, потому как для преобразования используется метода toString. Например, многие используют перечисления (Enum.)

Далее мы изменим выходные значения всех операций на results. Пример метода add() приведен в листинге 44:

Листинг 44. Пример action-метода, возвращающего признак results

                    
public String add() {

    FacesContext facesContext = FacesContext.getCurrentInstance();

    try {
        calculator.add();
        facesContext.addMessage(null, new FacesMessage(
                FacesMessage.SEVERITY_INFO, "Added successfully", null));

    } catch (Exception ex) {
        facesContext.addMessage(null, 
                new FacesMessage(FacesMessage.SEVERITY_ERROR, ex.getMessage(), null));
    }
    return "results";
}

Обратите внимание, что метод add() возвращает строку results, связанную со страницей results.jsp. В то же время обработчик нажатия на кнопку Cancel не возвращает results. В случае если метод возвращает значение, не связанное ни с одним правилом перехода, то JSF не меняет текущее представление.

Можно сделать правило еще более специализированным, как показано в листинге 45:

Листинг 45. Правило перехода, специфичное для данного action-метода

                    
<navigation-rule>
    <display-name>Calculator View</display-name>
    <from-view-id>/pages/calculator.jsp</from-view-id>
    <navigation-case>
        <from-action>#{calculatorController.add}</from-action>
        <from-outcome>results</from-outcome>
        <to-view-id>/pages/results.jsp</to-view-id>
    </navigation-case>
</navigation-rule>

Однако в этом случае придется создавать по правилу на каждый метод. Это может привести к ненужному дублированию.

Также необходимо добавить кнопку (<h:commandButton>) для возвращения с главной страницы Калькулятора на домашнюю страницу приложения, как показано в листинге 46:

Листинг 46. Пример кнопки для возвращения на домашнюю страницу

                    
<div>
  <h:commandButton action="#{calculatorController.add}" value="Add" />
  <h:commandButton action="#{calculatorController.multiply}" value="Multiply" />
  <h:commandButton action="#{calculatorController.divide}" value="Divide" />
  <h:commandButton action="#{calculatorController.clear}" value="Clear" immediate="true"/>
  <h:commandButton action="HOME" value="Home" immediate="true"/>
</div>

Отметьте, что значением атрибута value является HOME. В листинге 47 показано правило перехода, связывающее значение HOME с домашней страницей приложения:

Листинг 47. Правило перехода на домашнюю страницу

                    
<navigation-rule>
    <navigation-case>
        <from-outcome>HOME</from-outcome>
        <to-view-id>/home.jsp</to-view-id>
        <redirect/>            
    </navigation-case>
</navigation-rule>

Как видите, с точки зрения правил перехода и вызова обработчиков, элементы <h:commandButton> и <h:commandLink> работают совершенно одинаково.

В итоге, страница результатов содержит две ссылки: для перехода к главной странице Калькулятора и для возвращения к домашней странице (см. листинг 48):

Листинг 48. Страница результата с возможностью перехода на домашнюю либо главную страницу приложения

                    
<h:panelGrid columns="1" rowClasses="oddRow, evenRow">
    <h:commandLink action="calculator" value="Return to the calculator page"/>
    <h:commandLink action="HOME" value="Go to the home page"/>
    <h:commandLink action="calculatorMain" value="Go to main calculator page"/>
</h:panelGrid>

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

Листинг 49. Правило перехода с страницы результата обратно на главную страницу

                    
<navigation-rule>
    <display-name>Results Page</display-name>
    <from-view-id>/pages/results.jsp</from-view-id>
    <navigation-case>
        <from-outcome>calculator</from-outcome>
        <to-view-id>/pages/calculator.jsp</to-view-id>
    </navigation-case>
</navigation-rule>

Данное правило гласит, что если текущим представлением является страница результатов (/pages/results.jsp) и обработчик возвращается строку calculator, то необходим переход на главную страницу (/pages/calculator.jsp). Но иногда требуется правило более общее, чем приведенное в листинге 49, но все же более конкретное, чем глобальный переход. В этом случае можно использовать решение, показанное в листинге 50:

Листинг 50. Правило перехода с любой страницы, находящейся в pages/*, на главную страницу Калькулятора

                    
<navigation-rule>
    <from-view-id>/pages/*</from-view-id>
    <navigation-case>
        <from-outcome>calculatorMain</from-outcome>
        <to-view-id>/pages/calculator.jsp</to-view-id>
        <redirect/>
    </navigation-case>
</navigation-rule>

Как видно из листинга, можно определять логические области внутри приложения и описывать применимые к ним правила.

Заключение

В статье была рассмотрена технология JSF и ее компонентная архитектура, схожая с Swing, SWT и AWT, благодаря которой разработка приложений больше напоминает создание традиционных приложений GUI, чем Web-приложений. JSF-приложения, написанные на JSF, как правило короче, проще для понимания, и их легче поддерживать, чем их аналоги созданные в стиле Model 2. В настоящее время JSF привлекает к себе все больше внимания со стороны Java-разработчиков, что отражается и в требованиях при принятии на работу.

В настоящее время идет работа над JSF 2, которая будет включать поддержку Facelets, Ajax, а также в целом облегчит создание компонентов. JSF 2 должна еще повысить положительный эффект от изучения JSF. Помимо всего прочего, в JSF 2 ожидается поддержка частичной загрузки страниц через Aajx, что в настоящее время реализуемо только с помощью сторонних библиотек, например, Ajax4JSF.

При этом вышесказанное не означает, что у JSF нет конкурентов. Из серверных компонентных технологий неплохо выглядит Tapestry 5, однако она несовместима с Tapestry 4. Также серьезный, но пока недостаточный для широкого распространения, интерес вызывает Wicket.

Вдобавок существуют некомпонентные серверные решения, в частности, Struts 2.x, который сильно улучшил WebWork. Некоторые разработчики возлагают большие надежды на Struts 2.x, несмотря на то, что он создан на основе WebWork, а не Struts 1.x. Кроме этого, продолжает развиваться проект Spring MVC, который может быть неплохим вариантом, если не требуется поддержка серверных компонент GUI.

Наконец, существуют клиентские технологии, которые делегируют все вызовы к серверной части. Примерами являются Google Web Toolkit (GWT) и Adobe Flex. Их архитектуры сильно отличаются от JSF, но они предназначены для использования в схожих приложениях. У них есть свои преимущества и недостатки, и они могут оказать определенное влияние на распространение JSF.

Но, тем не менее, JSF, скорее всего, будет продолжать успешно развиваться, во-первых, потому что это стандарт для Java EE, а во-вторых, благодаря активному сообществу разработчиков. Интерес к JSF превышает интерес к Tapestry, Spring MVC, Java Flex и GWT, а JSF 2.0 может стать дополнительным стимулом к применению данной технологии.

Во второй статье серии мы расскажем о жизненном цикле приложений на JSF, а также рассмотрим вопросы работы с валидаторами, конвертаторами данных, слушателями фаз (phase listeners) и другими продвинутыми возможностями JSF.

Файлы для загрузки


 Распечатать »
 Правила публикации »
  Написать редактору 
 Рекомендовать » Дата публикации: 31.08.2008 
 

Магазин программного обеспечения   WWW.ITSHOP.RU
Rational ClearQuest Floating User License
Quest Software. TOAD for Oracle Edition
Oracle Database Personal Edition Named User Plus License
VMware Workstation 14 Player for Linux and Windows, ESD
The BAT! Home- 1 компьютер
 
Другие предложения...
 
Курсы обучения   WWW.ITSHOP.RU
 
Другие предложения...
 
Магазин сертификационных экзаменов   WWW.ITSHOP.RU
 
Другие предложения...
 
3D Принтеры | 3D Печать   WWW.ITSHOP.RU
 
Другие предложения...
 
Новости по теме
 
Рассылки Subscribe.ru
Информационные технологии: CASE, RAD, ERP, OLAP
Безопасность компьютерных сетей и защита информации
Новости ITShop.ru - ПО, книги, документация, курсы обучения
CASE-технологии
Программирование на Microsoft Access
Утиль - лучший бесплатный софт для Windows
Corel DRAW - от идеи до реализации
 
Статьи по теме
 
Новинки каталога Download
 
Исходники
 
Документация
 
 



    
rambler's top100 Rambler's Top100