Вы находитесь на странице: 1из 348

Дэвид Хеффельфингер

Разработка
приложений
Java EE 7
в NetBeans 8
Java EE 7 Development
with NetBeans 8

Develop professional enterprise Java EE


applications quickly and easily with this
popular IDE

David R. Heffelfinger

BIRMINGHAM - MUMBAI
Разработка
приложений Java EE 7
в NetBeans 8

Простая и быстрая разработка


корпоративных приложений Java EE
с помощью среды разработки NetBeans

Дэвид Хеффельфингер

Москва, 2016
УДК 004.438Java EE
ББК 32.973.26-018.2
Х41

Х41 Дэвид Хеффельфингер


Разработка приложений Java EE 7 в NetBeans 8. / пер. с англ. Кисе­
лев А. Н. – М.: ДМК Пресс, 2016. – 348 с.: ил.

ISBN 978-5-97060-329-1

Книга представляет собой практическое руководство по использованию


возможностей IDE NetBeans 8 для разработки корпоративных приложений,
совместимых со стандартом Java EE 7.
В книге показаны приемы эффективного программирования, задействую­
щие контекстные меню и «горячие» клавиши, мастера и шаблоны среды
NetBeans, затрагиваются вопросы создания, конфигурирования, развертыва­
ния, отладки и профилирования корпоративных приложений с использова­
нием средств, встроенных в IDE NetBeans.
Существенное внимание уделено основным API Java EE в контексте их
работы в среде NetBeans. Подробно рассмотрены возможности NetBeans по
автоматизации разработки приложений с использованием таких API, как
Servlet, JSP, JSTL, JSF, JMS, JPA, JDBC, EJB, JAX-WS, JAX-RS, а также по
созданию для них инфраструктурных, коммуникационных и конфигураци­
онных элементов. Затронуты вопросы взаимодействия среды NetBeans с раз­
личными серверами приложений, СУБД и внешними службами.
Приводится пример автоматического создания законченного корпоратив­
ного приложения из существующей схемы базы данных, а также примеры
создания веб-служб и автоматического создания их клиентов.
Книга рассчитана на программистов, желающих разрабатывать Java EE-
приложения c использованием функциональных возможностей IDE NetBeans.
Для чтения книги необходимо иметь некоторый опыт работы с Java, в то
время как начального знакомства с NetBeans и Java EE не требуется.

All rights reserved. No part of this book may be reproduced, stored in a retrieval system,
or transmitted in any form or by any means, without the prior written permission of the
publisher, except in the case of brief quotations embedded in critical articles or reviews.
Все права защищены. Любая часть этой книги не может быть воспроизведена в ка­
кой бы то ни было форме и какими бы то ни было средствами без письменного разре­
шения владельцев авторских прав.
Материал, изложенный в данной книге, многократно проверен. Но, поскольку ве­
роятность технических ошибок все равно существует, издательство не может гаран­
тировать абсолютную точность и правильность приводимых сведений. В связи с этим
издательство не несет ответственности за возможные ошибки, связанные с использо­
ванием книги.

ISBN 978-1-78398-352-0 (англ.) Copyright © 2015 Packt Publishing


ISBN 978-5-97060-3-1 (рус.) © Оформление, перевод на русский язык,
ДМК Пресс, 2016
Оглавление

Об авторе......................................................... 10
О рецензентах.................................................. 11
предисловие.................................................... 14
Вопросы, освещаемые в книге............................................................... 14
Что нужно для чтения этой книги............................................................ 16
Для кого эта книга.................................................................................. 16
Соглашения........................................................................................... 16
Отзывы и пожелания.............................................................................. 17
Скачивание исходного кода примеров................................................... 17
Список опечаток.................................................................................... 18
Нарушение авторских прав.................................................................... 18
Вопросы................................................................................................ 18

Глава 1.
Знакомство с NetBeans....................................... 19
Введение....................................................................................... 19
Получение NetBeans....................................................................... 20
Установка NetBeans....................................................................... 23
Microsoft Windows.................................................................................. 24
Mac OSx................................................................................................. 24
Linux...................................................................................................... 24
Другие платформы................................................................................ 25
Процедура установки............................................................................. 25
Первый запуск NetBeans................................................................ 31
Настройка NetBeans для разработки Java EE-приложений............. 32
Интегрирование NetBeans со сторонним сервером приложений........... 33
Интегрирование NetBeans с СУРБД стороннего производителя............ 36
Развертывание нашего первого приложения................................. 40
Подсказки NetBeans для эффективной разработки........................ 43
Автозавершение кода............................................................................ 43
Шаблоны кода....................................................................................... 47
Клавиши быстрого вызова..................................................................... 49
Изучение визуальных индикаторов NetBeans......................................... 53
Функция ускорения разработки HTML5.......................................... 54
Резюме.......................................................................................... 59
6 Оглавление

Глава 2.
Разработка веб-приложений с использованием
JavaServer Faces 2.2. ......................................... 60
Введение в JavaServer Faces.......................................................... 60
Разработка нашего первого приложения JSF................................. 61
Создание нового проекта JSF................................................................ 61
Добавление в страницу возможности ввода данных............................... 66
Создание именованного компонента CDI............................................... 73
Реализация страницы подтверждения................................................... 77
Запуск приложения................................................................................ 78
Проверка допустимости в JSF................................................................ 80
Шаблоны фейслетов...................................................................... 83
Добавление шаблона фейслетов........................................................... 84
Использование шаблона........................................................................ 86
Контракты библиотек ресурсов...................................................... 90
Составные компоненты.................................................................. 96
Потоки Faces Flow........................................................................ 101
Поддержка HTML5........................................................................ 108
HTML5-подобная разметка.................................................................. 108
Сквозные атрибуты.............................................................................. 111
Резюме........................................................................................ 113

Глава 3.
Библиотеки компонентов JSF............................ 114
Использование компонентов PrimeFaces в JSF-приложениях....... 114
Использование компонентов ICEfaces в JSF-приложениях........... 120
Использование компонентов RichFaces в JSF-приложениях......... 128
Резюме........................................................................................ 133

Глава 4.
Взаимодействие с базами данных через
Java Persistence API......................................... 135
Создание первой сущности JPA................................................... 136
Добавление сохраняемых полей в сущность........................................ 145
Создание объекта доступа к данным.................................................... 147
Автоматическое создание сущностей JPA.................................... 153
Именованные запросы и JPQL............................................................. 162
Проверка допустимости со стороны компонентов............................... 164
Отношения сущностей......................................................................... 164
Создание приложений JSF из сущностей JPA............................... 172
Резюме........................................................................................ 179
Оглавление 7

Глава 5.
Реализация уровня бизнес-логики на сеансовых
компонентах EJB............................................. 180
Введение в сеансовые компоненты.............................................. 181
Создание сеансового компонента в NetBeans.............................. 181
Доступ к компонентам из клиента................................................ 193
Запуск клиента.................................................................................... 196
Управление транзакциями в сеансовых компонентах .................. 197
Реализация аспектно-ориентированного программирования
с помощью интерцепторов........................................................... 199
Реализация класса интерцептора........................................................ 200
Декорирование компонентов EJB аннотацией @Interceptors................ 202
Служба таймеров EJB................................................................... 203
Автоматическое создание сеансовых компонентов
из сущностей JPA......................................................................... 206
Резюме........................................................................................ 211

Глава 6.
Контексты и внедрение зависимостей................ 213
Введение в CDI............................................................................. 213
Квалификаторы............................................................................ 219
Стереотипы.................................................................................. 225
Типы привязки интерцепторов..................................................... 227
Собственные контексты............................................................... 232
Резюме........................................................................................ 234

Глава 7.
Обмен сообщениями с применением JMS
и компонентов, управляемых сообщениями......... 236
Введение в JMS........................................................................... 236
Создание ресурсов JMS из NetBeans........................................... 237
Реализация продюсера сообщений JMS...................................... 243
Обработка сообщений компонентами, управляемыми
сообщениями............................................................................... 250
Наблюдение за приложением в действии............................................ 254
Резюме........................................................................................ 256

Глава 8.
Прикладной интерфейс JSON Processing............. 257
8 Оглавление

Объектная модель JSON-P........................................................... 257


Создание данных в формате JSON с использованием объектной
модели JSON-P ................................................................................... 258
Пример................................................................................................ 261
Парсинг данных в формате JSON с использованием объектной модели
JSON-P ............................................................................................... 265
Потоковая модель JSON-P........................................................... 268
Создание данных JSON с применением потоковой модели JSON-P..... 269
Парсинг данных JSON с применением потоковой модели JSON-P....... 271
Резюме........................................................................................ 274

Глава 9.
Прикладной интерфейс WebSocket..................... 275
Исследование приемов использования веб-сокетов
на типовых примерах................................................................... 275
Опробование примера приложения Echo............................................. 277
Программный код на Java ................................................................... 278
Программный код на JavaScript........................................................... 279
Создание собственных приложений с веб-сокетами.................... 281
Создание пользовательского интерфейса........................................... 283
Создание серверной конечной точки веб-сокета................................. 286
Реализация поддержки веб-сокетов на стороне клиента..................... 288
Резюме........................................................................................ 291

Глава 10.
Веб-службы RESTful на основе JAX-RS................ 293
Создание веб-службы RESTful на основе существующей базы
данных......................................................................................... 294
Анализ сгенерированного кода............................................................ 296
Тестирование веб-службы RESTful............................................... 300
Создание Java-клиента веб-службы RESTful................................. 307
Создание JavaScript-клиента веб-службы RESTful........................ 313
Резюме........................................................................................ 317

Глава 11.
Веб-службы SOAP на основе JAX-WS................... 318
Введение в веб-службы................................................................ 318
Создание простой веб-службы..................................................... 319
Тестирование веб-службы................................................................... 325
Создание клиента для веб-службы....................................................... 327
Экспортирование компонентов EJB в виде веб-служб.................. 332
Реализация новых веб-служб в виде EJB.............................................. 332
Экспортирование существующих EJB в виде веб-служб...................... 335
Оглавление 9

Создание веб-службы из существующего файла WSDL........................ 338


Резюме........................................................................................ 339

Предметный указатель..................................... 341


Об авторе

Дэвид Хеффельфингер (David R. Heffelfinger) – технический ди­


ректор Ensode Technology LLC – консалтинговой компании, спе­
циализирующейся на разработке программного обеспечения и
расположенной в районе большого Вашингтона, округ Колумбия. Дэ­
вид – профессиональный архитектор, проектировщик и разработчик
программного обеспечения с 1995 года и использует Java в качестве
основного языка программирования с 1996 года. Работал во многих
крупных проектах для ряда клиентов, в числе которых департамент
США по Национальной безопасности, Freddie Mac, Fannie Mae и
Министерство обороны США. Имеет степень магистра в области раз­
работки программного обеспечения Южного методического универ­
ситета. Также является главным редактором Ensode.net (http://www.
ensode.net), веб-сайта, посвященного Java, Linux и другим технологи­
ям. Часто выступает на конференциях Java-разработчиков, таких как
JavaOne. Вы можете следовать за Дэвидом в Твиттере, его учетная за­
пись: @ensode.
О Рецензентах

Саурабх Чхаджед (Saurabh Chhajed) – обладатель сертификатов


«Cloudera Certified Developer for Apache Hadoop» и «Certified Java/
J2EE Programmer» с 5-летним опытом профессиональной разработки
корпоративных приложений с применением новейших фреймворков,
инструментов и шаблонов проектирования. Имеет большой опыт
применения методологий гибкой разработки и активно продвигает
новые технологии, такие как NoSQL и приемы обработки Больших
Данных. Саурабх оказывал помощь некоторым крупным кампани­
ям из США в создании их корпоративных систем, что называется «с
нуля». В свободное от работы время любит путешествовать и обожа­
ет делиться опытом в своем блоге (http://saurzcode.in).

Халиль Каракосе (Halil Karaköse) – независимый разработчик


программного обеспечения. В 2005 году закончил университет Işık
University в Турции с квалификацией инженера по вычислительной
технике.
Десять лет работал в индустрии телекоммуникаций, в таких ком­
паниях, как Turkcell и Ericsson. В 2014 оставил работу в Ericsson и ос­
новал собственную консалтинговую компанию KODFARKI (http://
kodfarki.com).
Основное свое внимание он уделяет разработке программ на Java,
с применением Java EE, Spring и Primefaces. Также любит проводить
практические занятия по программированию на Java. Всегда проявлял
большой интерес к Java-инструментам, повышающим скорость разра­
ботки, таким как NetBeans и IntelliJ IDEA. В свободное время занима­
ется бегом, лыжами, иногда любит сразиться в «Pro Evolution Soccer».

Марио Перес Мадуэно (Mario Pérez Madueño) родился в 1975 году в


Турине, а сейчас живет в Барселоне. В 2010 году закончил Открытый
университет Каталонии (Open University of Catalonia, UOC) с квали­
12 О рецензентах

фикацией инженера по вычислительной технике. Марио – большой


энтузиаст применения технологий Java SE, ME и EE, и уже много
лет участвует в программе «приемочных испытаний сообществом»
NetBeans (NetBeans Community Acceptance Testing program, NetCAT).
Также был техническим рецензентом книг «Java EE 5 Development
with NetBeans 6» и «Building SOA-based Composite Applications Using
NetBeans IDE 6» (обе выпущены издательством Packt Publishing).

Я хотел бы выразить благодарность моей жене Марии (María) за ее


безоговорочную помощь и поддержку всех моих начинаний, а также
Мартина (Martín) и Матиаса (Matías), дающих мне силы идти вперед.

Дэвид Салтер (David Salter) – архитектор и разработчик корпора­


тивного программного обеспечения, занимающийся этой работой с
1991 года. Истоки его отношений с Java восходят к самому началу
развития этого языка, когда он использовал Java 1.0 для создания
настольных приложений и апплетов для интерактивных веб-сайтов.
Дэвид занимается разработкой корпоративных приложений на Java с
использованием технологии Java EE (и J2EE), а также с применением
открытых решений, начиная с 2001 года. Его перу принадлежат книги
«NetBeans IDE 8 Cookbook» и «Seam 2.x Web Development» (обе вы­
пущены издательством Packt Publishing). Также является соавтором
книги «Building SOA-Based Composite Application Using NetBeans
IDE 6», Packt Publishing.

Хочу поблагодарить мою семью за поддержку. Особое спасибо


моей жене – люблю тебя.

Манжит Сингх Сони (Manjeet Singh Sawhney) – в настоящее время


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

также работал репетитором в одном из 100 лучших университетов


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

Я очень благодарен своим родителям; моей жене Джаспал


(Jaspal); моему сыну Кохинуру (Kohinoor); и моей дочери Прабхнур
(Prabhnoor), за их поддержку и терпение, когда я, занимаясь рецен-
зированием этой книги, оторвал от семьи несколько моих вечеров
и выходных.
предисловие

Java EE 7 является последней версией спецификации Java EE, в ко­


торую добавлено несколько новых возможностей для упрощения
разработки корпоративных приложений. В эту последнюю версию
Java EE были включены новые версии существующих API Java EE.
Так, например, в JSF 2.2 значительно улучшена поддержка создания
диалоговых мастеров с применением FaceFlows и добавлена под­
держка HTML5. В NetBeans появилась поддержка новых особенно­
стей JPA 2.1, таких как Bean Validation и многих других. Сеансовые
компоненты EJB теперь могут автоматически генерироваться средой
NetBeans, что существенно упрощает использование возможностей
EJB, таких как транзакции и параллельное выполнение. Дополни­
тельные особенности CDI, такие как квалификаторы, стереотипы и
другие теперь легко могут быть задействованы с помощью мастеров
NetBeans. Значительно упрощена работа с JMS 2.0, что позволяет лег­
ко и быстро разрабатывать приложения, обменивающиеся сообщени­
ями. Java EE включает новый Java API JSON Processing (JSON-P),
что упрощает обработку данных в формате JSON. Кроме того, в со­
став NetBeans была включена поддержка некоторых особенностей,
позволяющих легко и просто разрабатывать веб-службы RESTful и
SOAP.
В этой книге мы исследуем все возможности NetBeans, которые
предназначены для разработки корпоративных приложений Java
EE 7.

Вопросы, освещаемые в книге


Глава 1, «Знакомство с NetBeans», представляет введение в NetBeans, а
также знакомит с подсказками, экономящими время, и приемами, ко­
торые позволяют более эффективно разрабатывать приложения Java.
Глава 2, «Разработка веб-приложений с использованием JavaServer
Faces 2.2», объясняет, как с помощью NetBeans можно облегчить раз­
работку веб-приложений, использующих преимущества фреймворка
JavaServer Faces 2.2.
Предисловие 15

Глава 3, «Библиотека компонентов JSF», показывает, насколько


просто с помощью NetBeans создавать JSF-приложения с примене­
нием популярных библиотек компонентов JSF, таких как PrimeFaces,
RichFaces и ICEfaces.
Глава 4, «Взаимодействие с базами данных через Java Persistence
API», объясняет, как с помощью NetBeans упрощается разработка
приложений, использующих возможности Java Persistence API (JPA),
включая автоматическое создание сущностей JPA из существующих
схем баз данных. В этой главе также объясняется, как сгенерировать
завершенное веб-приложение из существующей схемы базы данных
всего несколькими щелчками мыши.
Глава 5, «Реализация уровня бизнес-логики на сеансовых компо­
нентах EJB», наглядно демонстрирует, насколько NetBeans упрощает
разработку сеансовых компонентов EJB 3.1.
Глава 6, «Контексты и внедрение зависимостей», показывает, как
новый CDI API, введенный в Java EE 6, упрощает интегрирование
различных уровней корпоративного приложения.
Глава 7, «Обмен сообщениями с применением JMS и компонентов,
управляемых сообщениями», посвящена технологиям обмена сооб­
щениями Java EE, таким как Java Message Service (JMS) и Message-
Driven Beans (MDB), демонстрируя функциональность NetBeans,
которая упрощает разработку приложений, использующих возмож­
ности этих API.
Глава 8, «Прикладной интерфейс JSON Processing», рассказыва­
ет, как обрабатывать данные в формате JSON с применением нового
прикладного интерфейса JSON-P.
Глава 9, «Прикладной интерфейс WebSocket», рассказывает, как
использовать новый прикладной интерфейс Java к веб-сокетам
(WebSocket) для создания веб-приложений, поддерживающих пол­
ноценные двусторонние взаимодействия между клиентом и сервером.
Глава 10, «Веб-службы RESTful на основе JAX-RS», рассматри­
вает создание веб-служб RESTful на основе JAX-RS, попутно де­
монстрируя, как NetBeans может автоматически генерировать веб-
службы RESTful, а также клиентские RESTful-приложения на Java
и JavaScript.
Глава 11, «Веб-службы SOAP на основе JAX-WS», объясняет, как
с помощью NetBeans можно облегчить разработку веб-служб SOAP с
применением прикладного интерфейса Java API for XML (JAX-WS).
16 Предисловие

Что нужно для чтения этой книги


Для чтения этой книги нужно установить комплект разработчика
Java – Java Development Kit (JDK) версии 7.0 (или выше) и NetBeans
версии 8.0 (или выше) в редакции Java EE.

Для кого эта книга


Если вы Java-разработчик и желаете создавать приложения Java EE,
используя преимущества NetBeans для автоматизации рутинных за­
дач, эта книга для вас. Знакомство с NetBeans или Java EE совершен­
но необязательно.

Соглашения
В этой книге вы обнаружите несколько стилей оформления текста,
которые разделяют различные виды информации. Ниже приводятся
примеры этих стилей и поясняется их значение.
Элементы программного кода в тексте, имена таблиц в базах дан­
ных, имена папок и файлов, расширения файлов, пути к каталогам в
файловой системе, фиктивные адреса URL, ввод пользователя и учет­
ные записи в Twitter оформляются так: «Для поиска каталога JDK
NetBeans использует переменную окружения JAVA_HOME».
Блоки кода оформляются следующим образом:
<package com.ensode.flowscope.namedbeans;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.faces.flow.FlowScoped;
import javax.inject.Named;

@Named
@FlowScoped("registration")
public class RegistrationBean {
...

Чтобы привлечь ваше внимание к определенной части в блоке


кода, соответствующие строки или элементы будут выделены жир­
ным шрифтом:
package com.ensode.flowscope.namedbeans;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.faces.flow.FlowScoped;
Предисловие 17
import javax.inject.Named;

@Named
@FlowScoped("registration")
public class RegistrationBean {
...

Любой ввод или вывод в командной строке оформляется так:


chmod +x filename.sh

Важные (ключевые) слова в тексте выделяются жирным. Слова, ко­


торые вы видите на экране, в меню или в диалогах, оформляются так:
«Чтобы загрузить NetBeans, щелкните на кнопке Download (Загру­
зить)».

Так оформляются предупреждения или важные примечания.

Так оформляются советы и рекомендации.

Отзывы и пожелания
Мы всегда рады отзывам наших читателей. Расскажите нам, что вы
думаете об этой книге – что понравилось или может быть не понрави­
лось. Отзывы важны для нас, чтобы выпускать книги, которые будут
для вас максимально полезны.
Вы можете написать отзыв прямо на нашем сайте www.dmkpress.
com, зайдя на страницу книги и оставить комментарий в разделе «От­
зывы и рецензии». Также можно послать письмо главному редактору
по адресу dmkpress@gmail.com, при этом напишите название книги в
теме письма.
Если есть тема, в которой вы квалифицированы, и вы заинтересо­
ваны в написании новой книги, заполните форму на нашем сайте по
адресу http://dmkpress.com/authors/publish_book/ или напишите в
издательство по адресу dmkpress@gmail.com.

Скачивание исходного кода примеров


Скачать файлы с дополнительной информацией для книг издатель­
ства «ДМК Пресс» можно на сайте www.dmkpress.com или www.дмк.
рф в разделе «Читателям – Файлы к книгам».
18 Предисловие

Список опечаток
Хотя мы приняли все возможные меры для того, чтобы удостоверить­
ся в качестве наших текстов, ошибки всё равно случаются. Если вы
найдёте ошибку в одной из наших книг – возможно, ошибку в тексте
или в коде – мы будем очень благодарны, если вы сообщите нам о ней.
Сделав это, вы избавите других читателей от расстройств и поможете
нам улучшить последующие версии этой книги.
Если вы найдёте какие-либо ошибки в коде, пожалуйста, сообщите
о них главному редактору по адресу dmkpress@gmail.com, и мы ис­
правим это в следующих тиражах.

Нарушение авторских прав


Пиратство в Интернете по-прежнему остается насущной проблемой.
Издательства «ДМК Пресс» и «Packt» очень серьезно относятся к
вопросам защиты авторских прав и лицензирования. Если вы стол­
кнетесь в Интернете с незаконно выполненной копией любой нашей
книги, пожалуйста, сообщите нам адрес копии или веб-сайта, чтобы
мы могли принять меры.
Пожалуйста, свяжитесь с нами по адресу электронной почты
dmkpress@gmail.com со ссылкой на подозрительные материалы.
Мы высоко ценим любую помощь по защите наших авторов, и по­
могающую нам предоставлять вам качественные материалы.

Вопросы
Вы можете присылать любые вопросы, касающиеся данной книги, по
адресу dm@dmk-press.ru или questions@packtpub.com. Мы постараем­
ся разрешить возникшие проблемы.
Глава 1.
Знакомство с NetBeans

В этой главе рассказывается, как приступить к работе с NetBeans, и


затрагиваются следующие темы:
введение;
получение NetBeans;
установка NetBeans;
первый запуск NetBeans;
настройка NetBeans для разработки Java EE-приложений;
развертывание нашего первого приложения;
подсказки NetBeans, повышающие эффективность разработ­
ки.

Введение
NetBeans является интегрированной средой разработки (Integrated
Development Environment, IDE) и, в дополнение к этому, платфор­
мой. Хотя первоначально IDE NetBeans могла использоваться толь­
ко для разработки приложений на Java, начиная с версии 6, NetBeans
поддерживает несколько языков программирования. Это либо встро­
енная поддержка, либо поддержка, осуществляемая путем установки
дополнительных расширений. NetBeans имеет встроенную поддерж­
ку следующих языков программирования: Java, C, C++, PHP, HTML и
JavaScript. Посредством расширений поддерживаются также Groovy,
Scala и другие языки.
Однако NetBeans не только интегрированная среда разработки, но
еще и платформа. Разработчики могут использовать NetBeans API
для создания расширений NetBeans или автономных приложений.

С краткой историей NetBeans можно познакомиться по адресу:


http://NetBeans.org/about/history.html.
20 Глава 1. Знакомство с NetBeans

Хотя NetBeans поддерживает несколько языков программирова­


ния, всё-таки основным ее языком является Java, поэтому она наи­
более удобна для разработки на Java. Как Java IDE, NetBeans имеет
встроенную поддержку приложений Java SE (Standard Edition), ко­
торые обычно работают на настольных компьютерах или ноутбуках;
приложений Java ME (Micro Edition), которые обычно работают на
портативных устройствах, таких как сотовые телефоны или PDA; и
приложений Java EE (Enterprise Edition), которые обычно работают
на больших серверах и могут поддерживать одновременную работу
тысяч пользователей.
В этой книге мы сосредоточимся на изучении возможностей
NetBeans, используемых при разработке Java EE-приложений, а также
на том, как максимально полно использовать возможности NetBeans,
позволяющие более эффективно разрабатывать приложения Java EE.
Некоторые из функций NetBeans, которые мы рассмотрим, позво­
ляют существенно ускорить разработку веб-приложений с исполь­
зованием JavaServer Faces (JSF), веб-фреймворка на стандартных
компонентах Java EE, предоставляя отправные точки для артефак­
тов такого рода. Также будет рассмотрено, как с помощью NetBeans
автоматизировать создание сущностей Java Persistence API (JPA)
из существующей схемы базы данных (JPA – стандартный инстру­
мент объектно-реляционного отображения, включенный в состав
Java EE).
В дополнение к веб-разработке будет рассмотрено, как с помощью
NetBeans упрощается разработка компонентов Enterprise JavaBeans
(EJB) и веб-служб. Мы также увидим, как просто написать компо­
нент EJB, и клиента веб-службы, воспользовавшись некоторыми пре­
имуществами NetBeans.
Перед тем как воспользоваться вышеупомянутыми преимущества­
ми NetBeans, конечно, нужно установить NetBeans, как это описано в
следующем разделе.

Получение NetBeans
NetBeans можно загрузить по адресу: http://www.netbeans.org.
Чтобы загрузить NetBeans (см. рис. 1.1), щелкните на кнопке
Download (Загрузить). После щелчка откроется страница, со спи­
ском всех доступных дистрибутивов NetBeans (см. рис. 1.2).
Разные дистрибутивы NetBeans содержат разные комплекты с раз­
ными функциональными возможностями. В табл. 1.1 перечислены
Получение NetBeans 21

некоторые комплекты NetBeans и описана функциональность, кото­


рую они предоставляют:
Таблица 1.1. Комплекты NetBeans

Комплект Описание
NetBeans
Java SE Позволяет разрабатывать приложения Java для настольных
компьютеров.
Java EE Позволяет разрабатывать приложения Java Standard Edition
(обычные приложения для настольных компьютеров) и Java
Enterprise Edition (корпоративные приложения, работающие на
«большом железе»).
C/C++ Позволяет разрабатывать приложения на языках C или C++.
HTML5 & PHP Позволяет разрабатывать веб-приложения с использованием
HTML5 и/или популярного языка PHP.

All Включает функциональность всех комплектов поставки NetBeans

Рис. 1.1. Главная страница сайта http://netbeans.org


22 Глава 1. Знакомство с NetBeans

Рис. 1.2. Страница со списком дистрибутивов NetBeans


Для опробования примеров, приведенных в этой книге, необходим
комплект Java EE или All.

Все снимки экрана в этой книге были сделаны при использовании


комплекта Java EE. В комплектации All NetBeans может выглядеть
немного иначе, в частности можно заметить появление некоторых
дополнительных пунктов меню.

Официально поддерживаются следующие платформы:


• Windows;
• Linux (x86/x64);
• Mac OS X.
Дополнительно NetBeans может выполняться на любой платфор­
ме, где установлена версия Java 7 или выше. Также доступна для за­
грузки версия NetBeans, не зависящая от операционной системы, ко­
торая будет выполняться на любой из этих платформ.
Установка NetBeans 23

Даже при том, что версия NetBeans, не зависящая от операционной


системы, может выполняться на всех поддерживаемых платфор-
мах, рекомендуется использовать версию для конкретной плат-
формы.

Страница загрузки NetBeans сама определит используемую опера­


ционную систему для получения доступа к соответствующему дис­
трибутиву, а используемая платформа будет выбрана по умолчанию.
Если дело обстоит иначе или если требуется загрузить NetBeans для
установки на другой рабочей станции, требуемую платформу можно
выбрать (см. рис. 1.2) в раскрывающемся списке Platform (Платфор­
ма).
После выбора платформы щелкните на кнопке Download (Загру­
зить), соответствующей выбранному комплекту NetBeans. Для раз­
работки Java EE-приложений нужен комплект Java EE или комплект
All. После этого дистрибутив NetBeans будет загружен в указанный
каталог.

Приложения Java EE должны развертываться на сервере прило-


жений. На рынке существует несколько серверов приложений,
между тем NetBeans в комплектациях Java EE и All уже содержат
в себе GlassFish и Tomcat. Tomcat является популярным контейне-
ром сервлета с открытым исходным кодом и может использоваться
для развертывания приложений, использующих JSF. Однако он не
поддерживает других технологий Java EE, таких как EJB или JPA.
GlassFish – сервер приложений, полностью совместимый с Java EE.
Мы будем использовать поставляемый в комплекте сервер прило-
жений GlassFish для развертывания и выполнения наших примеров.

Установка NetBeans
Для установки NetBeans требуется наличие в системе комплекта раз­
работчика Java (Java Development Kit, JDK) версии 1.7 или выше.

Поскольку эта книга адресована опытным разработчикам Java, мы


не будем тратить много времени на объяснения, как установить и
настроить JDK, так как мы можем обоснованно предположить, что
все читатели этой книги уже имеют опыт установки JDK. Инструкции
по установке JDK можно найти по адресу: http://docs.oracle.com/
javase/7/docs/ webnotes/install/index.html.
24 Глава 1. Знакомство с NetBeans

Установка NetBeans немного отличается в зависимости от плат­


формы. В следующих нескольких разделах мы объясним, как устано­
вить NetBeans на каждой поддерживаемой платформе.

Microsoft Windows
NetBeans для платформ Microsoft Windows загружается в виде испол­
няемого файла с названием, подобным netbeans-8.0-javaee-windows.
exe (точное имя зависит от версии и комплектности NetBeans, вы­
бранной для загрузки). Чтобы установить NetBeans на платформах
Windows, просто перейдите к папке, куда был загружен дистрибутив
NetBeans, и дважды щелкните на исполняемом файле.

Mac OSx
Для Mac OS X загруженный файл называется наподобие netbeans-
8.0-javaeemacosx.dmg (точное имя зависит от версии и комплект­
ности NetBeans, выбранной для загрузки). Для установки NetBeans
перейдите в каталог, куда был загружен файл, и дважды щелкните
на нем.

Linux
NetBeans для Linux загружается в форме сценария командной обо­
лочки. Имя файла будет похоже на netbeans-8.0-javaee-linux.sh,
(точное имя зависит от версии и комплектности NetBeans, выбранной
для загрузки).
Прежде чем NetBeans можно будет установить в Linux, загружен­
ный файл следует сделать исполняемым. Это можно выполнить с
помощью командной строки, перейдя в каталог, куда был загружен
установщик NetBeans, и выполнив следующую команду:
chmod +x filename.sh

Замените filename.sh именем файла, соответствующим платформе


и комплектности NetBeans. После это можно запустить установку из
командной строки:
./filename.sh

И вновь замените filename.sh именем файла, соответствующим


платформе и комплектности NetBeans.
Установка NetBeans 25

Другие платформы
NetBeans для других платформ можно загрузить в виде независи­
мого от платформы ZIP-файла с именем, похожим на: netbeans-8.0-
201403101706-javaee.zip (точное имя файла может измениться в
зависимости от конкретной версии и комплектности NetBeans, вы­
бранной для загрузки).
Чтобы установить NetBeans на одной из этих платформ, извлеките
файлы из ZIP-архива в любой подходящий каталог.

Процедура установки
Несмотря на то, что на разных платформах установка запускается по-
разному, сам процесс установки мало чем отличается.

Исключением из этого правила является установка из ZIP-файла,


в котором, по сути, отсутствует программа-установщик. Установка
этой версии NetBeans заключается в простом извлечении файлов
из архива в любой подходящий каталог.

После запуска программы установки NetBeans на экране должно


появиться окно, как показано на рис. 1.3.

Рис. 1.3. Начальное окно мастера установки NetBeans


26 Глава 1. Знакомство с NetBeans

Перечень пакетов может изменяться, в зависимости от выбранной


комплектации NetBeans. На рис. 1.4 показан снимок экрана, соответ­
ствующий комплектации Java EE.
Чтобы продолжить установку, нужно щелкнуть на кнопке Next>
(Далее>).

Рис. 1.4. Диалог соглашения с условиями лицензирования NetBeans


NetBeans распространяется на условиях двух лицензий: GNU
Public License Version 2 (GPL) с исключением путей к классам
(Class Path Exception, CPE) и Common Development and Distribution
License (CDDL). Обе они одобрены организацией Open Source
Initiative (OSI).
Чтобы продолжить установку, установите флажок I accept the
terms in the license agreement (Я принимаю условия лицензионного
соглашения) и щелкните на кнопке Next> (Далее>) (см. рис. 1.5).
В состав NetBeans входит JUnit – популярный фреймворк тести­
рования программ на Java. Лицензия на использование JUnit отли­
чается от лицензии NetBeans, поэтому условия лицензионного согла­
шения для JUnit необходимо принять отдельно. Щелчок не кнопке
Next> (Далее>) вызовет переход к следующему диалогу мастера
установки (см. рис. 1.6).
Установка NetBeans 27

Рис. 1.5. Диалог соглашения с условиями лицензирования JUnit

Рис. 1.6. Выбор каталога установки NetBeans и каталога JDK


28 Глава 1. Знакомство с NetBeans

Далее мастер запросит каталог для установки NetBeans, и каталог,


куда установлен комплект JDK, который будет использоваться сре­
дой NetBeans.1 Здесь можно указать свой каталог или принять значе­
ние по умолчанию.
После выбора соответствующих каталогов для установки NetBeans
и JDK щелкните на кнопке Next> (Далее>) для продолжения уста­
новки.

Для заполнения поля каталога местоположения JDK NetBeans ис-


пользует значение переменной среды JAVA_HOME.

Далее будет предложено указать каталог для установки сервера


приложений GlassFish и каталог, куда установлен комплект JDK, ко­
торый будет использоваться сервером GlassFish (см. рис. 1.7). Здесь
можно указать свой каталог или принять значение по умолчанию и
щелкнуть на кнопке Next> (Далее>).

Рис. 1.7. Выбор каталога установки GlassFish


Если прежде (см. рис. 1.8) был выбран компонент Tomcat, на сле­
дующем шаге мастер установки предложит выбрать каталог для его
1
В системе может быть установлено несколько версий JDK. – Прим. перев.
Установка NetBeans 29

установки. И снова здесь можно указать свой каталог или принять


значение по умолчанию и щелкнуть на кнопке Next> (Далее>).

Рис. 1.8. Выбор каталога установки Tomcat

Рис. 1.9. Сводная информация о выбранных компонентах


для установки
30 Глава 1. Знакомство с NetBeans

На этом этапе мастер выведет на экран сводную информацию о вы­


бранных компонентах для установки (см. рис. 1.9). После ознакомле­
ния со списком щелкните на кнопке Install (Установить), чтобы на­
чать собственно установку.

Рис. 1.10. Процесс установки NetBeans и дополнительных


компонентов
После этого начнется установка. Мастер выведет на экран индика­
тор выполнения (см. рис. 1.10), указывающий, как далеко продвинул­
ся процесс установки.

Рис. 1.11. Предложение ввести данные об использовании


Первый запуск NetBeans 31

После того как NetBeans и все выбранные компоненты будут уста­


новлены, мастер сообщит об успешной установке и предоставит воз­
можность ввести анонимные данные об использовании продукта
(рис. 1.11). После того как мы сделаем наш вы­
бор, можно просто щелкнуть на кнопке Finish
(Готово), чтобы завершить работу мастера.
В большинстве платформ мастер установки
поместит ярлык NetBeans на рабочий стол (см.
рис. 1.12). Рис. 1.12.
Выполнив двойной щелчок на этом ярлыке Ярлык NetBeans
можно запустить NetBeans. на рабочем столе

Первый запуск NetBeans


Запустить NetBeans можно двойным щелчком на его ярлыке на ра­
бочем столе, после чего появится экранная заставка, которая будет
отображаться в течение всего времени запуска (см. рис. 1.13).

Рис. 1.13. Экранная заставка, появляющаяся


в момент запуска NetBeans
Как только NetBeans запустится, появится страница со ссылками
на демонстрационные примеры, учебные пособия, образцы проектов
и т. д. (см. рис. 1.14).
32 Глава 1. Знакомство с NetBeans

Рис. 1.14. Начальная страница NetBeans


По умолчанию NetBeans отображает эту начальную страницу при
каждом запуске. Если у вас нет желания видеть ее, данный режим
можно отключить, сняв флажок Show on Startup (Показывать при
запуске) внизу страницы. Вы всегда сможете вернуть отображение
начальной страницы, Выбрав пункт главного меню Help | Start Page
(Справка | Начальная страница).

Настройка NetBeans
для разработки
Java EE-приложений
Среда NetBeans предварительно настроена на использование серве­
ра приложений GlassFish и СУРБД (RDBMS) JavaDB. Если вы со­
бираетесь использовать включенные в дистрибутив сервер GlassFish
и СУРБД JavaDB, дополнительная настройка NetBeans не нужна.
Вмес­те с тем мы можем интегрировать NetBeans с другими серверами
приложений Java EE, например такими, как JBoss/WildFly, Weblogic
или WebSphere, а также с другими системами реляционных баз дан­
ных, такими, например, как MySQL, PostgreSQL, Oracle или любой
другой СУРБД, поддерживающей JDBC, что в общем-то означает –
с любой СУРБД.
Настройка NetBeans для разработки Java EE-приложений 33

Интегрирование NetBeans со сторонним


сервером приложений
Интегрировать NetBeans с сервером приложений очень просто. Для
этого нужно выполнить следующие действия:

В этом разделе демонстрируется, как интегрировать NetBeans с


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

1. Прежде всего выберите элемент меню Window | Services


(Окно | Службы) (см. рис. 1.15).

Рис. 1.15. Элемент меню Window | Services (Окно | Службы)


2. Затем щелкните правой кнопкой мыши на узле Servers (Сер­
веры) в дереве окна Services (Службы) и выберите в контекст­
ном меню пункт Add Server... (Добавить сервер...).

Рис. 1.16. Элемент контекстного меню Add Server... (Добавить сервер...)


34 Глава 1. Знакомство с NetBeans

3. В открывшемся окне выберите из списка сервер для установки


и щелкните на кнопке Next> (Далее>).

Рис. 1.17. Выбор сервера из списка


4. Введите путь к каталогу установки сервера приложений (см.
рис. 1.18) и щелкните на кнопке Next> (Далее>).

Рис. 1.18. Ввод каталога для установки сервера


Настройка NetBeans для разработки Java EE-приложений 35

5. Наконец, укажите домен, имя хоста и номер порта для серве­


ра приложений (см. рис. 1.19), после чего щелкните на кнопке
Finish (Готово).

Рис. 1.19. Ввод домена, имени хоста и номера порта для сервера
приложений
В окне Services (Службы) теперь должен отображаться вновь
добавленный сервер приложений (см. рис. 1.20).

Рис. 1.20. Вновь добавленный сервер приложений


в окне Services (Службы)

Вот и все! Мы успешно интегрировали NetBeans со сторонним


сервером приложений.

Powered by TCPDF (www.tcpdf.org)


36 Глава 1. Знакомство с NetBeans

Интегрирование NetBeans с СУРБД


стороннего производителя
NetBeans поставляется интегрированным со встроенной СУРБД
JavaDB. Дополнительно в состав NetBeans входят драйверы JDBC
для других систем СУРБД, таких, например, как Oracle, MySQL и
PostgreSQL.
Чтобы интегрировать среду разработки NetBeans со сторонней
СУРБД, нужно сообщить ей, где находится драйвер JDBC этой базы
данных.

В этом разделе мы создадим соединение с HSQLDB, СУРБД с от-


крытым исходным кодом, написанной на Java, чтобы показать, как
интегрировать NetBeans со сторонней системой СУРБД. Настрой-
ка соединения с другими СУРБД, такими как Oracle, Sybase, SQL
Server и пр., выполняется аналогично.

Добавление драйвера JDBC к NetBeans


Прежде чем пытаться установить соединение со сторонней СУРБД,
нужно добавить соответствующий драйвер JDBC. Для этого (см.
рис. 1.21) щелкните правой кнопкой мыши на узле Drivers (Драйверы)
в разделе Databases (Базы данных) во вкладке Services (Службы). За­
тем выберите в контекстном меню пункт New Driver (Новый драйвер).

Рис. 1.21. Элемент контекстного меню


New Driver (Новый драйвер)
Далее, в открывшемся диалоге (см. рис. 1.22) выберите JAR-файл
с драйвером JDBC для СУРБД. NetBeans «угадывает» имя класса
Настройка NetBeans для разработки Java EE-приложений 37

драйвера, содержащего драйвер JDBC. Если в JAR-файле находит­


ся более чем один класс драйвера, правильный драйвер может быть
выбран из раскрывающегося списке с названием Driver Class (Класс
драйвера). Сделав выбор, щелкните на кнопке OK, чтобы добавить
драйвер к NetBeans.

Рис. 1.22. Выбор драйвера СУРБД


После выполнения вышеописанной процедуры, новый драй­
вер JDBC появится в списке зарегистрированных драйверов (см.
рис. 1.23).

Рис. 1.23. Новый драйвер в списке


зарегистрированных драйверов
38 Глава 1. Знакомство с NetBeans

Соединение со сторонней СУРБД


После добавления драйвера JDBC в NetBeans все готово к установ­
ке соединения со сторонней СУРБД.
Чтобы установить соединение с СУРБД стороннего производите­
ля, щелкните правой кнопкой мыши на драйвере во вкладке Services
(Службы), в открывшемся контекстном меню выберите элемент
Connect Using... (Установить соединение с использованием...).

Рис. 1.24. Элемент контекстного меню Connect Using...


(Установить соединение с использованием...)
Введите URL JDBC, имя пользователя и пароль для базы данных
(см. рис. 1.25).

Рис. 1.25. Настройка параметров соединения


Настройка NetBeans для разработки Java EE-приложений 39

После щелчка на кнопке Next> (Далее>) NetBeans может попро­


сить выбрать схему базы данных. В данном случае (рис. 1.26) в рас­
крывающемся списке была выбрана схема PUBLIC.

Рис. 1.26. Выбор схемы базы данных


На следующем шаге (см. рис. 1.27) мастер настройки предложит
ввести строку подключения к базе данных или принять значение по
умолчанию.

Рис. 1.27. Ввод строки подключения к базе данных


40 Глава 1. Знакомство с NetBeans

После щелчка на кнопке Finish (Готово) наша база данных появится


в списке баз данных в окне Services (Службы). К ней можно подклю­
читься, щелкнув правой кнопкой мыши и выбрав пункт Connect...
(Установить соединение) в контекстном меню (см. рис. 1.28), после
чего ввести имя пользователя и пароль (при добавлении базы данных
мы можем выбрать режим NetBeans – «не запоминать» пароль).

Рис. 1.28. Элемент контекстного меню Connect...


(Установить соединение)
В результате проделанных действий мы благополучно подключи­
ли NetBeans к СУРБД стороннего производителя.

Развертывание нашего первого


приложения
NetBeans поставляется с набором примеров готовых приложений.
Чтобы убедиться, что у нас все настроено правильно, развернем один
из примеров на интегрированном сервере приложений GlassFish, ко­
торый поставляется в комплекте с NetBeans.
Чтобы открыть демонстрационный проект, нужно выбрать в ос­
новном меню пункт File | New Project (Файл | Создать проект), затем
в открывшемся диалоге, в списке Categories (Категории) выбрать
Samples (Примеры) | Java EE. После выбора пункта Java EE в списке
Projects (Проекты) появится список проектов. Для примера выберем
проект JavaServer Faces CDI (Java EE 7) (CDI-компонент платфор­
Развертывание нашего первого приложения 41

мы JavaServer Faces (Java EE 7)) (см. рис. 1.29). Этот проект являет­
ся простым примером использования фреймворка JSF и механизма
внедрения зависимостей Contexts and Dependency Injection (CDI).

Рис. 1.29. Выбор проекта примера


После щелчка на кнопке Next> (Далее>) будет предложено ввести
местоположение проекта (см. рис. 1.30). Значения по умолчанию в
данном случае нас вполне устраивают и их можно оставить.

Рис. 1.30. Выбор местоположения для сохранения проекта


42 Глава 1. Знакомство с NetBeans

После щелчка на кнопке Finish (Готово) наш новый проект появит­


ся (см. рис. 1.31) в окне Projects (Проекты).

Рис. 1.31. Новый проект появился в окне Projects (Проекты)


Мы можем скомпилировать, упаковать и развернуть проект в одно
действие, щелкнув на нем правой кнопкой мыши и выбрав пункт Run
(Выполнить) в открывшемся контекстном меню (см. рис. 1.32).

Рис. 1.32. Запуск компиляции, упаковки


и развертывания проекта в одно действие
На этом этапе мы должны увидеть результат работы сценария
сборки (см. рис. 1.33). Кроме того, автоматически должны запустить­
ся интегрированный сервер приложений GlassFish и интегрирован­
ная СУРБД JavaDB.

Рис. 1.33. Результаты работы сценария сборки


Подсказки NetBeans для эффективной разработки 43

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


ся новое окно или вкладка браузера, где появится страница по умол­
чанию для нашего приложения-примера (см. рис. 1.34).

Рис. 1.34. Страница по умолчанию для приложения-примера


Если ваш браузер вывел страницу, подобную показанной на
рис. 1.34, это может служить признаком, что NetBeans и GlassFish ра­
ботают должным образом и мы готовы начать разрабатывать наши
собственные приложения Java EE.

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

Автозавершение кода
Редактор программного кода NetBeans содержит очень удобную
функцию автозавершения. Например если нужно создать закрытую
переменную (область видимости private), не нужно вводить слово
«private» целиком – можно просто написать первые три буквы («pri»)
и нажать комбинацию клавиш Ctrl+Space – NetBeans самостоятель­
но завершит за нас слово «private».
Автозавершение кода также работает для типов переменных и воз­
вращаемых методом значений, например, чтобы объявить перемен­
ную типа java.util.List, достаточно ввести первые несколько сим­
волов типа, затем нажать комбинацию клавиш Ctrl+Space и NetBeans
попытается выполнить автозавершение, используя любые импорти­
рованные пакеты (см. рис. 1.35). Чтобы завершить попытку NetBeans
выполнить автозавершение для любого типа в CLASSPATH, следует
нажать комбинацию клавиш Ctrl+Space еще раз.
44 Глава 1. Знакомство с NetBeans

Рис. 1.35. Страница по умолчанию для приложения-примера


Как видно на рис. 1.35, для выбранного варианта автозавершения
NetBeans выводит на экран его описание JavaDoc. Другой экономя­
щей время функцией является автоматическое импортирование вы­
бранного класса.
После выбора типа переменной можно снова нажать комбинацию
Ctrl+Space, прямо после имени типа, и NetBeans предложит имена
переменных (см. рис. 1.36).

Рис. 1.36. Функция автозавершения может


предложить на выбор имена переменных
Подсказки NetBeans для эффективной разработки 45

Чтобы инициализировать переменную новым значением, можно


снова нажать комбинацию Ctrl+Space и на экране появится список
допустимых типов в качестве вариантов для завершения кода, как по­
казано на рис. 1.37.

Рис. 1.37. Список допустимых типов


для инициализации переменной

В нашем примере тип (java.util.List) является интерфейсом, по­


этому в качестве возможных кандидатов на автозавершение кода бу­
дут показаны все классы, реализующие этот интерфейс. Если бы тип
нашей переменной был классом, в качестве кандидатов на автозавер­
шение были бы показаны и наш класс, и все его подклассы.
Когда далее в коде потребуется использовать эту переменную,
можно просто ввести первые несколько символов ее имени и нажать
комбинацию Ctrl+Space, как показано на рис. 1.38.
Чтобы вызвать метод объекта, достаточно ввести точку в конце
имени переменной и все доступные методы будут выведены на экран,
как варианты завершения кода, как показано на рис. 1.39.
Обратите внимание, что для выбранного метода на экран автома­
тически выводится описание JavaDoc.
46 Глава 1. Знакомство с NetBeans

Рис. 1.38. Список подходящих имен переменных

Рис. 1.39. Список доступных методов


Подсказки NetBeans для эффективной разработки 47

Шаблоны кода
Шаблоны кода являются сокращениями для часто используемых
фрагментов кода. Чтобы использовать шаблон кода, нужно просто
ввести его в редакторе и нажать клавишу Tab, дабы развернуть сокра­
щение в полный фрагмент кода, который оно представляет.
Например, если ввести sout и нажать клавишу Tab, данное сокра­
щение будет развернуто в System.out.println(""); с текстовым кур­
сором между двойными кавычками.
Некоторые из наиболее полезных шаблонов кода перечислены в
табл. 1.2. Имейте в виду, что шаблоны кода являются чувствительны­
ми к регистру.
Таблица 1.2. Список шаблонов кода
Сокра-
Пример развернутого текста Описание
щение

Psf public static final Полезно для объявле-


ния открытых (public),
статических (static)
и финальных (final)
переменных.

fore for (Object object : list) { Используется для


определения расши-
} ренной версии цикла
for, выполняющего
обход коллекции.

ifelse if (boolVar) { Генерирует условный


оператор if-else.
} else {
}

psvm public static void Генерирует метод main


main(String[] args) { для класса.

soutv System.out.println("boolVar = " + Генерирует инструк-


boolVar); цию System.out.
println(), выводя-
щую на экран значение
переменной

trycatch try { Генерирует блок try/


} catch (Exception exception) { catch.

}
48 Глава 1. Знакомство с NetBeans

Сокра-
Пример развернутого текста Описание
щение

whileit while (iterator.hasNext()) { Генерирует цикл while


Object object = iterator.next(); для перебора итерато-
} ра (Iterator)

Чтобы увидеть полный список шаблонов кода, выберите в главном


меню пункт Tools | Options (Сервис | Параметры), затем щелкните на
значке Editor (Редактор) и выберите вкладку Code Templates (Ша­
блоны кода), как показано на рис. 1.40.

Рис. 1.40. Список шаблонов кода


Мы можем добавлять свои шаблоны, щелкая на кнопке New (Но­
вый). После щелчка будет предложено ввести сокращение (аббревиа­
туру) шаблона, после чего новый шаблон будет добавлен в список
шаблонов и автоматически выбран. Теперь можно ввести текст рас­
ширения для шаблона во вкладке Expanded Text (Раскрытый текст).
Не мешает упомянуть о том, что шаблоны кода поддерживаются не
только для Java, но также для HTML, CSS и всех других языков, под­
держиваемых в NetBeans. Чтобы увидеть/отредактировать шаблоны
Подсказки NetBeans для эффективной разработки 49

для других языков, просто выберите нужный язык в раскрывающемся


меню Language (Язык) во вкладке Code Templates (Шаблоны кода),
как показано на рис. 1.41.

Рис. 1.41. Выбор языка для получения списка шаблонов кода

Клавиши быстрого вызова


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

Некоторые из самых полезных комбинаций клавиш NetBeans пере-


числены в этом разделе, но этот список не является исчерпываю-
щим, полный список комбинаций клавиш NetBeans можно найти,
выбрав пункт меню Help | Keyboard Shortcuts Card (Справка | Таб­
лица сочетаний клавиш).

Одной из полезных комбинаций клавиш, позволяющей быстро пе­


ремещаться в пределах большого файла Java, является Ctrl+F12. Эта
50 Глава 1. Знакомство с NetBeans

комбинация передает фокус ввода в окно


Navigator (Навигатор), где отображается
схема текущего файла Java со всеми мето­
дами в нем и переменными-членами (см.
рис. 1.42).
Когда фокус ввода находится в окне
Navigator (Навигатор), можно просто
начать ввод искомого имени, чтобы со­
кратить список методов и переменных-
членов. Эта комбинация клавиш работает
очень быстро и довольно удобна для пере­
мещения по большим файлам.
Нажатие на Alt+F12 откроет окно
Hierarhy (Иерархия) представляющее по­ Рис. 1.42. Выбор языка
ложение текущего класса Java в иерархии для получения списка
классов, как показано на рис. 1.43. шаблонов кода

Рис. 1.43. Окно Hierarhy (Иерархия)


с иерархией классов
Эту комбинацию можно использовать
для быстрого перемещения к суперклас­
су или подклассу текущего класса.
Другой полезной комбинацией яв­
ляется Alt+Insert, она может исполь­
зоваться для вставки часто использу­
емого кода, такого как определение
конструктора, методов get и set и т.  д.
(см. рис. 1.44).
Код будет сгенерирован, начиная с те­
кущей позиции текстового курсора.
Кроме того, когда курсор находится
непосредственно за от открывающей Рис. 1.44. Окно Generate
или закрывающей фигурной скобкой, (Создать) для выбора соз-
комбинация Ctrl+[ переместит резуль­ даваемого элемента
Подсказки NetBeans для эффективной разработки 51

тат ввода внутрь соответствующей пары скобок. Эта комбинация ра­


ботает для фигурных, круглых и квадратных скобок. Нажатие комби­
нации Ctrl+Shift+[ имеет подобный эффект, но не только помещает
ввод в соответствующую пару фигурных скобок, а еще и выбирает
вставленный код (см. рис. 1.45).

Рис. 1.45. После вставки код автоматически выбирается


Иногда нужно узнать все точки в проекте, где вызывается опреде­
ленный метод. Мы легко можем получить эту информацию, выделив
метод и нажав комбинацию Alt+F7 (см. рис. 1.46).

Рис. 1.46. Список мест в проекте, где вызывается


интересующий метод
Данная комбинация работает также с переменными.
NetBeans указывает на ошибки компиляции в коде, подчеркивая
неправильные строки волнистой красной линией. Установив курсор в
пределы неправильного кода, и нажав комбинацию Alt+Enter, можно
выбрать подходящий вариант исправления проблемы из списка (см.
рис. 1.47).

Рис. 1.47. Ошибочный код и варианты его исправления


Иногда навигация по всем файлам в проекте может быть затруд­
нена, особенно если известно имя файла, который нужно открыть, но
52 Глава 1. Знакомство с NetBeans

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


нужную комбинацию клавиш Shift+Alt+O, которая позволяет бы­
стро открыть любой файл нашего проекта (см. рис. 1.48).

Рис. 1.48. Диалог выбора файлов проекта


Другими полезными комбинациями клавиш являются
Shift+Alt+F – для быстрого форматирования кода; Ctrl+E (Cmd+E
в Mac OS) – стирает текущую строку намного быстрее, чем вы­
деление строки c последующим нажатием клавиши Backspace.
Иногда мы импортируем некоторый класс, а позже решаем не ис­
пользовать его. Некоторые из нас удаляют строки, использующие
класс, однако забывают удалять строку импорта в верхней части
исходного файла, из-за чего NetBeans генерирует предупреждение
о неиспользуемом импорте. Нажатие комбинации Ctrl+Shift+I
удалит все инструкции импортирования неиспользуемых классов
одним махом и заодно попытается добавить недостающие инструк­
ции импортирования.
Стоит упомянуть еще об одной особенности, хотя, строго говоря,
она является не комбинацией клавиш, а очень полезной функцией ре­
дактора NetBeans, состоящей в том, что щелчок левой кнопкой мыши
на имени метода или переменной при нажатой клавише Ctrl превра­
тит метод или переменную в гиперссылку. Щелчок на этой гипер­
ссылке откроет объявление метода или переменной.
Подсказки NetBeans для эффективной разработки 53

Изучение визуальных индикаторов


NetBeans
В дополнение к комбинациям «быстрых» клавиш, шаблонов и функ­
ции автозавершения кода, NetBeans предлагает много визуальных
индикаторов, которые позволяют лучше понять код с первого взгля­
да. Некоторые из наиболее полезных показаны на рис. 1.49.

Рис. 1.49. Визуальные индикаторы в редакторе NetBeans


О предупреждениях компилятора NetBeans сообщает двумя спо­
собами: подчеркивает строку волнистой желтой линией и помещает
значок в поле слева от неправильной строки.
Лампочка в значке указывает, что у NetBeans есть предложения
относительно решения проблемы. Если установить текстовый кур­
сор в пределы неправильного кода и нажать комбинацию Alt+Enter,
это, как было описано в предыдущем разделе, приведет к тому, что
NetBeans предложит один или более вариантов решения.
Точно так же, при обнаружении ошибки компиляции, NetBeans
подчеркнет неправильную строку красной волнистой линией и по­
местит значок в поле слева от строки с ошибкой.
И вновь лампочка указывает, что у NetBeans есть предложения
относительно решения проблемы, нажатие комбинации клавиш
Alt+Enter в этом случае позволит увидеть эти предложения.
54 Глава 1. Знакомство с NetBeans

NetBeans не только визуально выделяет ошибки в коде, он также


предоставляет другие сигналы, например, если установить курсор ря­
дом с открывающей или закрывающей фигурной скобкой, обе парные
скобки – открывающая и закрывающая – будут подсвечены, как по­
казано в методе populateList() на рис. 1.49.
Если один из наших методов переопределяет метод родительско­
го класса, в поле слева, рядом с объявлением метода, будет помещен
значок .
Значок представляет собой букву «O» в верхнем регистре внутри
круга, где «O» означает «overrides», то есть «переопределяет».
Точно так же, когда один из наших методов является реализацией
метода, объявленного в интерфейсе, в поле слева, рядом с объявлени­
ем метода, будет помещен значок .
Значок с изображением латинской буквы «I» в верхнем регистре
внутри зеленого круга обозначает «implements», то есть «реализует».
NetBeans также предоставляет визуальные индикаторы, изменяя
форму шрифта или его цвет. Например, статические методы и пере­
менные отображаются курсивом, переменные-члены отображаются
зеленым цветом, а зарезервированные слова Java – синим цветом.
Другой удобной функцией редактора NetBeans является возмож­
ность подсветить метод или переменную везде, где он/она использу­
ется в текущем открытом файле.

Функция ускорения разработки


HTML5
NetBeans может обновлять развернутые веб-страницы в масштабе ре­
ального времени, по мере редактирования разметки страниц. Данная
возможность поддерживается для файлов HTML и фейслетов (face­
lets) JSF (обсуждаются в следующей главе).
Чтобы эта функция работала, необходимо использовать браузер на
основе WebKit, встроенный в NetBeans, или браузер Google Chrome
с расширением NetBeans Connector. Чтобы выбрать браузер для за­
пуска веб-приложений во время отладки, щелкните на ярлыке бра­
узера в панели инструментов NetBeans и выберите один из тех, что
присутствует в разделе With NetBeans Connector (С коннектором
NetBeans), как показано на рис. 1.50.
Функция ускорения разработки HTML5 по умолчанию настро­
ена на использование встроенного браузера на основе WebKit. Для
Функция ускорения разработки HTML5 55

ее проверки выберите встроенный браузер WebKit и запустите при­


ложение, развернутое нами выше в этой главе в разделе «Разверты­
вание нашего первого приложения». Приложение запустится в окне
NetBeans, если используется встроенный браузер (см. рис. 1.51).

Рис. 1.50. Выбор браузера с коннектором NetBeans

Рис. 1.51. При использовании встроенного браузера, приложение


запустится в окне NetBeans
56 Глава 1. Знакомство с NetBeans

Чтобы протестировать функцию ускорения разработки HTML5,


давайте внесем простое изменение в одну из страниц приложения.
Откройте файл home.xhtml и найдите строку со словом «Number».
<h:panelGrid border="1" columns="5" style="font-size: 18px;">
Number:
<h:inputText id="inputGuess" value="#{game.guess}"
required="true" size="3"
disabled="#{game.number eq game.guess}"
validator="#{game.validateNumberRange}">
</h:inputText>
<h:commandButton id="GuessButton" value="Guess"
action="#{game.check}"
disabled="#{game.number eq game.guess}"/>
<h:commandButton id="RestartButton" value="Reset"
action="#{game.reset}" immediate="true" />
<h:outputText id="Higher" value="Higher!"
rendered="#{game.number gt game.guess and
game.guess ne 0}"
style="color: red"/>
<h:outputText id="Lower" value="Lower!"
rendered="#{game.number lt game.guess and
game.guess ne 0}"
style="color: red"/>
</h:panelGrid>

Замените слово «Number» строкой «Your Guess», чтобы разметка


выглядела так:
<h:panelGrid border="1" columns="5" style="font-size: 18px;">
Your Guess:
<h:inputText id="inputGuess" value="#{game.guess}"
required="true" size="3"
disabled="#{game.number eq game.guess}"
validator="#{game.validateNumberRange}">
</h:inputText>
<h:commandButton id="GuessButton" value="Guess"
action="#{game.check}"
disabled="#{game.number eq game.guess}"/>
<h:commandButton id="RestartButton" value="Reset"
action="#{game.reset}" immediate="true" />
<h:outputText id="Higher" value="Higher!"
rendered="#{game.number gt game.guess and
game.guess ne 0}" style="color: red"/>
<h:outputText id="Lower" value="Lower!"
rendered="#{game.number lt game.guess and
game.guess ne 0}" style="color: red"/>
</h:panelGrid>
Функция ускорения разработки HTML5 57

Сохраните файл и, не выполняя повторного развертывания прило­


жения или обновления страницы, вернитесь в окно встроенного бра­
узера. Изменения должны отобразиться на странице (см. рис. 1.52).

Рис. 1.52. Изменения автоматически отобразились встроенным


браузером
Чтобы функция ускорения разработки HTML5 заработала в
Chrome, нужно в этом браузере установить расширение NetBeans
Connector. Если выбрать Chrome, как веб-браузер (в разделе With
NetBeans Connector (С коннектором NetBeans)) и попытаться запус­
тить приложение, NetBeans предложит установить упомянутое рас­
ширение (см. рис. 1.53).

Рис. 1.53. NetBeans предложит установить


расширение NetBeans Connector
58 Глава 1. Знакомство с NetBeans

Если щелкнуть на кнопке Go to Chrome Web Store (Перейти к


Интернет-магазину Chrome), в браузере откроется страница загрузки
расширения NetBeans Connector (рис. 1.54).

Рис. 1.54. Страница загрузки расширения


NetBeans Connector NetBeans Connector
Если щелкнуть на кнопке Free (Установить) в правом верхнем
углу, появится всплывающее окно, запрашивающее разрешение на
установку расширения NetBeans Connector (см. рис. 1.55)

Рис. 1.55. Запрос разрешения на установку


расширения NetBeans Connector
Резюме 59

Щелчок на кнопке Add (Добавить) автоматически установит рас­


ширение. После этого можно запустить проект в браузере Chrome и
любые изменения в разметке будут немедленно отражаться в браузе­
ре (см.рис. 1.56).

Рис. 1.56. После установки расширения Chrome будет немедленно


отображать любые изменения в разметке
Как показано на рис. 1.56, когда приложение запущено через кон­
нектор NetBeans, браузер Chrome отображает сообщение, предупреж­
дающее об этом факте.

Резюме
В этой главе мы узнали, как установить NetBeans.
Мы также узнали, как настроить NetBeans на работу со сторонни­
ми серверами приложений Java EE и с системами реляционных баз
данных от сторонних производителей, включая регистрацию драйве­
ра JDBC для рассматриваемой СУРБД (RDBMS).
Также мы создали и развернули наше первое приложение Java EE,
использовав один из демонстрационных проектов, поставляемых
вместе с NetBeans.
Наконец мы рассмотрели некоторые возможности NetBeans, такие
как автозавершение кода, шаблоны кода, комбинации клавиш и визу­
альные индикаторы, позволяющие нам, разработчикам программного
обеспечения, выполнять свою работу более эффективно.
Глава 2.
Разработка веб-приложений
с использованием
JavaServer Faces 2.2

JavaServer Faces – стандартный фреймворк Java EE, предназначен­


ный для создания веб-приложений. В этой главе мы узнаем, как ис­
пользование JSF может упростить разработку веб-приложений.
Здесь будут затронуты следующие темы:
создание проекта JSF в NetBeans;
верстка страниц JSF с использованием JSF-тега <h:panelGrid>;
статическая и динамическая навигация между страницами;
создание именованных компонентов, внедряемых с использо­
ванием CDI, для хранения данных и логики приложения;
реализация пользовательских валидаторов JSF;
как упростить создание шаблонов JSF 2.2 с помощью мастеров
NetBeans;
как упростить создание составных (сложных) компонентов
JSF 2.2 с помощью NetBeans.

Введение в JavaServer Faces


До появления JSF большинство веб-приложений на Java обычно раз­
рабатывалось с использованием нестандартных веб-фреймворков
(то есть, не являющихся частью спецификации Java EE), таких
как Struts Apache, Tapestry, Spring Web MVC и многих других. Эти
фреймворки создаются поверх стандартных Servlet API и JSP API,
и автоматизируют большую часть функциональности, которую при­
ходится кодировать вручную при непосредственном использовании
этих API.
Разработка нашего первого приложения JSF 61

Большое разнообразие веб-фреймворков часто приводит к «ана­


литическому параличу», то есть разработчики нередко тратят уйму
времени на оценку фреймворков для своих приложений.
В результате введения JSF в спецификацию Java EE, теперь стан­
дартный веб-фреймворк имеется в любом Java EE-совместимом сер­
вере приложений, а поскольку JSF стал частью стандарта, многие
разработчики выбирают его для создания своих пользовательских
интерфейсов.

Разработка нашего первого


приложения JSF
С точки зрения разработчика, приложение JSF состоит из ряда стра­
ниц XHTML, содержащих теги JSF, один или более управляемых
именованных компонентов CDI и необязательный конфигурацион­
ный файл с именем faces-config.xml.

Файл faces-config.xml был необходим в JSF 1.x, однако в JSF 2.0


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

Создание нового проекта JSF


Чтобы создать новый проект JSF, нужно выбрать пункт меню File |
New Project (Файл | Создать проект), в списке Categories (Катего­
рии) выбрать категорию Java Web и тип проекта Web Application
(Веб-приложение).
После щелчка на кнопке Next> (Далее) нужно ввести название
проекта (см. рис. 2.1) и, при необходимости, изменить другую инфор­
мацию о проекте, хотя NetBeans предоставляет разумные значения
по умолчанию.
На следующей странице мастера (рис. 2.2) можно выбрать сервер,
версию Java EE и путь контекста приложения. В нашем примере мы
просто примем значения по умолчанию.
На следующей странице мастера нового проекта (рис. 2.3) можно
выбрать фреймворки для использования в веб-приложении.
62 Глава 2. Разработка веб-приложений с использованием JavaServer...

Рис. 2.1. Ввод названия проекта и другой информации


(если необходимо)

Рис. 2.2. Выбор сервера, версии Java EE и пути контекста приложения


Неудивительно, что для приложений JSF мы выбрали фреймворк
JavaServer Faces.
Разработка нашего первого приложения JSF 63

Рис. 2.3. Выбор используемых фреймворков


После щелчка на кнопке Finish (Готово)
мастер сгенерирует скелет JSF-проекта,
состоящий из единственного файла фейс­
лета с именем index.xhtml и конфигураци­
онного файла web.xml (см. рис. 2.4).
Файл web.xml является стандартным
конфигурационным файлом для веб-
приложений на Java. Этот файл стал не­
обязательным в версии 3.0 Servlet API, ко­ Рис. 2.4. Содержимое
торый был введен в Java EE 6. Во многих скелета нового проекта
случаях в файле web.xml больше нет не­
обходимости, поскольку большинство параметров настройки теперь
может быть определено с помощью аннотаций. Тем не менее, в при­
ложениях JSF он не будет лишним, потому что позволит определить
этап проекта JSF (JSF project stage).
<?xml version='1.0' encoding='UTF-8'?>
<web-app version="3.1" xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd">
<context-param>
<param-name>javax.faces.PROJECT_STAGE</param-name>
64 Глава 2. Разработка веб-приложений с использованием JavaServer...

<param-value>Development</param-value>
</context-param>
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>/faces/*</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>
30
</session-timeout>
</session-config>
<welcome-file-list>
<welcome-file>faces/index.xhtml</welcome-file>
</welcome-file-list>
</web-app>

Как видите, NetBeans автоматически устанавливает этап проекта


JSF в значение Development (разработка). Выбор этапа Development
настраивает JSF для предоставления дополнительной отладочной
информации, недоступной на других этапах. Например, одна из ти­
пичных проблем при разработке страниц заключается неудачном
завершении проверки допустимости значения одного или несколь­
ких полей, когда разработчик своевременно не добавил в страницу
тег <h:message> или <h:messages> (подробнее об этом рассказывается
ниже). Когда это происходит, создается впечатление, что страница
ничего не делает или навигация между страницами перестала рабо­
тать. Когда для проекта устанавливается этап Development, сообщения
об ошибках проверки допустимости будут автоматически добавлены
к странице, без явного добавления разработчиком. Безусловно, мы
должны явно добавить эти теги, прежде чем выпустить промышлен­
ную версию кода, поскольку приложение с иным значением этапа
проекта не будет автоматически генерировать сообщения об ошибках
проверки допустимости и пользователи их не увидят.
Ниже приведены допустимые значения параметра контекста
javax.faces.PROJECT_STAGE для фейслета:
• Development (разработка);
• Production (промышленная эксплуатация);
• SystemTest (системное тестирование);
• UnitTest (модульное тестирование).
Разработка нашего первого приложения JSF 65

Как упоминалось выше, этап Development добавляет дополнитель­


ную отладочную информацию для упрощения разработки. На эта­
пе Production основное внимание уделяется производительности.
Другие два допустимых значения для этапа проекта (SystemTest и
UnitTest) позволяют реализовать нестандартное поведение для этих
двух фаз.
Класс javax.faces.application.Application имеет метод
getProjectStage(), возвращающий текущий этап проекта. На основа­
нии значения, возвращаемого этим методом, можно реализовать код,
который будет выполняться только на соответствующем этапе. Сле­
дующий фрагмент кода это иллюстрирует:
public void someMethod() {
FacesContext facesContext = FacesContext.getCurrentInstance();
Application application = facesContext.getApplication();
ProjectStage projectStage = application.getProjectStage();

if (projectStage.equals(ProjectStage.Development)) {
// выполнить операции на этапе разработки
} else if (projectStage.equals(ProjectStage.Production)) {
// выполнить операции на этапе промышленной эксплуатации
} else if (projectStage.equals(ProjectStage.SystemTest)) {
// выполнить операции на этапе системного тестирования
} else if (projectStage.equals(ProjectStage.UnitTest)) {
// выполнить операции на этапе модульного тестирования
}
}

Как показано выше, можно реализовать код, который будет выпол­


няться на любом допустимом этапе проекта, опираясь на значение,
возвращаемое методом getProjectStage() класса Application.
При создании веб-проекта Java с использованием JSF, автоматичес­
ки генерируется фейслет.
Файл этого фейслета будет выглядеть, как показано ниже:
<?xml version='1.0' encoding='UTF-8' ?>
<!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"
xmlns:h="http://xmlns.jcp.org/jsf/html">
<h:head>
<title>Facelet Title</title>
</h:head>
<h:body>
Hello from Facelets
</h:body>
</html>
66 Глава 2. Разработка веб-приложений с использованием JavaServer...

Как видно в приведенном фрагменте, фейслет является не чем


иным, как файлом XHTML, использующим некоторые специфичные
для JSF пространства имен XML. В странице выше сгенерировано
следующее определение пространства имен, которое позволяет ис­
пользовать библиотеку h компонентов JSF (для HTML):
xmlns:h="http://xmlns.jcp.org/jsf/html"

Это объявление пространства имен дает возможность использо­


вать определенные теги JSF, такие как <h:head> и <h:body>, которые
являются заменой поведения стандартных тегов HTML/XHTML
<head> и <body> соответственно.
Также в JSF часто используется пространство имен f, обычно опре­
деляемое так:
xmlns:f="http://xmlns.jcp.org/jsf/core"

Пространство имен f содержит теги, не отображаемые на странице


непосредственно, но позволяющие определять, например, элементы
для раскрывающихся списков или операции привязки (bind actions)
для компонентов JSF.
Приложение, сгенерированное мастером нового проекта, является
простым, но законченным веб-приложением JSF. Увидеть его в дей­
ствии можно, щелкнув правой кнопкой мыши на проекте в окне про­
екта и выбрав в контекстном меню пункт Run (Выполнить). После
этого будет запущен сервер приложений (если до этого он не рабо­
тал), приложение будет развернуто, и запустится браузер, установ­
ленный в системе по умолчанию, с открытой главной страницей при­
ложения (см. рис. 2.5).

Рис. 2.5. Браузер с открытой главной страницей приложения

Добавление в страницу возможности


ввода данных
Сгенерированное приложение, конечно, является всего лишь отправ­
ной точкой для создания нового приложения. Далее мы изменим сге­
Разработка нашего первого приложения JSF 67

нерированный файл index.xhtml и добавим в него возможность ввода


пользовательских данных.
Первое, что нужно сделать – добавить тег <h:form> в страницу. Тег
<h:form> эквивалентен тегу <form> в стандартных HTML-страницах.
После ввода первых нескольких символов тега <h:form>, NetBeans
автоматически предложит несколько вариантов завершения кода (см.
рис. 2.6).

Рис. 2.6. NetBeans автоматически предложит несколько вариантов


завершения кода
Как только будет выбран тот или иной тег (в данном случае
<h:form>), NetBeans выведет его описание.
После добавления тега <h:form> и нескольких дополнительных
тегов JSF наша страница будет выглядеть примерно так:
<?xml version='1.0' encoding='UTF-8' ?>
<!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"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:f="http://xmlns.jcp.org/jsf/core">
<h:head>
68 Глава 2. Разработка веб-приложений с использованием JavaServer...

<title>Registration</title>
<h:outputStylesheet library="css" name="styles.css"/>
</h:head>
<h:body>
<h3>Registration Page</h3>
<h:form>
<h:panelGrid columns="3"
columnClasses="rightalign,leftalign,leftalign">
<h:outputLabel value="Salutation: " for="salutation"/>
<h:selectOneMenu id="salutation" label="Salutation"
value="#{registrationBean.salutation}" >
<f:selectItem itemLabel="" itemValue=""/>
<f:selectItem itemLabel="Mr." itemValue="MR"/>
<f:selectItem itemLabel="Mrs." itemValue="MRS"/>
<f:selectItem itemLabel="Miss" itemValue="MISS"/>
<f:selectItem itemLabel="Ms" itemValue="MS"/>
<f:selectItem itemLabel="Dr." itemValue="DR"/>
</h:selectOneMenu>
<h:message for="salutation"/>
<h:outputLabel value="First Name:" for="firstName"/>
<h:inputText id="firstName" label="First Name"
required="true"
value="#{registrationBean.firstName}" />
<h:message for="firstName" />
<h:outputLabel value="Last Name:" for="lastName"/>
<h:inputText id="lastName" label="Last Name"
required="true"
value="#{registrationBean.lastName}" />
<h:message for="lastName" />
<h:outputLabel for="age" value="Age:"/>
<h:inputText id="age" label="Age" size="2"
value="#{registrationBean.age}"/>
<h:message for="age"/>
<h:outputLabel value="Email Address:" for="email"/>
<h:inputText id="email" label="Email Address"
required="true"
value="#{registrationBean.email}">
</h:inputText>
<h:message for="email" />
<h:panelGroup/>

<h:commandButton id="register" value="Register"


action="confirmation" />
</h:panelGrid>
</h:form>
</h:body>
</html>

На рис. 2.7 показано, как будет выглядеть страница во время вы­


полнения приложения.
Разработка нашего первого приложения JSF 69

Рис. 2.7. Страница с полями ввода во время


выполнения приложения
Все поля ввода JSF должны находиться внутри тега <h:form>. Тег
<h:panelGrid> упрощает аккуратное размещение тегов на странице
JSF. Его можно считать сеткой, в ячейки которой помещаются другие
теги JSF. Атрибут columns тега <h:panelGrid> определяет число столб­
цов в сетке. Каждый компонент JSF внутри <h:panelGrid> будет поме­
щен в отдельную ячейку. Когда внутрь <h:panelGrid> будет помещено
число компонентов, равное значению атрибута columns (три в нашем
примере), автоматически будет создана новая строка.
Следующая таблица показывает, как размещаются теги внутри
<h:panelGrid>:

Первый тег Второй тег Третий тег

Четвертый тег Пятый тег Шестой тег

Седьмой тег Восьмой тег Девятый тег

Каждая строка в теге <h:panelGrid> состоит из тега <h:outputLabel>,


поля ввода и тега <h:message>.
Атрибут columnClasses тега <h:panelGrid> позволяет присваивать
стили CSS каждому столбцу в сетке панели, значением атрибута
value должен быть список стилей CSS (определенных в таблице
стилей CSS), разделенных запятой. Первый стиль будет применен к
первому столбцу, второй – ко второму столбцу, третий – к третьему и
т. д. Если бы сетка имела более трех столбцов, к четвертому столбцу
был бы применен первый стиль из атрибута columnClasses, к пятому
столбцу – второй, и т. д.
70 Глава 2. Разработка веб-приложений с использованием JavaServer...

Если нужно определить стили для строк, сделать это можно с по­
мощью его атрибута rowClasses, который работает точно так же, как
columnClasses для столбцов.
Обратите внимание на тег <h:outputStylesheet> внутри <h:head>
в начале страницы. Этот тег был введен в JSF 2.0. Еще одна новая воз­
можность, которая появилась в JSF 2.0,
предоставляет таблице доступ к ката­
логу стандартных ресурсов. Ресурсы
(такие как таблицы стилей CSS, файлы
JavaScript и изображения) можно поме­
стить в каталог верхнего уровня с име­
нем resources, и теги JSF автоматически
получат доступ к этим ресурсам. В дан­
ном проекте каталог resources следует Рис. 2.8. Каталог resources
поместить в папку Web Pages (Веб- в папке Web Pages
страницы), как показано на рис. 2.8. (Веб-страницы)
Теперь нужно создать подкаталог для таблиц стилей CSS (в соот­
ветствии с соглашениями этот каталог должен иметь имя CSS) и по­
местить таблицы стилей CSS в этот подкаталог.

Таблица стилей CSS для нашего примера очень проста, поэтому не


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

Значение атрибута library в теге <h:outputStylesheet> должно


соответствовать имени каталога с файлом CSS, а значение атрибута
name – имени файла CSS.
В дополнение к файлам CSS, нужно поместить все файлы JavaScript
в подкаталог с названием javascript в каталоге resources. Благодаря
этому такие файлы станут доступны тегам <h:outputScript>, исполь­
зующим атрибут library со значением "javascript" и атрибут name
с именем файла.
Теперь, когда мы обсудили, как расположить элементы на странице
и получить доступ к ресурсам, сосредоточим наше внимание на эле­
ментах ввода/вывода на странице.
Тег <h:outputLabel> генерирует метку для поля ввода в форме, зна­
чение атрибута for должно совпадать со значением атрибута id соот­
ветствующего поля ввода.
Тег <h:message> генерирует сообщение об ошибке для поля ввода,
значение атрибута for должно совпадать со значением атрибута id со­
ответствующего поля ввода.

Powered by TCPDF (www.tcpdf.org)


Разработка нашего первого приложения JSF 71

Первая строка в нашей сетке содержит тег <h:selectOneMenu>. Этот


тег генерирует HTML-тег <select> в отображаемой странице.
У каждого тега JSF имеется атрибут id, значение этого атрибута
должно быть строкой, с уникальным для тега идентификатором. Если
не определить значение этого атрибута явно, оно будет сгенерирован
автоматически. Однако лучше явно определять идентификатор для
каждого компонента, поскольку он используется в сообщениях об
ошибках во время выполнения. Затронутые компоненты гораздо лег­
че определить, если явно задать их идентификаторы.
При использовании тегов <h:label> для создания меток полей вво­
да или тегов <h:message> для определения сообщений об ошибках,
возникающих при проверке допустимости, следует явно установить
значение атрибута id, поскольку его необходимо также указать в ка­
честве значения атрибута for соответствующих тегов <h:label> и
<h:message>.
Каждый тег поля ввода JSF имеет атрибут label. Этот атрибут ис­
пользуется для включения в сообщения об ошибках, возникающих
при проверке допустимости и отображаемых на странице. Если не
определить значение атрибута label явно, имя поля в сообщении об
ошибке будет определено по его атрибуту id.
Каждое поле ввода JSF имеет атрибут value, в теге <h:selectOneMenu>
этот атрибут указывает, какой из элементов в отображаемом теге
<select> будет выбран. Значение этого атрибута должно соот­
ветствовать значению атрибута itemValue одного из вложенных
тегов <f:selectItem>. Значением данного атрибута обычно явля­
ется выражение связывания (binding expression), которое озна­
чает, что значение читается из именованного компонента CDI во
время выполнения. В нашем примере используется выражение свя­
зывания #{registrationBean.salutation}. Во время выполнения
произойдет следующее: JSF найдет именованный компонент CDI с
именем registrationBean, затем атрибут этого компонента с именем
salutation, затем вызовет метод get() этого атрибута и возвращае­
мое им значение использует для определения выбранного значения в
HTML-теге <select>.
В тег <h:selectOneMenu> вложены несколько тегов <f:selectItem>.
Эти теги генерируют HTML-теги <option> внутри HTML-тега
<select>, сгенерированного тегом <h:selectOneMenu>. Атрибут
itemLabel определяет значение, которое будет видеть пользователь, в
то время как атрибут itemValue будет содержать значение, отправлен­
ное серверу с формой.
72 Глава 2. Разработка веб-приложений с использованием JavaServer...

Все другие строки в нашей сетке содержат теги <h:inputText>, этот


тег генерирует HTML-поле input для ввода текста (text), которое
принимает одну строку текста. Мы явно установили атрибуты id во
всех полях <h:inputText>, чтобы иметь возможность обращаться к
ним из соответствующих полей <h:outputLabel> и <h:message>. Мы
также установили атрибуты label во всех тегах <h:inputText>, чтобы
обеспечить вывод более информативных сообщений об ошибках.
Некоторые из наших полей <h:inputText> обязательно должны
заполняться пользователем. Эти поля имеют собственный атрибут
required со значением true, и каждое поле ввода JSF имеет атрибут
required. Если нужно, чтобы пользователь обязательно ввел значе­
ние в поле, его атрибуту required следует присвоить значение true.
Данный атрибут является необязательным, если не определить его
значение явно, он примет значение по умолчанию false.
В последнюю строку сетки мы добавили пустой тег <h:panelGroup>.
Назначение этого тега состоит в том, чтобы дать возможность доба­
вить в одну ячейку <h:panelGrid> несколько тегов. Любые теги, по­
мещенные внутрь этого тега, окажутся в той же ячейке сетки, где
находится сам тег <h:panelGrid>. В данном конкретном случае мы
просто хотим получить «пустую» ячейку в сетке, чтобы следующий
тег <h:commandButton> был выровнен с полями ввода в отображаемой
странице.
Тег <h:commandButton> используется для отправки формы серверу.
Его атрибут value определяет текст на отображаемой кнопке. Атри­
бут action определяет, какая страница будет выведена на экран после
щелчка на кнопке.
В нашем примере используется статическая навигация (static
navigation). В этом случае значение атрибута action кнопки
<h:commandButton> жестко зашито в разметке – оно соответствует
имени страницы, к которой требуется переместиться, за минусом ее
расширения .xhtml. В нашем примере, когда пользователь щелкает на
кнопке, нужно переместиться к файлу confirmation.xhtml, поэтому мы
использовали значение "confirmation" для ее атрибута action.
Альтернативой статической навигации является динамическая
навигация (dynamic navigation). При использовании динамической
навигации значением атрибута action кнопки является выражение
связывания, вызывающее метод именованного компонента CDI, воз­
вращающий строку. Данный метод может возвращать разные значе­
ния, исходя из определенных условий. То есть, переход будет выпол­
нен к странице, имя которой возвращается методом.
Разработка нашего первого приложения JSF 73

Метод именованного компонента CDI, вызываемый при использо-


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

При использовании динамической навигации возвращаемое зна­


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

В более ранних версиях JSF было необходимо определять правила


навигации в файле faces-config.xml. После введения соглаше-
ний, описанных в предыдущих абзацах, в этом больше нет необхо-
димости.

Создание именованного компонента CDI


Именованные компоненты CDI – это стандартные компоненты
JavaBean, которые используются для хранения данных в приложени­
ях JSF, вводимых пользователем.
Так как именованный компонент CDI является стандартным Java-
классом, он создается точно так же, как любой другой класс Java: пра­
вой кнопкой щелкните на папке Source Packages (Пакеты исходных
кодов) в окне Projects (Проекты) и выберите пункт контекстного меню
New | Java Class... (Новый | Класс Java...), как показано на рис. 2.9.
Далее будет предоставлена возможность изменить значения полей
Class Name (Имя класса) и Package (Пакет) для вновь создаваемого
компонента CDI (см. рис. 2.10).
Сгенерированный файл содержит пустой класс Java:
package com.ensode.jsf.namedbeans;

public class RegistrationBean {

Чтобы превратить его в именованный компонент CDI, достаточно


просто добавить аннотацию @Named. Аннотация @Named отмечает класс
как именованный компонент CDI. По умолчанию имя именован­
ного компонента CDI совпадает с именем класса (в данном случае:
RegistrationBean), первый символ которого преобразован в нижний
регистр (в данном случае: registrationBean). Если потребуется ис­
пользовать другое имя, это можно сделать, указав новое имя в атри­
74 Глава 2. Разработка веб-приложений с использованием JavaServer...

буте value аннотации @Named. Вообще говоря, при следовании согла­


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

Рис. 2.9. Пункт контекстного меню New | Java Class...


(Создать | Класс Java...)
Именованные компоненты CDI могут иметь разные контексты.
Контекст запроса означает, что компонент доступен только в преде­
лах одного HTTP-запроса. Контекст сеанса означает, что компонент
доступен в пределах HTTP-сеанса для одного пользователя. Контекст
диалога (conversation) означает, что компонент доступен в границах
последовательности HTTP-запросов.
Именованный компонент CDI может также иметь контекст при­
ложения, когда он доступен всем пользователям приложения. Также
именованный компонент CDI может иметь зависимый псевдокон­
текст – такие компоненты создаются по мере необходимости.
Наконец, именованный компонент CDI может иметь контекст по­
тока. В этом случае компонент доступен только в рамках определен­
ного JSF-потока (обсуждаются далее в этой главе). Чтобы определить
контекст именованного компонента CDI, необходимо добавить соот­
ветствующую аннотацию.
Разработка нашего первого приложения JSF 75

Рис. 2.10. Выбор имени и местоположения вновь создаваемого


компонента CDI
В табл. 2.1 перечислены возможные контексты для именованных
компонентов CDI с соответствующими аннотациями.
Таблица 2.1. Контексты именованных компонентов CDI
с соответствующими аннотациями

Контекст Аннотация

Запроса @RequestScoped

Сеанса @SessionScoped

Диалога @ConversationScoped

Приложения @ApplicationScoped

Зависимый @Dependent

Потока @FlowScoped

Все аннотации в табл. 2.1, кроме @FlowScoped, определены в пакете


javax.enterprise.context. Аннотация @FlowScoped определена в паке­
те javax.faces.flow.
Превратим Java-класс в именованный компонент CDI с контек­
стом запроса, добавив соответствующие аннотации:
package com.ensode.jsf.namedbeans;

import javax.enterprise.context.RequestScoped;
76 Глава 2. Разработка веб-приложений с использованием JavaServer...

import javax.inject.Named;

@Named
@RequestScoped
public class RegistrationBean {

Аннотация @Named указывает, что класс является именованным


компонентом CDI, а @RequestScoped указывает, что компонент имеет
область видимости (контекст) запроса.

Иногда NetBeans может не найти аннотацию @RequestScoped.


В таком случае добавьте cdi-api.jar в проект: щелкните правой
кнопкой на элементе Libraries (Библиотеки) в окне Projects (Про-
екты), выберите пункт Add JAR/Folder... (Добавить файл JAR/пап-
ку...) и выберите cdi-api.jar в папке modules, в каталоге уста-
новки glassfish.

Теперь следует изменить именованный компонент CDI, добавив


свойства для хранения вводимых пользователем значений.

Автоматическое создание методов получения и установки


(методов get и set). NetBeans может автоматически генерировать
методы get и set для свойств компонентов. Нужно просто нажать
комбинацию клавиш «вставить код» (Alt+Insert в Windows и Linux,
Ctrl+I в Mac OS) и выбрать пункт Getters and Setters (Добавить
свойство...).

package com.ensode.jsf.namedbeans;

import javax.enterprise.context.RequestScoped;
import javax.inject.Named;

@Named
@RequestScoped
public class RegistrationBean {
private String salutation;
private String firstName;
private String lastName;
private Integer age;
private String email;

// методы получения и установки для краткости опущены


}

Обратите внимание, что имена всех свойств компонента (перемен­


ные экземпляра) соответствуют именам, которые использовались в
Разработка нашего первого приложения JSF 77

выражениях связывания страницы. Эти имена должны соответство­


вать, чтобы JSF знал, как отображать свойства компонента в значения
выражений связывания.

Реализация страницы подтверждения


После того как пользователь заполнит поля формы и отправит ее,
нужно вывести страницу подтверждения, которая покажет, какие
значения были введены. Поскольку для каждого поля ввода на стра­
нице ввода использовалось выражение связывания, соответствую­
щие свойства именованного компонента будут заполнены пользо­
вательскими данными. Поэтому в странице подтверждения нужно
всего лишь отобразить данные из именованного компонента с помо­
щью серии JSF-тегов <h:outputText>.
Мы можем создать страницу подтверждения с помощью мастера
New JSF File (Создать файл JSF). Для этого выберите пункт меню
File | New File... (Файл | Создать файл...), щелкните на категории Ja-
vaServer Faces (Приложение JavaServer Faces) и выберите тип файла
JSF Page (Страница JSF), как показано на рис. 2.11.

Рис. 2.11. Выберите тип файла JSF Page (Страница JSF)


Убедитесь, что имя создаваемого файла соответствует значению
атрибута action кнопки на странице ввода (confirmation.xhtml), чтобы
статическая навигация работала должным образом.
78 Глава 2. Разработка веб-приложений с использованием JavaServer...

После изменения сгенерированной страницы, чтобы она удовлет­


воряла нашим требованиям, она должна выглядеть так:
<?xml version='1.0' encoding='UTF-8' ?>
<!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"
xmlns:h="http://xmlns.jcp.org/jsf/html">
<h:head>
<title>Confirmation Page</title>
<h:outputStylesheet library="css" name="styles.css"/>
</h:head>
<h:body>
<h2>Confirmation Page</h2>
<h:panelGrid columns="2"
columnClasses="rightalign-bold,leftalign">
<h:outputText value="Salutation:"/>
${registrationBean.salutation}
<h:outputText value="First Name:"/>
${registrationBean.firstName}
<h:outputText value="Last Name:"/>
${registrationBean.lastName}
<h:outputText value="Age:"/>
${registrationBean.age}
<h:outputText value="Email Address:"/>
${registrationBean.email}
</h:panelGrid>
</h:body>
</html>

Как видите, страница подтверждения очень проста. Она состоит из


серии тегов <h:outputText> с метками и значениями выражений свя­
зывания, ссылающихся на свойства именованного компонента. Тег
JSF <h:outputText> просто выводит значение выражения в его атри­
буте value.

Запуск приложения
Теперь все готово к запуску нового JSF-приложения. Проще всего сде­
лать это, щелкнув правой кнопкой мыши на проекте в окне Projects
(Проекты) и в контекстном меню выбрать элемент Run (Выполне­
ние).
На этом этапе автоматически запустится GlassFish (или иной
сервер приложений, настроенный для проекта), если он еще не был
запущен, и откроется веб-браузер, используемый в системе по умол­
чанию. Веб-браузер автоматически будет направлен по адресу URL
страницы.
Разработка нашего первого приложения JSF 79

После ввода некоторых данных на странице она должна будет вы­


глядеть, как показано на рис. 2.12.

Рис. 2.12. Страница ввода данных


После щелчка на кнопке Register (Зарегистрировать) наш име­
нованный компонент RegistrationBean заполнится значениями, вве­
денными в форму. Каждое свойство компонента получит значение из
соответствующего поля ввода, как определено выражением связыва­
ния.
Здесь же «сработает» механизм навигации JSF и браузер будет от­
правлен к странице подтверждения (см. рис. 2.13).

Рис. 2.13. Страница подтверждения


Значения, отображаемые на странице подтверждения, получены
из именованного компонента, чем подтверждается, что его свойства
были заполнены правильно.
80 Глава 2. Разработка веб-приложений с использованием JavaServer...

Проверка допустимости в JSF


Ранее в этой главе рассказывалось, как атрибут required полей ввода
JSF позволяет объявлять их обязательными для заполнения.
Если пользователь пытается отправить форму, не заполнив одно
или более обязательных полей, автоматически будет сгенерировано
сообщение об ошибке (см. рис. 2.14).

Рис. 2.14. Если не заполнить одно или более обязательных полей,


автоматически генерируется сообщение об ошибке
Сообщение об ошибке генерируется тегом <h:message>, соответ­
ствующим недопустимому полю. Слова «First Name» в сообщении
об ошибке соответствуют значению атрибута label поля, если бы мы
опустили атрибут label, вместо него в текст сообщения было бы встав­
лено значение атрибута id. Как видите, атрибут required существенно
упрощает реализацию функциональности обязательного поля.
Напомним, что поле age (возраст) связано со свойством типа
Integer нашего именованного компонента. Если пользователь вве­
дет в это поле значение, не являющееся допустимым целым числом,
автоматически будет сгенерировано сообщение об ошибке проверки
допустимости (см. рис. 2.15).

Рис. 2.15. Сообщение об ошибке, выявленной


при проверке допустимости значения
Разработка нашего первого приложения JSF 81

Конечно, отрицательный возраст не имеет смысла. Однако наше


приложение проверяет корректность ввода данных пользователем на
предмет допустимости целого числа практически без усилий с нашей
стороны.
Поле ввода адреса электронной почты в форме связано со свойст­
вом типа String именованного компонента. Здесь нет встроенной
проверки, чтобы убедиться в допустимости введенного адреса. В слу­
чаях, подобных этому, можно написать собственный блок проверки,
или валидатор.
Пользовательские валидаторы должны реализовать интерфейс
javax.faces.validator.Validator. Этот интерфейс содержит единст­
венный метод validate(),принимающий три параметра: экземп­
ляр javax.faces.context.FacesContext, экземпляр javax.faces.
component.UIComponent с проверяемым компонентом JSF и экземпляр
java.lang.Object с введенным значением для проверки. Следующий
пример иллюстрирует типичный нестандартный (пользовательский)
валидатор:
package com.ensode.jsf.validators;

import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.component.html.HtmlInputText;
import javax.faces.context.FacesContext;
import javax.faces.validator.FacesValidator;
import javax.faces.validator.Validator;
import javax.faces.validator.ValidatorException;

@FacesValidator(value ="emailValidator")
public class EmailValidator implements Validator {

@Override
public void validate(FacesContext facesContext,
UIComponent uiComponent, Object value) throws
ValidatorException {
Pattern pattern = Pattern.compile("\\w+@\\w+\\.\\w+");
Matcher matcher = pattern.matcher(
(CharSequence) value);
HtmlInputText htmlInputText =
(HtmlInputText) uiComponent;
String label;

if (htmlInputText.getLabel() == null ||
htmlInputText.getLabel().trim().equals("")) {
label = htmlInputText.getId();
82 Глава 2. Разработка веб-приложений с использованием JavaServer...

} else {
label = htmlInputText.getLabel();
}

if (!matcher.matches()) {
FacesMessage facesMessage =
new FacesMessage(label +
": not a valid email address");
throw new ValidatorException(facesMessage);
}
}
}

В нашем примере метод validate() проверяет соответствие прове­


ряемого значения регулярному выражению. Если значение соответ­
ствует выражению, считается, что проверка прошла успешно, в про­
тивном случае возникает ошибка и возбуждается исключение javax.
faces.validator.ValidatorException.

Основная цель примера нестандартного (пользовательского) ва-


лидатора состоит в том, чтобы показать, как писать собственные
валидаторы JSF, а не как создать совершенный и надежный вали-
датор адреса электронной почты. Существуют вполне допустимые
адреса электронной почты, которые не пройдут проверку данным
валидатором.

Конструктор ValidatorException принимает экземпляр javax.


faces.application.FacesMessage в качестве параметра. Этот объект
используется для вывода сообщения об ошибке, когда проверка до­
пустимости завершилась неудачей. Сообщение для вывода передает­
ся в виде строкового параметра конструктору FacesMessage. В нашем
примере, если атрибут label компонента имеет значение, отличное
от null или пустой строки, это значение включается в сообщение об
ошибке, в противном случае используется значение атрибута id. Это
поведение соответствует шаблону, установленному стандартными ва­
лидаторами JSF.
Валидатор должен декорироваться аннотацией @FacesValidator.
Значением атрибута value этой аннотации должен быть идентифи­
катор (ID), который будет использоваться для ссылки на валидатор
в страницах JSF.
Закончив реализацию валидатора, его можно использовать в на­
ших страницах.
В данном конкретном случае, чтобы задействовать наш нестан­
дартный валидатор, нужно изменить поле электронной почты:
Шаблоны фейслетов 83
<h:inputText id="email" label="Email Address"
required="true" value="#{registrationBean.email}">
<f:validator validatorId="emailValidator"/>
</h:inputText>

Для этого следует вложить тег <f:validator> в поле ввода, под­


лежащее проверке с использованием нестандартного валидатора.
Значение атрибута validatorId в теге <f:validator> должно соот­
ветствовать значению атрибута value в аннотации @FacesValidator
валидатора.
Теперь можно протестировать нестандартный валидатор (см.
рис. 2.16).

Рис. 2.16. Сообщение об ошибке, выявленной нестандартным


валидатором
После ввода недопустимого адреса электронной почты и отправки
формы выполнится логика нестандартного валидатора и строка, пере­
данная в параметре конструктору FacesMessage методом validator(),
появится как текст сообщения об ошибке в теге <h:message> рядом с
полем ввода.

Шаблоны фейслетов
Одним из преимуществ фейслетов перед JSP-страницами является
наличие собственного механизма обработки шаблонов. Шаблоны по­
зволяют определить макет страницы в одном месте и использовать
его во множестве клиентских страниц. Поскольку в веб-приложениях
час­то используется некий единый макет страниц, использование ша­
84 Глава 2. Разработка веб-приложений с использованием JavaServer...

блонов делает приложения намного более удобными в сопровожде­


нии, поскольку изменения в макете должны производиться лишь в
одном месте. Если вдруг понадобится изменить макет (например, до­
бавить нижний колонтитул, или переместить столбец с левой сторо­
ны страницы в правую), достаточно будет изменить только шаблон, и
произведенные изменения будут отражены во всех клиентах шаблона.

Добавление шаблона фейслетов


Чтобы добавить новый шаблон в проект, нужно выбрать пункт File
| New File... (Файл | Создать файл...) в главном меню, затем в от­
крывшемся диалоге выбрать категорию JavaServer Faces (Приложе­
ние JavaServer Faces) и затем выбрать тип файлов Facelets Template
(Шаб­лон Facelets), как показано на рис. 2.17.

Рис. 2.17. Создание шаблона фейслета


NetBeans предоставляет замечательную поддержку шаблонов
фейслетов и включает несколько готовых шаблонов типовых маке­
тов веб-страниц.
Мы можем выбрать один предопределенных шаблонов (см.
рис. 2.18) и использовать его в качестве основы для своего шаблона
или как «готовый продукт» («out of the box»).
Шаблоны фейслетов 85

Рис. 2.18. Выбор одного из предопределенных шаблонов


NetBeans позволяет использовать для макетирования HTML-
таблицы или CSS. Для большинства современных веб-приложений
CSS является предпочтительным выбором. Мы выберем макет, со­
держащий область заголовка, один левый столбец и основную об­
ласть.
После щелчка на кнопке Finish (Готово) NetBeans автоматически
сгенерирует шаблон вместе с необходимыми файлами CSS.
Вот как выглядит вновь созданный шаблон:
<?xml version='1.0' encoding='UTF-8' ?>
<!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"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:h="http://xmlns.jcp.org/jsf/html">
<h:head>
<meta http-equiv="Content-Type" content="text/html;
charset=UTF-8" />
<h:outputStylesheet name="./css/default.css"/>
<h:outputStylesheet name="./css/cssLayout.css"/>
<title>Facelets Template</title>
86 Глава 2. Разработка веб-приложений с использованием JavaServer...

</h:head>

<h:body>
<div id="top" class="top">
<ui:insert name="top">Top</ui:insert>
</div>
<div>
<div id="left">
<ui:insert name="left">Left</ui:insert>
</div>
<div id="content" class="left_content">
<ui:insert name="content">Content</ui:insert>
</div>
</div>
</h:body>
</html>

Как видите, шаблон не очень отличается от обычного файла фейс­


лета.
Обратите внимание, что шаблон использует следующее простран­
ство имен: xmlns:ui="http://xmlns.jcp.org/jsf/facelets". Это про­
странство имен позволяет нам использовать тег <ui:insert>. Содер­
жимое этого тега будет заменено содержимым соответствующего тега
<ui:define> в клиентах шаблона.

Использование шаблона
Чтобы использовать шаблон, достаточно просто создать клиента
шаблона. Для этого выберите пункт File | New File (Файл | Создать
файл) в главном меню, затем в открывшемся диалоге выберите кате­
горию JavaServer Faces (Приложение JavaServer Faces) и затем тип
файлов Facelets Template Client (Клиент шаблона Facelets), как по­
казано на рис. 2.19.
После щелчка на кнопке Next > (Далее>) в следующем диалоге ма­
стера введите имя файла (или примите имя, предложенное по умол­
чанию) и выберите шаблон, который должен использовать клиент
шаблона (см. рис. 2.20).
Если клиент шаблона не переопределит раздел, объявленный в ша­
блоне, на странице отобразится разметка из шаблона. Благодаря это­
му можно определить, например, заголовок страницы, который будет
отображаться на всех страницах приложения.
В нашем примере разделы top и left должны оставаться неизмен­
ными во всех страницах. Поэтому мы убрали соответствующие им
флажки (см. рис. 2.20), чтобы эти разделы не генерировались в кли­
енте шаблона.
Шаблоны фейслетов 87

Рис. 2.19. Создание клиента шаблона фейслета

Рис. 2.20. Выбор шаблона для использования клиентом


После щелчка на кнопке Finish (Готово) будет создан клиент ша­
блона:
88 Глава 2. Разработка веб-приложений с использованием JavaServer...

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


<!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"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
<body>
<ui:composition template="./template.xhtml">
<ui:define name="content">
content
</ui:define>
</ui:composition>
</body>
</html>

Как видите, клиент шаблона также использует пространство имен


xmlns:ui="http://xmlns.jcp.org/jsf/facelets". Тег <ui:composition>
в клиенте шаблона должен быть родительским тегом любых других
тегов, принадлежащих этому пространству имен. Любая разметка вне
этого тега не будет отображаться; вместо этого будет отображена раз­
метка шаблона.
Тег <ui:define> используется для вставки разметки в соответствую­
щий тег <ui:insert> шаблона. Значение атрибута name тега <ui:define>
должно совпадать с соответствующим тегом <ui:insert> шаблона.
После развертывания нашего приложения можно увидеть при­
менение шаблона в действии, указав в адресной строке веб-браузера
адрес URL клиента шаблона (см. рис. 2.21).

Рис. 2.21. Клиент шаблона


Как видите, NetBeans сгенерировала шаблон, позволивший нам
создать довольно изящную страницу при очень небольшом усилии
с нашей стороны. Конечно, мы должны заменить разметку в тегах
<ui:define> в соответствии с нашими потребностями.
Ниже приводится измененная версия шаблона с добавленной раз­
меткой, которая будет отображаться в соответствующих местах в ша­
блоне:
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
Шаблоны фейслетов 89
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
<body>
<ui:composition template="./template.xhtml">
<ui:define name="content">
<p>
В этот главный раздел страницы можно поместить основной
текст, изображения, формы и т. д. В данном примере
используется типичный текст-заполнитель, который так
любят веб-дизайнеры.
</p>
<p>
Lorem ipsum dolor sit amet, consectetur
adipiscing elit. Nunc venenatis, diam nec tempor dapibus,
lacus erat vehicula mauris, id lacinia nisi arcu vitae purus.
Nam vestibulum nisi non lacus luctus vel ornare nibh pharetra.
Aenean non lorem lectus, eu tempus lectus. Cras mattis nibh a
mi pharetra ultricies. In consectetur, tellus sit amet pretium
facilisis, enim ipsum consectetur magna, a mattis ligula massa
vel mi. Maecenas id arcu a erat pellentesque vestibulum at
vitae nulla. Nullam eleifend sodales tincidunt. Donec viverra
libero non erat porta sit amet convallis enim commodo. Cras
eu libero elit, ac aliquam ligula. Quisque a elit nec ligula
dapibus porta sit amet a nulla. Nulla vitae molestie ligula.
Aliquam interdum, velit at tincidunt ultrices, sapien mauris
sodales mi, vel rutrum turpis neque id ligula. Donec dictum
condimentum arcuut convallis. Maecenas blandit, ante eget tempor
sollicitudin, ligula eros venenatis justo, sed ullamcorper dui
leo id nunc. Suspendisse potenti. Ut vel mauris sem. Duis
lacinia eros laoreet diam cursus nec hendrerit tellus pellentesque.
</p>
</ui:define>
</ui:composition>
</body>
</html>

Поскольку раздел content единственный, переопределяемый кли­


ентом, в шаблоне нужно определить разделы top и left:
<?xml version='1.0' encoding='UTF-8' ?>
<!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"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:h="http://xmlns.jcp.org/jsf/html">
<!-- раздел <h:head> опущен для экономии места -->
<h:body>
<div id="top" class="top">
<ui:insert name="top">
<h2>Welcome to our Site</h2>
90 Глава 2. Разработка веб-приложений с использованием JavaServer...

</ui:insert>
</div>
<div>
<div id="left">
<ui:insert name="left">
<h3>Links</h3>
<ul>
<li>
<h:outputLink value="http://www.packtpub.com">
<h:outputText value="Packt Publishing"/>
</h:outputLink>
</li>
<li>
<h:outputLink value="http://www.ensode.net">
<h:outputText value="Ensode.net"/>
</h:outputLink>
</li>
<li>
<h:outputLink value="http://www.ensode.com">
<h:outputText value="Ensode Technology, LLC"/>
</h:outputLink>
</li>
<!-- другие ссылки опущены для экономии места -->
</ul>
</ui:insert>
</div>
<div id="content" class="left_content">
<ui:insert name="content">Content</ui:insert>
</div>
</div>
</h:body>
</html>

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


как показано на рис. 2.22.
Как видите, создание шаблонов фейслетов и клиентов шаблонов в
NetBeans выполняется всего в несколько щелчков мыши.

Контракты библиотек ресурсов


Контракты библиотек ресурсов (resource library contracts) – новая
особенность, появившаяся в версии JSF 2.2. Она основана на шабло­
нах фейслетов и позволяет создавать веб-приложения со «сменным»
оформлением. Например, приложение, обслуживающее множество
клиентов, можно написать так, что после регистрации каждый клиент
будет видеть собственное оформление, с логотипом своей компании.
Как вариант, можно дать пользователям возможность выбирать из
Контракты библиотек ресурсов 91

предопределенного множества тем оформления, именно эту возмож­


ность мы и реализуем в следующем примере.

Рис. 2.22. Клиент шаблона после заполнения разделов


Контракт библиотеки ресурсов можно создать, если выбрать пункт
File | New File... (Файл | Создать файл...) в главном меню, затем в
открывшемся диалоге выбрать категорию JavaServer Faces (При­
ложение JavaServer Faces) и затем тип файлов JSF Resource Library
Contract (Контракт библиотеки ресурсов JSF), как показано на
рис. 2.23.
Контракту библиотеки ресурсов нужно дать имя, указав его в поле
ввода Contract Name (Имя контракта), как показано на рис. 2.24. До­
полнительно можно позволить NetBeans сгенерировать начальные
шаблоны для контракта библиотеки ресурсов.
В данном примере мы позволим NetBeans создать начальный ша­
блон, затем немного изменим файл CSS так, чтобы на окончательной
странице текст отображался светлым шрифтом на темном фоне. Это
будет наша «темная» тема.
Далее мы создадим вторую тему оформления на основе того же ма­
кета, что и «темная» тема, но оставим файл CSS «как есть» (снимки
экрана не показаны).
92 Глава 2. Разработка веб-приложений с использованием JavaServer...

Рис. 2.23. Создание контракта библиотеки ресурсов

Рис. 2.24. Настройка нового контракта библиотеки ресурсов


После создания контрактов библиотеки ресурсов NetBeans создаст
соответствующие файлы в каталоге contracts (см. рис. 2.25).
Контракты библиотек ресурсов 93

Рис. 2.25. NetBeans создаст соответствующие


файлы в каталоге contracts
Теперь нужно создать клиента шаблона, как было показано в
предыдущем разделе, чтобы с его помощью использовать контрак­
ты в страницах приложения. Скопируйте следующий код в файл
resourcelibrarycontractsdemo.xhtml:
<?xml version='1.0' encoding='UTF-8' ?>
<!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"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:f="http://xmlns.jcp.org/jsf/core">
<body>
<f:view contracts="normal">
<ui:composition template="/template.xhtml">
<ui:define name="content">
<p>
Lorem ipsum dolor sit amet, consectetur
adipiscing elit. Nunc venenatis, diam nec tempor dapibus,
lacus erat vehicula mauris, id lacinia nisi arcu vitae
purus. Nam vestibulum nisi non lacus luctus vel ornare nibh
pharetra. Aenean non lorem lectus, eu tempus lectus.
Cras mattis nibh a mi pharetra ultricies. In consectetur,
tellus sit amet pretium facilisis, enim ipsum consectetur
magna, a mattis ligula massa vel mi. Maecenas id arcu a erat
pellentesque vestibulum at vitae nulla. Nullam eleifend sodales
tincidunt. Donec viverra libero non erat porta sit amet convallis
enim commodo. Cras eu libero elit, ac aliquam ligula. Quisque
a elit nec ligula dapibus porta sit amet a nulla. Nulla vitae
molestie ligula. Aliquam interdum, velit at tincidunt ultrices,
sapien mauris sodales mi, vel rutrum turpis neque id ligula.
94 Глава 2. Разработка веб-приложений с использованием JavaServer...

Donec dictum condimentum arcuut convallis. Maecenas blandit,


ante eget tempor sollicitudin, ligula eros venenatis justo,
sed ullamcorper dui leo id nunc. Suspendisse potenti. Ut vel
mauris sem. Duis lacinia eros laoreet diam cursus nec
hendrerit tellus pellentesque.
</p>
</ui:define>
</ui:composition>
</f:view>
</body>
</html>

Чтобы задействовать контракт библиотеки ресурсов, нужно заклю­


чить тег <ui:composition> в тег <f:view>, имеющий атрибут contracts,
значение которого должно совпадать с именем используемого кон­
тракта.
Если после развертывания приложения перейти в браузере по
адресу клиента шаблона, можно увидеть этот шаблон в действии (см.
рис. 2.26).

Рис. 2.26. Вновь созданный шаблон в действии


Если атрибуту contracts в теге <f:view> присвоить значение dark,
можно увидеть, как выглядит темное оформление (см. рис. 2.27).

Рис. 2.27. Так выглядит темное оформление


Контракты библиотек ресурсов 95

Разумеется, бессмысленно «жестко зашивать» имя контракта в


код, как это сделано в данном примере. Чтобы динамически изменять
оформление, нужно организовать присваивание атрибуту contracts
в теге <f:view> выражения связывания, возвращающего значение не­
которого свойства именованного компонента.
Для этого добавим в проект именованный компонент ThemeSelector,
который будет хранить имя темы, выбранной пользователем:
package com.ensode.jsf.resourcelibrarycontracts.namedbeans;

import javax.enterprise.context.RequestScoped;
import javax.inject.Named;

@Named
@RequestScoped
public class ThemeSelector {
private String themeName = "normal";

public String getThemeName() {


return themeName;
}

public void setThemeName(String themeName) {


this.themeName = themeName;
}
}

Затем изменим клиента шаблона, чтобы дать пользователям воз­


можность изменять оформление:
<?xml version='1.0' encoding='UTF-8' ?>
<!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"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:h="http://xmlns.jcp.org/jsf/html">
<body>
<f:view contracts="#{themeSelector.themeName}">
<ui:composition template="/template.xhtml">
<ui:define name="top">
<h:form>
<h:outputLabel for="themeSelector"
value="Select a theme"/>
<h:selectOneMenu id="themeSelector"
value="#{themeSelector.themeName}">
<f:selectItem itemLabel="normal"
itemValue="normal"/>
<f:selectItem itemLabel="dark"
96 Глава 2. Разработка веб-приложений с использованием JavaServer...

itemValue="dark"/>
</h:selectOneMenu>
<h:commandButton value="Submit"
action="resourcelibrarycontractsdemo"/>
</h:form>
</ui:define>
<ui:define name="content">
<p>
<!-- Текст-заполнитель опущен для экономии места -->
</p>
</ui:define>
</ui:composition>
</f:view>
</body>
</html>

Здесь был добавлен тег <h:selectOneMenu> и кнопка, с помощью ко­


торых пользователи смогут выбирать оформление по своему вкусу
(см. рис. 2.28).

Рис. 2.28. Элементы управления для выбора темы оформления

Составные компоненты
Очень интересной особенностью JSF является возможность созда­
ния пользовательских компонентов JSF. В JSF 2 создание пользова­
тельского компонента включает в себя немного больше, чем создание
разметки для него, при полном отсутствии необходимости писать
программный код на Java или определять какие-либо настройки.
Поскольку пользовательские компоненты обычно состоят из дру­
гих компонентов JSF, их часто называют составными компонентами
(composite components).
Чтобы создать составной компонент, выберите в главного меню
пункт File | New File (Файл | Создать файл), затем в открывшемся ди­
Составные компоненты 97

алоге выберите категорию JavaServer Faces (Приложение JavaServer


Faces) и затем тип файлов JSF Composite Component (Составной
компонент JSF), как показано на рис. 2.29.

Рис. 2.29. Создание составного компонента


После щелчка на кнопке Next > (Далее>) в следующем диалоге
мастера можно определить имя файла, проект и папку для пользова­
тельского компонента (см. рис. 2.30).

Чтобы воспользоваться преимуществами автоматической обра-


ботки ресурсов по соглашению в JSF 2.0, не рекомендуется изме-
нять папку, куда будет помещен пользовательский компонент.

После щелчка на кнопке Finish (Готово), NetBeans сгенерирует пу­


стой составной компонент, который можно использовать как основу
для определения функциональности нашего собственного компонен­
та:
<?xml version='1.0' encoding='UTF-8' ?>
<!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"
xmlns:cc="http://xmlns.jcp.org/jsf/composite">
<!-- ИНТЕРФЕЙС -->
<cc:interface>
98 Глава 2. Разработка веб-приложений с использованием JavaServer...

</cc:interface>

<!-- РЕАЛИЗАЦИЯ -->


<cc:implementation>
</cc:implementation>
</html>
Каждый составной компонент JSF 2 содержит два раздела: интер­
фейс и реализацию.

Рис. 2.30. Настройка составного компонента


Раздел интерфейса должен быть заключен в тег <cc:interface>.
В интерфейсе определяются любые атрибуты компонента. Раздел ре­
ализации должен содержать разметку для отображения при исполь­
зовании составного компонента.
В данном примере мы создадим простой компонент, который мож­
но использовать для ввода адресов. То есть, если в приложении по­
требуется организовать ввод адресов, логику и/или представление
можно инкапсулировать в составном компоненте. Если позднее по­
надобится изменить ввод адреса (например, для поддержки междуна­
родных адресов), достаточно будет изменить только сам компонент,
а все формы ввода адреса в приложении будут обновлены автомати­
чески.
После «устранения белых пятен» наш составной компонент теперь
выглядит так:
Составные компоненты 99
<?xml version='1.0' encoding='UTF-8' ?>
<!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"
xmlns:cc="http://xmlns.jcp.org/jsf/composite"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:f="http://xmlns.jcp.org/jsf/core">

<!-- ИНТЕРФЕЙС -->


<cc:interface>
<cc:attribute name="addrType"/>
<cc:attribute name="namedBean" required="true"/>
</cc:interface>

<!-- РЕАЛИЗАЦИЯ -->


<cc:implementation>
<h:panelGrid columns="2">
<f:facet name="header">
<h:outputText value="#{cc.attrs.addrType} Address"/>
</f:facet>
<h:outputLabel for="line1" value="Line 1"/>
<h:inputText id="line1"
value="#{cc.attrs.namedBean.line1}"/>
<h:outputLabel for="line2" value="Line 2"/>
<h:inputText id="line2"
value="#{cc.attrs.namedBean.line2}"/>
<h:outputLabel for="city" value="City"/>
<h:inputText id="city" value="#{cc.attrs.namedBean.city}"/>
<h:outputLabel for="state" value="state"/>
<h:inputText id="state" value="#{cc.attrs.namedBean.state}"
size="2" maxlength="2"/>
<h:outputLabel for="zip" value="Zip"/>
<h:inputText id="zip" value="#{cc.attrs.namedBean.zip}"
size="5" maxlength="5"/>
</h:panelGrid>
</cc:implementation>
</html>

Атрибуты компонента определяются с помощью тега <cc:attribute>.


Этот тег имеет атрибут name, определяющий имя атрибута компонен­
та, и необязательный атрибут required, определяющий обязатель­
ность описываемого атрибута компонента.
Тело тега <cc:implementation> выглядит как старая, добрая раз­
метка JSF, за одним исключением. По соглашению доступ к атрибу­
там тега можно получить с использованием выражения #{cc.attrs.
ATTRIBUTE_NAME}, которое в данном примере применяется для доступа
к атрибутам, объявленным в разделе интерфейса компонента. Обра­
тите внимание, что атрибут namedBean компонента должен ссылаться
100 Глава 2. Разработка веб-приложений с использованием JavaServer...

на именованный компонент. Страницы, использующие компонент,


должны использовать выражение JSF, возвращающее именованный
компонент, на который ссылается атрибут namedBean. Доступ к атри­
бутам этого именованного компонента можно получить, используя
знакомую точечную нотацию .property, которую мы использовали
ранее. Единственная разница состоит в том, что вместо имени име­
нованного компонента следует использовать имя атрибута, как оно
определено в разделе интерфейса.
Теперь у нас есть простой, законченный составной компонент, и мы
легко можем использовать его в своих страницах:
<?xml version='1.0' encoding='UTF-8' ?>
<!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"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:ezcomp="http://xmlns.jcp.org/jsf/composite/ezcomp">
<h:head>
<title>Address Entry</title>
</h:head>
<h:body>
<h:form>
<h:panelGrid columns="1">
<ezcomp:address namedBean="#{addressBean}"
addrType="Home"/>
<h:commandButton value="Submit" action="confirmation"
style="display: block; margin: 0 auto;"/>
</h:panelGrid>
</h:form>
</h:body>
</html>

В соответствии с соглашениями, пользовательские компоненты


всегда помещаются в пространство имен xmlns:ezcomp="http://xmlns.
jcp.org/jsf/composite/ezcomp" (именно поэтому важно не переопре­
делять папку по умолчанию для компонента, поскольку в противном
случае будет нарушено это соглашение). NetBeans поддерживает ав­
тозавершение кода для пользовательских составных компонентов,
точно так же, как для стандартных компонентов.
В своем приложении мы создали простой именованный компо­
нент addressBean. Это простой именованный компонент с нескольки­
ми свойствами и соответствующими методами get и set, поэтому он
здесь не показан (но его можно найти в пакете примеров для этой гла­
вы). Этот компонент используется как значение атрибута namedBean.
Мы также присвоили атрибуту addressType значение "Home" – это зна­
чение будет отображаться в заголовке компонента ввода адреса.
Потоки Faces Flow 101

После развертывания и запуска приложения можно увидеть ком­


понент в действии (см. рис. 2.31)

Рис. 2.31. Составной компонент ввода адреса в действии


Как видите, создание составных компонентов JSF 2 в NetBeans яв­
ляется очень простым делом.

Потоки Faces Flow


Традиционные веб-приложения не хранят информацию о состоянии,
то есть, страница, только что загруженная браузером, не имеет ни
малейшего представления о том, с какими данными работал пользо­
ватель на предыдущих страницах. Веб-фреймворки на Java решают
эту врожденную проблему веб-приложений, сохраняя информацию о
состоянии на сервере и связывая разные классы Java с разными кон­
текстами приложения. В JSF это делается путем применения соот­
ветствующих аннотаций к именованным компонентам CDI, как опи­
сывалось выше в этой главе.
Если потребуется организовать совместное использование данных
исключительно в двух страницах, следующих друг за другом, доста­
точно задействовать контекст запроса. Если потребуется обеспечить
доступность данных во всех страницах в приложении, можно вос­
пользоваться контекстом сеанса. Но, как быть, если данные должны
быть доступны трем или более страницам, но не всем страницам в
приложении? Прежде у нас не было подходящего для этого контек­
ста, но в версии JSF 2.2 появился контекст потока (flow scope).
Так как страницы в потоке (или последовательности) связаны друг
с другом, все они должны быть помещены в один подкаталог. В соот­
ветствии с соглашениями, имя подкаталога служит именем потока.
102 Глава 2. Разработка веб-приложений с использованием JavaServer...

Например, при создании потока с именем registration мы могли бы


поместить все страницы, принадлежащие этому потоку, в подкаталог
registration.
Создать этот подкаталог можно, щелкнув правой кнопкой мыши на
узле Web Pages (Веб-страницы) и выбрав в контекстном меню пункт
New | Other... (Новый | Другое...), как показано на рис. 2.32.

Рис. 2.32. Пункт New | Other... (Новый | Другое...)


контекстного меню
Далее следует выбрать тип Folder (Папка) в категории Other
(Прочее), как показано на рис. 2.33.
Затем дадим имя папке для хранения страниц, составляющих
поток – в данном случае registration (см. рис. 2.34).
Чтобы обеспечить нормальную работу потока, нужно добавить
в его каталог конфигурационный XML-файл. Файл должен иметь
имя, состоящее из имени каталога и окончания -flow. В данном случае
файл должен иметь имя registration-flow.xml. В NetBeans добавить
этот файл можно, щелкнув правой кнопкой мыши на каталоге потока
(с именем registration), выбрав в контекстном меню пункт New |
Other... (Новый | Другое...) и затем тип Empty File (Пустой файл) в
категории Other (Прочее), как показано на рис. 2.35.
Потоки Faces Flow 103

Рис. 2.33. Выбор типа Folder (Папка) в категории Other (Прочее)

Рис. 2.34. Назначение имени папке для хранения страниц,


составляющих поток
104 Глава 2. Разработка веб-приложений с использованием JavaServer...

Рис. 2.35. Выбор типа Empty File (Пустой файл)


в категории Other (Прочее)

Далее нужно дать файлу правильное имя, гарантировав его разме­


щение в каталоге потока (см. рис. 2.36).

Рис. 2.36. Назначение имени файлу конфигурационному


Потоки Faces Flow 105

Данные для потока (последовательности страниц) должны хра­


ниться в одном или нескольких именованных компонентах с контек­
стом потока, как показано ниже:
package com.ensode.flowscope.namedbeans;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.faces.flow.FlowScoped;
import javax.inject.Named;

@Named
@FlowScoped("registration")
public class RegistrationBean {

private String salutation;


private String firstName;
private String lastName;
private Integer age;
private String email;

private String line1;


private String line2;
private String city;
private String state;
private String zip;

@PostConstruct
public void init() {
System.out.println(this.getClass().getCanonicalName() +
" initialized.");
}

@PreDestroy
public void destroy() {
System.out.println(this.getClass().getCanonicalName() +
" destroyed.");
}

// методы get и set опущены для экономии места


}

В данном примере мы использовали простой именованный ком­


понент CDI с несколькими свойствами и соответствующими мето­
дами set и get. Обратите внимание на аннотации @PostConstruct и
@PreDestroy в примере. Это аннотации механизма CDI, которые обе­
спечивают вызов отмеченных ими методов сразу после создания ком­
понента и непосредственно перед его удалением, соответственно. Мы
добавили их, чтобы подтвердить, что именованный компонент с кон­

Powered by TCPDF (www.tcpdf.org)


106 Глава 2. Разработка веб-приложений с использованием JavaServer...

текстом потока создается и удаляется после входа и перед выходом из


потока регистрации.
Теперь нужно добавить JSF-страницы в поток. Первая страница
в потоке должна нести имя самого потока (в данном случае: regis-
tration.xhtml). На имена других страниц в потоке не накладывает­
ся никаких ограничений. Последняя страница должна находиться за
пределами каталога потока и иметь имя, состоящее из имени потока
и окончания -return. В данном случае последняя страница должна
иметь имя registration-return.xhtml.

В разметке страниц нет ничего такого, чего бы мы не видели пре-


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

После добавления всех необходимых файлов проект должен вы­


глядеть, как показано на рис. 2.37.

Рис. 2.37. Проект после добавления всех необходимых файлов


Вход в поток оформляется присваиванием имени потока атрибуту
action компонента <h:commandLink> или <h:commandButton>.
В данном примере мы добавили простой тег <h:commandButton> в
страницу index.xhtml:
<h:commandLink action="registration">
<h:outputText value="Begin Registration"/>
</h:commandLink>

Когда пользователь щелкнет на ссылке, браузер откроет первую


страницу в потоке (см. рис. 2.38).
Потоки Faces Flow 107

Рис. 2.38. Первая страница в потоке


Когда пользователь щелкнет на кнопке Continue (Продолжить),
будет создан экземпляр именованного компонента с контекстом по­
тока. В данном примере в этом можно убедиться, заглянув в файл
журнала GlassFish, где должна появиться такая строка:
Info: com.ensode.flowscope.namedbeans.RegistrationBean initialized.

Эта строка выводится методом init() компонента Registration-


Bean, отмеченного аннотацией @PostConstruct.
Разметка страниц в потоке включает теги <h:commandButton> для
организации переходов между ними. Например, ниже приводится
разметка кнопки Continue (Продолжить) в первой странице потока:
<h:commandButton id="continue" value="Continue"
action="registration-pg2" />

Вторая страница содержит две кнопки – для перехода назад и впе­


ред:
<h:commandButton id="back" value="Go Back"
action="registration" />
<h:commandButton id="continue" value="Continue"
action="registration-confirmation" />

Благодаря этому мы можем перемещаться между страницами в


прямом и обратном направлениях. Последняя страница содержит
кнопку Continue (Продолжить), осуществляющую выход из потока.
<h:commandButton value="Continue" action="registration-return"/>
108 Глава 2. Разработка веб-приложений с использованием JavaServer...

Когда пользователь щелкнет на кнопке Continue (Продолжить),


приложение выйдет из потока и компонент с контекстом потока будет
удален. Убедиться в этом можно, заглянув в файл журнала GlassFish:
Info: com.ensode.flowscope.namedbeans.RegistrationBean destroyed.

Эта строка выводится методом destroy() компонента Registra-


tionBean, отмеченного аннотацией @PreDestroy.

Поддержка HTML5
В JSF 2.2 еще больше была улучшена поддержка возможностей
HTML5. Две наиболее яркие черты этих улучшений: HTML5-
подобные теги и сквозные атрибуты.

HTML5-подобная разметка
Поддержка HTML5-подобных тегов помогает разрабатывать пред­
ставления JSF с использованием тегов HTML5, без применения те­
гов JSF. Чтобы задействовать эту поддержку, нужно подключить про­
странство имен http://xmlns.jcp.org/jsf к странице и использовать
хотя бы один тег с атрибутом из этого пространства имен.
В данном разделе мы перепишем приложение, созданное в разделе
«Разработка нашего первого приложения JSF», выше, с применением
HTML5-подобных тегов.
Для этого с помощью NetBeans создадим веб-приложение, выбрав,
как обычно, в категории Java Web тип проекта Web Application (Веб-
приложение) и при настройке платформы – фреймворк JavaServer
Faces. При добавлении страниц в это приложение следует выбирать
тип файлов XHTML в категории Web (Веб), как показано на рис. 2.39.
После этого в файл можно добавить разметку HTML страницы.

Страницы HTML можно разрабатывать очень быстро, перетаскивая


мышью компоненты из палитры NetBeans. Эту палитру можно от-
крыть, выбрав пункт главного меню Window | IDE Tools | Palette
(Окно | IDE и сервис | Палитра).

После добавления необходимой разметки страница должна выгля­


деть примерно так:
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
Поддержка HTML5 109
xmlns:jsf="http://xmlns.jcp.org/jsf"
xmlns:f="http://xmlns.jcp.org/jsf/core">
<head>
<title>Registration</title>
<meta name="viewport" content="width=device-width,
initialscale=1.0"/>
<link rel="stylesheet" type="text/css" href="css/styles.css"/>
</head>
<body>
<h3>Registration Page</h3>
<form jsf:id="mainForm" jsf:prependId="false">
<table border="0" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td class="rightalign">Salutation:</td>
<td class="leftalign">
<select name="salutation"
jsf:id="salutation"
jsf:value="#{registrationBean.salutation}"
size="1">
<f:selectItem itemValue="" itemLabel=""/>
<f:selectItem itemValue="MR" itemLabel="Mr."/>
<!-- другие теги <f:selectItem> опущены
для экономии места -->
</select>
</td>
</tr>
<tr>
<td class="rightalign">
First Name:
</td>
<td class="leftalign">
<input type="text" jsf:id="firstName"
jsf:value="#{registrationBean.firstName}"/>
</td>
</tr>
<tr>
<td class="rightalign">
Last Name:
</td>
<td class="leftalign">
<input type="text" jsf:id="lastName"
jsf:value="#{registrationBean.lastName}"/>
</td>
</tr>
<tr>
<td class="rightalign">
Age:
</td>
<td class="leftalign">
110 Глава 2. Разработка веб-приложений с использованием JavaServer...

<input type="number" jsf:id="age"


jsf:value="#{registrationBean.age}"/>
</td>
</tr>
<tr>
<td class="rightalign">
Email Address:
</td>
<td class="leftalign">
<input type="text" jsf:id="email"
jsf:value="#{registrationBean.email}"
placeholder="username@example.com"/>
</td>
</tr>
<tr>
<td></td>
<td>
<input type="submit" value="Submit"
jsf:action="confirmation" />
</td>
</tr>
</tbody>
</table>
</form>
</body>
</html>

Рис. 2.39. Выбор типа файлов XHTML в категории Web (Веб)


Поддержка HTML5 111

Чтобы заставить JSF интерпретировать теги HTML, нужно до­


бавить в них хотя бы один атрибут JSF – для этого подойдет лю­
бой JSF-атрибут. Эти атрибуты определяются в пространстве имен
xmlns:jsf="http://xmlns.jcp.org/jsf", которое нужно подключить к
странице.
В данном примере мы преобразовали HTML-форму в JSF-форму,
добавив атрибуты jsf:id и jsf:prependId в тег <form>. Также в каждое
поле input мы добавили атрибуты jsf:id и jsf:value. Эти атрибуты
сообщают фреймворку JSF, что данные теги должны интерпретиро­
ваться как их JSF-эквиваленты.
В предыдущей разметке мы использовали JSF-теги <f:selectItem>
для определения элементов раскрывающегося списка. Один из недо­
статков HTML5-подобных тегов в JSF заключается в неправильной
интерпретации тегов <option> внутри <select>, поэтому мы были вы­
нуждены определять элементы раскрывающегося списка с помощью
тегов <f:selectItem>.
После запуска приложения, страница будет отображаться в браузе­
ре, как показано на рис. 2.40.

Рис. 2.40. Вид страницы приложения, написанной


с применением HTML5-подобных тегов

Сквозные атрибуты
В HTML5 было добавлено несколько новых атрибутов в существую­
щие теги HTML. Эти атрибуты не поддерживались тегами JSF. Вмес­
то того, чтобы реализовать поддержку новых атрибутов, команда
112 Глава 2. Разработка веб-приложений с использованием JavaServer...

JSF пришла к идее «соответствия будущим требованиям», суть ко­


торой заключается в определении сквозных атрибутов (pass-through
attributes).
Сквозные атрибуты – это атрибуты, которые не интерпретируются
в JSF API, а передаются браузеру. Включив эту новую особенность в
JSF 2.2, разработчики JSF одним махом обеспечили поддержку новых
атрибутов, появившихся в HTML5, и всех атрибутов, что еще появят­
ся в будущем.
В предыдущем разделе мы переписали представление JSF с ис­
пользованием HTML5, где применили новый HTML5-подобный
атрибут placeholder. Этот атрибут действует именно так, как пред­
полагает его имя, – помещает некоторый текст-заполнитель в тек­
стовое поле, давая тем самым пользователю подсказку о том, какая
информация должна вводиться в данное поле. Это отличный пример
атрибута, добавленного в HTML5, который может использоваться в
JSF-страницах, созданных с использованием JSF-тегов:
<?xml version='1.0' encoding='UTF-8' ?>
<!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"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:p="http://xmlns.jcp.org/jsf/passthrough">
<h:head>
<title>Registration</title>
<h:outputStylesheet library="css" name="styles.css"/>
</h:head>
<h:body>
<h3>Registration Page</h3>
<h:form>
<h:panelGrid columns="3"
columnClasses="rightalign,leftalign,leftalign">
<!-- Часть разметки удалена, так как не имеет
отношения к обсуждаемой теме -->

<h:outputLabel value="Email Address:" for="email"/>


<h:inputText id="email" label="Email Address"
required="true"
p:placeholder="username@example.com"
value="#{registrationBean.email}">
<f:validator validatorId="emailValidator"/>
</h:inputText>
<h:message for="email" />

<h:panelGroup/>
<h:commandButton id="register" value="Register"
Резюме 113
action="confirmation" />
</h:panelGrid>
</h:form>
</h:body>
</html>

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


атрибуты, необходимо подключить к JSF-странице пространство
имен xmlns:jsf="http://xmlns.jcp.org/jsf/passthrough". После это­
го можно использовать любые атрибуты в JSF-тегах, просто добавляя
к ним префикс, определенный для этого пространства имен (в данном
случае p).

Резюме
В этой главе мы узнали, как NetBeans может облегчить создание но­
вых проектов JSF путем автоматического добавления всех необходи­
мых библиотек.
Мы видели, как быстро создавать JSF-страницы, используя пре­
имущества автозавершения кода NetBeans. Дополнительно мы узна­
ли, как сэкономить время и усилия, позволив NetBeans генерировать
JSF 2 шаблоны, включая необходимые таблицы CSS, позволяющие
легко и просто создавать довольно изящные веб-страницы. Мы также
видели, как NetBeans помогает в разработке пользовательских ком­
понентов JSF 2.
Кроме этого мы охватили новые возможности JSF 2.2, такие как
контракты библиотек ресурсов (resource library contracts), упрощаю­
щие создание приложений, поддерживающих сменные темы оформ­
ления, а также превосходную поддержку HTML5, реализованную в
JSF 2.2 – в частности: возможность создавать JSF-страницы с при­
менением разметки HTML5 и с использованием любых атрибутов
HTML5 в разметке JSF.
Глава 3.
Библиотеки компонентов JSF

В предыдущей главе мы обсудили приемы создания веб-приложений


с применением стандартных компонентов JSF. Одной из замечатель­
ных особенностей JSF является гибкость, благодаря которой при­
кладные программисты могут создавать собственные компоненты.
В настоящее время уже существует несколько готовых к употребле­
нию библиотек компонентов JSF, еще больше упрощающих жизнь
прикладным программистам. Наибольшей популярностью поль­
зуются библиотеки PrimeFaces, ICEfaces и RichFaces. NetBeans по
умолчанию включает поддержку всех трех этих библиотек.
Эта глава охватывает следующие темы:
использование компонентов PrimeFaces в JSF-приложениях;
использование компонентов ICEfaces в JSF-приложениях;
использование компонентов RichFaces в JSF-приложениях.

Использование компонентов
PrimeFaces в JSF-приложениях
PrimeFaces – очень популярная библиотека компонентов JSF, позво­
ляющая разработчикам создавать приложения с привлекательным
внешним видом, прикладывая минимум усилий. Чтобы задействовать
PrimeFaces в JSF-приложении, нужно создать проект обычного веб-
приложения. При выборе фреймворка JavaServer Faces щелкните на
вкладке Components (Компоненты) и отметьте флажок PrimeFaces,
как показано на рис. 3.1.

При выборе библиотеки PrimeFaces в нижней части окна может


появиться предупреждение, сообщающее, что библиотека не на-
строена должным образом и выполняется поиск подходящей би-
блиотеки Primefaces («JSF library PrimeFaces not set up properly:
Searching valid Primefaces library. Please wait...»). Подождите не-
сколько секунд и сообщение должно исчезнуть.
Использование компонентов PrimeFaces в JSF-приложениях 115

Рис. 3.1. Выбор библиотеки компонентов PrimaFaces


NetBeans немедленно сгенерирует приложение PrimeFaces, кото­
рое можно использовать как отправную точку. Вновь созданное при­
ложение можно запустить, чтобы посмотреть, как оно выглядит (см.
рис. 3.2.).

Рис. 3.2. Вновь созданное приложение PrimaFaces


116 Глава 3. Библиотеки компонентов JSF

Как видите, приложение имеет намного более привлекательный


вид, чем JSF-приложения, созданные в предыдущей главе. Веб-
приложения, созданные с применением PrimeFaces, получают такой
улучшенный внешний вид без каких-либо усилий со стороны разра­
ботчика (не требуется явно использовать таблицы стилей CSS), пото­
му что все таблицы CSS и сценарии JavaScript уже предоставляются
библиотекой PrimeFaces.
Давайте исследуем сгенерированный файл welcomePrimefaces.
xhtml:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:p="http://primefaces.org/ui">
<f:viewcontentType="text/html">
<h:head>
<f:facet name="first">
<meta content='text/html; charset=UTF-8'
http-equiv="Content-Type"/>
<title>PrimeFaces</title>
</f:facet>
</h:head>
<h:body>
<p:layout fullPage="true">
<p:layoutUnit position="north" size="100"
resizable="true"
closable="true" collapsible="true">
Header
</p:layoutUnit>
<p:layoutUnit position="south" size="100"
closable="true"
collapsible="true">
Footer
</p:layoutUnit>
<p:layoutUnit position="west" size="175" header="Left"
collapsible="true">
<p:menu>
<p:submenu label="Resources">
<p:menuitem value="Demo"
url="http://www.primefaces.org/showcase-labs/ui/home.jsf" />
<p:menuitem value="Documentation"
url="http://www.primefaces.org/documentation.html" />
<p:menuitem value="Forum"
url="http://forum.primefaces.org/" />
<p:menuitem value="Themes"
Использование компонентов PrimeFaces в JSF-приложениях 117
url="http://www.primefaces.org/themes.html" />
</p:submenu>
</p:menu>
</p:layoutUnit>
<p:layoutUnit position="center">
Welcome to PrimeFaces
</p:layoutUnit>
</p:layout>
</h:body>
</f:view>
</html>

Чтобы получить возможность использовать компоненты


PrimeFaces в JSF-страницах, нужно подключить пространство имен
xmlns:p="http://primefaces.org/ui". NetBeans автоматически добав­
ляет его во вновь создаваемые страницы.
Обратите внимание, что страница в примере выше разделена на
разделы (заголовок, нижний колонтитул, меню в левой колонке и
область основного содержимого). Обычно для создания подобного
макета используются HTML-теги <div> и таблицы CSS. Однако в
PrimeFaces имеется компонент <p:layout>, принимающий на себя все
рутинные хлопоты.
Внутрь <p:layout> мы добавили несколько вложенных компонен­
тов <p:layoutUnit>, создающих отдельные разделы на странице. Эле­
мент <p:layoutUnit> имеет атрибут position, с помощью которого
можно определить, какому разделу данный элемент соответствует.
• Если указать значение north (север), раздел будет отображать­
ся в верхней части страницы. По ширине раздел автоматиче­
ски будет охватывать всю доступную ширину окна браузера.
Это значение использовалось в листинге выше для создания
раздела Header.
• Если указать значение west (запад), раздел будет отображаться
в левой части страницы. По высоте раздел автоматически будет
охватывать всю доступную высоту окна браузера. Это значение
использовалось в листинге выше для создания раздела Left.
• Если указать значение south (юг), раздел будет отображаться
в нижней части страницы. По ширине раздел автоматически
будет охватывать всю доступную ширину окна браузера. Это
значение использовалось в листинге выше для создания раз­
дела Footer.
• Если указать значение center (центр), раздел будет отобра­
жаться в центре страницы. По высоте и ширине раздел авто­
матически будет охватывать все доступное пространство.
118 Глава 3. Библиотеки компонентов JSF

• Можно также использовать значение east (восток) для атри­


бута position тега <p:layoutUnit> (в примере выше не исполь­
зовалось). В этом случае раздел будет отображаться в правой
части страницы и по высоте охватывать всю доступную высоту
окна браузера.
Атрибут size компонента <p:layoutUnit> можно использовать, что­
бы определить ширину (если атрибут position имеет значение east
или west) или высоту (если атрибут position имеет значение north
или south). Когда атрибут position имеет значение center, атрибут
size игнорируется и раздел охватывает все доступное пространство.
Компонент <p:layoutUnit> имеет также атрибуты resizable, close-
able и collapsible; если присвоить этим атрибутам значение true,
пользователь сможет изменять размер, закрывать и сворачивать раз­
дел, соответственно.
В примере выше используется также компонент <p:menu>. Этот
компонент упрощает создание меню для навигации по приложению.
Внутри <p:menu> можно использовать компоненты <p:submenu>.
Этот тег позволяет сгруппировать взаимосвязанные пункты меню.
Компонент <p:submenu> имеет атрибут label, определяющий текст
для отображения на странице. В примере выше использовался компо­
нент <p:submenu> с меткой Resources (Ресурсы). Внутрь <p:submenu>
можно добавить один или более тегов <p:menuitem>, по одному для
каждого пункта меню. Компонент <p:submenu> имеет атрибут value,
значение которого отображается как текст пункта меню, и атрибут
url, в котором можно передать адрес URL страницы, куда должен
быть выполнен переход, если пользователь щелкнет на данном ком­
поненте.
Библиотека PrimeFaces имеет прямые аналоги для большинства
стандартных компонентов JSF, например, в PrimeFaces имеется
свой тег <p:inputText>, действующий аналогично стандартному тегу
<h:inputText>. Благодаря этому многие JSF-приложения легко пере­
ориентировать на использование PrimeFaces, для чего часто доста­
точно заменить префикс h: на p:.
На рис. 3.3 показана PrimeFaces-версия приложения регистрации,
созданного в предыдущей главе.
Как видите, приложение получило более привлекательный внеш­
ний вид, для чего потребовалось всего лишь заменить стандартные
JSF-компоненты их PrimeFaces-аналогами. Обратите внимание, что
к надписям рядом с полями, обязательными для заполнения, автома­
тически добавляется символ звездочки.
Использование компонентов PrimeFaces в JSF-приложениях 119

Рис. 3.3. PrimeFaces-версия приложения регистрации


В следующем фрагменте показаны наиболее интересные для нас
части страницы регистрации:
<p:messages/>
<h:form>
<p:panelGrid columns="2"
columnClasses="rightalign,leftalign">
<p:outputLabel value="Salutation: " for="salutation"/>
<p:selectOneMenu id="salutation" label="Salutation"
value="#{registrationBean.salutation}">
<f:selectItem itemLabel="" itemValue=""/>
<f:selectItem itemLabel="Mr." itemValue="MR"/>
<f:selectItem itemLabel="Mrs." itemValue="MRS"/>
<f:selectItem itemLabel="Miss" itemValue="MISS"/>
<f:selectItem itemLabel="Ms" itemValue="MS"/>
<f:selectItem itemLabel="Dr." itemValue="DR"/>
</p:selectOneMenu>
<p:outputLabel value="First Name:" for="firstName"/>
<p:inputText id="firstName" label="First Name"
required="true"
value="#{registrationBean.firstName}" />
<p:outputLabel value="Last Name:" for="lastName"/>
<p:inputText id="lastName" label="Last Name"
required="true"
value="#{registrationBean.lastName}" />
<p:outputLabel for="age" value="Age:"/>
<p:inputText id="age" label="Age" size="2"
value="#{registrationBean.age}"/>
<p:outputLabel value="Email Address:" for="email"/>
<p:inputText id="email" label="Email Address"
required="true"
value="#{registrationBean.email}">
<f:validatorvalidatorId="emailValidator"/>
</p:inputText>
<h:panelGroup/>
<p:commandButton id="register" value="Register"
120 Глава 3. Библиотеки компонентов JSF

action="confirmation"
ajax="false"/>
</p:panelGrid>
</h:form>

Как видите, по большей части мы просто заменили JSF-теги их


PrimeFaces-аналогами. Из эстетических соображений мы также изме­
нили число колонок в <p:panelGrid> на две (вместо трех, как в перво­
начальном элементе <h:panelGrid>) и заменили все теги <h:message> в
оригинальной странице единственным тегом <p:messages>.
По умолчанию кнопки в PrimeFaces поддерживают технологию
Ajax. Поэтому в данном конкретном примере мы явно отключили
поддержку Ajax, присвоив атрибуту ajax кнопки значение false.
Еще одной замечательной особенностью PrimeFaces является
оформление сообщений об ошибках и подсветка красным полей, не
прошедших проверку, как показано на рис. 3.4.

Рис. 3.4. Оформление сообщений об ошибках


и подсветка полей, не прошедших проверку
В этом разделе мы коротко познакомились с PrimeFaces. Библио­
тека имеет еще массу других компонентов и возможностей, не упо­
минавшихся здесь. За дополнительной информацией обращайтесь по
адресу: http://www.primefaces.org.

Использование компонентов
ICEfaces в JSF-приложениях
ICEfaces – еще одна популярная библиотека JSF, упрощающая
создание JSF-приложений. Чтобы задействовать ICEfaces в JSF-
Использование компонентов ICEfaces в JSF-приложениях 121

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


При выборе фреймворка JavaServer Faces щелкните на вкладке
Components (Компоненты) и отметьте флажок ICEFaces, как пока­
зано на рис. 3.5.

Рис. 3.5. Выбор библиотеки компонентов ICEFaces


В отличие от PrimeFaces, библиотека ICEfaces не включена в со­
став дистрибутива NetBeans. Поэтому ее придется загрузить с сайта
http://www.icesoft.com (см. рис. 3.6) и создать новую библиотеку.

Рис. 3.6. Страница загрузки ICEFaces на сайте http://www.icesoft.com


122 Глава 3. Библиотеки компонентов JSF

Загружать нужно двоичный дистрибутив с последней стабильной


версией ICEfaces (на момент написания этих строк требуемый файл
назывался ICEfaces-3.3.0-bin.zip).

Чтобы получить возможность загрузить библиотеку ICEfaces, необ-


ходимо зарегистрироваться на сайте ICESoft (www.icesoft.org).

После распаковки ZIP-архива, необходимые JAR-файлы можно


найти в каталоге icefaces/lib.
Чтобы создать новую библиотеку ICEfaces в NetBeans, щелкните
на кнопке More... (Больше...) рядом с надписью ICEfaces. В резуль­
тате появится диалог, изображенный на рис. 3.7.

Рис. 3.7. Диалог создания библиотеки ICEFaces

Несмотря на то, что диалог сообщает о необязательности наличия


файла icefaces-ace.jar, он требуется для разметки, генерируе-
мой средой разработки NetBeans.

Затем нужно щелкнуть на кнопке Create ICEfaces library (Создать


библиотеку ICEfaces), дать библиотеке подходящее имя (такое как
ICEfaces) и щелкнуть на кнопке OK (см. рис. 3.8).

Рис. 3.8. Дайте библиотеке подходящее имя


Использование компонентов ICEfaces в JSF-приложениях 123

После щелчка на кнопке OK нужно найти JAR-файлы ICEfaces в


файловой системе, щелкнув на кнопке Add JAR/Folder... (Добавить
JAR/папку...), и добавить их в библиотеку (см. рис. 3.9).

Рис. 3.9. Добавление в библиотеку JAR-файлов ICEfaces


После щелчка на кнопке OK NetBeans создаст библиотеку ICEfaces.

Рис. 3.10. Завершение создания библиотеки ICEfaces


Теперь щелкните на кнопке OK и Finish (Готово) в мастере нового
проекта, чтобы завершить его создание.
124 Глава 3. Библиотеки компонентов JSF

По аналогии с PrimeFaces, когда используется библиотека ICEfaces,


NetBeans генерирует пример приложения ICEfaces, которое можно
использовать в качестве отправной точки (см. рис. 3.11).

Рис. 3.11. Вновь созданное приложение ICEfaces


Сгенерированная страница содержит ссылки на дополнительные
ресурсы ICEfaces, как показано в следующем листинге:
<?xml version='1.0' encoding='UTF-8' ?>
<!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"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:icecore="http://www.icefaces.org/icefaces/core"
xmlns:ace="http://www.icefaces.org/icefaces/components">
<h:head>
<title>ICEfaces Welcome Page</title>
<!-- Следующая строка используется только компонентами ICE,
удалите ее, если в странице нет ни одного такого компонента.-->
<link rel="stylesheet"
type="text/css" href="./xmlhttp/css/rime/rime.css"/>
</h:head>
<h:body>
<h:form>
<ace:panel header="Welcome to ICEfaces">
<h:panelGrid columns="1">
<!-- ВНИМАНИЕ: чтобы запустить эту страницу, в пути
поиска классов (classpath) должна присутствовать
библиотека компонентов ICEfaces ACE. -->
<ace:linkButton id="linkButton1"
value="ICEfaces Overview"
Использование компонентов ICEfaces в JSF-приложениях 125
href="http://wiki.icesoft.org/display/ICE/ICEfaces+Overview">
</ace:linkButton>
<ace:linkButton id="linkButton2"
value="General Documentation"
href="http://wiki.icesoft.org/display/ICE/ICEfaces+Documentation">
</ace:linkButton>
<ace:linkButton id="linkButton3"
value="ICEfaces Demos"
href="http://www.icesoft.org/demos/icefaces-demos.jsf">
</ace:linkButton>
<ace:linkButton id="linkButton4" value="Tutorials"
href="http://www.icesoft.org/community/tutorials-samples.jsf">
</ace:linkButton>
<ace:linkButton id="linkButton5"
value="ACE components"
href="http://wiki.icesoft.org/display/ICE/ACE+Components">
</ace:linkButton>
<ace:linkButton id="linkButton6"
value="ICE components"
href="http://wiki.icesoft.org/display/ICE/ICE+Components">
</ace:linkButton>

<!-- Можно также использовать компоненты ICE. В этом


случае добавьте пространство имен ICE:
xmlns:ice="http://www.icesoft.com/icefaces/component" -->
<!-- <ice:outputLink id="aceLink" value="http://
wiki.icesoft.org/display/ICE/ACE+Components" target="_blank">ACE
components</ice:outputLink> -->
<!-- <ice:outputLink id="iceLink" value="http://
wiki.icesoft.org/display/ICE/ICE+Components" target="_blank">ICE
components</ice:outputLink> -->
</h:panelGrid>
</ace:panel>
</h:form>
</h:body>
</html>

ICEfaces включает два набора компонентов: компоненты ICE, функ-


циональность которых реализована в основном на стороне сервера
и с ограниченны применением JavaScript, и новейшие компонен-
ты ACE, реализванные как комбинация серверного и клиентского
кода. Согласно утверждениям ICESoft (компании, разрабатыва-
ющей ICEfaces), компоненты ICE должны использоваться, только
когда требуется обеспечить поддержку устаревших браузеров, при
переходе с более старых версий ICEfaces или когда нужно миними-
зировать участие JavaScript в отображении или обработке данных.
Чтобы задействовать последние возможности современных брау-
зеров, следует использовать компоненты ACE.
126 Глава 3. Библиотеки компонентов JSF

В приложениях ICEfaces, которые генерируются средой NetBeans,


используются только компоненты ICEfaces ACE и стандартные ком­
поненты JSF. ICEfaces-тег <ace:panel> отображает панель, включаю­
щую ссылки на странице. Этот тег имеет атрибут header, используя
который можно организовать вывод заголовка панели.
Внутри тега <ace:panel> имеется несколько тегов <ace:linkButton>,
отображающих ссылки на странице. Тег <ace:linkButton> обеспечива­
ет функциональность, аналогичную функциональности стандартных
JSF-тегов <h:outputLink> и <h:commandLink>. В данном примере кноп­
ки действуют подобно стандартному компоненту <h:outputLink>.
Адрес URL для перехода определяется атрибутом href. Чтобы за­
ставить <ace:linkButton> действовать подобно стандартному JSF-
компоненту <h:commandLink>, следует использовать атрибут action.
На рис. 3.12 показана ICEfaces-версия приложения регистрации.

Рис. 3.12. ICEfaces-версия приложения регистрации


В следующем фрагменте показаны наиболее интересные для нас
части страницы регистрации
<h:form>
<ace:panel header="Registration">
<ace:messages/>
<h:panelGrid columns="2"
columnClasses="rightalign,leftalign">
<h:outputLabel value="Salutation: "
for="salutation"/>
<ace:selectMenu id="salutation" label="Salutation"
value="#{registrationBean.salutation}" >
<f:selectItem itemLabel="" itemValue=""/>
<f:selectItem itemLabel="Mr." itemValue="MR"/>
<f:selectItem itemLabel="Mrs." itemValue="MRS"/>
<f:selectItem itemLabel="Miss" itemValue="MISS"/>
<f:selectItem itemLabel="Ms" itemValue="MS"/>
<f:selectItem itemLabel="Dr." itemValue="DR"/>
Использование компонентов ICEfaces в JSF-приложениях 127
</ace:selectMenu>
<h:outputLabel value="First Name:" for="firstName" />
<h:inputText id="firstName" label="First Name"
required="true"
value="#{registrationBean.firstName}" />
<h:outputLabel value="Last Name:" for="lastName" />
<h:inputText id="lastName" label="Last Name"
required="true"
value="#{registrationBean.lastName}" />
<h:outputLabel for="age" value="Age:"/>
<ace:sliderEntry id="age"
value="#{registrationBean.age}"
min="0" max="100" showLabels="true" />
<h:outputLabel value="Email Address:"
for="email"/>
<h:inputText id="email" label="Email Address"
required="true"
value="#{registrationBean.email}">
<f:validatorvalidatorId="emailValidator"/>
</h:inputText>
<h:panelGroup/>
<ace:pushButton id="register" value="Register"
action="confirmation" />
</h:panelGrid>
</ace:panel>
</h:form>

В данном примере использован описанный выше компонент


<ace:panel>, включающий поля ввода формы. Подобно PrimeFaces,
ICEfaces имеет компонент <ace:messages> для отображения сообще­
ний; поэтому он был добавлен в страницу, чтобы избежать лишних
действий, связанных с оформлением сообщений JSF (см. рис. 3.13).

Рис. 3.13. Оформление сообщений об ошибках


128 Глава 3. Библиотеки компонентов JSF

В ICEfaces нет своих аналогов компонентам <h:outputText> и


<h:inputText>, поэтому здесь используются стандартные компоненты:
Компонент <ace:selectMenu> является аналогом стандартного ком­
понента <h:selectOneMenu>. Он отображает раскрывающийся список
и действует подобно стандартному <h:selectOneMenu>.
Компонент <ace:sliderEntry> позволяет вводить числовые значе­
ния, перемещая движок мышью.
Компонент <ace:pushButton> является аналогом стандартного ком­
понента <h:commandButton>. Когда пользователь щелкает на кнопке,
автоматически вызывается метод, указанный в атрибуте action.
В этом разделе мы лишь слегка коснулись возможностей
ICEfaces. За дополнительной информацией обращайтесь к доку­
ментации ICEfaces по адресу: http://wiki.icesoft.org/display/ICE/
ICEfaces+Documentation.

Использование компонентов
RichFaces в JSF-приложениях
Третья библиотека компонентов, которую можно выбрать при соз­
дании нового веб-приложения на Java в NetBeans, – это библиотека
RichFaces. Дистрибутив NetBeans не включает JAR-файлы RichFaces;
поэтому, как и в случае с ICEfaces, библиотеку RichFaces придется за­
грузить вручную и создать из нее библиотеку в NetBeans (см. рис. 3.14).

Рис. 3.14. Выбор библиотеки компонентов RichFaces


Использование компонентов RichFaces в JSF-приложениях 129

Последнюю стабильную версию RichFaces можно загрузить на


странице http://www.jboss.org/richfaces/download/stable.html (см.
рис. 3.15).

Рис. 3.15. Страница загрузки RichFaces на сайте http://www.jboss.org/


Щелкните на ссылке Download (Загрузить) в строке с последней
версией RichFaces. После распаковки ZIP-архива добавьте в библи­
отеку среды NetBeans следующие файлы (точные имена зависят от
версии RichFaces):
• richfaces-components-a4j-4.5.1.Final.jar;
• richfaces-components-rich-4.5.1.Final.jar;
• richfaces-core-4.5.1.Final.jar;
Библиотека RichFaces имеет также несколько внешних зависимо­
стей, которые можно найти в каталоге lib архива ZIP (точные имена
зависят от версии RichFaces):
• guava-18.0.jar;
• sac-1.3.jar;
• cssparser-0.9.14.jar.
После загрузки RichFaces с внешними зависимостями можно при­
ступать к созданию библиотеки RichFaces в NetBeans (см. рис. 3.16).
После создания библиотеки RichFaces в NetBeans завершите созда­
ние проекта и NetBeans сгенерирует приложение RichFaces, которое
можно использовать в качестве отправной точки (см. рис. 3.17).
Подобно PrimeFaces и ICEfaces, страница, сгенерированная сре­
дой NetBeans и с использованием компонентов RichFaces, содержит
ссылки на дополнительные ресурсы links to additional RichFaces (см.
рис. 3.17), как показано в следующем фрагменте:
<?xml version='1.0' encoding='UTF-8' ?>
<!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"
xmlns:rich="http://richfaces.org/rich"
xmlns:h="http://java.sun.com/jsf/html">
<h:head>
130 Глава 3. Библиотеки компонентов JSF

<title>Richfaces Welcome Page</title>


</h:head>
<h:body>
<rich:panel header="Welcome to Richfaces">
RichFaces is an advanced UI component framework for easily
integrating Ajax capabilities into business applications
using JSF. Check out the links below to learn more about
using RichFaces in your application.
<ul>
<li>
<h:outputLink value="http://richfaces.org" >
Richfaces Project Home Page</h:outputLink>
</li>
<li>
<h:outputLink value="http://showcase.richfaces.org" >
Richfaces Showcase
</h:outputLink>
</li>
<li>
<h:outputLink
value="https://community.jboss.org/en/
richfaces?view=discussions" >
User Forum
</h:outputLink>
</li>
<li>
<h:outputLink value="http://www.jboss.org/richfaces/docs" >
Richfaces documentation...
</h:outputLink>
<ul>
<li>
<h:outputLink value="http://docs.jboss.org/richfaces/
latest_4_X/Developer_Guide/en-US/html_single/" >
Development Guide
</h:outputLink>
</li>
<li>
<h:outputLink value="http://docs.jboss.org/richfaces/
latest_4_X/Component_Reference/en-US/html/" >
Component Reference
</h:outputLink>
</li>
<li>
<h:outputLink value="http://docs.jboss.org/
richfaces/latest_4_X/vdldoc/" >
Tag Library Docs
</h:outputLink>
</li>
</ul>
</li>
Использование компонентов RichFaces в JSF-приложениях 131
</ul>
</rich:panel>
</h:body>
</html>

Единственный тег RichFaces, использованный здесь, – это


<rich:panel>,
который создает панель с текстом и ссылками на стра­
ницы.

Рис. 3.16. Диалог создания библиотеки RichFaces

Рис. 3.17. Вновь созданное приложение RichFaces


132 Глава 3. Библиотеки компонентов JSF

В результате переноса приложения регистрации на использова­


ние компонентов RichFaces получилась страница, как показано на
рис. 3.18.

Рис. 3.18. RichFaces-версия приложения регистрации


В следующем фрагменте показаны наиболее интересные для нас
части страницы регистрации:
<rich:panel header="Registration">
<h:formprependId="false">
<h:panelGrid columns="3"
columnClasses="rightalign,leftalign,leftalign">
<h:outputLabel value="Salutation: " for="salutation"/>
<rich:select id="salutation"
value="#{registrationBean.salutation}">
<f:selectItem itemLabel="" itemValue=""/>
<f:selectItem itemLabel="Mr." itemValue="MR"/>
<f:selectItem itemLabel="Mrs." itemValue="MRS"/>
<f:selectItem itemLabel="Miss" itemValue="MISS"/>
<f:selectItem itemLabel="Ms" itemValue="MS"/>
<f:selectItem itemLabel="Dr." itemValue="DR"/>
</rich:select>
<rich:message for="salutation"/>
<h:outputLabel value="First Name:" for="firstName"/>
<h:inputText id="firstName" label="First Name"
required="true"
value="#{registrationBean.firstName}" />
<rich:message for="firstName" />
<h:outputLabel value="Last Name:" for="lastName"/>
<h:inputText id="lastName" label="Last Name"
required="true"
value="#{registrationBean.lastName}" />
<rich:message for="lastName" />
<h:outputLabel for="age" value="Age:"/>
<rich:inputNumberSpinner id="age" label="age"
Резюме 133
value="#{registrationBean.age}"
minValue="0"
maxValue="110"/>
<rich:message for="age"/>
<h:outputLabel value="Email Address:" for="email"/>
<h:inputText id="email" label="Email Address"
required="true"
value="#{registrationBean.email}">
<f:validatorvalidatorId="emailValidator"/>
</h:inputText>
<rich:message for="email" />
<h:panelGroup/>
<h:commandButton id="register" value="Register"
action="confirmation" />
</h:panelGrid>
</h:form>
</rich:panel>

Форма заключена в тег <rich:panel>, чтобы она отображалась вну­


три панели. Тег <rich:select> – это компонент RichFaces, отобража­
ющий раскрывающийся список. Одно из преимуществ <rich:select>
перед стандартным тегом <h:selectOneMenu> в том, что <rich:select>
может быть настроен как поле ввода с комбинированным списком
(combobox), то есть, пользователь сможет не только выбирать значе­
ния из списка, но и вводить свои. Для этого нужно присвоить атрибу­
ту enableManualInput значение true.
Компонент <rich:inputNumberSpinner> дает пользователю возмож­
ность вводить числа, либо непосредственно в поле ввода, либо щел­
кая на кнопках со стрелками.
Также библиотека RichFaces включает два компонента –
<rich:messages> и <rich:message> – являющиеся аналогами стандарт­
ных <h:messages> и <h:message>. RichFaces-версии этих компонентов
выводят отформатированные и хорошо видимые сообщения. На
рис.  3.19 показано, как компоненты <rich:message> отображают со­
общения об ошибках.
И снова мы лишь слегка коснулись возможностей RichFaces. За до­
полнительной информацией о возможностях RichFaces обращайтесь
к документации по адресу: http://www.jboss.org/richfaces/docs.

Резюме
В этой главе мы познакомились с поддержкой трех наиболее популяр­
ных библиотек компонентов JSF: PrimeFaces, ICEfaces и RichFaces в
NetBeans.
134 Глава 3. Библиотеки компонентов JSF

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


PrimeFaces, входящей в состав NetBeans, а также обсудили порядок
настройки NetBeans, чтобы получить возможность создавать JSF-
приложения с использованием библиотек компонентов ICEfaces и
RichFaces. Наконец, мы рассмотрели, как NetBeans генерирует заго­
товки, которые можно использовать в качестве отправной точки для
приложений на основе PrimeFaces, ICEfaces и RichFaces.

Рис. 3.19. Компоненты <rich:messages> и <rich:message> выводят


отформатированные и хорошо видимые сообщения
Глава 4.
Взаимодействие
с базами данных через
Java Persistence API

Java Persistence API (JPA) – это прикладной интерфейс механизма


объектно-реляционного отображения (Object-Relational Mapping,
ORM). Инструменты ORM помогают автоматизировать отображе­
ние объектов Java в таблицы реляционной базы данных. Для решения
задачи объектно-реляционного отображения ранние версии J2EE ис­
пользовали объектные компоненты (Entity Beans), главной целью ко­
торых было обеспечить синхронизацию данных в памяти с данными
в базе. Это было хорошей идеей в теории, однако на практике данная
функциональность привела к серьезному снижению производитель­
ности приложений.
Для преодоления ограничений, свойственных объектным компо­
нентам, было разработано несколько инструментов объектно-реля­
ционного отображения, таких как Hibernate, iBatis, Cayenne и Toplink.
Начиная с Java EE 5 объектные компоненты были признаны уста­
ревшими и в настоящее время рекомендуется использовать JPA. JPA
перенял идеи от нескольких инструментов объектно-реляционного
отображения и включил их в стандарт. Как мы увидим далее в этой
главе, в NetBeans имеется несколько особенностей, упрощающих раз­
работку с использованием JPA.
В этой главе будут затронуты следующие темы:
создание нашей первой сущности JPA;
взаимодействие с сущностями JPA через EntityManager;
создание сущностей JPA из схемы существующей базы данных;
именованные запросы JPA и язык Java Persistence Query
Language (JPQL);
отношения сущностей;
создание законченного приложения JSF из сущностей JPA.
136 Глава 4. Взаимодействие с базами данных через Java Persistence API

Создание первой сущности JPA


Сущности JPA являются классами Java, поля которых сохраняются в
базе данных через JPA API. Сущности JPA являются простыми «пло-
скими» объектами Java (Plain Old Java Object, POJO), и как таковые
они не обязаны наследовать конкретный родительский класс или реа­
лизовать любой конкретный интерфейс. Класс Java определяется как
сущность JPA путем декорирования его аннотацией @Entity.
Чтобы создать и протестировать нашу первую сущность JPA, соз­
дадим новое веб-приложение на основе фреймворка JavaServer Faces,
и назовем это приложение jpaweb. Как и в случае со всеми другими
примерами, будем использовать поставляемый в комплекте сервер
приложений GlassFish.

Инструкции по созданию нового проекта JSF можно найти в гла-


ве 2, «Разработка веб-приложений с использованием JavaServer
Faces 2.2».

Чтобы создать новую сущность JPA, в главном меню NetBeans вы­


берите в списке Categories (Категории) категорию Persistence (Пер­
систентность) и затем в списке File Types (Типы файлов) тип файла
Entity Class (Класс сущности), как показано на рис. 4.1.

Рис. 4.1. Создание сущности JPA


Создание первой сущности JPA 137

После этого NetBeans запустит мастера New Entity Class (Новый


класс сущностей), как показано на рис. 4.2.

Рис. 4.2. Мастер создания нового класса сущностей JPA


Здесь нужно определить значения для полей Class Name (Имя
класса) и Package (Пакет) (в нашем примере это будут значения
Customer и com.ensode.jpaweb).
Проекты, использующие JPA, требуют наличия модуля персистент­
ности. Модули хранения определяются в файле persistence.xml. При
создании в проекте первой сущности JPA, NetBeans обнаружит, что
файл persistence.xml отсутствует, и автоматически установит фла­
жок Create Persistence Unit (Создать единицу персистентности). На
следующем шаге мастер предложит ввести информацию, необходи­
мую для создания модуля персистентности, как показано на рис. 4.3.
Мастер Provider and Database (Поставщик и база данных) предло­
жит имя для нашего модуля персистентности. В большинстве случаев
вполне безопасно принять предложенное значение по умолчанию.
JPA является спецификацией, для которой существует много реа­
лизаций. NetBeans поддерживает несколько реализаций JPA, вклю­
чая EclipseLink, Toplink Essentials, Hibernate, KODO и OpenJPA;
поскольку поставляемый с дистрибутивом NetBeans сервер приложе­
ний GlassFish по умолчанию включает реализацию EclipseLink, имеет
смысл принять это значение по умолчанию для поля Persistence Pro-
vider (Поставщик персистентности), если предполагается разверты­
вание приложения на сервере GlassFish.
138 Глава 4. Взаимодействие с базами данных через Java Persistence API

Рис. 4.3. Настройка модуля персистентности

Прежде чем взаимодействовать с базой данных из любого прило­


жения Java EE, на сервере приложений должны быть созданы пул со­
единений с базой данных и источник данных.
Пул соединений содержит информацию, позволяющую соеди­
няться с базой данных, такую как имя сервера, порт и учетные дан­
ные пользователя. В сравнении с непосредственным соединением
через JDBC, пул соединений имеет одно преимущество: соединения
в пуле соединений никогда не закрываются, они просто выделяют­
ся приложениям, которые в них нуждаются. Это улучшает произво­
дительность, поскольку отсутствуют операции открытия и закрытия
соединения с базой данных, которые обходятся дорого с точки зрения
производительности.
Источники данных дают возможность получить объект соедине­
ния из пула, для чего сначала приобретается экземпляр источника
данных, а затем, вызовом его метода getConnection(), приобретается
подключение к базе данных. Имея дело с JPA, не нужно непосред­
ственно получать ссылку на источник данных, это все автоматически
делает JPA API, но мы все еще должны указать источник данных для
использования в модуле персистентности приложения.
NetBeans поставляется с несколькими предварительно настро­
енными источниками данных и пулами соединений, мы можем ис­
пользовать в своих приложениях один из этих ресурсов. Кроме того,
NetBeans позволяет также создавать эти ресурсы «на лету», что мы и
будем делать в нашем примере.
Создание первой сущности JPA 139

Чтобы создать новый источник данных, нужно выбрать элемент


New Data Source... (Новый источник данных...) из поля комбиниро­
ванного списка Data Source (Источник данных).

Рис. 4.4. Создание нового источника данных


Источник данных должен взаимодействовать с пулом соединений.
В состав NetBeans уже входит несколько готовых пулов соединений,
но, так же как в случае с источниками данных, есть возможность соз­
давать новые пулы соединений «по требованию». Для этого следу­
ет выбрать элемент New Database Connection... (Создать соедине­
ние с базой данных...) из поля комбинированного списка Database
Connection (Подключение к базе данных), как показано на рис. 4.4.

Рис. 4.5. Добавление нового драйвера СУРБД


В состав NetBeans входят драйверы JDBC для нескольких систем
управления реляционными базами данных (Relational Database
Management Systems, RDBMS), сокращенно СУРБД, таких как
JavaDB, MySQL, PostgreSQL. JavaDB поставляется вместе с GlassFish
и NetBeans, поэтому мы выбрали JavaDB для нашего примера, чтобы
избежать необходимости установки внешней СУРБД.
140 Глава 4. Взаимодействие с базами данных через Java Persistence API

Для СУРБД, не поддерживаемых «из коробки», следует получить


драйвер JDBC и сообщить NetBeans о его местоположении, выбрав
New Driver (Новый драйвер) в комбинированном списке Driver
(Драйвер) и указав каталог с JAR-файлом драйвера JDBC (см.
рис.  4.5). За дополнительной информацией обращайтесь к доку-
ментации по используемой вами СУРБД.

После щелчка на кнопке Next > (Далее >) откроется диалог на­
стройки соединения, как показано на рис. 4.6.

Рис. 4.6. Диалог настройки параметров соединения


Драйвер JavaDB уже установлен на рабочей станции, поэтому вы­
брано имя сервера localhost. По умолчанию JavaDB прослушивает
порт 1527, поэтому данный порт указан также в строке URL. В приме­
ре предполагается устанавливать соединение с базой данных jpaintro,
поэтому данное название указано в качестве имени базы данных.
По умолчанию для каждого пользователя используется собствен­
ная схема базы данных, имя которой совпадает с именем пользова­
теля, а поскольку каждая база данных JavaDB уже содержит схему с
именем APP, можно избежать лишних сложностей, создав пользова­
теля с именем APP и паролем по выбору.
Поскольку базы данных с именем jpaintro еще не существует, ее
нужно создать. Сделать это можно, щелкнув на кнопке Connection
Properties (Свойства соединения) и ввести свойство с именем create
и значением true, как показано на рис. 4.7.

Powered by TCPDF (www.tcpdf.org)


Создание первой сущности JPA 141

Рис. 4.7. Настройка свойств соединения


На следующем шаге (см. рис. 4.8) нужно выбрать схему для ис­
пользования в приложении. В приложениях, взаимодействующих
с  СУРБД через драйвер JavaDB, часто используется схема APP, по­
этому выберем ее.

Рис. 4.8. Выбор схемы базы данных для использования


в приложении
142 Глава 4. Взаимодействие с базами данных через Java Persistence API

Далее NetBeans предложит ввести описательное имя соединения


(см. рис. 4.9).

Рис. 4.9. Ввод описательного имени соединения


Здесь можно ввести свое название или принять название, предло­
женное по умолчанию. После создания нового источника данных и
пула соединений можно продолжать настройку модуля персистент­
ности (см. рис. 4.10).

Рис. 4.10. Продолжение настройки модуля персистентности


Создание первой сущности JPA 143

Предпочтительнее оставить отмеченным флажок Use Java


Transaction APIs (Использовать API-интерфейс Java Transaction).
В этом случае реализация JPA будет использовать механизм транзак­
ций Java Transaction API (JTA), что позволит серверу приложений
управлять транзакциями. Если снять этот флажок, придется вручную
писать код управления транзакциями.
Большинство реализаций JPA позволяют определять стратегию
создания таблиц. Можно настроить реализацию JPA так, что она
будет создавать таблицы для сущностей при развертывании прило­
жения, удалять и вновь создавать таблицы при повторном развер­
тывании приложения или не создавать никакие таблицы вообще.
NetBeans позволяет определять стратегию создания таблиц выбором
соответствующего значения в группе переключателей Table Genera-
tion Strategy (Стратегия создания таблицы).

В процессе разработки приложения разумным выглядит выбор


стратегии Drop and Create (Удалить и создать). Это позволит до-
бавлять, удалять и переименовывать поля в JPA-сущностях без не-
обходимости производить те же самые изменения в схеме базы
данных. При выборе данной стратегии таблицы в схеме базы дан-
ных будут удаляться и воссоздаваться каждый раз, когда будет раз-
вертываться приложение, поэтому любые данные, сохраненные
ранее, будут потеряны.

После создания нового источника данных, соединения с базой дан­


ных и модуля персистентности, мы готовы создать новую сущность
JPA.
Сделать это можно щелчком на кнопке Finish (Готово). В результа­
те NetBeans сгенерирует исходный код сущности JPA.

JPA позволяет отображать поле первичного ключа сущности JPA


в столбец любого типа (VARCHAR, NUMBER и т. д.). Однако предпо-
чтительнее иметь числовой суррогатный первичный ключ, то есть
первичный ключ, который играет роль простого идентификатора и
не несет никакого прикладного смысла. Выбор типа по умолчанию
(см. рис. 4.2) Long в поле Primary Key Type (Тип первичного клю-
ча) позволит охватить достаточно широкий диапазон значений для
первичных ключей сущностей.

Класс Customer имеет несколько отличительных особенностей (вы­


делены в следующем фрагменте), о которых стоит поговорить отдель­
но:
144 Глава 4. Взаимодействие с базами данных через Java Persistence API

package com.ensode.jpaweb;

import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class Customer implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;

public Long getId() {


return id;
}

public void setId(Long id) {


this.id = id;
}

// Остальные методы, сгенерированные автоматически,


// (equals(), hashCode(), toString())
// для простоты не показаны
}

Как видите, сущность JPA является стандартным объектом Java.


Поэтому нет никакой необходимости расширять какой-либо специ­
альный класс или реализовать какой-либо специальный интерфейс.
Что действительно отличает сущность JPA от иных объектов Java, –
это несколько аннотаций JPA.
Аннотация @Entity указывает, что класс является сущностью JPA.
Любой объект, который потребуется сохранить в базе данных через
JPA, должен быть декорирован этой аннотацией.
Аннотация @Id указывает на поле в сущности JPA, являющееся пер­
вичным ключом. Первичный ключ – это уникальный идентификатор
сущности. Ни у каких двух объектов не может быть одного и того же
значения первичного ключа. Этой аннотацией может быть отмечено
поле, играющее роль первичного ключа (этой стратегии следует ма­
стер NetBeans), но точно так же этой аннотацией можно отметить ме­
тод получения значения из поля первичного ключа.
Аннотации @Entity и @Id являются двумя аннотациями, минималь­
но необходимыми классу, чтобы считаться сущностью JPA. JPA под­
держивает автоматическую генерацию первичных ключей. Чтобы
Создание первой сущности JPA 145

воспользоваться этой возможностью, можно использовать аннота­


цию @GeneratedValue. Как видно из примера выше, сгенерированная
сущность JPA использует эту аннотацию. Данная аннотация опреде­
ляет стратегию генерации значений первичных ключей. Все возмож­
ные стратегии перечислены в табл. 4.1.
Таблица 4.1. Стратегии генерации первичных ключей
Стратегия генерации
Описание
первичного ключа
GenerationType.AUTO Стратегия будет выбираться поставщиком
автоматически. Используется по умолчанию,
если стратегия генерации первичного ключа
не указана явно.
GenerationType.IDENTITY Указывает, что для создания значений первич-
ного ключа в сущности JPA должен использо-
ваться столбец идентификаторов в таблице
базы данных.
GenerationType.SEQUENCE Указывает, что для создания значений первич-
ного ключа в сущности JPA должна использо-
ваться последовательность в базе данных.
GenerationType.TABLE Указывает, что для создания значений первич-
ного ключа в сущности JPA должна использо-
ваться таблица в базе данных.

В большинстве случаев предпочтительнее использовать стратегию


GenerationType.AUTO, поэтому она используется почти всегда, и по
этой же причине мастер New Entity Class (Создание класса сущно­
сти) использует эту стратегию.

При использовании стратегии создания значения первичного клю-


ча на основе последовательности (sequence) или таблицы (table),
возможно, придется указать, какая последовательность или табли-
ца должна использоваться. Они могут быть определены с помощью
аннотаций @SequenceGenerator и @TableGenerator, соответ-
ственно. Дополнительную информацию можно получить из JavaDoc
по Java EE 7: http://docs.oracle.com/javaee/7/api/.

Добавление сохраняемых полей


в сущность
На данном этапе наша сущность JPA содержит единственное поле –
первичный ключ. По общему мнению, поле, не очень полезное с точки
146 Глава 4. Взаимодействие с базами данных через Java Persistence API

зрения бизнес-логики. Нужно добавить еще несколько полей, кото­


рые будут сохраняться в базе данных:
package com.ensode.jpaweb;

import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class Customer implements Serializable {

private static final long serialVersionUID = 1L;

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String firstName;
private String lastName;

public Long getId() {


return id;
}

public void setId(Long id) {


this.id = id;
}

public String getFirstName() {


return firstName;
}

public void setFirstName(String firstName) {


this.firstName = firstName;
}

public String getLastName() {


return lastName;
}

public void setLastName(String lastName) {


this.lastName = lastName;
}
}

В измененную версию сущности JPA было добавлено два поля, ко­


торые будут храниться в базе данных; поля firstName и lastName будут
Создание первой сущности JPA 147

использоваться для хранения имени и фамилии пользователя. Сущ­


ности JPA должны соответствовать соглашениям по стандартам коди­
рования JavaBean, это означает, что они должны иметь общедоступ­
ные (public) конструкторы, не принимающие параметров (каждый
автоматически генерируется компилятором Java, если не указывают­
ся никакие другие конструкторы), а все поля должны быть частными
(private), и доступ к ним должен осуществляться через методы get()
и set().

Автоматическая генерация методов get() и set(). В NetBeans ме-


тоды get() и set() могут быть созданы автоматически, достаточно
объявить поля как обычно, а затем, после нажатия комбинации кла-
виш Alt+Insert, выбрать в раскрывающемся окне пункт Getter and
Setter (Методы получения и установки), отметить флажок рядом с
именем класса, чтобы выбрать все поля и щелкнуть на кнопке Gen-
erate (Создать).

Прежде чем можно будет использовать JPA для сохранения полей


сущности в базе данных, нужно написать некоторый дополнитель­
ный код.

Создание объекта доступа к данным


Всякий раз, когда пишется код взаимодействия с базой данных, пред­
почтительнее следовать шаблону проектирования Объект доступа к
данным (Data Access Object, DAO). Шаблон проектирования DAO
сохраняет всю функциональность доступа к базе данных в классах
DAO. Он обладает преимуществом четкого разделения проблем,
оставляя другие уровни нашего приложения, такие как логика поль­
зовательского интерфейса и бизнес-логика, свободными от любой ло­
гики сохранения данных.
NetBeans может помочь сгенерировать классы контроллеров JPA
из имеющихся сущностей. Эти классы контроллеров JPA следуют ша­
блону проектирования DAO. Чтобы создать класс контроллера JPA,
нужно в диалоговом окне New File (Создать файл) выбрать катего­
рию Persistence (Персистентность) и затем тип файла JPA Controller
Classes from Entity Classes (Классы контроллеров JPA из классов
сущностей), как показано на рис. 4.11.
На следующем шаге следует выбрать классы сущностей, для
которых нужно сгенерировать классы контроллеров JPA (см.
рис. 4.12).
148 Глава 4. Взаимодействие с базами данных через Java Persistence API

Рис. 4.11. Создание класса контроллера JPA

Рис. 4.12. Выбор классов сущностей для создания классов


контроллеров JPA
Создание первой сущности JPA 149

Теперь нужно определить проект и пакет для размещения классов


контроллеров JPA (см. рис. 4.13).

Рис. 4.13. Выбор проекта и пакета для размещения классов


контроллеров JPA
После щелчка на кнопке Finish (Готово) будет создан новый класс
контроллера JPA:
package com.ensode.jpaweb;
// инструкции импортирования опущены
public class CustomerJpaController implements Serializable {

public CustomerJpaController(UserTransaction utx,


EntityManagerFactory emf) {
this.utx = utx;
this.emf = emf;
}
private UserTransaction utx = null;
private EntityManagerFactory emf = null;

public EntityManager getEntityManager() {


return emf.createEntityManager();
}

public void create(Customer customer) throws


RollbackFailureException, Exception {
EntityManager em = null;
try {
utx.begin();
em = getEntityManager();
150 Глава 4. Взаимодействие с базами данных через Java Persistence API

em.persist(customer);
utx.commit();
} catch (Exception ex) {
try {
utx.rollback();
} catch (Exception re) {
throw new RollbackFailureException(
"An error occurred attempting to roll back the transaction.",
re);
}
throw ex;
} finally {
if (em != null) {
em.close();
}
}
}

public void edit(Customer customer) throws


NonexistentEntityException,
RollbackFailureException, Exception
{
EntityManager em = null;
try {
utx.begin();
em = getEntityManager();
customer = em.merge(customer);
utx.commit();
} catch (Exception ex) {
try {
utx.rollback();
} catch (Exception re) {
throw new RollbackFailureException(
"An error occurred attempting to roll back the transaction.",
re);
}
String msg = ex.getLocalizedMessage();
if (msg == null || msg.length() == 0) {
Long id = customer.getId();
if (findCustomer(id) == null) {
throw new NonexistentEntityException(
"The customer with id " + id
+ " no longer exists.");
}
}
throw ex;
} finally {
if (em != null) {
em.close();
}
Создание первой сущности JPA 151
}
}

public void destroy(Long id) throws NonexistentEntityException,


RollbackFailureException, Exception {
EntityManager em = null;
try {
utx.begin();
em = getEntityManager();
Customer customer;
try {
customer = em.getReference(Customer.class, id);
customer.getId();
} catch (EntityNotFoundException enfe) {
throw new NonexistentEntityException(
"The customer with id " + id
+ " no longer exists.", enfe);
}
em.remove(customer);
utx.commit();
} catch (Exception ex) {
try {
utx.rollback();
} catch (Exception re) {
throw new RollbackFailureException(
"An error occurred attempting to roll back the transaction.",
re);
}
throw ex;
} finally {
if (em != null) {
em.close();
}
}
}

public List<Customer> findCustomerEntities() {


return findCustomerEntities(true, -1, -1);
}

public List<Customer> findCustomerEntities(int maxResults,


int firstResult) {
return findCustomerEntities(false, maxResults, firstResult);
}

private List<Customer> findCustomerEntities(boolean all, int


maxResults, int firstResult) {
EntityManager em = getEntityManager();
try {
CriteriaQuery cq = em.getCriteriaBuilder().createQuery();
152 Глава 4. Взаимодействие с базами данных через Java Persistence API

cq.select(cq.from(Customer.class));
Query q = em.createQuery(cq);
if (!all) {
q.setMaxResults(maxResults);
q.setFirstResult(firstResult);
}
return q.getResultList();
} finally {
em.close();
}
}

public Customer findCustomer(Long id) {


EntityManager em = getEntityManager();
try {
return em.find(Customer.class, id);
} finally {
em.close();
}
}

public int getCustomerCount() {


EntityManager em = getEntityManager();
try {
CriteriaQuery cq = em.getCriteriaBuilder().createQuery();
Root<Customer> rt = cq.from(Customer.class);
cq.select(em.getCriteriaBuilder().count(rt));
Query q = em.createQuery(cq);
return ((Long) q.getSingleResult()).intValue();
} finally {
em.close();
}
}
}

Как видите, среда разработки NetBeans сгенерировала методы для


создания, чтения, изменения и удаления сущностей JPA.
Метод создания новой сущности называется create(), он прини­
мает экземпляр нашей сущности JPA в единственном аргументе. Этот
метод просто вызывает метод persist() объекта EntityManager, кото­
рый заботится о сохранении данных в базе.
Для чтения данных сгенерировано несколько методов. Метод
findCustomer() принимает в единственном параметре первичный ключ
сущности JPA, которую нужно извлечь, затем вызывает метод find()
объекта EntityManager для получения данных из базы и возвращает
экземпляр найденной сущности JPA. Метод findCustomerEntities()
имеет несколько перегруженных версий, позволяющих получить бо­
Автоматическое создание сущностей JPA 153

лее одной сущности JPA. Версия, делающая всю «основную работу»,


имеет следующую сигнатуру:
private List<Customer> findCustomerEntities(boolean all,
int maxResults,int firstResult)

Первый параметр типа boolean определяет, требуется ли извлечь


все значения, имеющиеся в базе данных. Второй параметр определяет
максимальное количество результатов, которые требуется получить,
и последний параметр дает возможность определить порядковый
номер первого результата, который мы хотим получить. Этот метод
использует Criteria API, введенный в JPA 2.0 для программного соз­
дания запросов. Если в параметре all передать false, этот метод при­
мет во внимание максимальное количество результатов и порядко­
вый номер первого результата, и передаст соответствующие значения
методам setMaxResults() и setFirstResult() объекта запроса Query.
Метод edit() используется для изменения имеющихся сущностей.
Он принимает экземпляр сущности JPA в единственном параметре.
Этот метод вызывает метод merge() объекта EntityManager, который
изменяет данные в базе, синхронизируя их с содержимым сущности
JPA, которую получает в качестве параметра.
Метод destroy() удаляет сущности. Он принимает первичный
ключ удаляемого объекта в единственном параметре. Сначала он
проверяет наличие в базе требуемой сущности, если такой сущности
нет, возбуждается исключение, в противном случае соответствующая
запись удаляется из базы данных вызовом метода remove() объекта
EntityManager.
Теперь у нас имеется все, что нужно для сохранения свойств сущно­
сти в базе данных. Для программного выполнения операций CRUD –
сокращение от Create (создать), Read (прочитать), Update (из­
менить) и Delete (удалить) – с сущностью JPA достаточно просто
вызывать методы сгенерированного контроллера JPA.

Автоматическое создание
сущностей JPA
Во многих проектах приходится работать с существующей схемой
базы данных, созданной администратором. NetBeans может генери­
ровать сущности JPA на основе имеющейся схемы базы данных, обе­
регая нас от большой и потенциально утомительной работы.
154 Глава 4. Взаимодействие с базами данных через Java Persistence API

В этом разделе мы будем использовать свою схему базы данных.


Чтобы создать схему, следует выполнить SQL-сценарий, кото­
рый создаст схему и заполнит некоторые таблицы. Для этого нуж­
но должны перейти в окно Services (Службы), щелкнуть правой
кнопкой мыши на элементе JavaDB и выбрать пункт контекстного
меню Create Database... (Создать базу данных...), как показано на
рис. 4.14.

Рис. 4.14. Создание базы данных


Затем добавить информацию о базе данных в мастере Create
JavaDB Database (Создание базы данных JavaDB), как показано на
рис. 4.15.

Рис. 4.15. Добавление информации о базе данных


Теперь можно открыть сценарий SQL (см. рис. 4.16), выбрав в
главном меню пункт File | Open File... (Файл | Открыть файл...) и от­
крыв его. Наш сценарий хранится в файле с именем create_populate_
tables.sql. Он включен в пакет примеров с исходными кодами кода
для этой главы.
Автоматическое создание сущностей JPA 155

Рис. 4.16. Содержимое сценария create_populate_tables.sql


Открыв сценарий SQL, нужно выбрать недавно созданное соеди­
нение в поле комбинированного списка Connection (Соединение),
как показано на рис. 4.17.

Рис. 4.17. Выбор недавно созданного соединения

Затем щелкнуть на значке , чтобы выполнить его.


После этого в базе данных появится множество таблиц, как пока­
зано на рис. 4.18.
Чтобы сгенерировать сущности JPA из существующей схемы, как
те, что мы только что создали, нужно создать новый проект. Выбрать
в главном меню пункт File | New... (Файл | Создать файл...) и в ка­
тегории Persistence (Персистентность) выбрать тип файлов Entity
Classes from Database (Классы сущностей из базы данных), как по­
казано на рис. 4.19.

NetBeans позволяет генерировать сущности JPA практически для


любых проектов. В нашем примере мы будем использовать проект
веб-приложения.
156 Глава 4. Взаимодействие с базами данных через Java Persistence API

Рис. 4.18. Таблицы, созданные сценарием


create_populate_tables.sql

Рис. 4.19. Создание сущностей из базы данных


Автоматическое создание сущностей JPA 157

Здесь можно выбрать существующий источник данных или, как


в предыдущем примере, создать его «на лету». В нашем примере мы
создали новый источник и затем выбрали соединение с базой данных,
созданное ранее в этом разделе.

Рис. 4.20. Создание сущностей для всех таблиц

После создания или выбора источника данных, нужно выбрать


одну или более таблиц, чтобы сгенерировать сущности JPA. Если тре­
буется создать сущности для всех таблиц, можно просто щелкнуть на
кнопке Add All >> (Добавить все >>), как показано на рис. 4.20.
После щелчка на кнопке Next > (Далее >) NetBeans предоставит
возможность изменить названия сгенерированных классов, хотя зна­
чения по умолчанию, как правило, являются вполне приемлемыми.
Также следует определить пакет для классов и желательно устано­
вить флажок Generate Named Query Annotations for Persistent Fields
(Сгенерировать аннотации именованных запросов для сохраняемых
полей). Можно также дополнительно сгенерировать аннотации Java
API для связывания с XML (Java API for XML Binding, JAXB) и соз­
дать модуль персистентности (см. рис. 4.21).

Именованные запросы подробно описываются в следующем под-


разделе.
158 Глава 4. Взаимодействие с базами данных через Java Persistence API

Рис. 4.21. Определение пакета для сгенерированных классов

В следующем диалоге мастера можно выбрать способ извлече­


ния связанных сущностей (с опережением (eagerly) или по запросу
(lazily)). По умолчанию выбирается поведение, когда сущности, на­
ходящиеся в отношении «один к одному» и «многие к одному», из­
влекаются с опережением, а сущности, находящиеся в отношении
«один ко многим» и «многие ко многим», извлекаются по запросу
(см. рис. 4.22).
Дополнительно можно выбрать тип коллекции для стороны «ко
многим» в отношениях «один ко многим» или «многие ко многим».
Значением по умолчанию является java.util.Collection, другими
допустимыми значениями являются java.util.List и java.util.
Set.
Установка флажка Fully Qualified Database Table Names (Полные
имена таблиц базы данных) приводит к добавлению элементов ката­
лога и схемы таблицы, отображаемой аннотацией @Table для каждой
сгенерированной сущности.
Установка флажка Attributes for Regenerating Tables (Атрибу­
ты для регенерации таблиц) (см. рис. 4.22) приводит к добавлению
аннотаций @Column с атрибутами, такими как length (определяет
максимальную допустимую длину столбца), nullable (определяет
допустимость «пустых» значений (NULL) в столбце), precision и
Автоматическое создание сущностей JPA 159

scale (определяют точность и множитель десятичных значений со­


ответственно). Установка этого флажка также добавляет атрибут
uniqueConstraints в аннотации @Table, определяющий любые ограни­
чения уникальности данных, которые применяются к таблице.

Рис. 4.22. Определение параметров извлечения сущностей

Установка флажка Use Column Names in Relationships (Исполь­


зовать имена столбцов в отношениях) (см. рис. 4.22) приводит к ис­
пользованию в отношениях «один ко многим» и «один к одному»
имен, соответствующих именам полей в таблице базы данных. По
умолчанию этот флажок установлен. Однако, если снять этот фла­
жок, получается, на мой взгляд, более читаемый код.
Установка флажка Use Defaults if Possible (Использовать умолча­
ния если возможно) приводит к тому, что NetBeans будет генериро­
вать только аннотации, переопределяющие умолчания.
Установка флажка Generate Fields for Unresolved Relationships
(Генерировать поля для неразрешенных отношений) приводит к
тому, что NetBeans будет генерировать поля для сущностей, которые
не удается разрешить.
После щелчка на кнопке Finish (Готово) NetBeans сгенериру­
ет сущности JPA для всех таблиц в базе данных, как показано на
рис. 4.23
160 Глава 4. Взаимодействие с базами данных через Java Persistence API

Рис. 4.23. NetBeans сгенерирует сущности JPA


для всех таблиц
Наша база данных содержала таблицу под названием Customer, да­
вайте рассмотрим сгенерированную сущность Customer.
package com.ensode.jpa;

import java.io.Serializable;
import java.util.Collection;
import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

@Entity
@Table(name = "CUSTOMER")
@NamedQueries({
@NamedQuery(name = "Customer.findAll",
query = "SELECT c FROM Customer c"),
@NamedQuery(name = "Customer.findByCustomerId",
query = "SELECT c FROM Customer c WHERE c.customerId = :customerId"),
Автоматическое создание сущностей JPA 161
@NamedQuery(name = "Customer.findByFirstName",
query = "SELECT c FROM Customer c WHERE c.firstName = :firstName"),
@NamedQuery(name = "Customer.findByMiddleName",
query = "SELECT c FROM Customer c WHERE c.middleName = :middleName"),
@NamedQuery(name = "Customer.findByLastName",
query = "SELECT c FROM Customer c WHERE c.lastName = :lastName"),
@NamedQuery(name = "Customer.findByEmail",
query = "SELECT c FROM Customer c WHERE c.email = :email")})

public class Customer implements Serializable {


private static final long serialVersionUID = 1L;
@Id
@Basic(optional = false)
@NotNull
@Column(name = "CUSTOMER_ID")
private Integer customerId;
@Size(max = 20)
@Column(name = "FIRST_NAME")
private String firstName;
@Size(max = 20)
@Column(name = "MIDDLE_NAME")
private String middleName;
@Size(max = 20)
@Column(name = "LAST_NAME")
private String lastName;
// @Pattern(regexp="[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'
*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9]
(?:[a-z0-9-]*[a-z0-9])?", message="Invalid email")
// если поле предназначено для ввода адреса электронной почты,
// для его проверки предпочтительнее использовать эту аннотацию
@Size(max = 30)
@Column(name = "EMAIL")
private String email;
@OneToMany(mappedBy = "customer")
private Collection<Telephone> telephoneCollection;
@OneToMany(mappedBy = "customer")
private Collection<CustomerOrder> customerOrderCollection;
@OneToMany(mappedBy = "customer")
private Collection<Address> addressCollection;
// сгенерированные конструкторы и методы для простоты не показаны.
}

Как видите, NetBeans генерирует класс, декорированный аннота­


цией @Entity, которая отмечает класс как сущность JPA. Обратите
внимание, что NetBeans автоматически добавила к одному из полей
аннотацию @Id, опираясь на ограничение первичного ключа в табли­
це, использованной для создания сущности JPA. Заметьте, что не
указана никакая стратегия генерации первичного ключа, мы должны
или заполнить первичный ключ самостоятельно, или добавить анно­
162 Глава 4. Взаимодействие с базами данных через Java Persistence API

тацию @GeneratedValue вручную. Чтобы показать, что поле является


обязательным, оно было отмечено аннотацией @Basic.
Также обратите внимание на аннотацию @Table. Эта дополнитель­
ная аннотация указывает, в какую таблицу отображается сущность
JPA. В отсутствие аннотации @Table сущность отобразится в табли­
цу, имеющую то же имя, что и (без учета регистра символов) класс
сущности. В данном конкретном примере аннотация @Table избыточ­
на, но бывают случаи, когда ее использование полезно. Например, в
некоторой схеме базы данных таблицы названы во множественном
числе (то есть CUSTOMERS), тогда как имеет смысл называть сущности
в единственном числе (Customer). Дополнительно стандартное согла­
шение о присвоении имен таблицам базы данных, содержащих боль­
ше чем одно слово, состоит в использовании символа подчеркивания
для разделения слов (то есть CUSTOMER_ORDER), а в Java-стандарте нуж­
но использовать «верблюжий» регистр (то есть CustomerOrder). Ан­
нотация @Table позволяет нам следовать установленным стандартам
именования и в реляционной базе данных, и в Java-мире.

Именованные запросы и JPQL


Далее можно видеть аннотацию @NamedQueries (эта аннотация гене­
рируется, только если установлен флажок Generate Named Query
Annotations for Persistent Fields (Генерировать аннотации именован­
ных запросов для сохраняемых полей) в мастере New Entity Classes
from Database (Новые классы сущностей из базы данных). Данная
аннотация имеет атрибут value (название атрибута в коде может быть
опущено, поскольку это единственный атрибут данной аннотации).
Значением атрибута является массив аннотаций @NamedQuery. Ан­
нотация @NamedQuery имеет атрибут name, который используется для
определения логического имени (в соответствии с соглашениями,
в качестве части имени запроса используется имя сущности JPA –
как видно в сгенерированном коде, мастер New Entity Classes from
Database (Новые классы сущностей из базы данных) следует этим
соглашениям) и атрибут query, который определяющий запрос на
языке запросов Java Persistence Query Language (JPQL), который бу­
дет выполняться именованным запросом.
JPQL – это специализированный язык запросов для JPA с синтак­
сисом, подобным SQL. Мастер New Entity Classes from Database
(Новые классы сущности из базы данных) генерирует запрос JPQL
для каждого поля сущности. Результатом выполнения запроса явля­
ется список, содержащий все экземпляры сущности, соответствую­
Автоматическое создание сущностей JPA 163

щие заданному критерию. Следующий фрагмент кода иллюстрирует


этот процесс:
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.Query;

public class CustomerDAO {

public List findCustomerByLastName(String someLastName)


{

// код, выполняющий поиск EntityManager, для простоты не показан

Query query =
em.createNamedQuery("Customer.findByLastName");
query.setParameter("lastName", someLastName);
List resultList = query.getResultList();
return resultList;
}
}

Здесь приводится определение объекта DAO с методом, который


возвращает список сущностей Customer для клиентов, фамилии кото­
рых соответствуют параметру метода. Для этого нужно получить эк­
земпляр объекта типа javax.pesistence.Query. Как показано во фраг­
менте выше, это можно сделать вызовом метода createNamedQuery()
объекта EntityManager, передав ему имя запроса (определенное в ан­
нотации @NamedQuery). Обратите внимание, что именованные запро­
сы, сгенерированные мастером NetBeans, содержат строки с двоето­
чием (:) в начале. Эти строки являются именованными параметрами
(named parameters) – они действуют подобно переменным, которым
можно присваивать значения в процессе работы приложения.
В нашем примере мы присваиваем именованному параметру
lastName в запросе JPQL значение аргумента someLastName, который
передается методу.
После заполнения всех параметров запроса можно получить спи­
сок всех соответствующих сущностей, вызвав метод getResultList()
объекта Query.
Возвращаясь к сгенерированной сущности JPA, обратите внима­
ние, что мастер автоматически добавил аннотацию @Id к полю, ото­
бражаемому в первичный ключ таблицы. Дополнительно каждое
поле декорируется аннотацией @Column, которая позволяет следовать
стандартным соглашениям об именовании в реляционной базе дан­
ных и в Java-мире.
164 Глава 4. Взаимодействие с базами данных через Java Persistence API

Проверка допустимости со стороны


компонентов
Проверка допустимости со стороны компонентов берет свое на­
чало от запроса на спецификацию Java (Java Specification Request)
JSR 303, введенного в Java EE 6. Проверка допустимости со стороны
компонентов реализована в виде набора аннотаций в пакете javax.
validation. Мастер создания сущностей JPA в NetBeans в полной
мере пользуется этим механизмом, добавляя аннотации проверки до­
пустимости к любым полям компонентов, опираясь на определения
столбцов таблиц, которые используются для создания сущностей.
Некоторые такие аннотации можно видеть в сущности Customer.
Поле customerId декорировано аннотацией @NotNull, которая, как
подразумевает ее имя, препятствует присваивания этому полю «пу­
стого» значения.
Несколько полей в сущности Customer декорированы аннотацией
@Size. Эта аннотация определяет максимальное число символов, ко­
торые может принять свойство компонента. И вновь мастер NetBeans
получает эту информацию из таблиц, на основе которых генерирует
сущности.
Другой аннотацией проверки допустимости со стороны компонен­
тов, которую можно использовать, является аннотация @Pattern. Эта
аннотация гарантирует соответствие значения декорированного поля
данному регулярному выражению.
Обратите внимание, что непосредственно перед свойством email в
сущности Customer мастер добавил аннотацию @Pattern и закомменти­
ровал ее. Мастер заметил, что столбец таблицы имеет имя EMAIL, и запо­
дозрил (но не смог проверить), что это поле предназначено для хране­
ния адреса электронной почты. Поэтому мастер добавил аннотацию с
регулярным выражением для проверки соответствия адресу электрон­
ной почты, но поскольку не смог убедиться, что поле действительно
предназначено для хранения адреса электронной почты, закомменти­
ровал эту строку кода. Это свойство действительно предназначается
для хранения адреса электронной почты, поэтому мы должны убрать
комментарий в этой автоматически сгенерированной строке.

Отношения сущностей
Имеется несколько аннотаций, которые можно использовать в сущ­
ностях JPA для определения отношений между ними. В сущности
Customer, показанной выше, видно, что мастер обнаружил несколько
Автоматическое создание сущностей JPA 165

отношений «один ко многим» в таблице CUSTOMER и автоматически до­


бавил аннотацию @OneToMany, чтобы определить эти отношения в сущ­
ности. Обратите внимание, что каждое поле c аннотацией @OneToMany
имеет тип java.util.Collection, стороной «один» этого отношения
является Customer, поскольку у заказчика может быть много заказов,
много адресов (улица, электронная почта и т. д.), или много номеров
телефонов (домашний, рабочий, сотовый и т. д.). Обратите внимание,
что мастер использует обобщенные типы (generics) для определения
объектов, которые можно добавить к каждой коллекции. Объекты в
этих коллекциях являются сущностями JPA, отображающимися в со­
ответствующие таблицы в схеме базы данных.
Примите к сведению, что аннотация имеет атрибут mappedBy. Этот
очень важный атрибут, поскольку каждое из этих отношений являет­
ся двунаправленным (можно получить доступ ко всем адресам кли­
ента, а для данного адреса можно определить, какому клиенту он при­
надлежит). Значение этого атрибута должно соответствовать имени
поля с другой стороны отношения. Давайте рассмотрим сущность
Address, чтобы продемонстрировать другую сторону отношения за­
казчик – адрес.
package com.ensode.jpa;

import java.io.Serializable;
import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

@Entity
@Table(name = "ADDRESS")
@NamedQueries({
@NamedQuery(name = "Address.findAll",
query = "SELECT a FROM Address a"),
@NamedQuery(name = "Address.findByAddressId",
query = "SELECT a FROM Address a WHERE a.addressId = :addressId"),
@NamedQuery(name = "Address.findByAddrLine1",
query = "SELECT a FROM Address a WHERE a.addrLine1 = :addrLine1"),
@NamedQuery(name = "Address.findByAddrLine2",
query = "SELECT a FROM Address a WHERE a.addrLine2 = :addrLine2"),
166 Глава 4. Взаимодействие с базами данных через Java Persistence API

@NamedQuery(name = "Address.findByCity",
query = "SELECT a FROM Address a WHERE a.city = :city"),
@NamedQuery(name = "Address.findByZip",
query = "SELECT a FROM Address a WHERE a.zip = :zip")})

public class Address implements Serializable {


private static final long serialVersionUID = 1L;
@Id
@Basic(optional = false)
@NotNull
@Column(name = "ADDRESS_ID")
private Integer addressId;
@Size(max = 100)
@Column(name = "ADDR_LINE_1")
private String addrLine1;
@Size(max = 100)
@Column(name = "ADDR_LINE_2")
private String addrLine2;
@Size(max = 100)
@Column(name = "CITY")
private String city;
@Size(max = 5)
@Column(name = "ZIP")
private String zip;
@JoinColumn(name = "ADDRESS_TYPE_ID",
referencedColumnName = "ADDRESS_TYPE_ID")
@ManyToOne
private AddressType addressType;
@JoinColumn(name = "CUSTOMER_ID",
referencedColumnName = "CUSTOMER_ID")
@ManyToOne
private Customer customer;
@JoinColumn(name = "US_STATE_ID",
referencedColumnName = "US_STATE_ID")
@ManyToOne
private UsState usState;
// сгенерированные конструкторы и методы для простоты не показаны
}

Обратите внимание, что сущность Address имеет поле customer


типа Customer – тип сущности, которую мы только что обсудили.

Если бы флажок Use Column Names in Relationships (Использо-


вать имена столбцов в отношениях) в мастере Entity Classes from
Database (Классы сущностей из базы данных) остался отмечен-
ным, сгенерированное поле customer получило бы имя customerId.
В большинстве случаев снятие флажка позволяет получить более
ясные имена полей, определяющих отношения между сущностями,
как в данном примере.
Автоматическое создание сущностей JPA 167

Обратите внимание, что поле декорировано аннотацией @ManyToOne.


Эта аннотация отмечает сторону «ко многим» в отношениях «один ко
многим» между сущностями Customer и Address. Заметьте, что поле
также декорировано аннотацией @JoinColumn. Атрибут name этой ан­
нотации указывает на столбец базы данных, в который отображается
определение ограничения внешнего ключа между таблицами ADDRESS
и CUSTOMER (в данном случае, столбец CUSTOMER_ID в таблице ADDRESS).
Атрибут referencedColumnName аннотации @JoinColumn определяет
столбец первичного ключа таблицы на стороне «один» в отношении
(в данном случае, столбец CUSTOMER_ID в таблице CUSTOMER).
В дополнение к отношениям «один ко многим» и «многие к одно­
му» JPA предоставляет аннотации для определения отношений «мно­
гие ко многим» и «один к одному». В нашей схеме базы данных у нас
имеется отношение «многие ко многим» между таблицами CUSTOMER_
ORDER и ITEM, поскольку заказ может иметь много элементов и один
элемент может принадлежать к нескольким заказам.

Таблица с заказами называется CUSTOMER_ORDER, потому что слово


«ORDER» является зарезервированным в SQL.

Рассмотрим сущность CustomerOrder, чтобы понять, как определя­


ются отношения «многие ко многим»:
package com.ensode.jpa;

import java.io.Serializable;
import java.util.Collection;
import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

@Entity
@Table(name = "CUSTOMER_ORDER")
@NamedQueries({
@NamedQuery(name = "CustomerOrder.findAll",
168 Глава 4. Взаимодействие с базами данных через Java Persistence API

query = "SELECT c FROM CustomerOrder c"),


@NamedQuery(name = "CustomerOrder.findByCustomerOrderId",
query = "SELECT c FROM CustomerOrder c "
+ "WHERE c.customerOrderId = :customerOrderId"),
@NamedQuery(name = "CustomerOrder.findByOrderNumber",
query = "SELECT c FROM CustomerOrder c "
+ "WHERE c.orderNumber = :orderNumber"),
@NamedQuery(name = "CustomerOrder.findByOrderDescription",
query = "SELECT c FROM CustomerOrder c "
+ "WHERE c.orderDescription = :orderDescription")})

public class CustomerOrder implements Serializable {


private static final long serialVersionUID = 1L;
@Id
@Basic(optional = false)
@NotNull
@Column(name = "CUSTOMER_ORDER_ID")
private Integer customerOrderId;
@Size(max = 10)
@Column(name = "ORDER_NUMBER")
private String orderNumber;
@Size(max = 200)
@Column(name = "ORDER_DESCRIPTION")
private String orderDescription;
@JoinTable(name = "ORDER_ITEM",
joinColumns = {
@JoinColumn(name = "CUSTOMER_ORDER_ID",
referencedColumnName = "CUSTOMER_ORDER_ID")},
inverseJoinColumns = {
@JoinColumn(name = "ITEM_ID",
referencedColumnName = "ITEM_ID")})
@ManyToMany
private Collection<Item> itemCollection;
@JoinColumn(name = "CUSTOMER_ID",
referencedColumnName = "CUSTOMER_ID")
@ManyToOne
private Customer customer;
// сгенерированные конструкторы и методы для простоты не показаны.
}

Сущность CustomerOrder имеет свойство типа java.util.Collection


с именем itemCollection. Это свойство содержит все элементы заказа.
Обратите внимание, что поле декорировано аннотацией @ManyToMany,
эта аннотация используется для объявления отношения «многие ко
многим» между сущностями JPA CustomerOrder и Item. Заметьте, что
поле также декорировано аннотацией @JoinTable, эта аннотация со­
вершенно необходима, поскольку всякий раз, когда имеется отноше­
ние «многие ко многим», в схеме базы данных должна присутствовать
Автоматическое создание сущностей JPA 169

объединяющая таблица. Использование объединяющей таблицы по­


зволяет обеспечить нормализацию данных в базе.
Аннотация @JoinTable позволяет определить в схеме базы данных
таблицу для поддержки отношения «многие ко многим». Значение
атрибута name в аннотации @JoinTable должно соответствовать име­
ни объединяющей таблицы в схеме базы данных. Значение атрибу­
та joinColumns в аннотации @JoinColumn должно совпадать с именем
внешнего ключа между объединяющей таблицей и стороной от­
ношения, владеющей отношением. Мы уже обсуждали аннотацию
@JoinColumn, рассматривая отношения «один ко многим». В этом
случае атрибут name должен соответствовать имени столбца в объ­
единяющей таблице, у которой есть отношение внешнего ключа, а
атрибут referencedColumnName должен содержать имя столбца первич­
ного ключа на стороне, владеющей отношением. Значение атрибута
inverseJoinColumns в аннотации @JoinTable играет ту же роль, что и
атрибут joinColumns, за исключением того, что определяет соответ­
ствующие столбцы на стороне, не владеющей отношением.
Сторона отношения «многие ко многим», содержащая вышеупо­
мянутые аннотации, как говорят, является стороной, владеющей от­
ношением (owning side). Давайте посмотрим, как отношение «многие
ко многим» определяется на стороне, не владеющей отношением, ко­
торой в нашем случае является сущность Item:
package com.ensode.jpa;

import java.io.Serializable;
import java.util.Collection;
import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

@Entity
@Table(name = "ITEM")
@NamedQueries({
@NamedQuery(name = "Item.findAll",
query = "SELECT i FROM Item i"),
@NamedQuery(name = "Item.findByItemId",
query = "SELECT i FROM Item i WHERE i.itemId = :itemId"),
170 Глава 4. Взаимодействие с базами данных через Java Persistence API

@NamedQuery(name = "Item.findByItemNumber",
query = "SELECT i FROM Item i WHERE i.itemNumber = :itemNumber"),
@NamedQuery(name = "Item.findByItemShortDesc",
query = "SELECT i FROM Item i "
+ "WHERE i.itemShortDesc = :itemShortDesc"),
@NamedQuery(name = "Item.findByItemLongDesc",
query = "SELECT i FROM Item i "
+ "WHERE i.itemLongDesc = :itemLongDesc")})

public class Item implements Serializable {


private static final long serialVersionUID = 1L;
@Id
@Basic(optional = false)
@NotNull
@Column(name = "ITEM_ID")
private Integer itemId;
@Size(max = 10)
@Column(name = "ITEM_NUMBER")
private String itemNumber;
@Size(max = 100)
@Column(name = "ITEM_SHORT_DESC")
private String itemShortDesc;
@Size(max = 500)
@Column(name = "ITEM_LONG_DESC")
private String itemLongDesc;
@ManyToMany(mappedBy = "itemCollection")
private Collection<CustomerOrder> customerOrderCollection;

// сгенерированные конструкторы и методы для простоты не показаны.

Как видите, на этой стороне отношения нужно создать лишь свой­


ство Collection, декорировав его аннотацией @ManyToMany, и опреде­
лить имя свойства на другой стороне отношения как значение его
атрибута mappedBy.
В дополнение к отношениям «один ко многим» и «многие ко мно­
гим» между сущностями JPA возможно создать отношения «один к
одному».
Для этого используется аннотация @OneToOne. В нашей схеме нет
ни одного отношения «один к одному» между таблицами, поэтому
данная аннотация не была добавлена ни в одну из сущностей, сгене­
рированных мастером.

Отношения «один к одному» не очень популярны в схемах баз дан-


ных. Тем не менее JPA поддерживает отношения «один к одному»
для случаев, когда это окажется необходимым.
Автоматическое создание сущностей JPA 171

Процедура определения отношения «один к одному» между двумя


сущностями подобна той, которую мы уже видели. Сторона, владею­
щая отношением, должна иметь поле типа сущности, а другая сторона
отношения должна иметь соответствующее поле, декорированное ан­
нотациями @OneToOne и @JoinColumn.
Предположим, что имеется схема базы данных, в которой отно­
шение «один к одному» было определено между таблицами PERSON
и BELLY_BUTTON, это будет отношение «один к одному», поскольку у
каждого человека имеется всего один пупок (belly-button) и каждый
пупок принадлежит только одному человеку (причина, по которой
подобная схема должна быть смоделирована именно так, вместо того
чтобы в таблице PERSON определить столбцы, связанные с таблицей
BELLY_BUTTON, мне не понятна, но отнеситесь к этому с пониманием,
мне с трудом удается придумывать хорошие примеры!).
@Entity
public class Person implements Serializable {
@JoinColumn(name="BELLY_BUTTON_ID")
@OneToOne
private BellyButton bellyButton;

public BellyButton getBellyButton(){


return bellyButton;
}

public void setBellyButton(BellyButton bellyButton){


this.bellyButton = bellyButton;
}
}

Если бы отношение «один к одному» было однонаправленным


(unidirectional) (когда можно получить только пупок от человека),
ничего больше не нужно было бы делать. Если же отношение явля­
ется двунаправленным (bidirectional), нужно добавить аннотацию
@OneToOne с другой стороны отношения и использовать ее атрибут
mappedBy, чтобы указать на другую сторону отношения:
@Entity
@Table(name="BELLY_BUTTON")
public class BellyButton implements Serializable(
{
@OneToOne(mappedBy="bellyButton")
private Person person;

public Person getPerson(){


return person;
172 Глава 4. Взаимодействие с базами данных через Java Persistence API

public void getPerson(Person person){


this.person=person;
}
}

Как видите, процедура определения отношения «один к одному»


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

Создание приложений JSF


из сущностей JPA
В NetBeans имеется одна очень удобная функция, позволяющая ге­
нерировать приложения JSF на основе имеющихся сущностей JPA,
которые будут выполнять операции создания, чтения, изменения
и удаления (CRUD). Эта функциональность в сочетании с воз­
можностью создания сущностей JPA из существующей схемы базы
данных, как описано в предыдущем разделе, позволяет в рекордно
короткие сроки писать веб-приложения, взаимодействующие с ба­
зой данных.
Чтобы создать JSF-страницы из существующих сущностей JPA,
нужно в главном меню выбрать пункт File | New File (Файл | Создать
файл), затем в категории JavaServer Faces выбрать тип файла JSF
Pages from Entity Classes (Страницы JSF на основе классов сущно­
стей), как показано на рис. 4.24.

Чтобы сгенерировать JSF-страницы из имеющихся сущностей JPA,


текущий проект должен быть проектом веб-приложения.

После щелчка на кнопке Next > (Далее >) следует выбрать одну
или более сущностей JPA. Обычно выбираются все сущности, что
легко может быть сделано щелчком на кнопке Add All >> (Добавить
все >>), как показано на рис. 4.25.
Следующая страница мастера (см. рис. 4.26) позволяет определить
пакет для создаваемых компонентов JSF. Мастер создаст два типа
Создание приложений JSF из сущностей JPA 173

классов: контроллеры JPA (JPA Controllers) и классы JSF (JSF


Classes). Для каждого из них можно определить отдельный пакет.

Рис. 4.24. Создание страниц JSF на основе


имеющихся сущностей JPA

Рис. 4.25. Выбор всех имеющихся сущностей JPA


174 Глава 4. Взаимодействие с базами данных через Java Persistence API

Рис. 4.26. Настройка параметров создания страниц JSF


Здесь же предоставляется возможность определить папку для
хранения JSF-страниц. Если оставить это поле незаполненным,
страницы будут создаваться в папке Web Pages (Веб-страницы)
проекта.

Поля Session Bean Package (Пакет сеансовых компонентов) и JSF


Classes Package (Пакет классов JSF) по умолчанию ссылаются на
пакет, где находятся сущности JPA. Правильным будет изменить
эти значения по умолчанию, потому что сохранение компонентов
JSF в отдельном пакете поможет отделить классы доступа к данным
от пользовательского интерфейса и контроллеров приложения.

Мастер позволяет также использовать два вида шаблонов:


Standard JavaServer Faces (Стандартное приложение JavaServer
Faces) и PrimeFaces, как показано на рис. 4.27. Если выбрать шаблон
Standard JavaServer Faces (Стандартное приложение JavaServer
Faces), NetBeans создаст самое просто стандартное веб-приложение,
которое можно использовать как основу для дальнейшего развития.
Если выбрать шаблон PrimeFaces, NetBeans создаст веб-приложение
с довольно привлекательным оформлением. В нашем примере мы
выберем шаблон PrimeFaces, но дальнейший порядок действий поч­
ти ничем не отличается, если выбрать шаблон Standard JavaServer
Faces (Стандартное приложение JavaServer Faces). Выбор шаблона
осуществляется в поле раскрывающегося списка Choose Templates
(Выберите шаблоны).
Создание приложений JSF из сущностей JPA 175

Не забудьте добавить в проект библиотеку PrimeFaces 4.0, если со-


бираетесь использовать шаблон PrimeFaces. Подробности ищите
в главе 3, «Библиотеки компонентов JSF».

После щелчка на кнопке Finish (Го­


тово) будет создано законченное веб-
приложение, способное выполнять
операции CRUD (см. рис. 4.27).
Как видите, NetBeans создала под­
папки для каждой сущности в папке
Web Pages (Веб-страницы) проекта
приложения. В каждой подпапке на­
ходятся XHTML-файлы с именами
Create, Edit, List и View (см. рис. 4.27).
Это – страницы JSF, использующие
фейслеты в качестве технологии
уровня представления. Так как мы
выбрали шаблон PrimeFaces, наши
страницы используют компоненты
PrimeFaces. Страница Create реали­
Рис. 4.27. Законченное
зует возможность создания новых
веб-приложение, поддержи-
сущностей; страница Edit позволяет вающее операции CRUD
изменять информацию в конкретной
сущности, страница List выводит список всех экземпляров кон­
кретной сущности в базе данных, страница View отображает все
свойства сущности.
Сгенерированное приложение является обычным приложением
JSF. Его можно запустить, просто щелкнув правой кнопкой мыши на
проекте и выбрав в контекстном меню пункт Run (Выполнить). Далее
все происходит как обычно – запускается сервер приложений, если
он еще не был запущен, развертывается приложение и открывается
окно браузера с начальной страницей приложения (см. рис. 4.28).
Как видите, начальная страница содержит ссылки, соответствую­
щие каждой из сущностей JPA. При переходе по любой ссылке откры­
вается страница с таблицей, содержащей список всех экземпляров
выбранной сущности, хранящихся в базе данных. Если щелкнуть на
ссылке Show All Customer Items (показать список всех заказчиков),
откроется страница, изображенная на рис. 4.29.
Поскольку база данных пока не содержит никакой информации, на
страницу выводится сообщение No Customer Items Found (заказчи­

Powered by TCPDF (www.tcpdf.org)


176 Глава 4. Взаимодействие с базами данных через Java Persistence API

ки не найдены). Добавим заказчика в базу данных, щелкнув на ссылке


Create New Customer (Создать нового заказчика).

Рис. 4.28. Окно браузера с начальной страницей приложения

Рис. 4.29. Страница со списком всех заказчиков


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

Как видите, для первичного ключа сущности также было сгенериро-


вано поле ввода. Это поле генерируется, только если сущность JPA
не использует стратегию генерации первичного ключа.
Создание приложений JSF из сущностей JPA 177

Рис. 4.30. Диалог создания нового заказчика


После ввода информации и щелчка на кнопке Save (Сохранить) в
таблицу добавляется новая запись и выводится сообщение Customer
was successfully created (Заказчик успешно создан), как показано на
рис. 4.31.

Рис. 4.31. В таблицу добавляется новая запись и выводится


сообщение об успехе операции
Обратите внимание, что на странице присутствуют кнопки View
(Посмотреть), Edit (Изменить) и Delete (Удалить) для выполнения
операций с экземплярами сущности.
Чтобы перейти к выполнению операций с другими сущностями,
можно выбрать требуемую сущность в сгенерированном раскры­
178 Глава 4. Взаимодействие с базами данных через Java Persistence API

вающемся списке Maintenance (Обслуживание), как показано на


рис. 4.32.

Рис. 4.32. В списке Maintenance (Обслуживание) можно выбрать


другую сущность
Предположим, что нам нужно добавить адрес заказчика. Сделать
это можно, выбрав пункт Address (Адрес) в раскрывающемся спи­
ске Maintenance (Обслуживание) и затем щелкнув на кнопке Create
(Создать).

Рис. 4.33. Диалог создания нового адреса


Резюме 179

Сущность Address находится на стороне «один» для нескольких от­


ношений «один ко многим». Обратите внимание, что для каждой из
сущностей на стороне «ко многим» сгенерировано поле в виде рас­
крывающегося списка. Поскольку этот адрес требуется присвоить за­
казчику, который только что был добавлен, можно просто выбрать его
в раскрывающемся списке Customer (Заказчик).
Если щелкнуть на раскрывающемся списке, появится загадочное,
практически неразборчивое (с точки зрения пользователя) наимено­
вание нашего заказчика. Причина, в том, что эти наименования гене­
рируются для каждого элемента в раскрывающемся списке вызовом
метода toString() сущности, использованной для заполнения этого
поля. Эту проблему можно решить, изменив реализацию метода to-
String() так, чтобы он возвращал удобочитаемую строку, подходя­
щую для использования в списке.
Как видите, в код, сгенерированный мастерами NetBeans, можно
внести некоторые улучшения, например: изменить методы toString()
всех сущностей JPA, чтобы их результаты можно было использовать в
качестве элементов списков, или изменить надписи в сгенерирован­
ных страницах JSF, чтобы они были более удобны для пользователя.
Но, как бы то ни было, мы получили законченное рабочее приложе­
ние, созданное лишь несколькими щелчками мыши. Эта функцио­
нальность, безусловно, позволяет сэкономить уйму времени и усилий
(только не говорите об этом вашему боссу).

Резюме
В этой главе мы узнали, как NetBeans может помочь ускорить разра­
ботку приложений, использующих возможности JPA.
Мы увидели, как NetBeans генерирует новые классы JPA сразу
со всеми необходимыми аннотациями в положенном для них месте.
Также мы познакомились с возможностью автоматического создания
кода для сохранения сущности JPA в таблице базы данных. Изучили,
как NetBeans генерирует сущности JPA на основе существующей схе­
мы базы данных, включая автоматическое создание запросов JPQL,
именованных запросов и проверку допустимости. Наконец, мы уз­
нали, как NetBeans может создать законченное приложение JSF из
существующих сущностей JPA.
Глава 5.
Реализация уровня
бизнес-логики на сеансовых
компонентах EJB

К большинству корпоративных приложений выдвигается множество


общих требований, таких как поддержка транзакций, безопасность,
масштабируемость и т. д. Компоненты Enterprise JavaBeans (EJB)
позволяют разработчикам приложений уделять основное внимание
реализации бизнес-логики и не беспокоиться о реализации упомяну­
тых выше общих требований. Имеются два типа компонентов EJB:
сеансовые компоненты (Session Beans) и компоненты, управляемые
сообщениями (Message-Driven Beans). В этой главе мы будем об­
суждать сеансовые компоненты, которые значительно упрощает ре­
ализацию бизнес-логики на стороне сервера. В главе 7 мы обсудим
компоненты, управляемые сообщениями, позволяющие без труда ре­
ализовать обмен сообщениями в приложениях.

Предыдущие версии J2EE включали также объектные компоненты


(Entity Beans), начиная с версии Java EE 5, объектные компоненты
были признаны устаревшими и в настоящее время рекомендуется
использовать JPA.

В этой главе будут затронуты следующие темы:


введение в сеансовые компоненты;
создание сеансовых компонентов в NetBeans;
управление транзакциями EJB;
реализация аспектно-ориентированного программирования с
интерцепторами;
служба таймеров EJB;
автоматическое создание сеансовых компонентов из сущнос­
тей JPA.
Создание сеансового компонента в NetBeans 181

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

Даже при том, что мы не реализуем непосредственно общие тре-


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

Имеется два типа сеансовых компонентов: сеансовые компонен-


ты без сохранения состояния (stateless session beans), сеансовые
компоненты с сохранением состояния (stateful session beans) и се-
ансовые компоненты-одиночки (singleton session beans). Сеансовые
компоненты с сохранением состояния сохраняют состояние диалога
со своими клиентами между вызовами методов, тогда как сеансовые
компоненты без сохранения состояния этого не делают.

Создание сеансового
компонента в NetBeans
Сеансовые компоненты могут создаваться в проектах NetBeans трех
типов: Enterprise Application (Корпоративное приложение), EJB
Module (Модуль EJB) и Web Application (Веб-приложение). Проекты
модулей EJB могут содержать только компоненты EJB, тогда как про­
екты корпоративных приложений могут содержать компоненты EJB
наряду с их клиентами, которыми могут быть веб-приложения или
«автономные» приложения Java. Возможность добавления компонен­
тов EJB была введена в Java EE 6. Ее наличие позволяет упростить
упаковку и развертывание веб-приложений при использовании EJB.
Теперь можно упаковать веб-приложение и компонент EJB в один
WAR-файл, тогда как в предыдущих версиях Java EE и J2EE прихо­
дилось создавать файл EAR (архив корпоративного приложения).
182 Глава 5. Реализация уровня бизнес-логики на сеансовых компонентах EJB

При развертывании корпоративных приложений на сервере при­


ложений GlassFish, включенном в NetBeans, можно развернуть ав­
тономных клиентов как часть приложения. После этого указанные
автономные клиенты будут доступны через Java Web Start (http://
www.oracle.com/technetwork/java/javase/javawebstart/index.html);
эта функция также упрощает доступ к компонентам EJB из клиент­
ского кода с помощью аннотаций. Настоящие автономные клиенты,
выполняющиеся вне сервера приложений, требуют обращения к
службе имен и каталогов Java (Java Naming and Directory Interface,
JNDI) для получения ссылки на компонент EJB. В нашем первом
примере мы создадим сеансовый компонент EJB и клиента Java Web
Start, которые оба будут развертываться в рамках единого корпора­
тивного приложения.
Чтобы создать проект корпоративного приложения, выберите в
главном меню пункт File | New Project (Файл | Создать проект) и за­
тем тип проекта Enterprise Application (Приложение Enterprise) в
категории Java EE, как показано на рис. 5.1.

Рис. 5.1. Выбор типа проекта при создании корпоративного


приложения
После щелчка на кнопке Next > (Далее >) укажите название про­
екта (см. рис. 5.2).
Создание сеансового компонента в NetBeans 183

Рис. 5.2. Определение названия и местоположения проекта

При желании можно изменить значение в поле Project Loca-


tion (Расположение проекта), это автоматически повлечет за со­
бой соответствующие изменения в поле Project Folder (Папка
проекта).
На следующем шаге мастер предложит выбрать модули для вклю­
чения в корпоративное приложение. По умолчанию флажки Create
EJB Module (Создать модуль EJB) и Create Web Application Module
(Создать модуль веб-приложения) отмечены. В данном примере мы
не будем создавать модуль веб-приложения, поэтому сбросим фла­
жок выделение Create Web Application Module (Создать модуль веб-
приложения), как показано на рис. 5.3.
В нашем примере проект корпоративного приложения получил имя
SessionBeanIntro, а модуль компонента EJB – имя SessionBeanIntro-
ejb (см. рис. 5.4).
Прежде чем двинуться дальше, нужно создать еще проект клиент­
ского приложения, в котором будет находиться программный код
клиента, использующего компонент EJB. Для этого в мастере New
Project (Создать проект) нужно выбрать тип Enterprise Applica-
tion Client (Клиент приложения Enterprise) в категории Java EE (см.
рис. 5.5).
184 Глава 5. Реализация уровня бизнес-логики на сеансовых компонентах EJB

Рис. 5.3. Дополнительные настройки проекта

Рис. 5.4. Два новых проекта

Рис. 5.5. Создание клиента корпоративного приложения


Создание сеансового компонента в NetBeans 185

Ввести имя проекта в поле Project Name (Имя проекта) и, при


желании, изменить значение поля Project Location (Расположение
проекта), как

Рис. 5.6. Ввод имени и местоположения проекта клиентского


приложения
На следующем шаге следует выбрать наш проект корпоративно­
го приложения в поле раскрывающегося списка Add to Enterprise
Application (Добавить в приложение J2EE) и затем определить желае­
мое имя в поле Main Class (Основной класс), как показано на рис. 5.7.

Рис. 5.7. Выбор корпоративного приложения и имени основного класса


186 Глава 5. Реализация уровня бизнес-логики на сеансовых компонентах EJB

После щелчка на кнопке Finish (Готово) будет создан новый проект


(см. рис. 5.8).

Рис. 5.8. Появился новый проект клиентского приложения


Поскольку клиент и компонент EJB будут выполняться под управ­
лением разных виртуальных машин JVM, нам понадобится проект би­
блиотеки классов Java (Java Class Library), реализующий удаленный
интерфейс к сеансовому компоненту. Чтобы создать его, выберите в
главном меню пункт File | New Project... (Файл | Создать проект...) и
в категории Java – тип проекта Java Class Library (Библиотека клас­
сов Java), как показано на рис. 5.9.

Рис. 5.9. Создание проекта библиотеки классов Java


На следующем шаге нужно определить имя проекта и его местопо­
ложение, как показано на рис. 5.10.
После щелчка на кнопке Finish (Готово) будет создан проект биб­
лиотеки классов Java (см. рис. 5.11).
Создание сеансового компонента в NetBeans 187

Рис. 5.10. Ввод имени и местоположения проекта библиотеки классов

Рис. 5.11. Появился проект библиотеки классов Java


Теперь необходимо добавить проект библиотеки классов Java как
библиотеку в проект клиентского приложения. Сделать это можно,
щелкнув правой кнопкой мыши на узле Libraries (Библиотеки) и вы­
брав пункт контекстного меню Add Project... (Добавить проект...),
как показано на рис. 5.12.

Рис. 5.12. Пункт Add Project... (Добавить проект...)


в контекстном меню
188 Глава 5. Реализация уровня бизнес-логики на сеансовых компонентах EJB

Далее, в появившемся окне, следует выбрать наш проект библиоте­


ки классов Java (см. рис. 5.13).

Рис. 5.13. Выбор проекта библиотеки классов Java


Теперь, после создания всех необходимых проектов, можно при­
ступать к созданию нашего первого сеансового компонента. Для этого
можно щелкнуть правой кнопкой мыши на модуле EJB, выбрать пункт
New | Other (Новый | Другое...) и в категории Enterprise JavaBeans
выбрать тип файлов Session Bean (Сеансовый компонент), как по­
казано на рис. 5.14.

Рис. 5.14. Создание сеансового компонента


Создание сеансового компонента в NetBeans 189

Теперь нужно настроить некоторые параметры компонента (см.


рис. 5.15):
• переопределить имя сеансового компонента, присвоенное по
умолчанию;
• определить пакет для нашего сеансового компонента;
• определить тип сеансового компонента: без сохранения со­
стояния, с сохранением состояния или компонент-одиночка
(singleton):
сеансовые компоненты c сохранением состояния поддер­
живают состояние диалога с клиентом (то есть, значения
любых задействованных переменных находятся в непро­
тиворечивом состоянии между вызовами метода);
сеансовые компоненты без сохранения состояние не под­
держивают состояние диалога, по этой причине они вы­
полняются быстрее, чем сеансовые компоненты с сохра­
нением состояния;
сеансовые компоненты-одиночки (Singleton) появились
в Java EE 6. При развертывании приложения создается
единственный экземпляр каждого такого компонента-
одиночки. Сеансовые компоненты-одиночки удобно ис­
пользовать для кэширования данных, часто читаемых из
базы данных.
• определить, будет ли сеансовый компонент иметь удаленный
интерфейс для использования клиентами, выполняющимися
в иной JVM, нежели сам компонент, локальный интерфейс,
предназначенный для клиентов, работающих в той же самой
JVM, что и сам компонент, или оба интерфейса сразу.

В ранних версиях Java EE локальные интерфейсы были обязатель-


ными, если компоненты EJB и их клиенты выполнялись в одной JVM.
В Java EE 6 это требование было смягчено и теперь нет необходи-
мости создавать какие-либо интерфейсы для сеансовых компонен-
тов, если к ним получают доступ только клиенты, выполняющиеся в
той же самой JVM.

В нашем примере компонент не должен поддерживать состояние


диалога со своими клиентами, поэтому его следует сделать сеансовым
компонентом без сохранения состояния. Единственный клиент ком­
понента будет выполняться в другой JVM, поэтому требуется создать
удаленный интерфейс и не создавать локального.
190 Глава 5. Реализация уровня бизнес-логики на сеансовых компонентах EJB

Рис. 5.15. Настройка параметров сеансового компонента


При создании удаленного ин­
терфейса NetBeans потребует
указать библиотеку клиента, куда
будет добавлен удаленный интер­
фейс. Именно для этого нам по­
требовалось создать библиотеку
классов Java ранее. Библиотека
клиента выбирается по умолча­
нию.
После настройки всех соответ­
ствующих параметров и щелчка
на кнопке Finish (Готово), сеан­
совый компонент будет создан в
проекте модуля EJB, а удаленный
интерфейс – в проекте библиоте­ Рис. 5.16. Созданы сеансовый
компонент и удаленный
ки клиента (см. рис. 5.16).
интерфейс к нему
Сгенерированный код для се­
ансового компонента является просто пустым классом с аннотацией
@Stateless и реализованным удаленным интерфейсом (см. рис. 5.17).
Обратите внимание, что наш компонент реализует удаленный ин­
терфейс, который на этом этапе является пустым интерфейсом с ан­
Создание сеансового компонента в NetBeans 191

нотацией @Remote (см. рис. 5.18). Эта аннотация была добавлена, по­
тому что был отмечен флажок создания удаленного интерфейса.

Рис. 5.17. Сгенерированный код для сеансового компонента

Рис. 5.18. Удаленный интерфейс с аннотацией @Remote


Причина, почему нам потребовался удаленный и/или необяза­
тельный локальный интерфейс, в том, что клиенты сеансового ком­
понента никогда не вызывают методов компонента непосредственно,
вместо этого они получают ссылку на класс, реализующий удаленный
и/или локальный интерфейс, и вызывают методы этого класса. Начи­
ная с Java EE 6 больше нет необходимости создавать локальный ин­
терфейс; сервер приложений может генерировать его автоматически
при развертывании приложения.
192 Глава 5. Реализация уровня бизнес-логики на сеансовых компонентах EJB

Реализация удаленного и/или локаль­


ного интерфейса создается автоматически
контейнером EJB при развертывании ком­
понента. Эта реализация выполняет не­
которые операции перед вызовом метода
сеансового компонента. Поскольку методы
должны быть определены и в интерфейсе,
и в компоненте, следует добавить сигна­
туру метода и к компонент, и в его удален­
ный и/или локальный интерфейс. Однако
при работе с сеансовыми компонентами в
NetBeans можно просто щелкнуть правой Рис. 5.19. Пункт меню
кнопкой на исходном коде компонента и Add Business Method
в контекстном меню выбрать пункт Insert (Добавить бизнес-
Code | Add Business Method (Вставка кода | метод...)
Добавить бизнес-метод...), как показано на рис. 5.19, и добавить метод
одновременно в компонент и его удаленный/локальный интерфейс.
В результате появится диалог (см. рис. 5.20), где можно определить
имя метода, тип возвращаемого значения, параметры и интерфейс(ы)
для добавления метода (удаленный и/или локальный).

Рис. 5.20. Диалог настройки добавляемого метода


Доступ к компонентам из клиента 193

В данном примере мы добавим метод с именем echo, который при­


нимает и возвращает строку (значение типа String). Поскольку ком­
понент имеет только удаленный интерфейс, переключатели Local
(Локальный) и Both (Оба) отображаются как неактивные.
После ввода соответствующей информации метод будет добавлен и
в компонент, и в его удаленный интерфейс, как показано на рис. 5.21.

Рис. 5.21. Новый метод echo


По умолчанию метод просто возвращает null. Давайте изменим
его, чтобы он возвращал строку, начинающуюся со слова «echoing:»
и заканчивающуюся значением входного параметра, как показано на
рис. 5.22.

Рис. 5.22. Измененная реализация метода echo


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

Доступ к компонентам из клиента


Теперь пора обратить внимание на клиента. Для поддержки уда­
ленных компонентов, проект клиента должен использовать проект
194 Глава 5. Реализация уровня бизнес-логики на сеансовых компонентах EJB

библиотеки классов Java с удаленным


интерфейсом. Именно для этого нам по­
требовалось создать библиотеку классов
Java ранее.
После добавления в проект клиентско­
го приложения библиотеки с удаленным
интерфейсом компонента мы готовы вы­
звать метод EJB. Клиентский код должен
получить ссылку на экземпляр класса,
реализующего удаленный интерфейс ком­
понента. В NetBeans это сделать очень
просто – нужно щелкнуть правой кнопкой Рис. 5.23. Пункт меню
Call Enterprise Bean
мыши на клиентском коде (com.ensode.
(Вызов компонента EJB...)
sessionbeanintro.Main в проекте клиентс­
кого приложения) и выбрать Insert Code... | Call Enterprise Bean
(Вставка кода... | Вызов компонента EJB...), как показано на рис. 5.23.
В результате появится диалог со списком всех открытых проектов,
где имеются компоненты EJB (см. рис. 5.24). Мы должны выбрать
компонент, к которому хотим получить доступ.

Рис. 5.24. Диалог со списком компонентов EJB


Доступ к компонентам из клиента 195

Если бы компонент имел оба интерфейса, локальный и удаленный,


нам была бы предоставлена возможность выбрать требуемый интер­
фейс. Однако, поскольку в данном случае имеется только удаленный
интерфейс, переключатель выбора локального интерфейса неакти­
вен. Но, даже если бы у нас была возможность выбрать локальный
интерфейс, мы все равно должны были бы выбрать удаленный ин­
терфейс, потому что клиент будет выполняться в другой JVM, а взаи­
модействия по локальным интерфейсам через границы JVM невоз­
можны.
Когда выбор будет сделан, в код клиента добавится переменная-
член типа EchoRemote (удаленный интерфейс компонента), декориро­
ванная аннотацией @EJB. Эта аннотация используется для внедрения
экземпляра удаленного интерфейса во время выполнения.

В предыдущих версиях J2EE было необходимо выполнить поиск в


JNDI, чтобы получить ссылку на домашний интерфейс компонента
и затем использовать его для получения ссылки на удаленный или
локальный интерфейс. Как видите, процедура получения ссылки на
EJB была значительно упрощена в Java EE.

Получившийся программный код показан на рис. 5.25.

Рис. 5.25. Новая переменная-член типа EchoRemote


196 Глава 5. Реализация уровня бизнес-логики на сеансовых компонентах EJB

Теперь можно просто добавить вызов метода echo() в удаленный


интерфейс (см. рис. 5.26), и создание клиента можно считать завер­
шенным.

Рис. 5.26. Вызов метода echo()

Запуск клиента
Чтобы запустить клиента, достаточно щелкнуть правой кнопкой
мыши на проекте корпоративного приложения и выбрать в контекст­
ном меню пункт Run (Выполнение). После нескольких секунд ожи­
дания появится диалоговое окно со строкой, полученной вызовом
метода сеансового компонента (см. рис. 5.27).

Рис. 5.27. Диалоговое окно со строкой, которую


сгенерировал метод сеансового компонента
Клиенты, развернутые таким способом, используют преимуще­
ства технологии веб-запуска Java Web Start. Приложения Java Web
Start работают на клиентской рабочей станции, однако они могут
быть выполнены на удаленном сервере. По умолчанию NetBeans
формирует URL веб-запуска для клиентских модулей корпоратив­
ных приложений, составляя его из названия проекта корпоративного
приложения, за которым следует имя модуля клиентского приложе­
ния. Для нашего примера URL был бы таким: http://localhost:8080/
SessionBeanIntro/SessionBeanIntro-app-client. Его можно прове­
рить, введя в адресную строку браузера этот URL. После короткого
ожидания клиент приложения будет выполнен.

На момент написания этих строк, данная процедура не работала в


Google Chrome.
Управление транзакциями в сеансовых компонентах 197

Управление транзакциями
в сеансовых компонентах
Как уже говорилось выше, одним из преимуществ компонентов
EJB является автоматическая поддержка транзакций. Тем не менее,
есть некоторые настройки, которые нужно сделать, чтобы улучшить
управление транзакциями.
Транзакции позволяют выполнить все шаги в методе, либо, если
на одном из шагов возникнет сбой (например, будет возбуждено ис­
ключение), произвести откат изменений, выполненных в этом ме­
тоде.
Прежде всего нужно настроить поведение компонента в случае, если
один из его методов вызывается во время выполнения транзакции.
Должен ли метод продолжить выполняться в рамках существующей
транзакции? Следует ли приостановить существующую транзакцию
и создать новую только для этого метода? Выполнить необходимые
настройки можно с помощью аннотации @TransactionAttribute.
Аннотация @TransactionAttribute позволяет управлять поведе­
нием методов EJB при вызове во время выполнения транзакции и в
отсутствие транзакций. Эта аннотация имеет единственный атрибут
value, который можно использовать, чтобы указать, как метод компо­
нента будет вести себя в обоих перечисленных случаях.
В табл. 5.1 перечислены все допустимые значения, которые можно
присвоить аннотации @TransactionAtttibute.
Таблица 5.1. Допустимые значения атрибута value аннотации
@TransactionAtttibute
Метод
Метод вызывается
Значения вызывается
во время выполнения
@TransactionAttribute в отсутствие
транзакции
транзакции

TransactionAttributeType. Метод становится частью Возбуждается


MANDATORY существующей транзакции. исключение
Transaction-
RequiredEx-
ception.
TransactionAttributeType. Возбуждается исключение Продолжает
NEVER RemoteException. выполнение
без поддержки
транзакций.
198 Глава 5. Реализация уровня бизнес-логики на сеансовых компонентах EJB

Метод
Метод вызывается
Значения вызывается
во время выполнения
@TransactionAttribute в отсутствие
транзакции
транзакции

TransactionAttributeType. Клиентская транзакция Продолжает


NOT_SUPPORTED временно приостанавлива- выполнение
ется, сам метод выполняет- без поддержки
ся без поддержки транзак- транзакций.
ций, а затем клиентская
транзакция возобновляется
TransactionAttributeType. Метод становится частью Для метода
REQUIRED существующей транзакции. создается новая
транзакция.
TransactionAttributeType. Клиентская транзакция Для метода
REQUIRES_NEW временно приостанавлива- создается новая
ется, для метода создается транзакция.
новая транзакция, а затем
клиентская транзакция воз-
обновляется.
TransactionAttributeType. Метод становится частью Продолжает
SUPPORTS существующей транзакции. выполнение
без поддержки
транзакций.

Аннотация @TransactionAttribute может использоваться для де­


корирования всего объявления класса EJB или одного из его ме­
тодов. Если декорируется объявление класса, соответствующее
поведение будет применено ко всем методам, если декорируется
конкретный метод, объявленным поведением будет обладать толь­
ко декорированный метод. Если компонент отмечен аннотацией
@TransactionAttribute и на уровне класса, и на уровне метода, аннота­
ция уровня метода имеет более высокий приоритет. При применении
к методу транзакции без атрибута, по умолчанию используется атри­
бут TransactionAttributeType.REQUIRED.
В следующем примере продемонстрировано, как использовать эту
аннотацию:
package com.ensode.sessionbeanintro.ejb;

import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;

@Stateless
Реализация аспектно-ориентированного программирования с помощью... 199
public class Echo

@override
@TransactionAttribute(
TransactionAttributeType.REQUIRES_NEW)
public String echo(String.saying) {
return "echoing: " + saying;
}
}

Как видите, достаточно просто декорировать метод анно­


тацией TransactionAttribute с соответствующей константой
TransactionAttributeType, чтобы настроить поддержку транзакций
для единственного метода. Как уже говорилось выше, если одна и та
же стратегия поддержки транзакций должна действовать для всех
методов, можно поместить аннотацию @TransactionAttribute на уро­
вень объявления класса.

Реализация
аспектно-ориентированного
программирования с помощью
интерцепторов
Иногда нужно выполнить некоторую логику непосредственно перед
и/или сразу после выполнения основной логики метода. Например,
чтобы измерить время выполнения метода для поиска проблемы,
связанной с производительностью, или чтобы отправить сообщение
в журнал при каждом входе в метод и выходе из него для облегчения
поиска ошибки или исключения.
Часто подобные задачи решаются добавлением в начало и в конец
каждого метода некоторого кода, реализующего логику профилиро­
вания или регистрации. У этого подхода имеется несколько проблем:
логика должна быть реализована несколько раз, и если потом потре­
буется изменить или удалить эту функциональность, придется изме­
нить несколько методов.
Аспектно-ориентированное программирование (Aspect-Oriented
Programming, AOP) является парадигмой, которая решает вышеупо­
мянутые проблемы, предоставляя возможность реализовать в отдель­
ном классе логику, которая будет выполняться непосредственно пе­
ред и/или сразу после основной логики метода. В EJB 3.0 появилась
200 Глава 5. Реализация уровня бизнес-логики на сеансовых компонентах EJB

возможность реализации аспектно-ориентированного программиро­


вания через интерцепторы (interceptors).
Реализация AOP через интерцепторы состоит из двух шагов: опре­
деление класса Interceptor и декорирование компонентов EJB ан­
нотацией @Interceptors. Эти шаги подробно описаны в следующем
разделе.

Реализация класса интерцептора


Интерцептор (или перехватчик) является стандартным классом Java
с единственным методом, имеющим следующую сигнатуру:
@AroundInvoke
public Object methodName(InvocationContext invocationContext)
throws Exception

Обратите внимание, что метод должен декорироваться аннота­


цией @AroundInvoke, которая отмечает метод как метод-перехватчик
(интерцептор). Параметр InvocationContext можно использовать для
получения информации из подконтрольного метода, такой как имя
метода, его параметры, имя класса метода и другой информации. Он
может иметь метод proceed(), который используется для выполнения
подконтрольного метода.
В табл. 5.2 перечислены некоторые из наиболее полезных методов
InvocationContext. Полный список можно найти в документации Java
EE 7 JavaDoc (доступна в NetBeans через пункт главного меню Help |
JavaDoc References | Java (TM) EE 7 Specification APIs (Справка |
Справочные сведения JavaDoc | Java (TM) EE 7 Specification APIs)).
Таблица 5.2. Некоторые из наиболее полезных методов
InvocationContext

Имя метода Описание


getMethod() Возвращает экземпляр java.lang.reflect.Method,
который можно использовать для исследования пере-
хваченного метода.
getParameters() Возвращает массив объектов с параметрами, передан-
ными перехваченному методу.
getTarget() Возвращает объект с методом, который был вызван,
возвращаемое значение имеет тип java.lang.Object.
proceed() Вызывает перехваченный метод.

В следующем примере демонстрируется простой класс интерцеп­


тора:
Реализация аспектно-ориентированного программирования с помощью... 201
package com.ensode.sessionbeanintro.ejb;

import java.lang.reflect.Method;
import javax.interceptor.AroundInvoke;
import javax.interceptor.InvocationContext;

public class LoggingInterceptor {

@AroundInvoke
public Object logMethodCall(
InvocationContext invocationContext)
throws Exception {
Object interceptedObject =
invocationContext.getTarget();
Method interceptedMethod =
invocationContext.getMethod();

System.out.println("Entering " +
interceptedObject.getClass().getName() + "." +
interceptedMethod.getName() + "()");

Object o = invocationContext.proceed();

System.out.println("Leaving " +
interceptedObject.getClass().getName() + "." +
interceptedMethod.getName() + "()");

return o;
}
}

Этот пример реализует запись сообщения в журнал сервера при­


ложений перед и после выполнения прерванного метода. Целью этого
примера является что-то вроде реализации помощи в отладке при­
ложений.

Для простоты пример выше использует System.out.println для вы-


вода сообщений в журнал сервера приложений. Реальное же при-
ложение вероятнее всего будет использовать API журналирования,
такой как Java Logging API или Log4j.

Первое, что делает метод-перехватчик, – получает ссылку на объ­


ект и прерванный метод, а затем выводит в журнал сообщение, со­
держащее имя вызванного метода и его класса. Этот код выполняется
непосредственно перед передачей управления прерванному методу,
что осуществляется вызовом метода invocationContext.proceed().
Значение, возвращаемое этим методом, сохраняется в переменной
202 Глава 5. Реализация уровня бизнес-логики на сеансовых компонентах EJB

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


сразу после завершения перехваченного метода. В данном примере
мы просто отправляем дополнительную строку текста в журнал сер­
вера приложений. В конце наш метод вернет значение, возвращаемое
invocationContext.proceed().

Декорирование компонентов EJB


аннотацией @Interceptors
Метод компонента EJB, который должен прерываться, следует деко­
рировать аннотацией @Interceptors. Эта аннотация имеет единствен­
ный атрибут – массив классов. Данный атрибут содержит все интер­
цепторы, которые будут выполняться до и/или после вызова метода.
Аннотация @Interceptors может использоваться на уровне мето­
да, когда она применяется только к декорированному методу, или на
уровне класса, когда она применяется применяется к каждому методу
компонента.
Следующий пример является обновленной версией сеансового
компонента EchoBean, немного измененного, чтобы обеспечить пре­
рывание метода echo() с помощью интерцептора LoggingInterceptor,
реализованного в предыдущем разделе:
package com.ensode.sessionbeanintro.ejb;

import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.interceptor.Interceptors;

@Stateless
public class Echo implements EchoRemote {
// Добавьте бизнес-логику ниже. (Щелкните правой кнопкой мыши
// и выберите в контекстном меню пункт
// Insert Code > Add Business Method
// (Вставка кода | Добавить бизнес-метод)

@Interceptors({LoggingInterceptor.class})
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public String echo(String saying) {
return "echoing: " + saying;
}
}

Обратите внимание, что единственное изменение в сеансовом


компоненте – добавление аннотации @Interceptors к методу echo().
Служба таймеров EJB 203

В данном случае атрибут включает единственное значение – класс


LoggingInterceptor, который был определен выше. В нашем примере
мы использовали единственный перехватчик для метода компонента.
Если бы потребовалось обеспечить перехват вызова метода более чем
одним перехватчиком, это можно было бы сделать, добавляя допол­
нительные классы интерцепторов между фигурными скобками в ан­
нотации @Interceptors. Элементы списка интерцепторов в фигурных
скобках должны разделяться запятыми.
Теперь мы готовы протестировать наш интерцептор. В NetBeans
можно просто щелкнуть правой кнопкой мыши на проекте в окне
Projects (Проекты) и в контекстном меню выбрать пункт Run (Вы­
полнение). После этого должен появиться вывод перехватчика
logMethodCall() в окне GlassFish Server 4, как показано на рис. 5.28.

Рис. 5.28. Вывод перехватчика в окне GlassFish Server 4

Служба таймеров EJB


Сеансовые компоненты без сохранения состояния и компоненты,
управляемые сообщениями (еще один тип компонентов EJB, обсуж­
даемый в следующей главе) могут иметь метод, вызываемый авто­
матически через регулярные интервалы времени. Это может приго­
диться, если нужно периодически (один раз в неделю, каждый день,
каждый час и т. д.) выполнять некоторую логику без необходимости
явно вызывать любые методы. Данная возможность обеспечивается
службой таймеров EJB (EJB Timer Service).
Чтобы воспользоваться службой таймеров EJB, нужно добавить
аннотацию @Schedule к требуемому методу и определить, когда вы­
зывать этот метод. В NetBeans на этот случай имеется удобный ма­
стер, который поможет выполнить все необходимые настройки (см.
рис. 5.29).
204 Глава 5. Реализация уровня бизнес-логики на сеансовых компонентах EJB

Рис. 5.29. Включение службы таймеров в проект


На следующем шаге (см. рис. 5.30) мастер дает возможность вы­
полнить некоторые настройки.

Рис. 5.30. Настройка службы таймеров


Служба таймеров EJB 205

Поддержку службы таймеров можно реализовать в виде сеансо­


вого компонента без сохранения состояния или компонента-оди­
ночки (singleton). Можно также определить локальный или удален­
ный интерфейс. Локальный интерфейс является необязательным, а
удаленный интерфейс необходим, только если потребуется органи­
зовать доступ к компоненту из другой JVM. В нашем примере мы
решили не создавать никаких интерфейсов. В поле Method schedule
(План методов) нужно ввести атрибуты и значения для аннотации @
Schedule, которая будет добавлена к создаваемому сеансовому ком­
поненту.

В аннотации @Schedule используется синтаксис, напоминающий


синтаксис утилиты-планировщика cron, широко используемой
в Unix и Unix-подобных операционных системах, таких как Linux.
Очень хорошее введение в cron можно найти по адресу: http://www.
unixgeeks.org/security/newbie/unix/cron-1.html.1

После щелчка на кнопке Finish (Готово) NetBeans сгенерирует но­


вый сеансовый компонент, как показано ниже:
package com.ensode.ejbtimer.ejb;

import java.util.Date;
import javax.ejb.Schedule;
import javax.ejb.Stateless;
import javax.ejb.LocalBean;

@Stateless
@LocalBean
public class EjbTimerDemo {

@Schedule(hour = "*", minute = "*", second = "*/30")


public void myTimer() {
System.out.println("Timer event: " + new Date());
}

// Добавьте бизнес-логику ниже. (Щелкните правой кнопкой мыши


// и выберите в контекстном меню пункт
// Insert Code > Add Business Method
// (Вставка кода | Добавить бизнес-метод)
}

Обратите внимание, что атрибуты и значения в аннотации


@Schedule совпадают с тем, что было введено в мастере. Здесь исполь­

1
Хорошая статья о cron на русском языке: https://ru.wikipedia.org/wiki/Cron. –
Прим. перев.
206 Глава 5. Реализация уровня бизнес-логики на сеансовых компонентах EJB

зовалось значение "*" для атрибута hour аннотации, чтобы опреде­


лить, что метод должен вызываться каждый час. Для атрибута minute
также использовалось значение "*", чтобы определить, что метод дол­
жен вызываться каждую минуту. Наконец, для атрибута second ис­
пользовалось значение "*/30", чтобы определить, что метод должен
вызываться каждые 30 секунд.
После развертывания и запуска проекта в консоли GlassFish долж­
ны появиться строки, как показано на рис. 5.31.

Рис. 5.31. Результат работы таймера


Как видите, метод myTimer() методично вызывается службой тай­
меров EJB и выводит строки в консоль GlassFish каждые 30 секунд
(как было указано в аннотации @Schedule).

Автоматическое создание
сеансовых компонентов
из сущностей JPA
Одной из очень удобных возможностей NetBeans является способ­
ность генерировать сеансовые компоненты без сохранения состояния
из существующих сущностей JPA. При этом сгенерированные сеан­
совые компоненты действуют как объекты доступа к данным (Data
Access Objects, DAO). Эта функция в сочетании с возможностью
автоматического создания сущностей JPA из существующей схемы
базы данных позволяет полностью автоматизировать создание уров­
ней доступа к данным в приложениях, без необходимости самостоя­
тельно писать код на Java.
Чтобы воспользоваться преимуществами этой функциональности,
нужно создать проект EJB (выбрав в главном меню пункт File | New
Автоматическое создание сеансовых компонентов из сущностей JPA 207

Project (Файл | Создать проект) и затем тип проекта EJB Module


(Модуль EJB) в категории Java EE), либо добавить в проект EJB из
категории Java EE несколько сущностей JPA и вручную ввести их код
или сгенерировать из существующей схемы, как это обсуждалось в
главе 4 «Взаимодействие с базами данных через Java Persistence API».
После того как в проекте появятся сущности JPA, нужно в главном
меню выбрать пункт File | New File (Файл | Создать файл), затем вы­
брать категорию Persistence (Персистентность) и далее тип файлов
Session Beans For Entity Classes (Сеансовые компоненты для клас­
сов сущностей) (см. рис. 5.32).

Рис. 5.32. Создание сеансовых компонентов на основе сущностей JPA


На следующем шаге мастер позволяет выбрать существующие в
проекте классы сущностей JPA, для создания сеансовых компонен­
тов. В большинстве случаев необходимо сгенерировать компонен­
ты для всех сущностей, что можно сделать щелчком на кнопке Add
All >> (Добавить все >>), как показано на рис. 5.33.
На последнем шаге мастер дает возможность определять проект,
пакет и необходимость создания локальных и/или удаленных
интерфейсов (см. рис. 5.34).
После щелчка на кнопке Finish (Готово) сеансовые компоненты бу­
дут созданы и помещены в указанный пакет.
208 Глава 5. Реализация уровня бизнес-логики на сеансовых компонентах EJB

Рис. 5.33. Создание сеансовых компонентов для всех классов


сущностей JPA

Рис. 5.34. Последний этап создания сеансовых компонентов


на основе сущностей JPA
Все сгенерированные сеансовые компоненты наследуют абстракт­
ный класс AbstractFacade, который также генерируется мастером
создания сеансовых компонентов для классов сущностей. Этот аб­
Автоматическое создание сеансовых компонентов из сущностей JPA 209

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


операции CRUD (Create (создание), Read (чтение), Update (измене­
ние), Delete (удаление)) с сущностями.
package com.ensode.ejbdao.sessionbeans;

import java.util.List;
import javax.persistence.EntityManager;

public abstract class AbstractFacade<T> {


private Class<T> entityClass;

public AbstractFacade(Class<T> entityClass) {


this.entityClass = entityClass;
}

protected abstract EntityManager getEntityManager();

public void create(T entity) {


getEntityManager().persist(entity);
}

public void edit(T entity) {


getEntityManager().merge(entity);
}

public void remove(T entity) {


getEntityManager().remove(getEntityManager().merge(entity));
}

public T find(Object id) {


return getEntityManager().find(entityClass, id);
}

public List<T> findAll() {


javax.persistence.criteria.CriteriaQuery cq =
getEntityManager().getCriteriaBuilder().createQuery();
cq.select(cq.from(entityClass));
return getEntityManager().createQuery(cq).getResultList();
}

public List<T> findRange(int[] range) {


javax.persistence.criteria.CriteriaQuery cq =
getEntityManager().getCriteriaBuilder().createQuery();
cq.select(cq.from(entityClass));
javax.persistence.Query q = getEntityManager().
createQuery(cq);
q.setMaxResults(range[1] - range[0] + 1);
q.setFirstResult(range[0]);
return q.getResultList();
210 Глава 5. Реализация уровня бизнес-логики на сеансовых компонентах EJB

public int count() {


javax.persistence.criteria.CriteriaQuery cq =
getEntityManager().getCriteriaBuilder().createQuery();
javax.persistence.criteria.Root<T> rt = cq.from(entityClass);
cq.select(getEntityManager().getCriteriaBuilder().count(rt));
javax.persistence.Query q = getEntityManager().
createQuery(cq);
return ((Long) q.getSingleResult()).intValue();
}
}

Как видите, AbstractFacade – не более, чем фасад для EntityManager,


обертывание его вызовов в сеансовом компоненте предоставляет все
его преимущества, такие как управление транзакциями и распре­
деленный код. Сгенерированный метод create() используется для
создания новых сущностей, метод edit() изменяет имеющуюся сущ­
ность, метод remove() удаляет сущность. Метод find() находит сущ­
ность с заданным первичным ключом, а метод findAll() возвращает
список всех сущностей в базе данных. Метод findRange() возвращает
подмножество сущностей в базе данных; он принимает массив значе­
ний типа int в качестве его единственного параметра. Первый элемент
в этом массиве должен иметь индекс первого элемента в возвращае­
мом результате, а второй – должен иметь индекс последнего элемента
в результате. Метод count() возвращает число сущностей, своим дей­
ствием он напоминает стандартную инструкцию SQL select count(*)
from TABLE_NAME.
Как уже говорилось выше, все сгенерированные сеансовые ком­
поненты наследуют AbstractFacade. Давайте рассмотрим один из
них:
package com.ensode.ejbdao.sessionbeans;

import com.ensode.ejbdao.entities.Customer;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

@Stateless
public class CustomerFacade extends AbstractFacade<Customer> {
@PersistenceContext(unitName = "EjbDaoPU")
private EntityManager em;

@Override
protected EntityManager getEntityManager() {
return em;

Powered by TCPDF (www.tcpdf.org)


Резюме 211
}

public CustomerFacade() {
super(Customer.class);
}
}

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


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

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


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

Резюме
В этой главе мы познакомились с сеансовыми компонентами и узна­
ли, как NetBeans может помочь ускорить их разработку.
Мы узнали, как компоненты Enterprise JavaBeans вообще и сеансо­
вые компоненты в частности позволяют реализовать разные страте­
гии поддержки транзакций в корпоративных приложениях. Мы также
посмотрели, как использовать приемы аспектно-ориентированного
программирования и создавать свои интерцепторы. Дополнительно
мы узнали, что сеансовые компоненты могут определять методы, ко­
торые периодически вызывается контейнером EJB с помощью служ­
212 Глава 5. Реализация уровня бизнес-логики на сеансовых компонентах EJB

бы таймеров EJB (EJB Timer Service). Наконец, мы изучили, как с по­


мощью NetBeans можно существенно ускорить реализацию уровней
доступа к данным в приложениях путем автоматического создания
сеансовых компонентов, реализующих шаблон проектирования Объ-
ект доступа к данным (Data Access Object, DAO).
Глава 6.
Контексты и внедрение
зависимостей

Механизм контекстов и внедрения зависимостей (Contexts and


Dependency Injection, CDI) можно использовать, чтобы упростить
интегрирование разных уровней приложения Java EE. Например,
CDI дает возможность использовать сеансовый компонент в ка­честве
управляемого компонента, позволяя тем самым пользоваться преи­
муществами функциональности EJB, такими как транзакции, непо­
средственно в управляемых компонентах.
В этой главе мы затронем следующие темы:
введение в CDI;
квалификаторы;
стереотипы;
типы привязки интерцепторов;
собственные контексты.

Введение в CDI
Веб-приложения JavaServer Faces (JSF), использующие механизм
CDI, очень похожи на JSF-приложения без поддержки CDI. Основ­
ное отличие состоит в том, что в первых вместо компонентов JSF в
роли моделей и контроллеров используются именованные компонен­
ты CDI. Что делает приложения CDI проще в разработке и сопрово­
ждении, так это превосходная поддержка внедрения зависимостей в
виде CDI API.
Точно так же как стандартные приложения JSF, приложения CDI
используют фейслеты в качестве своей технологии отображения.
Следующий пример иллюстрирует типичную разметку для страни­
цы CDI:
214 Глава 6. Контексты и внедрение зависимостей

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


<!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"
xmlns:h="http://xmlns.jcp.org/jsf/html">
<h:head>
<title>Create New Customer</title>
</h:head>
<h:body>
<h:form>
<h3>Create New Customer</h3>
<h:panelGrid columns="3">
<h:outputLabel for="firstName" value="First Name"/>
<h:inputText id="firstName" value="#{customer.firstName}"/>
<h:message for="firstName"/>

<h:outputLabel for="middleName" value="Middle Name"/>


<h:inputText id="middleName"
value="#{customer.middleName}"/>
<h:message for="middleName"/>

<h:outputLabel for="lastName" value="Last Name"/>


<h:inputText id="lastName" value="#{customer.lastName}"/>
<h:message for="lastName"/>

<h:outputLabel for="email" value="Email Address"/>


<h:inputText id="email" value="#{customer.email}"/>
<h:message for="email"/>
<h:panelGroup/>
<h:commandButton value="Submit"
action="#{customerController.navigateToConfirmation}"/>
</h:panelGrid>
</h:form>
</h:body>
</html>

Как видите, разметка выше мало чем отличается от разметки в JSF-


приложении, где не используется механизм CDI. Приведенная выше
страница отображается, как показано на рис. 6.1 (после ввода произ­
вольных данных):
В данной разметке имеются компоненты JSF, использующие вы­
ражения на унифицированном языке выражений (Unified Expression
Language) для связывания ее со свойствами и методами именован­
ного компонента CDI. Давайте для начала взглянем на компонент
customer:
package com.ensode.cdiintro.model;

import java.io.Serializable;
Введение в CDI 215

Рис. 6.1. Внешний вид формы создания нового заказчика


import javax.enterprise.context.RequestScoped;
import javax.inject.Named;

@Named
@RequestScoped
public class Customer implements Serializable {

private String firstName;


private String middleName;
private String lastName;
private String email;

public Customer() {
}

public String getFirstName() {


return firstName;
}

public void setFirstName(String firstName) {


this.firstName = firstName;
}

public String getMiddleName() {


return middleName;
}

public void setMiddleName(String middleName) {


this.middleName = middleName;
}

public String getLastName() {


return lastName;
216 Глава 6. Контексты и внедрение зависимостей

public void setLastName(String lastName) {


this.lastName = lastName;
}

public String getEmail() {


return email;
}

public void setEmail(String email) {


this.email = email;
}
}

Аннотация @Named отмечает класс как именованный компонент


CDI. По умолчанию именем компонента станет имя класса с первым
символом, переключенным в нижний регистр (в данном случае ком­
понент получит имя «customer», поскольку класс имеет имя Customer).
Это поведение по умолчанию можно переопределить, просто переда­
вая требуемое имя в атрибуте value аннотации @Named, а именно:
@Named(value="customerBean")

Методы и свойства именованного компонента CDI доступны через


фейслеты, так же как для обычных компонентов JSF.
Подобно компонентам JSF, именованные компоненты CDI могут
иметь один из нескольких контекстов, перечисленных в табл. 6.1.
Приведенный выше именованный компонент имеет контекст запро­
са, что обозначено аннотацией @RequestScoped.
Таблица 6.1. Контексты именованных компонентов CDI с
соответствующими аннотациями

Контекст Аннотация Описание


Запроса @RequestScoped Компоненты с контекстом запроса до-
ступны, только пока обрабатывается
единственный запрос. Запросом мо-
жет быть HTTP-запрос, вызов метода
EJB, вызов веб-службы или отправка
JMS-сообщения компоненту, управля-
емому сообщениями.
Сеанса @SessionScoped Сеансовые компоненты доступны
во всех запросах в пределах HTTP-
сеанса. В контексте сеанса каждый
пользователь приложения получает
собственный экземпляр компонента.
Введение в CDI 217

Контекст Аннотация Описание


Диалога @ConversationScoped В контексте диалога компоненты могут
существовать в течение нескольких за-
просов, однако, как правило, время их
жизни короче, чем продолжительность
существования в контексте сеанса.
Приложения @ApplicationScoped В контексте приложения срок жиз-
ни компонента совпадает со сроком
жизни приложения. Такие компоненты
являются общими для всех пользова-
тельских сеансов.
Зависимый @Dependent В зависимом контексте компоненты
не являются общедоступными. Вся-
кий раз, когда выполняется внедрение
компонента в зависимом контексте,
создается новый экземпляр.

Как видите, в механизме CDI имеются все контексты, эквива­


лентные контекстам JSF. Дополнительно CDI добавляет еще два
контекста. Первым таким контекстом является контекст диалога
(conversation scope), охватывающий несколько запросов, но он име­
ет более короткий срок жизни, чем контекст сеанса. Вторым специ­
фичным CDI-контекстом является зависимый контекст (dependent
scope), фактически являющийся псевдоконтекстом. Компоненты
CDI в зависимом контексте зависят от других объектов; экземпляры
компонентов в этом контексте создаются, когда создается экземпляр
объекта, которому они принадлежат, создает, и уничтожаются вместе
с ним.
В нашем приложении имеются два именованных компонента CDI.
Мы уже обсуждали компонент customer. Другим именованным ком­
понентом CDI в нашем приложении является компонент контролле­
ра:
package com.ensode.cdiintro.controller;

import com.ensode.cdiintro.model.Customer;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.inject.Named;

@Named
@RequestScoped
public class CustomerController {

@Inject
218 Глава 6. Контексты и внедрение зависимостей

private Customer customer;

public Customer getCustomer() {


return customer;
}

public void setCustomer(Customer customer) {


this.customer = customer;
}

public String navigateToConfirmation() {


// В действующем приложении здесь будет
// выполняться сохранение данных в базе.

return "confirmation";
}
}

Внедрение класса Customer в приведенный выше класс выполня­


ется во время выполнения, достигается это благодаря аннотации
@Inject. Данная аннотация упрощает использование механизма вне­
дрения зависимостей в приложениях. Так как класс Customer отмечен
аннотацией @RequestScoped, в каждый запрос будет внедряться новый
экземпляр Customer.
Метод navigateToConfirmation() в приведенном выше классе вызы­
вается, когда пользователь щелкает на кнопке Submit (Отправить) на
странице. Он действует точно так же, как эквивалентный ему метод в
компоненте JSF, то есть возвращает строку, и на основании значения
этой строки приложение перемещается к соответствующей странице.
Точно так же как в случае с JSF, по умолчанию имя целевой страницы
составляется из значения, возвращаемого этим методом, и расшире­
ния .xhtml. Например, если в методе navigateToConfirmation() не воз­
никнет никаких исключений, пользователь будет отправлен на стра­
ницу confirmation.xhtml:
<?xml version='1.0' encoding='UTF-8' ?>
<!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"
xmlns:h="http://xmlns.jcp.org/jsf/html">
<h:head>
<title>Success</title>
</h:head>
<h:body>
New Customer created successfully.
<h:panelGrid columns="2" border="1" cellspacing="0">
<h:outputLabel for="firstName" value="First Name"/>
Квалификаторы 219
<h:outputText id="firstName" value="#{customer.firstName}"/>
<h:outputLabel for="middleName" value="Middle Name"/>
<h:outputText id="middleName"
value="#{customer.middleName}"/>

<h:outputLabel for="lastName" value="Last Name"/>


<h:outputText id="lastName" value="#{customer.lastName}"/>

<h:outputLabel for="email" value="Email Address"/>


<h:outputText id="email" value="#{customer.email}"/>
</h:panelGrid>
</h:body>
</html>

И снова ничего особенного, что нужно было сделать для получе­


ния доступа к свойствам именованного компонента. Разметка выше
действует так же, как если бы компонент был компонентом JSF, и ото­
бражается, как показано на рис. 6.2.

Рис. 6.2. Внешний вид страницы подтверждения, использующей


компонент CDI с контекстом сеанса
Как видите, приложения CDI работают точно так же, как и при­
ложения JSF, но имеют несколько преимуществ перед JSF, например,
дополнительные контексты CDI, отсутствующие в JSF. Кроме того,
механизм CDI позволяет отделить код Java от API JSF. Также, как го­
ворилось выше, CDI дает возможность использовать сеансовые ком­
поненты в качестве именованных компонентов.

Квалификаторы
Иногда тип внедряемого компонента может быть интерфейсом или
суперклассом Java, а нам нужно внедрить подкласс или класс, реали­
зующий интерфейс. Для таких случаев в CDI имеются квалификато­
220 Глава 6. Контексты и внедрение зависимостей

ры, которые можно использовать для ссылки на конкретный тип для


внедрения.
Квалификатор CDI – это аннотация, которая дополнительно долж­
на быть декорирована аннотацией @Qualifier. Эта аннотация может
быть использована для декорирования конкретного подкласса или
интерфейса. В этом разделе мы определим квалификатор Premium для
компонента, представляющего заказчика. Премиальные заказчики
могут получать льготы и скидки, не доступные обычным клиентам.
В NetBeans квалификаторы CDI создаются очень просто. Для это­
го нужно выбрать в главном меню пункт File | New File (Файл | Соз­
дать файл) и затем – тип файлов Qualifier Type (Тип квалификатора)
в категории Contexts and Dependency Injection (Учет контекстов и
зависимостей), как показано на рис. 6.3.

Рис. 6.3. Выбор типа файлов Qualifier Type (Тип квалификатора)


На следующем шаге нужно определить имя квалификатора и пакет
для его размещения (см. рис. 6.4).
После выполнения этих двух простых шагов NetBeans сгенерирует
код для квалификатора:
package com.ensode.cdiintro.qualifier;

import static java.lang.annotation.ElementType.TYPE;


import static java.lang.annotation.ElementType.FIELD;
Квалификаторы 221

Рис. 6.4. Выбор имени квалификатора и пакета


import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.inject.Qualifier;

@Qualifier
@Retention(RUNTIME)
@Target({METHOD, FIELD, PARAMETER, TYPE})
public @interface Premium {
}

Квалификаторы – это стандартные аннотации Java. Обычно они


запоминаются средой выполнения и предназначены для методов, по­
лей, параметров или типов. Единственное отличие квалификаторов
от стандартных аннотаций: квалификаторы декорируются аннотаци­
ей @Qualifier.
После создания квалификатора, его можно использовать для деко­
рирования конкретного подкласса или реализации интерфейса, как
показано ниже:
package com.ensode.cdiintro.model;

import com.ensode.cdiintro.qualifier.Premium;
222 Глава 6. Контексты и внедрение зависимостей

import javax.enterprise.context.RequestScoped;
import javax.inject.Named;

@Named
@RequestScoped
@Premium
public class PremiumCustomer extends Customer {

private Integer discountCode;

public Integer getDiscountCode() {


return discountCode;
}

public void setDiscountCode(Integer discountCode) {


this.discountCode = discountCode;
}
}

После декорирования конкретного экземпляра, требующего ква­


лификации, квалификаторы можно использовать в клиентском коде
для определения точного типа зависимости:
package com.ensode.cdiintro.controller;

import com.ensode.cdiintro.model.Customer;
import com.ensode.cdiintro.model.PremiumCustomer;
import com.ensode.cdiintro.qualifier.Premium;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.inject.Named;

@Named
@RequestScoped
public class PremiumCustomerController {

private static final Logger logger = Logger.getLogger(


PremiumCustomerController.class.getName());
@Inject
@Premium
private Customer customer;

public String saveCustomer() {


PremiumCustomer premiumCustomer =
(PremiumCustomer) customer;

logger.log(Level.INFO, "Saving the following information \n"


+ "{0} {1}, discount code = {2}",
Квалификаторы 223
new Object[]{premiumCustomer.getFirstName(),
premiumCustomer.getLastName(),
premiumCustomer.getDiscountCode()});

// В действующем приложении здесь должен быть код,


// сохраняющий данные заказчика в базе данных.
return "premium_customer_confirmation";
}
}

Поскольку поле customer было декорировано квалификатором


@Premium,в это поле будет внедрен экземпляр PremiumCustomer, по­
скольку этот класс также декорирован квалификатором @Premium.
Что касается наших страниц JSF, мы получаем доступ к именован­
ному компоненту как обычно, используя его имя:
<?xml version='1.0' encoding='UTF-8' ?>
<!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"
xmlns:h="http://xmlns.jcp.org/jsf/html">
<h:head>
<title>Create New Premium Customer</title>
</h:head>
<h:body>
<h:form>
<h3>Create New Premium Customer</h3>
<h:panelGrid columns="3">
<h:outputLabel for="firstName" value="First Name"/>
<h:inputText id="firstName"
value="#{premiumCustomer.firstName}"/>
<h:message for="firstName"/>

<h:outputLabel for="middleName" value="Middle Name"/>


<h:inputText id="middleName"
value="#{premiumCustomer.middleName}"/>
<h:message for="middleName"/>

<h:outputLabel for="lastName" value="Last Name"/>


<h:inputText id="lastName"
value="#{premiumCustomer.lastName}"/>
<h:message for="lastName"/>

<h:outputLabel for="email" value="Email Address"/>


<h:inputText id="email"
value="#{premiumCustomer.email}"/>
<h:message for="email"/>

<h:outputLabel for="discountCode" value="DiscountCode"/>


<h:inputText id="discountCode"
224 Глава 6. Контексты и внедрение зависимостей

value="#{premiumCustomer.discountCode}"/>
<h:message for="discountCode"/>

<h:panelGroup/>
<h:commandButton value="Submit"
action="#{premiumCustomerController.saveCustomer}"/>
</h:panelGrid>
</h:form>
</h:body>
</html>

В этом примере используется имя по умолчанию для компонента,


совпадающее с именем класса, где первая буква переключена в ниж­
ний регистр.
Теперь можно протестировать работу приложения (см. рис. 6.5).

Рис. 6.5. Форма создания нового премиального заказчика


После отправки формы откроется страница подтверждения (см.
рис. 6.6).

Рис. 6.6. Страница, подтверждающая создание премиального


заказчика
Стереотипы 225

Стереотипы
Стереотип CDI позволяет создавать новые аннотации, включающие
несколько аннотаций CDI. Например, если потребуется создать не­
сколько именованных компонентов CDI с контекстом сеанса, нам
придется отметить каждый компонент двумя аннотациями, а именно
@Named и @SessionScoped. Чтобы не добавлять по две аннотации к каж­
дому компоненту, можно создать стереотип (stereotype) и отмечать
компоненты им.
Чтобы создать стереотип CDI в NetBeans, нужно выбрать в глав­
ном меню пункт Файл (File) File | New File (Файл | Создать файл)
и затем – тип файлов Stereotype (Стереотип) в категории Contexts
and Dependency Injection (Учет контекстов и зависимостей), как по­
казано на рис. 6.7.

Рис. 6.7. Выбор типа файлов Stereotype (Стереотип)


Затем нужно определить имя нового стереотипа и пакет для его
размещения (см. рис. 6.8).
После этого NetBeans сгенерирует следующий код:
package com.ensode.cdiintro.stereotype;

import static java.lang.annotation.ElementType.TYPE;


import static java.lang.annotation.ElementType.FIELD;
226 Глава 6. Контексты и внедрение зависимостей

import static java.lang.annotation.ElementType.METHOD;


import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.enterprise.inject.Stereotype;

@Stereotype
@Retention(RUNTIME)
@Target({METHOD, FIELD, TYPE})
public @interface NamedSessionScoped {
}

Рис. 6.8. Выбор имени стереотипа и пакета


Теперь нужно просто добавить перед стереотипом необходимые
аннотации CDI, которыми требуется отметить классы. В данном слу­
чае нам нужно превратить классы в именованные компоненты с кон­
текстом сеанса, поэтому добавим аннотации @Named и @SessionScoped:
package com.ensode.cdiintro.stereotype;

import static java.lang.annotation.ElementType.TYPE;


import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.enterprise.context.SessionScoped;
Типы привязки интерцепторов 227
import javax.enterprise.inject.Stereotype;
import javax.inject.Named;

@Named
@SessionScoped
@Stereotype
@Retention(RUNTIME)
@Target({METHOD, FIELD, TYPE})
public @interface NamedSessionScoped {
}

Теперь стереотип можно использовать в своем коде:


package com.ensode.cdiintro.beans;

import com.ensode.cdiintro.stereotype.NamedSessionScoped;
import java.io.Serializable;

@NamedSessionScoped
public class StereotypeClient implements Serializable {

private String property1;


private String property2;

public String getProperty1() {


return property1;
}

public void setProperty1(String property1) {


this.property1 = property1;
}

public String getProperty2() {


return property2;
}

public void setProperty2(String property2) {


this.property2 = property2;
}
}

Мы аннотировали класс StereotypeClient нашим стереотипом


NamedSessionScoped, эквивалентным одновременному использованию
аннотаций @Named и @SessionScoped.

Типы привязки интерцепторов


Одним из преимуществ компонентов EJB является простота реа­
лизации аспектно-ориентированного программирования (Aspect
228 Глава 6. Контексты и внедрение зависимостей

Oriented Programming, AOP) с помощью интерцепторов. Механизм


CDI позволяет описать типы привязки перехватчика (Interceptor
Binding Types), чтобы затем с их помощью связывать интерцепторы
с компонентами, не создавая при этом компонентов, непосредствен­
но зависящих от интерцептора. Типы привязки интерцепторов  –
это аннотации, которые в свою очередь декорируются аннотацией
@InterceptorBinding.
Чтобы создать тип привязки интерцепторов в NetBeans, нужно
выбрать в главном меню File | New File (Файл | Создать файл) и за­
тем – тип файлов Interceptor Binding Type (Тип привязки устройства
перехвата) в категории Contexts and Dependency Injection (Учет
контекстов и зависимостей), как показано на рис. 6.9.

Рис. 6.9. Выбор типа файлов Interceptor Binding Type


(Тип привязки устройства перехвата)
На следующем шаге нужно определить имя класса нового типа
привязки интерцепторов и пакет для его размещения (см. рис. 6.10).
После этого NetBeans сгенерирует код для нового типа привязки
интерцепторов:
package com.ensode.cdiintro.interceptorbinding;

import static java.lang.annotation.ElementType.TYPE;


import static java.lang.annotation.ElementType.METHOD;
Типы привязки интерцепторов 229
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.interceptor.InterceptorBinding;

@Inherited
@InterceptorBinding
@Retention(RUNTIME)
@Target({METHOD, TYPE})
public @interface LoggingInterceptorBinding {
}

Рис. 6.10. Выбор имени типа привязки интерцепторов


Сгенерированный код полностью функционален в него ничего не
нужно добавлять. Чтобы использовать новый тип привязки интер­
цепторов, следует написать интерцептор и аннотировать его нашим
типом привязки:
package com.ensode.cdiintro.interceptor;

import com.ensode.cdiintro.interceptorbinding.LoggingInterceptorBinding;
import java.io.Serializable;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.interceptor.AroundInvoke;
import javax.interceptor.Interceptor;
230 Глава 6. Контексты и внедрение зависимостей

import javax.interceptor.InvocationContext;

@LoggingInterceptorBinding
@Interceptor
public class LoggingInterceptor implements Serializable{

private static final Logger logger =


Logger.getLogger(LoggingInterceptor.class.getName());

@AroundInvoke
public Object logMethodCall(InvocationContext invocationContext)
throws Exception {
logger.log(Level.INFO, new StringBuilder("entering ").append(
invocationContext.getMethod().getName()).append(
" method").toString());

Object retVal = invocationContext.proceed();

logger.log(Level.INFO, new StringBuilder("leaving ").append(


invocationContext.getMethod().getName()).append(
" method").toString());

return retVal;
}
}

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


интерцепторов, в остальном он является стандартным интерцепто­
ром, точно таким же, как тот, что использовался с сеансовыми компо­
нентами EJB (за дополнительной информацией обращайтесь к гла­
ве 5, «Реализация уровня бизнес-логики на сеансовых компонентах
EJB»).
Чтобы наши типы привязки интерцепторов работали должным об­
разом, следует добавить в проект конфигурационный файл beans.xml,
как показано на рис. 6.11.
И зарегистрировать в нем типы привязки интерцепторов:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"
bean-discovery-mode="all">
<interceptors>
<class>
com.ensode.cdiintro.interceptor.LoggingInterceptor
</class>
</interceptors>
</beans>
Типы привязки интерцепторов 231

Рис. 6.11. Добавление в проект конфигурационного файла beans.xml


Чтобы зарегистрировать интерцептор, нужно определить атрибут
bean-discovery-mode со значением "all" в теге <beans> и добавить тег
<interceptor> в файл beans.xml с одним или более вложенными тега­
ми <class>, содержащими полностью определенные (квалифициро­
ванные) имена интерцепторов.
В заключение, прежде чем использовать тип привязки интерцепто­
ров, следует аннотировать класс, обращения к которому будут пере­
хватываться нашим типом привязки интерцепторов:
package com.ensode.cdiintro.controller;

import com.ensode.cdiintro.interceptorbinding.LoggingInterceptorBinding;
import com.ensode.cdiintro.model.Customer;
import com.ensode.cdiintro.model.PremiumCustomer;
import com.ensode.cdiintro.qualifier.Premium;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.inject.Named;

@LoggingInterceptorBinding
@Named
@RequestScoped
232 Глава 6. Контексты и внедрение зависимостей

public class PremiumCustomerController {

private static final Logger logger = Logger.getLogger(


PremiumCustomerController.class.getName());
@Inject
@Premium
private Customer customer;

public String saveCustomer() {


PremiumCustomer premiumCustomer = (PremiumCustomer) customer;
logger.log(Level.INFO, "Saving the following information \n"
+ "{0} {1}, discount code = {2}",
new Object[]{premiumCustomer.getFirstName(),
premiumCustomer.getLastName(),
premiumCustomer.getDiscountCode()});

// В действующем приложении здесь должен быть код,


// сохраняющий данные заказчика в базе данных.
return "premium_customer_confirmation";
}
}

Теперь все готово к использованию интерцептора. После выполне­


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

Рис. 6.12. Признаки работы типа привязки интерцепторов


Строки entering saveCustomer method (вход в метод saveCustomer)
и leaving saveCustomer method (выход из метода saveCustomer) были
добавлены в журнал нашим интерцептором, который был косвенным
способом вызван типом привязки интерцепторов.

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

стов. Тем не менее, в NetBeans имеется мастер, помогающий опреде­


лять собственные контексты CDI.
Чтобы создать новый контекст CDI, нужно выбрать в главном
меню пункт File | New File (Файл | Создать файл) и затем – тип
файлов Scope Type (Тип контекста) в категории Contexts and De-
pendency Injection (Учет контекстов и зависимостей), как показано
на рис. 6.3.

Рис. 6.13. Выбор типа файлов Scope Type (Тип контекста)


Затем следует определить имя контекста и пакет для его размеще­
ния (см. рис. 6.14).
После щелчка на кнопке Finish (Готово) NetBeans создаст новый
контекст:
package com.ensode.cdiintro.scopes;

import static java.lang.annotation.ElementType.TYPE;


import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.inject.Scope;

@Inherited
234 Глава 6. Контексты и внедрение зависимостей

@Scope // или @javax.enterprise.context.NormalScope


@Retention(RUNTIME)
@Target({METHOD, FIELD, TYPE})
public @interface CustomScope {
}

Чтобы задействовать новый контекст в своих приложениях CDI,


его потребовалось бы сначала создать, однако, как уже говорилось
выше, это интересно в основном разработчикам фреймворков, а не
программистам, создающим программы Java EE. Поэтому мы не бу­
дем продолжать эту тему. Тем, кому интересна данная тема, можно по­
рекомендовать обратиться к книге «JBoss Weld CDI for Java Platform»
Кена Финнигана (Ken Finnigan), выпущенной издательством Packt
Publishing. (JBoss Weld – популярная реализация механизма CDI,
включенная в GlassFish.)

Рис. 6.14. Выбор имени контекста и пакета

Резюме
В этой главе мы узнали, как NetBeans поддерживает новый Java EE
API управления контекстами и внедрением зависимостей (CDI), вве­
денный в спецификацию Java EE 6. Мы получили общее представле­
ние о CDI и исследовали некоторые дополнительные возможности,
Резюме 235

которые предоставляет CDI API поверх стандартного JSF. Мы также


посмотрели, как устранить неоднозначность при внедрении компо­
нентов с помощью квалификаторов CDI. Дополнительно познакоми­
лись с возможностью группировки аннотаций CDI через стереотипы.
Посмотрели, как CDI помогает в аспектно-ориентированном про­
граммировании через типы привязки интерцепторов. Наконец, мы
узнали, что есть возможность создавать свои собственные контексты
CDI.
Глава 7.
Обмен сообщениями
с применением JMS и
компонентов, управляемых
сообщениями

Служба обмена сообщениями Java (Java Messaging Service, JMS) –


это стандартный API обмена сообщениями в Java EE, позволяющий
организовать слабосвязанные, асинхронные взаимодействия между
компонентами Java EE.
NetBeans включает в замечательную поддержку, упрощающую
создание приложений, использующих возможности JMS API, авто­
матически генерируя массу типового кода, и тем самым позволяя нам
сосредоточиться на бизнес-логике.
В этой главе мы затронем следующие темы:
введение в JMS;
создание ресурсов JMS в NetBeans;
реализация продюсера JMS-сообщений;
обработка JMS-сообщения компонентами, управляемыми со­
общениями.

Введение в JMS
JMS – это стандартный Java EE API, который позволяет организовать
слабосвязанные, асинхронные взаимодействия между компонентами
Java EE. Приложения, использующие возможности JMS, не взаимо­
действуют друг с другом непосредственно, а используют продюсеров
сообщений JMS, которые отправляют сообщения в пункт назначения
(очередь или тему JMS), а потребители сообщений JMS получают со­
общения оттуда.
Создание ресурсов JMS из NetBeans 237

Механизмом JMS поддерживается два режима обмена сообщения­


ми: точка-точка (Point-to-Point, PTP), когда каждое сообщение JMS
обрабатывается только одним получателем, и публикация/подписка
(Publish/Subscribe (pub/sub)), когда каждое сообщение, принадле­
жащее определенной теме, передается каждому получателю, подпи­
санному на эту тему. При использовании разновидности обмена со­
общениями «точка-точка», в качестве пунктов приема (приемников)
сообщений используются очереди; а при использовании разновидно­
сти pub/sub –темы сообщений.

Создание ресурсов JMS


из NetBeans
Прежде чем посылать и принимать сообщения JMS, нужно добавить
в сервер приложений приемник JMS (очередь или тему). Когда ис­
пользуется сервер приложений GlassFish, создавать приемники JMS
можно прямо из проектов Java EE в NetBeans.

В старых версиях Java EE, в дополнение к приемникам, требовалось


создавать фабрику соединений JMS. Спецификация Java EE 7 тре-
бует от всех совместимых с ней серверов приложений включать фа-
брику соединений JMS по умолчанию; поэтому данный шаг больше
не является обязательным.

Приемники JMS – это промежуточные контейнеры, куда продю­


серы JMS помещают сообщения и откуда получатели JMS извлекают
их. Когда используется разновидность обмена «точка-точка», роль
приемников JMS играют очереди, а при использовании разновиднос­
ти pub/sub – темы.
В примере ниже мы будем использовать разновидность обмена
«точка-точка» (Point-to-Point, PTP) и, соответственно, – нам нужно
создать очередь; процедура создания темы практически идентична.
Сначала нужно создать новый проект Java EE. В данном случае
создадим проект веб-приложения, как показано на рис. 7.1.
На следующем шаге дадим проекту имя JMSIntro (см. рис. 7.2).
Далее примем все настройки по умолчанию, предложенные масте­
ром (см. рис. 7.3).
На следующем шаге нужно выбрать фреймворк JavaServer Faces
(см. рис. 7.4).
238 Глава 7. Обмен сообщениями с применением JMS и компонентов...

Рис. 7.1. Создание нового проекта веб-приложения

Рис. 7.2. Выбор имени проекта


Создание ресурсов JMS из NetBeans 239

Рис. 7.3. Настройки по умолчанию, предложенные мастером

Рис. 7.4. Выбор фреймворка JavaServer Faces


240 Глава 7. Обмен сообщениями с применением JMS и компонентов...

И щелкнуть на кнопке Finish (Готово), чтобы создать проект.


Для создания очереди сообщений нам нужно выбрать в главном
меню пункт File | New (Файл | Создать файл), затем в открывшемся
диалоге выбрать категорию GlassFish в списке Categories (Катего­
рии) и в списке File Types (Типы файлов) – JMS Resource (Ресурсы
JMS), как показано на рис. 7.5.

Рис. 7.5. Выбор типа файлов JMS Resource (Ресурсы JMS)

Далее нужно указать имя JNDI очереди. В нашем примере мы про­


сто выбрали имя по умолчанию JMS/MyQueue и приняли тип ресурса по
умолчанию javax.jms.Queue (см. рис. 7.6).
Очереди сообщений JMS требуют определить значение для свой­
ства Name, поэтому в нашем примере мы просто использовали в каче­
стве значения имя JNDI очереди (минус префикс JMS/), как показано
на рис. 7.7.
Итак, мы создали очередь JMS для использования ее в качестве
приемника JMS в приложении.
NetBeans добавит созданные ресурсы GlassFish в файл с именем
sun-resources.xml. Этот файл будет добавлен в узел Server Resources
(Ресурсы сервера), на вкладке Projects (Проекты), как показано на
рис. 7.8.
Создание ресурсов JMS из NetBeans 241

Рис. 7.6. Выбор имени и типа ресурса JMS

Рис. 7.7. Определение значения свойства Name


При развертывании проекта на сервере GlassFish сервер прочита­
ет этот файл и создаст ресурсы, определенные в файле. Увидеть со­
держимое этого файла (см. рис. 7.9) можно, распахнув узел Server
Resources (Ресурсы сервера) на вкладке Projects (Проекты) и дваж­
ды щелкнув на его имени.
242 Глава 7. Обмен сообщениями с применением JMS и компонентов...

Рис. 7.8. Файл sun-resources.xml будет добавлен


в узел Server Resources (Ресурсы сервера)

Рис. 7.9. Содержимое файла sun-resources.xml


Убедиться, что очередь была успешно создана можно с помощью
веб-консоли GlassFish. Чтобы открыть ее, следует перейти на вкладку
Services (Службы), распахнуть
узел Servers (Серверы), щел­
кнуть правой кнопкой мыши на
узле GlassFish Server 4 и вы­
брать в контекстном меню пункт
View Domain Admin Console
(Просмотр консоли админи­
стратора домена), как показано
на рис. 7.10.
Спустя несколько секунд от­
кроется окно браузера с выхо­
дом в веб-консоль администра­
тора GlassFish (см. рис. 7.11).
Итак, чтобы убедиться в ус­
пешном создании очереди, мож­ Рис. 7.10. Открытие веб-консоли
GlassFish
Реализация продюсера сообщений JMS 243

но распахнуть узел JMS Resources (Ресурсы JMS), слева, затем


распахнуть узел Destination Resources (Ресурсы приемников) и
проверить присутствие только что созданной очереди в списке (см.
рис. 7.12).

Рис. 7.11. Веб-консоль администратора GlassFish


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

Реализация продюсера
сообщений JMS
В этом разделе будет создано простое
JSF-приложение. Один из компонентов
CDI в этом приложении будет произво­
дить сообщения JMS и отправлять их в
очередь, настроенную в предыдущем раз­
деле.
Создадим новый класс Java с именем
JmsMessageModel (см. рис.  7.13). Этот
класс будет хранить текст сообщения для Рис. 7.12. Очередь
отправки в очередь. действительно
присутствует в списке
244 Глава 7. Обмен сообщениями с применением JMS и компонентов...

Рис. 7.13. Создание класса JmsMessageModel


Чтобы превратить класс в именованный компонент CDI, его следу­
ет отметить аннотацией @Named. Также его следует отметить аннотаци­
ей @RequestScoped, чтобы дать ему контекст запроса.
Далее нужно добавить в класс приватную переменную-член с име­
нем msgText типа String с соответствующими методами get() и set().

Автоматическое создание методов get() и set(). Методы get()


и set() можно создать автоматически, нажав клавиши Alt+Insert
и затем выбрав пункт Getter and Setter (Методы получения и уста-
новки...).

По завершении класс должен выглядеть так:


package com.ensode.jmsintro;

import javax.enterprise.context.RequestScoped;
import javax.inject.Named;

@Named
@RequestScoped
public class JmsMessageModel {

private String msgText;

public String getMsgText() {


return msgText;
Реализация продюсера сообщений JMS 245
}

public void setMsgText(String msgText) {


this.msgText = msgText;
}
}

Теперь можно обратить наше внимание на контроллер, осуществляю­


щий фактическую отправку сообщений JMS в очередь. С помощью ма­
стера NetBeans создадим новый класс Java с именем JmsMesageController
и отметим его аннотациями @Named и @RequestScoped.
Вот как должен выглядеть этот класс на данном этапе:
package com.ensode.jmsintro;

import javax.enterprise.context.RequestScoped;
import javax.inject.Named;

@Named
@RequestScoped
public class JmsMessageController {
}

Добавим в него код, осуществляющий отправку сообщений JMS.


NetBeans может помочь нам в этом. Для этого нужно нажать клавиши
Alt+Insert и выбрать пункт Send JMS Message... (Отправка сообще­
ния JMS...), как показано на рис. 7.14.

Рис. 7.14. Автоматическая реализация отправки сообщения JMS


В результате на экране появится диалог Send JMS Message (От­
правка сообщения JMS), как показано на рис. 7.15. Здесь нужно щел­

Powered by TCPDF (www.tcpdf.org)


246 Глава 7. Обмен сообщениями с применением JMS и компонентов...

кнуть на переключателе Server Destinations (Адресаты сервера) и


выбрать очередь jms/myQueue в соответствующем раскрывающемся
списке (это та самая очередь, что была создана в предыдущем раз­
деле).

Рис. 7.15. Диалог Send JMS Message (Отправка сообщения JMS)


После щелчка на кнопке OK NetBeans сгенерирует код, необходи­
мый для отправки сообщения JMS:
package com.ensode.jmsintro;

import javax.annotation.Resource;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.inject.Named;
import javax.jms.JMSConnectionFactory;
import javax.jms.JMSContext;
import javax.jms.Queue;

@Named
@RequestScoped
public class JmsMessageController {
@Resource(mappedName = "jms/myQueue")
private Queue myQueue;
@Inject
@JMSConnectionFactory("java:comp/DefaultJMSConnectionFactory")
private JMSContext context;

private void sendJMSMessageToMyQueue(String messageData) {


context.createProducer().send(myQueue, messageData);
Реализация продюсера сообщений JMS 247
}
}

NetBeans создаст приватную переменную с именем myQueue типа


javax.jms.Queue, и отметит ее аннотацией @Resource, которая свяжет
переменную myQueue с очередью JMS, созданной в предыдущем раз­
деле.
Также NetBeans добавит приватную переменную context типа
JMSContext и отметит ее аннотацией @Inject, благодаря чему сервер
приложений (в данном случае GlassFish) внедрит в нее экземпляр
JMSContext во время выполнения. Переменная context отмечена так­
же аннотацией @JMSConnectionFactory, которая свяжет ее с фабрикой
соединений JMS.

В прежних версиях Java EE, в дополнение к приемникам JMS прихо-


дилось также создавать фабрику соединений JMS. В Java EE 7 была
реализована фабрика соединений по умолчанию, которую мы мо-
жем использовать в своих программах. NetBeans генерирует код,
используя эту новую возможность Java EE 7.

Наконец, NetBeans добавит метод, осуществляющий фактическую


отправку сообщения JMS в очередь. Имя метода зависит от имени
очереди. В данном случае, так как очередь имеет имя myQueue, метод
получит имя sendJMSMessageToMyQueue().
В методе используется упрощенный JMS 2.0 API, добавленный в
Java EE 7. Он вызывает метод createProducer() внедренного экзем­
пляра JMSContext, чтобы получить экземпляр javax.jms.JMSProducer,
и отправляет сообщение в очередь, вызывая метод send() продюсера
JMSProducer. В первом параметре методу send() передается приемник
JMS, куда должно быть помещено сообщение; во втором параметре
передается строка, содержащая само сообщение.
В очереди JMS можно отправлять сообщения разных типов (все
стандартные типы сообщений JMS описываются далее в этой гла­
ве). Чаще других, пожалуй, используется тип javax.jms.TextMessage.
В предыдущих версиях JMS API требовалось явно использовать этот
интерфейс, чтобы отправлять сообщения JMS, содержащие простые
строки. Новый JMS 2.0 API автоматически создает экземпляр клас­
са, реализующего данный интерфейс, когда в качестве сообщения
отправляется строка; это существенно упростило труд прикладных
программистов.
Теперь нужно добавить несколько изменений в код, сгенерирован­
ный несколькими щелчками мыши: добавим свой метод, который
248 Глава 7. Обмен сообщениями с применением JMS и компонентов...

будет вызывать метод sendJMSMessageToMyQueue() с сообщением, под­


лежащим отправке:
package com.ensode.jmsintro;

import javax.annotation.Resource;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.inject.Named;
import javax.jms.JMSConnectionFactory;
import javax.jms.JMSContext;
import javax.jms.Queue;

@Named
@RequestScoped
public class JmsMessageController {

@Inject
private JmsMessageModel jmsMessageModel;

@Resource(mappedName = "jms/myQueue")
private Queue myQueue;
@Inject
@JMSConnectionFactory("java:comp/DefaultJMSConnectionFactory")
private JMSContext context;

public String sendMsg() {


sendJMSMessageToMyQueue(jmsMessageModel.getMsgText());
return "confirmation";
}

private void sendJMSMessageToMyQueue(String messageData) {


context.createProducer().send(myQueue, messageData);
}
}

Как видите,нужно просто внедрить экземпляр класса


JmsMessageModel,созданного ранее, и добавить простой метод, вызы­
вающий сгенерированный метод sendJMSMessageToMyQueue() и переда­
ющий ему текст сообщения.
Далее нужно изменить сгенерированный файл index.xhtml, чтобы
связать переменную msgText экземпляра JmsMessageModel с текстовым
полем и кнопку – с вызовом метода sendMsg():
<?xml version='1.0' encoding='UTF-8' ?>
<!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"
xmlns:h="http://xmlns.jcp.org/jsf/html">
Реализация продюсера сообщений JMS 249
<h:head>
<title>Send JMS Message</title>
</h:head>
<h:body>
<h:form>
<h:panelGrid columns="2">
<h:outputLabel for="msgText" value="Enter Message Text:"/>

<h:inputText id="msgText"
value="#{jmsMessageModel.msgText}"/>
<h:panelGroup/>
<h:commandButton value="Submit"
action="#{jmsMessageController.sendMsg()}"/>
</h:panelGrid>
</h:form>
</h:body>
</html>

Тег <h:inputText> сохранит ввод пользователя в переменной


msgText экземпляра JMSMessageModel, благодаря выражению на унифи­
цированном языке Unified Expression Language (#{jmsMessageModel.
msgText}).
Как явно следует из значения атрибута action, тег
<h:commandButton> передаст управление методу sendMsg() экземпля­
ра JmsMessageController, когда пользователь щелкнет на кнопке. Как
уже говорилось выше, JmsMessageController.sendMsg() принимает
значение JmsMessageModel.msgText и помещает его в очередь сообще­
ний. Метод JmsMessageController.sendMsg() также возвращает поль­
зователю страницу подтверждения:
<?xml version='1.0' encoding='UTF-8' ?>
<!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"
xmlns:h="http://xmlns.jcp.org/jsf/html">
<h:head>
<title>JMS message sent</title>
</h:head>
<h:body>
JMS message sent successfully.
</h:body>
</html>

Как видите, страница подтверждения в данном примере очень прос­


та – она лишь сообщает, что JMS-сообщение было успешно отправлено.
Теперь, закончив с реализацией отправки сообщений, можно при­
ступать к разработке кода, который будет извлекать сообщения из
очереди.
250 Глава 7. Обмен сообщениями с применением JMS и компонентов...

Обработка сообщений
компонентами, управляемыми
сообщениями
Наиболее общий способ организации приема и обработки сообщений
JMS заключается в создании компонентов, управляемых сообщения­
ми. Компоненты, управляемые сообщениями, – это особый тип ком­
понентов Enterprise JavaBean (EJB), цель которых ждать появления
сообщений JMS в очереди или в теме. Компоненты, управляемые со­
общениями, обладают всеми возможностями EJB, такими как управ­
ление транзакциями и способность к масштабированию.

В действующих системах продюсеры и потребители сообще-


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

Чтобы создать компонент, управляемый сообщениями, нужно вы­


брать в главном меню пункт File | New File (Файл | Создать файл) и
в категории Enterprise JavaBeans выбрать тип Message-Driven Bean
(Компонент, управляемый сообщениями), как показано на рис. 7.16.

Рис. 7.16. Создание компонента, управляемого сообщениями


Обработка сообщений компонентами, управляемыми сообщениями 251

Далее нужно указать EJB Name (Имя EJB) и выбрать соответству­


ющее значение для поля Project Destinations (Адресаты проекта)
или Server Destinations (Адресаты сервера). В данном примере сле­
дует выбрать переключатель Server Destinations (Адресаты сервера)
и приемник, созданный выше в этой главе (см. рис. 7.17).

Рис. 7.17. Выбор приемника Server Destinations (Адресаты сервера)


На следующем шаге, в диалоге Activation Config Properties (Свой­
ства настройки активации), можно определить значения некоторых
свойств с информацией о компоненте, управляемом сообщениями
(см. рис. 7.18).
Некоторые из этих свойств описываются в табл. 7.1.
Таблица. 7.1. Свойства активации компонента, управляемого
сообщениями
Допустимые
Свойство Описание
значения
acknowledgeMode AUTO_ Когда установлено значение AUTO_
ACKNOWLEDGE ACKNOWLEDGE, сервер приложений бу-
или дет подтверждать сообщения сразу
DUPS_OK_ после их приема. Когда установлено
ACKNOWLEDGE значение DUPS_OK_ACKNOWLEDGE, сер-
вер приложений будет подтверждать
сообщения некоторое время спустя,
после их приема.
252 Глава 7. Обмен сообщениями с применением JMS и компонентов...

Допустимые
Свойство Описание
значения
clientId В свободной Идентификатор клиента для подписчи-
форме ков с длительной подпиской. Использу-
ется только в режиме обмена сообщени-
ями издатель/подписчик (то есть, когда
вместо очередей используются темы).
connectionFacto- В свободной Имя фабрики соединений в JNDI. По
ryLookup форме умолчанию хранит имя фабрики соеди-
нений по умолчанию.
destinationType QUEUE или Определяет тип приемника – очередь
TOPIC или тема.
destinationLookup В свободной Имя приемника (очереди или темы) в
форме JNDI.
messageSelector В свободной Позволяет компонентам, управляемым
форме сообщениями, обрабатывать сообще-
ния избирательно.
subscriptionDura- DURABLE Определяет длительность подписки  –
bility или длительная или не длительная. Дли-
NON_DURABLE тельные подписки сохраняются при
перезапуске сервера приложений и
возможны только в режиме обмена со-
общениями издатель/подписчик.
subscriptionName В свободной Определяет имя для длительной подпи-
форме ски.

В данном примере достаточно просто принять все значения по


умолчанию и щелкнуть на кнопке Finish (Готово). Ниже показано,
как выглядит вновь созданный компонент:
package com.ensode.jmsintro;

import javax.ejb.ActivationConfigProperty;
import javax.ejb.MessageDriven;
import javax.jms.Message;
import javax.jms.MessageListener;

@MessageDriven(activationConfig = {
@ActivationConfigProperty(propertyName =
"destinationLookup", propertyValue = "jms/myQueue"),
@ActivationConfigProperty(propertyName = "destinationType",
propertyValue = "javax.jms.Queue")
})
public class MessageReceiver implements MessageListener {

public MessageReceiver() {
Обработка сообщений компонентами, управляемыми сообщениями 253
}

@Override
public void onMessage(Message message) {
}
}

Рис. 7.18. Настройка свойств активации компонента, управляемого


сообщениями
Аннотация @MessageDriven превращает класс в компонент, управ­
ляемый сообщениями. Ее атрибут activationConfig принимает массив
аннотаций @ActivationConfigProperty, каждая из которых определяет
имя свойства JMS и его значение. Обе аннотации – @MessageDriven
и соответствующая ей @ActivationConfig – создаются автоматически
на основе значений, выбранных на последнем шаге мастера создания
компонента, управляемого сообщениями (см. рис. 7.18).
Отметьте, что сгенерированный класс реализует интерфейс javax.
jms.MessageListener. Это обязательное требование для компонентов,
управляемых сообщениями. Данный интерфейс определяет единст­
венный метод onMessage(), принимающий экземпляр класса, который
реализует интерфейс javax.jms.Message, и ничего не возвращающий.
Этот метод будет вызываться автоматически, когда в приемнике по­
явится сообщение для данного компонента. Нам остается только до­
бавить в этот метод обработку сообщения:
254 Глава 7. Обмен сообщениями с применением JMS и компонентов...

@Override
public void onMessage(Message message) {
TextMessage textMessage = (TextMessage) message;
try {
System.out.println("received message: " +
textMessage.getText());
} catch (JMSException ex) {
Logger.getLogger(MessageReceiver.class.getName()).log(
Level.SEVERE, null, ex);
}
}

Все типы сообщений JMS реализуют интерфейс javax.jms.Message.


Чтобы обработать сообщение, нужно привести его к типу определен­
ного интерфейса, наследующего Message. В данном случае принимае­
мые сообщения являются экземплярами javax.jms.TextMessage.
В этом простом примере мы просто будем посылать содержимое
сообщений в журнал сервера приложений, вызывая System.out.
println() и передавая ему textMessage.getText(). Метод getText()
экземпляра javax.jms.TextMessage возвращает строку с текстом со­
общения. В действующем приложении мы могли бы сделать нечто
более существенное, например, записать содержимое сообщения в
базу данных или отправить его в другой приемник JMS, исходя из
содержимого этого сообщения.
Наконец, метод getText() экземпляра javax.jms.TextMessage мо­
жет возбудить исключение JMSException, поэтому необходимо доба­
вить блок catch для его обработки.

Наблюдение
за приложением
в действии
Теперь, завершив разра­
ботку приложения, можно
понаблюдать за ним в дей­
ствии. Приложение мож­
но развернуть и запустить,
щелкнув правой кнопкой на
проекте и выбрав пункт Run
(Выполнение) в контекст­
ном меню, как показано на
рис. 7.19. Рис. 7.19. Запуск проекта
Обработка сообщений компонентами, управляемыми сообщениями 255

Спустя короткий промежуток времени откроется окно браузера с


начальной страницей приложения (см. рис. 7.20).

Рис. 7.20. Начальная страница приложения


Если ввести некоторый текст и щелкнуть на кнопке Submit (От­
править), можно увидеть результат работы метода onMessage() компо­
нента, управляемого сообщениями, в журнале сервера приложений,
как показано на рис. 7.21.

Рис. 7.21. Результат работы метода onMessage() в журнале сервера


приложений
Как видите, разработка приложений, реализующих обмен сообще­
ниями с использованием JMS 2.0 API, не представляет большого тру­
да и упрощается еще больше, благодаря мастерам NetBeans и функци­
ям автоматического создания кода.
В нашем примере мы познакомились только с одним типом со­
общений – javax.jms.TextMessage. В табл. 7.2 коротко описаны все
остальные типы сообщений JMS.
Таблица 7.2. Типы сообщений JMS

Интерфейс Описание

BytesMessage Используется для отправки сообщений в виде массивов бай-


тов.
256 Глава 7. Обмен сообщениями с применением JMS и компонентов...

Интерфейс Описание

MapMessage Используется для отправки сообщений в виде пар имя/зна-


чение. Имена должны быть строковыми объектами, значе-
ния – простыми типами или Java-объектами.
ObjectMessage Используется для отправки сообщений в виде сериализуемых
объектов. Сериализуемый объект – это экземпляр любого
класса, реализующего интерфейс java.io.Serializable.
StreamMessage Используется для отправки сообщений в виде потока значе-
ний простых типов Java.

TextMessage Используется для отправки сообщений в виде строк.

Резюме
В этой главе мы в общих чертах познакомились с системами обмена
сообщениями JMS. Поговорили о двух режимах обмена сообщения­
ми JMS – «точка-точка», когда каждое сообщение передается для об­
работки только одному получателю, и «издатель/подписчик», когда
каждое сообщение передается всем получателям, подписавшимся на
данную тему.
Затем мы посмотрели, как создавать ресурсы JMS, такие как очере­
ди сообщений, используя для этого мастера ресурсов JMS в NetBeans.
Мы также узнали, как отправлять сообщения JMS с использова­
нием JMS 2.0 API, реализованного в Java EE 7, и как с помощью Net­
Beans сгенерировать большую часть типового кода.
Затем мы перешли к созданию программного кода, осуществляю­
щего прием и обработку сообщений JMS. В частности мы создали
компонент, управляемый сообщениями, воспользовавшись услугами
мастера Message-Driven Bean (Компонент, управляемый сообщени­
ями) в NetBeans.
Глава 8.
Прикладной интерфейс
JSON Processing

JSON (сокращенно от JavaScript Object Notation – форма записи объ­


ектов JavaScript) – это легковесный формат обмена данными. Главное
преимущество JSON перед другими форматами, такими как XML, в
том, что он легко воспринимается человеком и прост для создания и
анализа компьютерными программами. Он часто используется в со­
временных веб-приложениях.
В Java EE 7 появился новый прикладной интерфейс для обработки
JSON (Java API for JSON Processing, JSON-P), ставший стандартным
для парсинга и создания данных JSON.
JSON-P поддерживает два API для парсинга и создания данных в
формате JSON: прикладной интерфейс объектной модели и потоко­
вый прикладной интерфейс.
В этой главе рассматриваются следующие темы:
прикладной интерфейс объектной модели JSON-P:
• создание данных в формате JSON с помощью прикладного
интерфейса объектной модели JSON-P;
• парсинг данных в формате JSON с помощью прикладного
интерфейса объектной модели JSON-P;
потоковый прикладной интерфейс JSON-P:
• создание данных в формате JSON с помощью потокового
прикладного интерфейса JSON-P;
• парсинг данных в формате JSON с помощью потокового
прикладного JSON-P;

Объектная модель JSON-P


Прикладной интерфейс объектной модели JSON-P позволяет генери­
ровать древовидное представление объектов JSON в памяти. С этой
258 Глава 8. Прикладной интерфейс JSON Processing

целью JSON-P API использует шаблон построителей, с помощью ко­


торого разработчики приложений легко могут создавать представле­
ние JSON для объектов Java.

Создание данных в формате JSON


с использованием объектной модели
JSON-P
Когда используется API объектной модели JSON-P, работа обыч­
но начинается с вызова метода add() реализации интерфейса
JsonObjectBuilder. Этот метод возвращает другой экземпляр
реали­зации интерфейса JsonObjectBuilder. Благодаря этому вы­
зовы JsonObject.add() можно объединять в цепочки и создавать
предс­тавления JSON для объектов Java. Этот процесс демонстри­
рует следую­щий пример:
package com.ensode.jsonpmodelapi;

// инструкции импортирования опущены

@Named
@RequestScoped
public class JsonPModelApiBean {

@Inject
private Person person;
private String jsonStr;

public String generateJson() {


JsonObjectBuilder jsonObjectBuilder =
Json.createObjectBuilder();

JsonObject jsonObject = jsonObjectBuilder.


add("firstName", person.getFirstName()).
add("middleName", person.getMiddleName()).
add("lastName", person.getLastName()).
add("gender", person.getGender()).
add("age", person.getAge()).
build();

StringWriter stringWriter = new StringWriter();

try (JsonWriter jsonWriter = Json.createWriter(stringWriter))


{
jsonWriter.writeObject(jsonObject);
}
Объектная модель JSON-P 259
setJsonStr(stringWriter.toString());

// динамическая навигация JSF


return "generated_json";
}
// другие методы опущены
}

Это пример является частью JSF-приложения, в частности, имено-


ванного компонента CDI. Мы показали только код, который имеет
отношение к обсуждаемой теме.

В данном примере генерируется JSON-представление просто­


го объекта Person, имеющего несколько простых свойств, таких как
firstName, middleName, lastName и так далее, а также соответствующие
им методы get() и set().
Первое, что делается в этом примере, – приобретается экземпляр
класса, реализующего интерфейс JsonObjectBuilder вызовом стати­
ческого метода createObjectBuilder() класса Json. Этот метод возвра­
щает экземпляр класса, реализующего интерфейс JsonObjectBuilder,
который можно использовать в качестве отправной точки для созда­
ния JSON-представления объекта Java.
После приобретения экземпляра JsonObjectBuilder нужно вызвать
один из его перегруженных методов add(), каждый из которых при­
нимает строку в первом параметре и значение во втором. Этот метод
возвращает другой экземпляр JsonObjectBuilder, как можно видеть
в примере выше. Благодаря этому можно объединять вызовы add()
в цепочки, чтобы легко и просто сгенерировать требуемое JSON-
представление. В примере наглядно показано, как действует такой
шаблон построителя.
В примере выше использовались две версии метода
JsonObjectBuilder.add(), одна принимает строку во втором параме­
тре, а другая – целое число. (В данном примере этому методу переда­
ется объект Integer. Механизм приведения типов в Java сам позабо­
тится о преобразовании объекта в простое значение типа int.) Есть и
другие перегруженные версии JsonObjectBuilder.add(). Они обеспе­
чивают большую гибкость в создании JSON-представлений объектов
Java. В табл. 8.1 описываются все имеющиеся перегруженные версии
JsonObjectBuilder.add(); все они принимают в первом параметре имя
JSON-свойства, а во втором – соответствующее значение.
260 Глава 8. Прикладной интерфейс JSON Processing

Таблица 8.1. Перегруженные версии JsonObjectBuilder.add()

Метод add() Описание


add(String name, BigDecimal value) Добавляет в объект JSON предс­
тавление JsonNumber значения
типа BigDecimal.
add(String name, BigInteger value) Добавляет в объект JSON предс­
тавление JsonNumber значения
типа BigInteger.
add(String name, boolean value) Добавляет в объект JSON значение
JsonValue.TRUE или JsonValue.
FALSE, в зависимости от значения
параметра value.
add(String name, double value) Добавляет в объект JSON предс­
тавление JsonNumber значения
типа double.
add(String name, int value) Добавляет в объект JSON предс­
тавление JsonNumber значения
типа int.
add(String name, JsonArrayBuilder Добавляет в объект JSON массив
builder) объектов JSON.
add(String name, JsonObjectBuilder Добавляет в объект JSON другой
builder) объект JSON.
add(String name, JsonValue value) Добавляет в объект JSON реализа-
цию интерфейса JsonValue.
add(String name, long value) Добавляет в объект JSON предс­
тавление JsonNumber значения
типа long.
add(String name, String value) Добавляет в объект JSON строко-
вое значение.

А теперь вернемся к примеру. После вызова цепочки мето­


дов add() вызывается метод build() получившейся реализации
JsonObjectBuilder. Этот метод вернет экземпляр класса, реализую­
щего интерфейс JsonObject.
После получения экземпляра JsonObject, остается только пре­
образовать его в строку. Для этого нужно вызвать статический
метод createWriter() класса Json, передав ему новый экземпляр
StringWriter. Этот метод вернет экземпляр класса, реализующего
интерфейс JsonWriter. Теперь нужно вызвать метод writeObject() эк­
земпляра JsonWriter, передав ему экземпляр JsonObject, созданный
перед этим. Этот метод заполнит объект StringWriter, использовав­
Объектная модель JSON-P 261

шийся для создании интерфейса JsonWriter. Теперь, чтобы получить


строковое представление JSON нашего объекта, достаточно вызвать
метод toString() объекта StringWriter.

Пример
Наш пример является простым веб-приложением, использующим
JSF для заполнения именованного компонента CDI и создания из
него строки в формате JSON. Ниже приводится разметка страницы
JSF:
<?xml version='1.0' encoding='UTF-8' ?>
<!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"
xmlns:h="http://xmlns.jcp.org/jsf/html">
<h:head>
<title>Object to JSON With the JSON-P Object Model API</title>
</h:head>
<h:body>
<h:form>
<h:panelGrid columns="2">
<h:outputLabel for="firstName" value="First Name"/>
<h:inputText id="firstName" value="#{person.firstName}"/>
<h:outputLabel for="middleName" value="Middle Name"/>
<h:inputText id="middleName"
value="#{person.middleName}"/>
<h:outputLabel for="lastName" value="Last Name"/>
<h:inputText id="lastName" value="#{person.lastName}"/>
<h:outputLabel for="gender" value="Gender"/>
<h:inputText id="gender" value="#{person.gender}"/>
<h:outputLabel for="age" value="Age"/>
<h:inputText id="age" value="#{person.age}"/>
<h:panelGroup/>
<h:commandButton value="Submit"
action="#{jsonPModelApiBean.generateJson()}"/>
</h:panelGrid>
</h:form>
</h:body>
</html>

Как видите, разметка очень простая. Она содержит форму с несколь­


кими текстовыми полями ввода, связанными со свойствами имено­
ванного компонента CDI Person. На странице имеется также кнопка,
которая вызывает метод generateJson() класса JsonPModelApiBean, как
обсуждалось в предыдущем разделе.
Ниже приводится исходный код определения компонента Person:
262 Глава 8. Прикладной интерфейс JSON Processing

package com.ensode.jsonpmodelapi;

import java.io.Serializable;
import javax.enterprise.context.SessionScoped;
import javax.inject.Named;

@Named
@SessionScoped
public class Person implements Serializable {
private String firstName;
private String middleName;
private String lastName;
private String gender;
private Integer age;

public String getFirstName() {


return firstName;
}

public void setFirstName(String firstName) {


this.firstName = firstName;
}

public String getMiddleName() {


return middleName;
}

public void setMiddleName(String middleName) {


this.middleName = middleName;
}

public String getLastName() {


return lastName;
}

public void setLastName(String lastName) {


this.lastName = lastName;
}

public String getGender() {


return gender;
}

public void setGender(String gender) {


this.gender = gender;
}

public Integer getAge() {


return age;
}

public void setAge(Integer age) {


Объектная модель JSON-P 263
this.age = age;
}
}

И снова в классе Person нет ничего необычного для нас – это про­
стой именованный компонент CDI с приватными свойствами и соот­
ветствующими им методами set() и get().
Приложение можно запустить как обычно, щелкнув правой кноп­
кой мыши на проекте и выбрав в контекстном меню пункт Run (Вы­
полнение), как показано на рис. 8.1.

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


Через несколько секунд должно появиться окно браузера с откры­
той страницей JSF (см. рис. 8.2).

Рис. 8.2. Страница JSF приложения


Если щелкнуть на кнопке Submit (Отправить), управление будет
передано контроллеру, о котором рассказывалось в предыдущем раз­
264 Глава 8. Прикладной интерфейс JSON Processing

деле. Затем будет выполнен переход к странице JSF, отображающей


JSON-представление объекта Person.
Вот как выглядит разметка этой страницы:
<?xml version='1.0' encoding='UTF-8' ?>
<!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"
xmlns:h="http://xmlns.jcp.org/jsf/html">
<h:head>
<title>Generated JSON with the JSON-P Object Model API</title>
</h:head>
<h:body>
<h:form>
<h:panelGrid columns="2">
<h:outputLabel for="parsedJson" value="Parsed JSON"/>
<h:inputTextarea
value="#{jsonPModelApiBean.jsonStr}" rows="4"/>
<h:panelGroup/>
<h:commandButton value="Submit"
action="#{jsonPModelApiBean.parseJson()}"/>
</h:panelGrid>
</h:form>
</h:body>
</html>

Она просто отображает текстовую область с JSON-представлением


объекта Person, как показано на рис. 8.3.

Рис. 8.3. Страница JSF с JSON-представлением объекта Person


Обратите внимание, что имена свойств совпадают с именами
свойств в объекте JSON и все значения соответствуют введенным на
начальной странице. Теперь можно изменить строку JSON, отобража­
емую в текстовой области (это должен быть текст, соответствующий
всем требованиям формата JSON). После щелчка на кнопке Submit
(Отправить) объект Person будет заполнен значениями из обновлен­
ной строки JSON.
Объектная модель JSON-P 265

Парсинг данных в формате JSON


с использованием объектной модели
JSON-P
Теперь, когда известно, как генерировать JSON-представление для
объектов Java, обратим наше внимание на решение обратной задачи –
заполнения объектов Java значениями из строк JSON.
Как это делается, показано в следующем коде:
package com.ensode.jsonpmodelapi;

// инструкции импортирования опущены

@Named
@RequestScoped
public class JsonPModelApiBean {

@Inject
private Person person;
private String jsonStr;

public String generateJson() {


// тело опущено для простоты
}

public String parseJson() {


JsonObject jsonObject;
try (JsonReader jsonReader = Json.createReader(
new StringReader(jsonStr))) {
jsonObject = jsonReader.readObject();
}

person.setFirstName(jsonObject.getString("firstName"));
person.setMiddleName(jsonObject.getString("middleName"));
person.setLastName(jsonObject.getString("lastName"));
person.setGender(jsonObject.getString("gender"));
person.setAge(jsonObject.getInt("age"));
return "display_populated_obj";
}

public String getJsonStr() {


return jsonStr;
}

public void setJsonStr(String jsonStr) {


this.jsonStr = jsonStr;
}
}
266 Глава 8. Прикладной интерфейс JSON Processing

Первое, что нужно сделать, – создать экземпляр java.io.StringReader


из строки JSON. Делается это передачей строки с данными в форма­
те JSON конструктору StringReader. Затем, полученный экземпляр
StringReader передается в вызов статического метода createReader()
класса javax.json.Json. Этот метод вернет реализацию интерфейса
javax.json.JsonReader, которую затем можно использовать для полу­
чения реализации javax.json.JsonObject.
Объект JSON имеет несколько методов get() для извлечения дан­
ных из строки JSON. Эти методы принимают имя свойства JSON и
возвращают соответствующее значение. В нашем примере исполь­
зованы два таких метода – getString() и getInt() – для заполнения
экземпляра объекта Person. В табл. 8.2 приводится список всех до­
ступных методов get().
Таблица 8.2. Доступные методы get() объекта JSON

Метод Описание
getBoolean(String name) Возвращает значение указанного свойства
как булево значение.
getInt(String name) Возвращает значение указанного свойства
как целое число.
getJsonArray(String name) Возвращает значение указанного свойства
как массив в виде объекта, реализующего ин-
терфейс JsonArray.
getJsonNumber(String name) Возвращает значение указанного числового
свойства как объект, реализующий интерфейс
JsonNumber. Возвращаемый объект может
быть преобразован в значение типа int, long
или double вызовом метода intValue(),
longValue() или doubleValue(), соответ-
ственно.
getJsonObject(String name) Возвращает значение указанного свой-
ства как объект, реализующий интерфейс
JsonString.
getJsonString(String name) Возвращает значение указанного свойства
как строку.

getString(String name) Возвращает значение указанного свойства

Вернемся к нашему примеру. Как можно заметить, метод


parseJson() класса контроллера JsonModelApiBean возвращает стро­
ку "display_populated_obj". А, как мы знаем, в соответствии с согла­
шениями согласно JSF, эта строка будет использована для перехода
Объектная модель JSON-P 267

к странице display_populated_obj.xhtml. Разметка этой страницы


представлена ниже:
<?xml version='1.0' encoding='UTF-8' ?>
<!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"
xmlns:h="http://xmlns.jcp.org/jsf/html">
<h:head>
<title>Java Object Properties Populated from JSON</title>
</h:head>
<h:body>
<table>
<tr>
<td>
First Name:
</td>
<td>
#{person.firstName}
</td>
</tr>
<tr>
<td>
Middle Name:
</td>
<td>
#{person.middleName}
</td>
</tr>
<tr>
<td>
Last Name:
</td>
<td>
#{person.lastName}
</td>
</tr>
<tr>
<td>
Gender:
</td>
<td>
#{person.gender}
</td>
</tr>
<tr>
<td>
Age:
</td>
<td>
268 Глава 8. Прикладной интерфейс JSON Processing

#{person.age}
</td>
</tr>
</table>
</h:body>
</html>

Как видите (см. рис. 8.4), страница просто отображает все свойства
объекта Person с использованием выражений на унифицированном
языке выражений (Unified Expression Language). Свойства объек­
та заполняются из строки JSON,отображавшейся на предыдущей
странице и которая была связана со свойством jsonStr компонента
JsonPModelApiBean.

Рис. 8.4. Страница просто отображает все свойства объекта Person


Как вы могли убедиться, заполнение и парсинг строки JSON с ис­
пользованием объектной модели JSON-P реализуется достаточно
просто и понятно. Этот API с успехом можно использовать при об­
работке небольших объемов данных. Однако, он испытывает пробле­
мы с производительностью при обработке значительных объемов ин­
формации. В таких ситуациях лучше использовать потоковую модель
JSON-P.

Потоковая модель JSON-P


Потоковая модель JSON-P позволяет читать данные в формате JSON
из потоков и записывать их в потоки (с помощью подкласса java.
io.OutputStream или java.io.Writer). Она имеет более высокую про­
изводительность и более эффективно расходует память, чем объект­
ная модель. Эти преимущества в отношении производительности и
эффективности, однако, накладывают некоторые ограничения. По­
токовая модель JSON-P поддерживает только последовательное чте­
ние данных JSON – она не дает возможности обращаться к свойствам
Потоковая модель JSON-P 269

JSON непосредственно, как это позволяет объектная модель. Вообще


говоря, потоковую модель следует использовать только для обработ­
ки больших объемов данных JSON; в других случаях лучше исполь­
зовать более простую объектную модель.
В следующих разделах будет представлена другая версия примера
из предыдущего раздела, реализованная с применением потоковой
модели JSON-P.

Так как примеры в этом разделе в значительной степени повторя-


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

Создание данных JSON с применением


потоковой модели JSON-P
При использовании потоковой модели JSON-P, создание данных в
формате JSON осуществляется с помощью класса JsonGenerator, вы­
зовом одного или более его перегруженных методов write(), добавля­
ющих свойства JSON и соответствующие им значения.
Следующий пример иллюстрирует создание данных посредством
потоковой модели JSON-P:
package com.ensode.jsonpstreamingapi;

// инструкции импортирования опущены

@Named
@RequestScoped
public class JsonPStreamingApiBean {

@Inject
private Person person;
private String jsonStr;

public String generateJson() {


StringWriter stringWriter = new StringWriter();
try (JsonGenerator jsonGenerator
= Json.createGenerator(stringWriter)) {
jsonGenerator.writeStartObject().
write("firstName", person.getFirstName()).
write("middleName", person.getMiddleName()).
write("lastName", person.getLastName()).
write("gender", person.getGender()).
270 Глава 8. Прикладной интерфейс JSON Processing

write("age", person.getAge()).
writeEnd();
}

setJsonStr(stringWriter.toString());
return "generated_json";
}
}

Этот фрагмент является частью JSF-приложения, в частности –


именованного компонента CDI, поэтому здесь показан только код,
имеющий непосредственное отношение к дискуссии.

Чтобы сгенерировать данные JSON с применением потоковой


модели JSON-P, сначала нужно вызвать статический метод Json.
createGenerator(). Этот метод возвращает экземпляр класса, реали­
зующего интерфейс javax.json.stream.JsonGenerator. Существует
две перегруженные версии метода Json.createGenerator(), одна при­
нимает экземпляр класса java.io.OutputStream (или одного из его
подклассов), а вторая – экземпляр класса java.io.Writer (или одного
из его подклассов). В данном примере используется вторая версия и
ей передается экземпляр класса java.io.StringWriter.
После получения экземпляра JsonGenerator нужно вызвать его метод
writeStartObject(). Этот метод записывает начальный символ объекта
JSON (открывающая фигурная скобка {) в экземпляр OutputStream или
Writer, который передается в вызов Json.createGenerator(). Метод
writeStartObject() возвращает другой экземпляр JsonGenerator, что
дает возможность тут же вызвать метод write().
Метод write() класса JsonGenerator добавляет свойство JSON в
поток. Его первый строковый параметр представляет имя свойства,
а второй – соответствующее значение. Существует несколько пере­
груженных версий метода write(), по одной для каждого типа зна­
чений, поддерживаемых JSON (String или числовые типы, такие
как BigInteger или double). В данном примере мы добавляем только
строковые и целочисленные свойства, для чего используются соот­
ветствующие версии метода write(). В табл. 8.3 перечислены все име­
ющиеся версии метода write().
Таблица 8.3. Перегруженые версии методв write()

Метод write() Описание


write (String name, BigDecimal Добавляет в данные JSON свойство типа
value) BigDecimal.
Потоковая модель JSON-P 271

Метод write() Описание


write (String name, BigInteger Добавляет в данные JSON свойство типа
value) BigInteger.
write (String name, JsonValue Добавляет в данные JSON свойство типа
value) реализующее интерфейс JsonValue
или один из его подынтерфейсов
(JsonArray, JsonObject, JsonString
или JsonStructure).
write (String name, String Добавляет в данные JSON свойство типа
value) String.
write (String name, boolean Добавляет в данные JSON свойство типа
value) boolean.
write (String name, double Добавляет в данные JSON свойство типа
value) double.
write (String name, int value) Добавляет в данные JSON свойство типа
int.
write (String name, long Добавляет в данные JSON свойство типа
value) long.

Закончив добавление свойств JSON, нужно вызвать метод


writeEnd() класса JsonGenerator, который добавит заключитель­
ный символ JSON (закрывающую фигурную скобку }) в строку
JSON.
В этой точке объект Writer или OutputStream, что был передан в
вызов Json.createGenerator(), будет содержать законченный объект
JSON. Что дальше делать с этим объектом, зависит от целей, которые
преследует приложение. В данном примере мы просто вызываем ме­
тод toString() экземпляра StringWriter и присваиваем возвращаемое
им значение переменной jsonStr.

Парсинг данных JSON с применением


потоковой модели JSON-P
Следующий пример иллюстрирует порядок парсинга данных в фор­
мате JSON с использованием потоковой модели JSON-P:
package com.ensode.jsonpstreamingapi;

// инструкции импортирования опущены

@Named
@RequestScoped
272 Глава 8. Прикладной интерфейс JSON Processing

public class JsonPStreamingApiBean {

@Inject
private Person person;
private String jsonStr;

public String parseJson() {


StringReader stringReader = new StringReader(jsonStr);

JsonParser jsonParser = Json.createParser(stringReader);

Map<String, Object> jsonMap = new HashMap<>();


String jsonKeyNm = null;
Object jsonVal = null;

while (jsonParser.hasNext()) {
JsonParser.Event event = jsonParser.next();

if (event.equals(Event.KEY_NAME)) {
jsonKeyNm = jsonParser.getString();
} else if (event.equals(Event.VALUE_STRING)) {
jsonVal = jsonParser.getString();
} else if (event.equals(Event.VALUE_NUMBER)) {
jsonVal = jsonParser.getInt();
}

jsonMap.put(jsonKeyNm, jsonVal);
}

person.setFirstName((String) jsonMap.get("firstName"));
person.setMiddleName((String) jsonMap.get("middleName"));
person.setLastName((String) jsonMap.get("lastName"));
person.setGender((String) jsonMap.get("gender"));
person.setAge((Integer) jsonMap.get("age"));

return "display_populated_obj";
}
}

Для чтения и анализа данных в формате JSON с использованием


потоковой модели JSON-P, нужно получить реализацию интерфей­
са JsonParser. Класс Json имеет две перегруженные версии метода
createParser(), которые можно использовать для этой цели. Одна
версия принимает экземпляр класса java.io.InputStream (или одного
из его подклассов), а другая – экземпляр класса java.io.Reader (или
одного из его подклассов). В данном примере используется вторая
версия и ей передается экземпляр класса java.io.StringReader (кото­
рый наследует java.io.Reader), содержащий строку JSON.
Потоковая модель JSON-P 273

После получения ссылки на экземпляр JsonParser в цикле while


вызывается его метод hasNext(). Метод JsonParser.hasNext() возвра­
щает true, если в строке JSON имеются еще непрочитанные свойства,
иначе возвращается false.
Внутри цикла вызывается метод JsonParser.next(). Он возвраща­
ет экземпляр перечисления JsonParser.Event. Конкретное значение
возвращаемого экземпляра перечисления JsonParser.Event позво­
ляет узнать тип читаемых данных (имя ключа, строковое значение,
числовое значение, и т.д.). В данном примере строка JSON содержит
только строковые и числовые значения, поэтому проверяются только
эти два типа, путем сравнения экземпляра перечисления JsonParser.
Event, полученного вызовом JsonParser.next(), со значениями Event.
VALUE_STRING и Event.VALUE_NUMBER, соответственно. Также проверяет­
ся чтение имен ключей JSON путем сравнения со значением Event.
KEY_NAME. Что дальше делать с парой ключ/значение, полученной из
строки JSON, зависит от целей, преследуемых приложением. В дан­
ном примере мы просто заполняем хэш-массив.
Выше мы видели только три типа значений, которые можно полу­
чить при чтении данных в формате JSON вызовом JsonParser.next().
В табл. 8.4 приводится список всех возможных значений.
Таблица 8.4. Список всех возможных значений, возвращаемых
JsonParser.next()
Значение экземпляра
Описание
перечисления Event

Event.START_OBJECT Прочитан начальный символ объекта JSON.

Event.END_OBJECT Прочитан конечный символ объекта JSON.

Event.KEY_NAME Прочитано имя свойства JSON.

Event.VALUE_STRING Прочитано строковое значение.

Event.VALUE_NUMBER Прочитано числовое значение.

Event.VALUE_TRUE Прочитано значение true.

Event.VALUE_FALSE Прочитано значение false.

Event.VALUE_NULL Прочитано значение null.

Event.VALUE_START_ARRAY Прочитан начальный символ массива.

Event.VALUE_END_ARRAY Прочитан конечный символ массива.


274 Глава 8. Прикладной интерфейс JSON Processing

Резюме
В этой главе мы познакомились с поддержкой обработки данных в
формате JSON-P, недавно ставшей частью спецификации Java EE.
Мы рассмотрели создание и чтение данных с использованием про­
стой объектной модели JSON-P, а затем переключились на более
произ­водительную и экономную (в отношении использования памя­
ти) потоковую модель, рассмотрев приемы манипуляции данными
JSON и с применением этой модели.
Глава 9.
Прикладной интерфейс
WebSocket

Традиционно веб-приложения следуют модели запрос/ответ. То есть,


браузер посылает HTTP-запрос серверу, а сервер возвращает HTTP-
ответ. Веб-сокеты (WebSocket) – это новая технология HTML5, обе­
спечивающая поддержку двустороннего, дуплексного обмена между
клиентом (обычно веб-браузером) и сервером. Иными словами, она
позволяет серверу посылать данные браузеру в масштабе реального
времени, не ожидая, пока поступит HTTP-запрос. Java EE 7 включает
все необходимое для разработки приложений на основе веб-сокетов, а
NetBeans включает некоторые удобства, делающие разработку таких
приложений Java EE еще проще.
В этой главе мы:
исследуем приемы использования веб-сокетов на типовых
примерах, включенных в состав NetBeans;
создадим приложение Java EE, использующее веб-сокеты.

Исследование приемов
использования веб-сокетов
на типовых примерах
В состав NetBeans входит множество примеров проектов, которые
можно использовать в качестве основы для своих проектов. В их чис­
ло входит проект приложения Echo, использующее веб-сокеты для
передачи некоторых данных со стороны сервера браузеру.
Прежде чем приступить к исследованиям, нужно создать типовой
проект. Для этого выберите пункт главного меню File | New Project
(Файл | Создать проект...) и затем в категории Samples | Java EE (При­
276 Глава 9. Прикладной интерфейс WebSocket

меры | Java EE) выберите проект Echo WebSocket (Java EE 7) (Эхо-


сервер WebSocket (Java EE 7)), как показано на рис. 9.1.

Рис. 9.1. Выбор примера проекта Echo WebSocket (Java EE 7)


(Эхо-сервер WebSocket (Java EE 7))
На следующем шаге мастера укажите папку для размещения про­
екта или примите значение по умолчанию (см. рис. 9.2).

Рис. 9.2. Выбор папки для размещения проекта


Исследование приемов использования веб-сокетов на типовых примерах 277

Щелкните на кнопке Finish (Готово), чтобы закончить создание


проекта.

Опробование примера приложения Echo


Прежде чем приступать к исследованию исходного кода, давайте
посмотрим, что делает приложение Echo. Запустить его можно как
обычно, щелкнув правой кнопкой на проекте и выбрав в контекстном
меню пункт Run (Выполнение), как показано на рис. 9.3.

Рис. 9.3. Запуск проекта


Спустя несколько секунд откроется окно браузера и автоматически
запустится приложение.
Текстовое поле ввода автоматически заполняется текстом «Hello
WebSocket!». Если щелкнуть на кнопке Press me (Нажми меня), этот
текст будет отправлен серверу веб-сокетов, который просто отправит
текст обратно клиенту. Результаты этой операции можно видеть на
рис. 9.4.

Рис. 9.4. Вид главной страницы приложения


278 Глава 9. Прикладной интерфейс WebSocket

Проект примера приложения состоит из двух файлов: index.html со­


держит функции на JavaScript, генерирующие события веб-сокетов, и
файл с классом Java, обрабатывающим эти события. Сначала мы рас­
смотрим класс Java, а затем перейдем к JavaScript на стороне клиента.

Программный код на Java


В следующем фрагменте приводится исходный код на Java, реализую­
щий конечную точку сервера веб-сокетов:
package org.glassfish.samples.websocket.echo;

import javax.websocket.OnMessage;
import javax.websocket.server.ServerEndpoint;

@ServerEndpoint("/echo")
public class EchoEndpoint {

@OnMessage
public String echo(String message) {
return message;
}
}

Класс, обрабатывающий WebSocket-запросы на стороне сервера


называют серверной конечной точкой. Как видите, для создания сер­
верной конечной точки WebSocket с использованием Java API требу­
ется совсем немного кода.
Превратить класс Java в серверную конечную точку можно с по­
мощью аннотации @ServerEndPoint. Ее атрибут value определяет уни-
версальный идентификатор ресурса (Uniform Resource Identifier,
URI) конечной точки, используя который клиенты (обычно клиент­
ские части веб-приложений) обращаются к конечной точке.
Всякий раз, когда клиент посылает запрос конечной точке, авто­
матически вызывается любой метод, отмеченный аннотацией @OnMes-
sage. Методы, отмеченные этой аннотацией, должны принимать стро­
ковый аргумент с содержимым сообщения, отправленного клиентом.
Текст сообщения может быть любым, однако, на практике чаще по­
сылаются данные в формате JSON. В данном примере приложения
методу echo() передается простая текстовая строка.
Значение, возвращаемое методом с аннотацией @OnMessage, отправ­
ляется обратно клиенту. На практике часто возвращаются строки в
формате JSON, но в данном примере возвращается та же самая стро­
ка, что была получена в виде аргумента.
Исследование приемов использования веб-сокетов на типовых примерах 279

Программный код на JavaScript


Другой файл в проекте приложения Echo, входящем в состав примеров
NetBeans – это файл HTML с программным кодом на JavaScript, ис­
пользуемым для взаимодействий с серверной конечной точкой на Java:
<html>
<head>
<meta http-equiv="content-type"
content="text/html; charset=ISO-8859-1">
</head>
<body>
<meta charset="utf-8">
<title>Web Socket JavaScript Echo Client</title>
<script language="javascript" type="text/javascript">
var wsUri = getRootUri() + "/websocket-echo/echo";

function getRootUri() {
return "ws://" +
(document.location.hostname == "" ?
"localhost" :
document.location.hostname) +
":" +
(document.location.port == "" ?
"8080" :
document.location.port);
}

function init() {
output = document.getElementById("output");
}

function send_echo() {
websocket = new WebSocket(wsUri);
websocket.onopen = function (evt) {
onOpen(evt)
};

websocket.onmessage = function (evt) {


onMessage(evt)
};

websocket.onerror = function (evt) {


onError(evt)
};
}

function onOpen(evt) {
writeToScreen("CONNECTED");
doSend(textID.value);
}

function onMessage(evt) {
280 Глава 9. Прикладной интерфейс WebSocket

writeToScreen("RECEIVED: " + evt.data);


}

function onError(evt) {
writeToScreen('<span style="color: red;">ERROR:</span> ' +
evt.data);
}

function doSend(message) {
writeToScreen("SENT: " + message);
websocket.send(message);
}

function writeToScreen(message) {
var pre = document.createElement("p");
pre.style.wordWrap = "break-word";
pre.innerHTML = message;
//alert(output);
output.appendChild(pre);
}

window.addEventListener("load", init, false);


</script>

<h2 style="text-align: center;">WebSocket Echo Client</h2>

<br></br>

<div style="text-align: center;">


<form action="">
<input onclick="send_echo()" value="Press me" type="button">
<input id="textID" name="message" value="Hello WebSocket!"
type="text"><br>
</form>
</div>
<div id="output"></div>
</body>
</html>

Особый интерес здесь представляет встроенный код на JavaScript


между тегами <script>. Обратите внимание на функцию send_echo(),
которая создает новый JavaScript-объект веб-сокета и затем связы­
вает обработчики событий onopen, onmessage и onerror этого объекта
с функциями onOpen(), onMessage() и onError(). Функция onOpen()
будет автоматически вызываться при открытии нового WebSocket-
соединения, функция onMessage() будет вызываться при получении
сообщения от сервера и функция onError()будет вызываться при
каждой ошибке в веб-сокете.
Приложение Echo просто обновляет элемент div с атрибутом
id="output", записывая в него сообщение получено от сервера. Де­

Powered by TCPDF (www.tcpdf.org)


Создание собственных приложений с веб-сокетами 281

лается это в функции writeToScreen(), которая вызывается из onMes-


sage() при получении сообщения от сервера.
Пример приложения Echo может служить отличным пособием по
созданию собственных приложений, использующих Java WebSocket
API. В следующем разделе мы напишем собственное приложение, без
стеснения заимствуя из примера Echo все, что только возможно.

Создание собственных
приложений с веб-сокетами
В предыдущем разделе был показан пример приложения на основе
веб-сокетов, входящий в состав NetBeans, который можно исполь­
зовать как основу для своих проектов. В этом разделе мы создадим
такое веб-приложение, включающее серверную конечную точку
WebSocket, с помощью которой поля формы будут заполняться зна­
чениями по умолчанию.
Чтобы создать проект, выберите пункт главного меню File | New
Project (Файл | Создать проект...) и затем в категории Java Web вы­
берите тип проекта Web Application (Веб-приложение), как показано
на рис. 9.5.

Рис. 9.5. Создание проекта веб-приложения,


включающего серверную конечную точку WebSocket
282 Глава 9. Прикладной интерфейс WebSocket

Затем определите имя проекта и его местоположение (см. рис. 9.6).

Рис. 9.6. Выбор имени проекта и папки для его размещения


Прикладной Java WebSocket API впервые появился в Java EE  7,
поэтому для разработки веб-приложений, использующих веб-сокеты,
следует выбрать эту версию Java EE (см. рис. 9.7). Мастер нового про­
екта предлагает на этом шаге вполне разумные значения по умолча­
нию, поэтому их можно оставить «как есть».

Рис. 9.7. Достаточно разумным будет оставить значения по умолчанию


Создание собственных приложений с веб-сокетами 283

В данном примере пользовательский интерфейс будет построен с


применением JSF. Поэтому в списке Frameworks (Платформы) выбе­
рите JavaServer Faces (Приложение JavaServer Faces), как показано
на рис. 9.8.

Рис. 9.8. Выбор платформы JavaServer Faces


Теперь можно приступать к разработке веб-приложения.

Создание пользовательского интерфейса


Прежде чем приступать к коду, связанному с веб-сокетами, сначала
создадим пользовательский с применением возможностей JSF 2.2 и
HTML5-подобной разметки, как описывалось в главе 2, «Разработка
веб-приложений с использованием JavaServer Faces 2.2».
Когда в проект NetBeans добавляется поддержка фреймворка JSF,
автоматически создается файл index.xhtml, имеющий следующее со­
держимое:
<?xml version='1.0' encoding='UTF-8' ?>
<!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"
xmlns:h="http://xmlns.jcp.org/jsf/html">
<h:head>
<title>Facelet Title</title>
</h:head>
<h:body>
284 Глава 9. Прикладной интерфейс WebSocket

Hello from Facelets


</h:body>
</html>

Как видите, в сгенерированной разметке используются теги JSF.


Мы внесем в нее небольшие изменения, задействовав HTML5-
подобные теги, как показано ниже:
<?xml version='1.0' encoding='UTF-8' ?>
<!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"
xmlns:jsf="http://xmlns.jcp.org/jsf">
<head jsf:id="head">
<title>Facelet Title</title>
<head>
<body jsf:id="body">
Hello from Facelets
</body>
</html>

Самой важной здесь является замена пространства имен


xmlns:h=http://xmlns.jcp.org/jsf/html на xhmlns:jsf=http://xmlns.
jsp.org/jsf. Первое из них определяет теги JSF (которые мы решили
не использовать в своем приложении), а второе определяет атрибуты
JSF (которые мы будем использовать дальше). Затем были заменены
JSF-теги <h:head> и <h:body> их стандартными HTML-аналогами, в
которые был добавлен атрибут jsf:id. Как рассказывалось в главе 2,
«Разработка веб-приложений с использованием JavaServer Faces 2.2»,
чтобы заставить JSF интерпретировать теги HTML, нужно добавить
хотя бы по одному атрибуту JSFв теги.
Теперь нужно добавить форму с парой простых полей ввода. Далее
мы будем использовать Java WebSocket API для заполнения этих по­
лей значениями по умолчанию.
После внесения изменений, описанных выше, разметка приобрела
следующий вид:
<?xml version='1.0' encoding='UTF-8' ?>
<!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"
xmlns:jsf="http://xmlns.jcp.org/jsf">
<head jsf:id="head">
<title>WebSocket and Java EE</title>
</head>
<body jsf:id="body">
<form method="POST" jsf:prependId="false">
<table>
Создание собственных приложений с веб-сокетами 285
<tr>
<td>First Name</td>
<td>
<input type="text" jsf:id="firstName"
jsf:value="#{person.firstName}"/>
</td>
</tr>
<tr>
<td>Last Name</td>
<td>
<input type="text" jsf:id="lastName"
jsf:value="#{person.lastName}"/>
</td>
</tr>
<tr>
<td></td>
<td>
<input type="submit" value="Submit"
jsf:action="confirmation"/>
</td>
</tr>
</table>
</form>
</body>
</html>

Мы просто добавили поля ввода в разметку и использовали атри­


буты JSF, чтобы HTML-теги интерпретировались как их JSF-аналоги.
Обратите внимание, что поля ввода в разметке связаны со свой­
ствами именованного компонента CDI с именем person. Компонент
Person имеет следующее определение:
package com.ensode.websocket;

import javax.enterprise.context.RequestScoped;
import javax.inject.Named;

@Named
@RequestScoped
public class Person {

private String firstName;


private String lastName;

public String getFirstName() {


return firstName;
}

public void setFirstName(String firstName) {


this.firstName = firstName;
286 Глава 9. Прикладной интерфейс WebSocket

public String getLastName() {


return lastName;
}

public void setLastName(String lastName) {


this.lastName = lastName;
}
}

Как видите, компонент Person – это обычный именованный компо­


нент CDI с контекстом запроса.
Теперь, когда у нас имеет простое JSF-приложение, использующее
HTML5-подобную разметку, можно приступать к реализации сервер­
ной части с применением Java WebSocket API.

Создание серверной конечной точки


веб-сокета
Закончив работу на JSF-приложением, можно приступить к соз­
данию серверной конечной точки веб-сокета. Для этого выберите в
главном меню пункт File | New File (Файл | Создать файл) и в катего­
рии Web (Веб) выберите тип WebSocket Endpoint (Конечная точка
WebSocket), как показано на рис. 9.9.

Рис. 9.9. Выбор типа WebSocket Endpoint (Конечная точка WebSocket)


Создание собственных приложений с веб-сокетами 287

Дайте конечной точке имя и укажите значение в поле WebSocket


URI (см. рис. 9.10).

Рис. 9.10. Выбор имени и URI конечной точки


После щелчка на кнопке Finish (Готово), NetBeans сгенерирует ко­
нечную точку веб-сокета:
package com.ensode.websocket;

import javax.websocket.OnMessage;
import javax.websocket.server.ServerEndpoint;

@ServerEndpoint("/defaultdataendpoint")
public class DefaultDataEndpoint {

@OnMessage
public String onMessage(String message) {
return null;
}
}

Обратите внимание, что значение атрибута в аннотации


@ServerEndpoint совпадает со значением, введенным в поле WebSocket
URI (см. рис. 9.10) при создании конечной точки. Кроме того,
NetBeans сгенерировала заготовку метода с аннотацией @OnMessage.
Мы изменим этот метод так, чтобы он возвращал строку JSON, ко­
торая будет анализироваться на стороне клиента. Ниже показан уже
измененный метод onMessage():
288 Глава 9. Прикладной интерфейс WebSocket

@OnMessage
public String onMessage(String message) {
String retVal;

if (message.equals("get_defaults")) {
retVal = new StringBuilder("{").
append("\"firstName\":\"Auto\",").
append("\"lastName\":\"Generated\"").
append("}").toString();
} else {
retVal = "";
}

return retVal;
}

В этом примере строка JSON генерируется вручную, но имейте в


виду, что генерировать данные в формате JSON можно с помощью
Java JSON-P API. За подробностями обращайтесь к главе 8, «При-
кладной интерфейс JSON Processing».

В параметре message методу onMessage() передается текст сообще­


ния, полученного от клиента. Если onMessage() получит строку get_
defaults, он сгенерирует строку JSON со значениями по умолчанию,
которые должны использоваться для заполнения полей формы.

Обычно для передачи сообщений со стороны клиентов также ис-


пользуется формат JSON. Однако в данном примере используется
произвольная строка.

Строку JSON, сгенерированную сервером, нужно проанализиро­


вать на стороне клиента в коде на JavaScript. Чтобы реализовать этот
последний фрагмент мозаики, добавим немного кода на JavaScript в
разметку JSF.

Реализация поддержки веб-сокетов


на стороне клиента
Теперь добавим код на JavaScript в разметку, чтобы обеспечить взаи­
модействие с серверной конечной точкой:
<?xml version='1.0' encoding='UTF-8' ?>
<!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"
Создание собственных приложений с веб-сокетами 289
xmlns:jsf="http://xmlns.jcp.org/jsf">
<head jsf:id="head">
<title>WebSocket and Java EE</title>
<script language="javascript" type="text/javascript">
var wsUri = getRootUri() +
"/WebSocketJavaEE/defaultdataendpoint";

function getRootUri() {
return "ws://" + (document.location.hostname == "" ?
"localhost" :
document.location.hostname) + ":" +
(document.location.port == "" ?
"8080" : document.location.port);
}

function init() {
websocket = new WebSocket(wsUri);
websocket.onopen = function (evt) {
onOpen(evt)
};
websocket.onmessage = function (evt) {
onMessage(evt)
};
websocket.onerror = function (evt) {
onError(evt)
};
}

function onOpen(evt) {
console.log("CONNECTED");
}

function onMessage(evt) {
console.log("RECEIVED: " + evt.data);

var json = JSON.parse(evt.data);

document.getElementById('firstName').value =
json.firstName;
document.getElementById('lastName').value =
json.lastName;
}

function onError(evt) {
console.log('ERROR: ' + evt.data);
}

function doSend(message) {
console.log("SENT: " + message);
websocket.send(message);
}

window.addEventListener("load", init, false);


290 Глава 9. Прикладной интерфейс WebSocket

</script>
</head>
<body jsf:id="body">
<form method="POST" jsf:prependId="false">
<input type="button" value="Get Defaults"
onclick="doSend('get_defaults')"/>
<table>
<tr>
<td>First Name</td>
<td>
<input type="text" jsf:id="firstName"
jsf:value="#{person.firstName}"/>
</td>
</tr>
<tr>
<td>Last Name</td>
<td>
<input type="text" jsf:id="lastName"
jsf:value="#{person.lastName}"/>
</td>
</tr>
<tr>
<td></td>
<td>
<input type="submit" value="Submit"
jsf:action="confirmation"/>
</td>
</tr>
</table>
</form>
</body>
</html>

Мы взяли за основу код на JavaScript из примера приложения Echo,


входящего в состав NetBeans и обсуждавшегося в начале этой главы.
Прежде всего мы изменили значение переменной wsUri, чтобы оно
совпадало с URI серверной конечной точки. Идентификаторы URI
конечных точек веб-сокетов, которые нам доведется создавать, всегда
будут состоять из корня контекста приложения, за которым следу­
ет значение атрибута аннотации @ServerEndpoint (в данном примере,
/defaultdataendpoint).

Корень контекста приложения Java EE является частью URL, который


следует сразу за номером порта; по умолчанию, корень контекс­та
совпадает с именем файла WAR.
Например, наше приложение имеет URL http://localhost:8080/,
поэтому корнем контекста приложения будет строка
WebSocketJavaEE.
Резюме 291

В оригинальном примере приложения Echo новое соединение с


веб-сокетом создается при каждом щелчке на кнопке Press me (Наж­
ми меня). Мы изменили код на JavaScript так, чтобы соединение
устанавливалось только однажды, в момент первой загрузки страни­
цы. Все необходимые для этого вызовы функций были добавлены в
функцию init(). В ней выполняется связывание некоторых функ­
ций с событиями WebSocket. Функция onOpen() вызывается, когда
устанавливается соединение с серверной конечной точкой. Функция
onMessage() вызывается, когда поступает сообщение от конечной точ­
ки. И функция onError() вызывается, когда в процессе взаимодей­
ствий с конечной точкой возникает какая-либо ошибка.
Функции onOpen() и onError() были немного изменены, если срав­
нивать их с аналогичными функциями в примере приложения Echo.
В данном случае они просто выводят сообщение в журнал браузера.

Чтобы открыть консоль, в большинстве браузеров можно нажать


клавишу F12 и затем выбрать вкладку Console (Консоль).

Функция onMessage() выполняет парсинг строки JSON, получен­


ной от серверной конечно точки, и заполняет поля формы соответ­
ствующими значениями.
Также в разметку была добавлена кнопка Get Defaults (Получить
значения по умолчанию), которая вызывает функцию doSend() и пе­
редает ей строку "get_defaults". Функция doSend() в свою очередь
отправляет полученную строку серверной конечной точке с помощью
функции send() объекта WebSocket. Получив такую строку, конечная
серверная точка возвращает клиенту строку в формате JSON со зна­
чениями по умолчанию.
На рис. 9.11 изображено наше приложение в действии.
На данном рисунке показано, что происходит после щелчка на
кнопке Get Defaults (Получить значения по умолчанию). Текстовые
поля заполняются значениями, извлеченными из строки в формате
JSON, которая была получена с сервера. В нижней части рис. 9.11
можно видеть, как выглядит вывод в журнал браузера.

Резюме
В этой главе мы посмотрели, как разрабатывать приложения Java EE
с использованием нового протокола WebSocket. Сначала мы иссле­
довали пример приложения, входящий в состав NetBeans, изучили
292 Глава 9. Прикладной интерфейс WebSocket

его исходный код, чтобы лучше понять, как можно написать соб­
ственное приложение. Затем мы использовали полученные знания
для создания собственного приложения, использующего новый Java
WebSocket API, введенный в Java EE 7, воспользовавшись всеми пре­
имуществами мастеров NetBeans, оказывающими помощь в создании
приложений.

Рис. 9.11. Приложение в действии


Глава 10.
Веб-службы RESTful
на основе JAX-RS

Передача репрезентативного состояния (Representational State


Transfer, REST) – это архитектурный стиль, в соответствии с кото­
рым веб-службы рассматриваются как ресурсы и могут идентифици­
роваться унифицированными идентификаторами ресурсов (Uniform
Resource Identifiers, URI).
Веб-службы, разработанные в стиле REST, известны как веб-
службы RESTful. В Java EE 6 поддержка веб-служб RESTful была ре­
ализована посредством добавления Java API для веб-служб RESTful
(Java API for RESTful Web Services, JAX-RS). JAX-RS некоторое вре­
мя был доступен как автономный API и стал частью спецификации
Java EE в версии 6.
Одним из наиболее распространенных случаев применения веб-
служб RESTful является их использование в качестве интерфейса к
базам данных, то есть клиенты веб-службы RESTful могут использо­
вать ее для выполнения операций CRUD (Create (Создание), Read
(Чтение), Update (Изменение), Delete (Удаление)) в базе данных.
Поскольку упомянутый случай встречается очень часто, в NetBeans
была включена превосходная его поддержка, позволяющая создавать
веб-службы RESTful, играющие роль интерфейса к базе данных, все­
го несколькими щелчками мыши.
Вот некоторые из тем, которые будут затронуты в этой главе:
создание веб-служб RESTful на основе существующей базы
данных;
тестирование веб-служб RESTful с помощью инструментов
NetBeans;
создание Java-клиента веб-службы RESTful;
создание JavaScript-клиента веб-службы RESTful.
294 Глава 10. Веб-службы RESTful на основе JAX-RS

Создание веб-службы RESTful


на основе существующей базы
данных
Чтобы создать веб-службу RESTful на основе существующей базы
данных, в проекте веб-приложения достаточно просто выбрать в
главном меню пункт File | New File (Файл | Создать файл) и затем в
категории Web Services (Веб-службы) выбрать тип файлов RESTful
Web Services From Database (RESTful веб-службы из базы данных),
как показано на рис. 10.1.

Рис. 10.1. Выбор типа файлов RESTful Web Services From Database
(RESTful веб-службы из базы данных)
На следующем шаге нужно выбрать источник данных и одну или
более таблиц для создания веб-службы. В нашем примере мы созда­
дим веб-службу для таблицы CUSTOMER в примере базы данных, входя­
щем в состав NetBeans (см. рис. 10.2).
Теперь нужно указать имя пакета для размещения программного
кода веб-службы (см. рис. 10.3).
Теперь нужно выбрать Resource Package (Пакет ресурса) или про­
сто принять значение service, предлагаемое по умолчанию. Правиль­
Создание веб-службы RESTful на основе существующей базы данных 295

нее будет ввести имя пакета, которое следует стандартному соглаше­


нию об именовании пакетов (см. рис. 10.4).

Рис. 10.2. Выбор источника данных и таблицы

Рис. 10.3. Ввод имени пакета


296 Глава 10. Веб-службы RESTful на основе JAX-RS

Рис. 10.4. Ввод имени пакета ресурса


После щелчка на кнопке Finish (Готово) NetBeans сгенерирует код
веб-службы.

Анализ сгенерированного кода


Мастер, обсуждавшийся в предыдущем разделе, создаст сущность
JPA для каждой выбранной таблицы плюс класс AbstractFacade и
класс фасада для каждой сгенерированной сущности JPA. Программ­
ный код генерируется в соответствии с шаблоном проектирования
Фасад (Facade). По сути, каждый класс-фасад является оберткой для
кода JPA.

Дополнительную информацию о шаблоне проектирования Фасад


можно найти по адресу: https://ru.wikipedia.org/wiki/Фасад_(ша-
блон_проектирования).

Сгенерированные классы фасада развертываются как веб-службы


RESTful и доступны как таковые (см. рис. 10.5).
Класс AbstractFacade служит родительским классом для всех дру­
гих классов фасада:
Создание веб-службы RESTful на основе существующей базы данных 297
package com.ensode.netbeansbook.jaxrs.service;

import java.util.List;
import javax.persistence.EntityManager;

public abstract class AbstractFacade<T> {


private Class<T> entityClass;

public AbstractFacade(Class<T> entityClass) {


this.entityClass = entityClass;
}

protected abstract EntityManager getEntityManager();

public void create(T entity) {


getEntityManager().persist(entity);
}

public void edit(T entity) {


getEntityManager().merge(entity);
}

public void remove(T entity) {


getEntityManager().remove(getEntityManager().merge(entity));
}

public T find(Object id) {


return getEntityManager().find(entityClass, id);
}

public List<T> findAll() {


javax.persistence.criteria.CriteriaQuery cq =
getEntityManager().getCriteriaBuilder().createQuery();
cq.select(cq.from(entityClass));
return getEntityManager().createQuery(cq).getResultList();
}

public List<T> findRange(int[] range) {


javax.persistence.criteria.CriteriaQuery cq =
getEntityManager().getCriteriaBuilder().createQuery();
cq.select(cq.from(entityClass));
javax.persistence.Query q =
getEntityManager().createQuery(cq);
q.setMaxResults(range[1] - range[0] + 1);
q.setFirstResult(range[0]);
return q.getResultList();
}

public int count() {


javax.persistence.criteria.CriteriaQuery cq =
298 Глава 10. Веб-службы RESTful на основе JAX-RS

getEntityManager().getCriteriaBuilder().createQuery();
javax.persistence.criteria.Root<T> rt = cq.from(entityClass);
cq.select(getEntityManager().getCriteriaBuilder().count(rt));
javax.persistence.Query q =
getEntityManager().createQuery(cq);
return ((Long) q.getSingleResult()).intValue();
}
}

Как видите, класс AbstractFacade имеет переменную entityClass


тип которой определяется через обобщения (generics) в дочерних
классах. В нем также имеются методы создания, изменения, удале­
ния, поиска и подсчета сущностей. Реализации этих методов содер­
жат стандартный код JPA и к настоящему моменту должны быть вам
знакомы.

Рис. 10.5. Сгенерированные классы фасада


Как отмечалось выше, мастер генерирует фасад для каждой сгене­
рированной сущности JPA. В этом примере была выбрана единствен­
ная таблица (CUSTOMER), поэтому создана всего одна сущность JPA.
Класс фасада для этой сущности JPA называется CustomerFacadeREST:
package com.ensode.netbeansbook.jaxrs.service;

import com.ensode.netbeansbook.jaxrs.Customer;
import java.util.List;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.ws.rs.Consumes;
Создание веб-службы RESTful на основе существующей базы данных 299
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;

@Stateless
@Path("com.ensode.netbeansbook.jaxrs.customer")
public class CustomerFacadeREST extends AbstractFacade<Customer> {
@PersistenceContext(unitName = "jaxrxPU")
private EntityManager em;

public CustomerFacadeREST() {
super(Customer.class);
}

@POST
@Override
@Consumes({"application/xml", "application/json"})
public void create(Customer entity) {
super.create(entity);
}

@PUT
@Path("{id}")
@Consumes({"application/xml", "application/json"})
public void edit(@PathParam("id") Integer id, Customer entity) {
super.edit(entity);
}

@DELETE
@Path("{id}")
public void remove(@PathParam("id") Integer id) {
super.remove(super.find(id));
}

@GET
@Path("{id}")
@Produces({"application/xml", "application/json"})
public Customer find(@PathParam("id") Integer id) {
return super.find(id);
}

@GET
@Override
@Produces({"application/xml", "application/json"})
public List<Customer> findAll() {
return super.findAll();
300 Глава 10. Веб-службы RESTful на основе JAX-RS

@GET
@Path("{from}/{to}")
@Produces({"application/xml", "application/json"})
public List<Customer> findRange(@PathParam("from") Integer from,
@PathParam("to") Integer to) {
return super.findRange(new int[]{from, to});
}

@GET
@Path("count")
@Produces("text/plain")
public String countREST() {
return String.valueOf(super.count());
}

@Override
protected EntityManager getEntityManager() {
return em;
}
}

Как можно заключить по наличию аннотации @Stateless, сгене­


рированный класс является сеансовым компонентом без сохранения
состояния. Аннотация @Path определяет унифицированный иденти­
фикатор ресурса (Uniform Resource Identifier, URI), запросы к кото­
рому будут обслуживаться классом. Как видите, некоторые методы
класса декорированы аннотациями @POST, @PUT, @DELETE и @GET. Эти
методы будут автоматически вызываться веб-службой для обработки
соответствующих HTTP-запросов. Обратите внимание, что некото­
рые методы также отмечены аннотацией @Path. Причина в том, что
некоторые из них требуют передачи входного параметра. Например,
чтобы удалить запись из таблицы CUSTOMER, нужно передать методу
первичный ключ соответствующей записи. Значение атрибута анно­
тации @Path должно иметь формат "{varName}", где текст в фигурных
скобках известен как параметр пути (path parameter). Обратите вни­
мание, что метод имеет соответствующие параметры, декорирован­
ные аннотацией @PathParam.

Тестирование веб-службы RESTful


Развернув проект, можно удостовериться, что вместе с ним благопо­
лучно была развернута и веб-служба, для чего следует распахнуть
узел RESTful Web Services (Веб-службы RESTful) в проекте, щел­
Тестирование веб-службы RESTful 301

кнуть правой кнопкой мыши на веб-службе RESTful и в контекстном


меню выбрать пункт Test Resource Uri (Протестировать Uri ресурс),
как показано на рис. 10.6.

Рис. 10.6. Тестирование URI веб-службы RESTful


В результате этих действий будет вызван метод findAll() веб-
службы (поскольку это единственный метод, не требующий параме­
тров) и сгенерированный ответ в формате XML автоматически от­
кроется в браузере (см. рис. 10.7).
Ответ в формате XML, возвращаемый веб-службой, содержит дан­
ные из таблицы CUSTOMER, преобразованные в формат XML.
Также просто можно протестировать и другие методы веб-службы,
щелкнув правой кнопкой мыши на проекте и выбирав в контекстном
меню пункт Test RESTful Web Services (Протестировать веб-службы
RESTful), как показано на рис. 10.8.
На этот раз на экране появится диалог, как показано на рис. 10.9.
В большинстве случаев можно принять клиента по умолчанию, вы­
брав переключатель Web Test Client (Тестовый веб-клиент), посколь­
ку он способен работать с основными браузерами в большинстве опе­
рационных систем.
После щелчка на кнопке OK в веб-браузере автоматически откро­
ется страница, как показано на рис. 10.10.
302 Глава 10. Веб-службы RESTful на основе JAX-RS

Рис. 10.7. Ответ в формате XML

Рис. 10.8. Тестирование других методов веб-службы


Распахните любой узел слева и щелкните на веб-службе. Выбери­
те GET (application/json) в раскрывающемся списке Choose method
to test (Выберите метод для тестирования) и щелкните на кнопке
Test (Протестировать). В результате веб-службе будет отправлен
HTTP-запрос GET, на который она вернет ответ в формате JSON (см.
рис. 10.11).
Теперь на странице отображается представление данных из табли­
цы CUSTOMER в формате JSON.
Тестирование веб-службы RESTful 303

Рис. 10.9. Диалог, появляющийся при попытке тестирования веб-служб

Рис. 10.10. Страница тестирования веб-служб


Наша веб-служба RESTful может возвращать или принимать до­
кументы в формате XML либо JSON. Убедиться в этом можно, по­
смотрев на значения в аннотациях @Produces и @Consumes.
Если потребуется получить результаты работы метода findAll()
в формате XML, выберите в раскрывающемся списке пункт GET
(application/xml) и щелкните на кнопке Test (Протестировать). На
этот раз в ответ на HTTP-запрос GET веб-служба вернет данные в
формате XML, как показано на рис. 10.12.
Аналогично можно добавить одну запись, выбрав HTTP-метод
POST в раскрывающемся списке и передав данные в формате XML
или JSON. Например, чтобы проверить передачу данных в форма­
те JSON, выберите пункт POST(application/json), как показано на
рис. 10.13, введите строку в формате JSON с информацией о новом
заказчике и щелкните на кнопке Test (Протестировать).
304 Глава 10. Веб-службы RESTful на основе JAX-RS

Рис. 10.11. Ответ веб-службы в формате JSON

Рис. 10.12. Ответ веб-службы в формате XML


Тестирование веб-службы RESTful 305

Рис. 10.13.Тестирование передачи данных в формате JSON


Теперь, убедившись, что веб-служба RESTful успешно развернута
и действует, можно приступать к реализации клиентского приложе­
ния, которое будет пользоваться веб-службой. Но прежде давайте
рассмотрим класс ApplicationConfig, который сгенерировала среда
разработки NetBeans (см. рис. 10.14).

Рис. 10.14. Местоположение класса ApplicationConfig


Ниже приводится исходный код этого класса:
306 Глава 10. Веб-службы RESTful на основе JAX-RS

package com.ensode.netbeansbook.jaxrs.service;

import java.util.Set;
import javax.ws.rs.core.Application;

@javax.ws.rs.ApplicationPath("webresources")
public class ApplicationConfig extends Application {

@Override
public Set<Class<?>> getClasses() {
Set<Class<?>> resources = new java.util.HashSet<>();
addRestResourceClasses(resources);
return resources;
}

/**
* Не изменяйте метод addRestResourceClasses().
* Он автоматически заполняется ресурсами
* объявленными в проекте.
* Если потребуется, закомментируйте вызов
* этого метода в getClasses().
*/
private void addRestResourceClasses(Set<Class<?>> resources) {
resources.add(
com.ensode.netbeansbook.jaxrs.service.CustomerFacadeREST.class
);
resources.add(
com.ensode.netbeansbook.jaxrs.service.DiscountCodeFacadeREST.class
);
resources.add(
com.ensode.netbeansbook.jaxrs.service.MicroMarketFacadeREST.class
);
}
}

Назначение этого класса – настройка JAX-RS. Единственное


требование к нему: класс должен наследовать javax.ws.rs.core.
Application и должен быть декорирован аннотацией @javax.ws.rs.
ApplicationPath. Эта аннотация определяет базовый URI для всех пу­
тей, определяемых аннотациями @Path в классах веб-служб RESTful.
По умолчанию NetBeans использует для всех веб-служб RESTful
путь webresources.
NetBeans переопределяет метод getClasses() базового класса
javax.ww.rs.core.Application, чтобы он возвращал множество клас­
сов, содержащих все веб-службы RESTful в приложении (классы,
декорированные аннотацией @Path). NetBeans автоматически добав­
ляет все веб-службы RESTful в тело метода addRestResourceClasses()
и вызывает его из сгенерированного метода getClasses().
Создание Java-клиента веб-службы RESTful 307

Создание Java-клиента веб-


службы RESTful
В NetBeans имеется мастер, способный автоматически сгенерировать
клиентский код на Java, вызывающий методы веб-службы RESTful
через соответствующие HTTP-запросы.
Чтобы сгенерировать этот код, нужно в главном меню выбрать
пункт File | New File (Файл | Создать файл) и затем тип файлов
RESTful Java Client (Клиент Java RESTful) в категории Web Services
(Веб-службы), как показано на рис. 10.15.

Рис. 10.15. Выбор типа файлов RESTful Java Client


(Клиент Java RESTful)
На следующем шаге нужно ввести имя класса и пакета для разме­
щения клиента JAX-RS (см. рис. 10.16).

Сервер приложений GlassFish включает реализацию JAX-RS под на-


званием Jersey. Так как в своих примерах мы используем сервер
GlassFish, входящий в состав NetBeans, NetBeans предлагает зна-
чение по умолчанию NewJerseyClient для поля Class Name (Имя
класса). Это имя класса по умолчанию нас вполне устраивает в дан-
ном примере.
308 Глава 10. Веб-службы RESTful на основе JAX-RS

Рис. 10.16. Настройка параметров клиента


Теперь следует выбрать веб-службу RESTful для использования
клиентом. В нашем случае нужно установить переключатель From
Project (Из проекта) в группе Select the REST resource (Выберите
REST-ресурс) и щелкнуть на кнопке Browse... (Обзор...), как пока­
зано на рис. 10.16.
В открывшемся диалоге достаточно просто выбрать веб-службу,
созданную нами ранее, как показано на рис. 10.17.
После этого NetBeans сгенерирует следующий код:
/*
* Чтобы изменить этот заголовок лицензии, выберите раздел
* License Headers (Заголовки лицензий) в свойствах проекта.
* Чтобы изменить этот шаблон файла, выберите Tools | Templates
* (Сервис | Шаблоны) и откройте шаблон в редакторе.
*/
package com.ensode.netbeansbook.jaxrsclient;

import javax.ws.rs.ClientErrorException;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.WebTarget;

/**
* Клиент Jersey REST, сгенерированный для
* REST resource:CustomerFacadeREST
Создание Java-клиента веб-службы RESTful 309

Рис. 10.17. Выбор веб-службы RESTful


* [com.ensode.netbeansbook.jaxrs.customer]<br>
* ПОРЯДОК ИСПОЛЬЗОВАНИЯ:
* <pre>
* NewJerseyClient client = new NewJerseyClient();
* Object response = client.XXX(...);
* // обработка ответа
* client.close();
* </pre>
*
*/
public class NewJerseyClient {
private WebTarget webTarget;
private Client client;
private static final String BASE_URI =
"http://localhost:8080/jaxrx/webresources";
public NewJerseyClient() {
client = javax.ws.rs.client.ClientBuilder.newClient();
webTarget = client.target(BASE_URI).path(
"com.ensode.netbeansbook.jaxrs.customer");
}
public String countREST() throws ClientErrorException {
310 Глава 10. Веб-службы RESTful на основе JAX-RS

WebTarget resource = webTarget;


resource = resource.path("count");
return resource.request(
javax.ws.rs.core.MediaType.TEXT_PLAIN).get(String.class);
}
public void edit_XML(Object requestEntity, String id)
throws ClientErrorException {
webTarget.path(
java.text.MessageFormat.format("{0}",
new Object[] {id})).request(
javax.ws.rs.core.MediaType.APPLICATION_XML)
.put(javax.ws.rs.client.Entity.entity(requestEntity,
javax.ws.rs.core.MediaType.APPLICATION_XML));
}
public void edit_JSON(Object requestEntity, String id)
throws ClientErrorException {
webTarget.path(
java.text.MessageFormat.format("{0}", new Object[]
{id})).request(
javax.ws.rs.core.MediaType.APPLICATION_JSON)
.put(javax.ws.rs.client.Entity.entity(requestEntity,
javax.ws.rs.core.MediaType.APPLICATION_JSON));
}
public <T> T find_XML(Class<T> responseType, String id)
throws ClientErrorException {
WebTarget resource = webTarget;
resource = resource.path(java.text.MessageFormat.format(
"{0}", new Object[]{id}));
return resource.request(
javax.ws.rs.core.MediaType.APPLICATION_XML).
get(responseType);
}
public <T> T find_JSON(Class<T> responseType, String id)
throws ClientErrorException {
WebTarget resource = webTarget;
resource = resource.path(java.text.MessageFormat.format("{0}",
new Object[]{id}));
return resource.request(
javax.ws.rs.core.MediaType.APPLICATION_JSON).
get(responseType);
}
public <T> T findRange_XML(Class<T> responseType, String from,
String to) throws ClientErrorException {
WebTarget resource = webTarget;
resource = resource.path(java.text.MessageFormat.format(
"{0}/{1}",
new Object[]{from, to}));
return resource.request(
javax.ws.rs.core.MediaType.APPLICATION_XML).
get(responseType);
Создание Java-клиента веб-службы RESTful 311
}
public <T> T findRange_JSON(Class<T> responseType, String from,
String to) throws ClientErrorException {
WebTarget resource = webTarget;
resource = resource.path(
java.text.MessageFormat.format("{0}/{1}",
new Object[]{from, to}));
return resource.request(
javax.ws.rs.core.MediaType.APPLICATION_JSON).
get(responseType);
}
public void create_XML(Object requestEntity)
throws ClientErrorException {
webTarget.request(javax.ws.rs.core.MediaType.APPLICATION_XML).
post(javax.ws.rs.client.Entity.entity(requestEntity,
javax.ws.rs.core.MediaType.APPLICATION_XML));
}
public void create_JSON(Object requestEntity)
throws ClientErrorException {
webTarget.request(javax.ws.rs.core.MediaType.APPLICATION_JSON).
post(javax.ws.rs.client.Entity.entity(requestEntity,
javax.ws.rs.core.MediaType.APPLICATION_JSON));
}
public <T> T findAll_XML(Class<T> responseType)
throws ClientErrorException {
WebTarget resource = webTarget;
Return resource.request(
javax.ws.rs.core.MediaType.APPLICATION_XML).
get(responseType);
}
public <T> T findAll_JSON(Class<T> responseType)
throws ClientErrorException {
WebTarget resource = webTarget;
return resource.request(
javax.ws.rs.core.MediaType.APPLICATION_JSON).
get(responseType);
}
public void remove(String id) throws ClientErrorException {
webTarget.path(java.text.MessageFormat.format("{0}",
new Object[]{id})).request().delete();
}
public void close() {
client.close();
}
}

Сгенерированный код Java-клиента использует клиентский JAX-RS


API, появившийся в версии JAX-RS 2.0.
Как видите, NetBeans cгенерировала методы-обертки для всех ме­
тодов нашей веб-службы RESTful. NetBeans создает две версии для
312 Глава 10. Веб-службы RESTful на основе JAX-RS

каждого метода: одна создает или принимает документ XML, а другая


документ JSON. Каждый метод использует обобщения, чтобы можно
было определить типы значений, возвращаемых этими методами во
время выполнения.
Самый простой подход к применению этих методов заключа­
ется в использовании строк, например метод find_JSON(Class<T>
responseType, String id) можно вызвать, как показано ниже:
public class Main {
public static void main(String[] args) {
NewJerseyClient newJerseyClient = new NewJerseyClient();
String response = newJerseyClient.find_JSON(String.class, "1");

System.out.println("response is: " + response);

newJerseyClient.close();
}
}

Предыдущий вызов вернет строку с JSON-представлением значе­


ний из записи в таблице, где ID = 1. Выполнив предыдущий код, мы
должны увидеть следующий вывод:
response is: {"addressline1":"111 E. Las Olivas Blvd","addressline2":
"Suite 51","city":"Fort Lauderdale","creditLimit" :100000,
"customerId":1,"discountCode":{"discountCode":"N","rate":
0.00 },"email":"jumboeagle@example.com","fax":"305-555-0189",
"name":"Jumbo Eagle Corp","phone":"305-555-0188","state":"FL",
"zip":{"areaLength":547.967,"areaWidth":468.858,"radius":
755.778,"zipCode":"95117"}}

Теперь можно выполнять парсинг ответа и манипулировать им, как


обычно.
Кроме того, есть возможность отправить данные веб-службе в фор­
мате XML или JSON. Для этого нужно лишь создать строку в форма­
те XML или JSON и передать ее одному из сгенерированных методов.
Например, ниже показано, как добавить новую запись в базу данных:
package com.ensode.netbeansbook.jaxrsclient;

import javax.ws.rs.ClientErrorException;

public class Main1 {


public static void main(String[] args) {
String json = "{\"addressline1\":\"123 Icant Dr.\","
+ "\"addressline2\":\"Apt 42\",\"city\":"
+ "\"Springfield\",\"creditLimit\":1000,"
+ "\"customerId\":999,\"discountCode\":"
Создание JavaScript-клиента веб-службы RESTful 313
+ "{\"discountCode\":\"N\",\"rate\":0.00},"
+ "\"email\":\"customer@example.com\","
+ "\"fax\":\"555-555-1234\",\"name\":"
+ "\"Customer Name\",\"phone\":"
+ "\"555-555-2345\",\"state\":"
+ "\"AL\",\"zip\":{\"areaLength\":"
+ "547.967,\"areaWidth\":468.858,\""
+ "radius\":755.778,"
+ "\"zipCode\":\"12345\"}}";

NewJerseyClient newJerseyClient = new NewJerseyClient();

newJerseyClient.create_JSON(json);

newJerseyClient.close();
}
}

Этот код генерирует строку JSON, отформатированную так, чтобы


веб-служба RESTful смогла понять ее, затем она передается методу
create_JSON() сгенерированного клиентского класса. Этот класс, в
свою очередь, вызывает веб-службу, которая затем добавляет запись
в базу данных.
Мы можем удостовериться, что данные были вставлены успешно,
выполнив запрос в базу данных.

Создание JavaScript-клиента
веб-службы RESTful
В предыдущем разделе мы увидели, как создаются Java-клиенты для
веб-служб RESTful. Аналогично создаются JavaScript-клиенты, вы­
полняющиеся в браузере – NetBeans способна генерировать клиент­
ский не только на Java, но и на JavaScript.
Чтобы сгенерировать клиентский код на JavaScript для работы
с веб-службой RESTful, выберите в главном меню пункт File | New
File (Файл | Создать файл) и затем в категории Web Services (Веб-
службы) выберите тип файлов RESTful JavaScript Client (Клиент
JavaScript RESTful) в категории Web Services (Веб-службы), как по­
казано на рис. 10.18.
На следующем шаге выберите элемент Tablesorter UI в раскры­
вающемся списке Choose resulting UI (Выбрать конечный поль­
зовательский интерфейс), чтобы сгенерировать полноценное
CRUD-приложение. Получившееся веб-приложение использует
314 Глава 10. Веб-службы RESTful на основе JAX-RS

JavaScript-библиотеку Backbone.js, поэтому установите флажок Add


Backbone.js to project sources (Добавить Backbone.js в исходные
коды проекта), как показано на рис. 10.19 – если этого не сделать, би­
блиотека будет загружаться из популярной сети доставки контента
(Content Delivery Network, CDN) http://cdnjs.com.

Рис. 10.18. Выбор типа файлов RESTful JavaScript Client


(Клиент JavaScript RESTful)

Рис. 10.19. Добавление библиотеки Backbone.js в исходные коды проекта


Создание JavaScript-клиента веб-службы RESTful 315

Теперь осталось только выбрать REST-ресурс щелчком на кнопке


Browse... (Обзор...), как показано на рис. 10.20.

Рис. 10.20. Выбор REST-ресурса


В этом примере мы выбрали веб-службу CustomerFacadeREST, соз­
данную в начале главы.
На следующем шаге нужно выбрать имя для HTML-файла, кото­
рый должен быть сгенерирован (см. рис. 10.21).

Рис. 10.21. Выбор имени для HTML-файла


И затем останется только развернуть приложение и ввести в
адресной строке браузера адрес сгенерированной страницы (см.
рис. 10.22).

Powered by TCPDF (www.tcpdf.org)


316 Глава 10. Веб-службы RESTful на основе JAX-RS

Рис. 10.22. Сгенерированная страница в браузере


Щелчком на кнопке Create (Создать) в верхнем левом углу можно
добавить новую запись в базу данных. Поля ввода будут отображены
в нижней части страницы. Кроме того, если щелкнуть на значении
в столбце ID, будет предоставлена возможность изменить значения
полей в выбранной записи, как показано на рис. 10.23.

Рис. 10.23. Вид страницы после щелчка на значении в столбце ID


Резюме 317

Как видите, NetBeans способна генерировать полноценные клиент­


ские приложения на JavaScript, пользующиеся услугами веб-служб
RESTful, не заставляя разработчика написать хотя бы строчку кода.

Резюме
В этой главе мы познакомились с некоторыми мощными возможно­
стями создания веб-служб RESTful, предлагаемых средой разработки
NetBeans. Мы видели, как NetBeans запросто генерирует веб-службу
RESTful из существующей схемы базы данных. Мы также видели,
как легко и просто можно протестировать веб-службы, используя ин­
струменты, предоставляемые NetBeans и GlassFish.
Кроме того, мы узнали, как несколькими щелчками мыши создать
клиента веб-службы и как создать JavaScript-клиента с минимумом
усилий.
Глава 11.
Веб-службы SOAP
на основе JAX-WS

Веб-службы позволяют создавать функциональность, к которой мож­


но получить доступ через сеть. От других подобных технологий, таких
как EJB или удаленный вызов методов (Remote Method Invocation,
RMI), веб-службы отличаются независимостью от языка и платфор­
мы. Например, веб-службу, написанную на Java, могут использовать
клиенты, написанные на любых других языках, и наоборот.
В этой главе мы затронем следующие темы:
введение в веб-службы;
создание простой веб-службы;
создание клиента веб-службы;
экспортирование компонентов EJB в виде веб-служб.

Введение в веб-службы
Веб-службы позволяют создавать функциональность, к которой мож­
но получить доступ через сеть, независимым от языка и платформы
способом.
Существует два разных подхода, часто используемых для разра­
ботки веб-служб: первый заключается в использовании простого про­
токола доступа к объектам (Simple Object Access Protocol, SOAP) и
второй – в использовании протокола передачи репрезентативного со­
стояния (Representational State Transfer, REST). NetBeans поддержи­
вает создание веб-служб с использованием любого из этих подходов.
В предыдущей главе мы познакомились с веб-службами RESTful, а в
этой займемся веб-службами SOAP.
При использовании протокола SOAP функциональность веб-
службы определяется в файле XML, на языке определения веб-
служб (Web Services Definition Language, WSDL). После создания
Создание простой веб-службы 319

файла WSDL реализация веб-служб выполняется на выбранном


языке программирования, таком как Java. Процесс создания WSDL
сложен и подвержен ошибкам. К счастью, при работе с Java EE есть
возможность автоматически сгенерировать файл WSDL на основе
веб-службы, написанной на Java, в момент ее развертывании на сер­
вере приложений. Кроме того, если имеется готовый файл WSDL и
нужно реализовать веб-службу на Java, NetBeans может автоматиче­
ски сгенерировать большую часть кода на Java, создав класс с метода­
ми-заглушками для каждой операции, поддерживаемой веб-службой.
Нам останется лишь реализовать фактическую логику каждого мето­
да, а весь код «инфраструктуры» будет сгенерирован автоматически.

Создание простой веб-службы


В этом разделе мы создадим веб-службу, выполняющую преобразо­
вание единиц измерения длины. Веб-служба будет иметь операции
преобразования дюймов в сантиметры и сантиметров в дюймы.
Для веб-службы нужно создать новый проект веб-приложения.
В данном примере проект будет называться UnitConversion. Создать
саму веб-службу можно, выбрав в главном меню пункт File | New
File (Файл | Создать файл) и затем в категории Web Services (Веб-
службы)  – тип файлов Web Service (Веб-служба), как показано на
рис. 11.1.

Рис. 11.1. Выбор типа файлов Web Service (Веб-служба)


320 Глава 11. Веб-службы SOAP на основе JAX-WS

После щелчка по кнопке Next > (Далее >) нужно ввести имя и па­
кет для веб-службы (см. рис. 11.2).

Рис. 11.2. Ввод имени веб-службы и пакета


После щелчка на кнопке Finish (Завершить) NetBeans создаст веб-
службу и автоматически откроет исходный код (см. рис. 11.3).

Рис. 11.3. Исходный код веб-службы


Создание простой веб-службы 321

Как видите, NetBeans автоматически сгенерировала простую веб-


службу «Привет, мир». Аннотация @WebService на уровне класса пре­
вращает этот класс в веб-службу. Аннотация @WebMethod на уровне
метода превращает аннотируемый метод в операцию веб-службы;
атрибут operationName этой аннотации определяет имя операции
веб-службы. Это имя будет использоваться клиентами. Аннотация
@WebParam определяет свойства параметров операции веб-службы.
Атрибут name этой аннотации определяет имя параметра в WSDL, ко­
торый генерируется при развертывании веб-службы.
NetBeans позволяет изменять веб-службы посредством графиче­
ского интерфейса – добавлять и/или удалять операции веб-службы и
их параметры, щелкая на них мышью, после чего в код будут автома­
тически добавляться соответствующие методы-заглушки и аннота­
ции. Чтобы получить доступ к графическому дизайнеру веб-службы,
достаточно просто щелкнуть на кнопке Design (Конструктор) в верх­
нем левом углу окна с исходным кодом веб-службы (см. рис. 11.4).

Рис. 11.4. Внешний вид конструктора веб-службы с графическим


интерфейсом
Первое, что следует сделать, – удалить автоматически сгенериро­
ванную операцию. Для этого просто щелкните на кнопке Remove Op-
eration (Удалить операцию), как показано на рис. 11.5.
Чтобы добавить в веб-службу новую операцию, щелкните на кноп­
ке Add Operation... (Добавить операцию...) и заполните пустые поля
в открывшемся окне.
322 Глава 11. Веб-службы SOAP на основе JAX-WS

Рис. 11.5. Первый шаг – удаление операции hello


Наша веб-служба должна иметь две операции: одна для преоб­
разования дюймов в сантиметры, и другая – сантиметров в дюймы.
Обе операции принимают единственный параметр типа double и воз­
вращают значение также типа double. После щелчка на кнопке Add
Operation... (Добавить операцию...) нужно ввести информацию, не­
обходимую для создания операции inchesToCentimeters, как показано
на рис. 11.6.

Рис. 11.6. Информация, необходимая для создания


операции inchesToCentimeters
Создание простой веб-службы 323

Затем то же нужно проделать создания операции centimeters­


ToInches (здесь не показана). После этого в окне конструктора по­
явятся вновь добавленные операции (см. рис. 11.7).

Рис. 11.7. Вновь добавленные операции в окне проекта


Помимо добавления операций в веб-службу в окне конструктора
можно управлять качественными характеристиками службы путем
установки или снятия флажков.
Веб-службы обмениваются данными с клиентами, пересылая их
в виде текстовых сообщений в формате XML. Иногда бывает не­
обходимо передать двоичные данные, такие как изображения. Дво­
ичные данные обычно встраиваются в сообщение SOAP с исполь­
зования механизма оптимизации передачи сообщения (Message
Transmission Optimization Mechanism, MTOM). Двоичные данные
отправляются в виде вложения, что делает передачу двоичных дан­
ных более эффективной. В NetBeans можно указать необходимость
использования MTOM, просто установив флажок Optimize Transfer
Of Binary Data (MTOM) (Оптимизировать передачу двоичных дан­
ных (MTOM)).
Установкой флажка Reliable Message Delivery (Надежная достав­
ка сообщений) можно указать, что сообщения должны передаваться
324 Глава 11. Веб-службы SOAP на основе JAX-WS

по крайней мере один раз и не более одного раза. Использование ме­


ханизма надежной доставки сообщений позволяет приложениям вос­
станавливаться после ситуаций, когда сообщения могут быть потеря­
ны в процессе передачи.
Установка флажка Secure Service (Служба безопасности) акти­
вирует средства защиты информации, такие как шифрование со­
общений и требование аутентификации клиента для доступа к веб-
службе.
Увидеть сгенерированные методы-заглушки можно на вкладке
Source (Источник), как показано на рис. 11.8.

Рис. 11.8. Исходный код веб-службы

Теперь нам осталось заменить сгенерированные реализации ме­


тодов в классе «настоящими», развернуть приложение и веб-служба
будет готова к работе. В данном случае нужно лишь разделить дюймы
на коэффициент 2.54, чтобы преобразовать дюймы в сантиметры, и
умножить на коэффициент 2.54, чтобы преобразовать сантименты в
дюймы.
После замены реализаций методов фактически необходимыми,
все готово к развертыванию веб-службы, что может быть выполнить
щелчком правой кнопки мыши на проекте и выбором в контекстном
меню пункта Deploy (Развернуть).
Создание простой веб-службы 325

Тестирование веб-службы
Для начала нужно обратить внимание на узел Web Services (Веб-
службы) в окне Projects (Проекты). Если распахнуть его, можно бу­
дет увидеть недавно созданную веб-службу (см. рис. 11.9).

Рис. 11.9. Недавно созданная веб-служба

Если развернуть веб-службу на сервере приложений GlassFish,


включенном в NetBeans, ее можно протестировать, просто щел­
кнув на ней правой кнопкой мыши в окне Projects (Проекты) и
выбрав в контекстном меню пункт Test Web Service (Тестировать
веб-службу).

Если в журнале GlassFish появится ошибка: «Failed to read schema


document ‘xjc.xsd’, because ‘bundle’ access is not allowed due to
restriction set by the accessExternalSchema property» («ошиб-
ка чтения документа схемы ‘xjc.xsd’, потому что доступ к ‘при-
вязке’ запрещен из-за ограничений, определяемых свойством
accessExternalSchema»), тогда создайте файл с именем jaxp.
properties и добавьте в него строку:
javax.xml. accessExternalSchema=all.
Поместите этот файл в каталог (путь к JDK): /jre/lib

В браузере откроется страница тестирования веб-службы (см.


рис. 11.10) где можно проверить работу методов веб-службы, просто
вводя некоторые значения в текстовые поля и щелкая на соответству­
ющих кнопках. Например, введите число 2.54 во втором текстовом
поле и щелкните на кнопке centimetersToInches – в браузере должна
появиться страница, как показано на рис. 11.11, с результатом преоб­
разования.
326 Глава 11. Веб-службы SOAP на основе JAX-WS

Рис. 11.10. Страница тестирования веб-службы

Рис. 11.11. Результат преобразования сантиметров в дюймы


Вверху страницы отображаются параметры, переданные мето­
ду, и возвращаемое значение. Внизу – фактические запрос и ответ
SOAP.
Создание простой веб-службы 327

Создание клиента для веб-службы


Теперь, когда у нас имеется готовая и протестированная веб-служба,
мы попробуем создать простого клиента, который будет вызывать веб-
службу. Клиентом веб-службы может быть проект Java любого вида,
такой, например, как стандартное приложение Java, приложение Java
ME, веб-приложение или проект корпоративного приложения. Что­
бы избежать ненужных сейчас сложностей, мы создадим клиента на
основе проекта Java Application (Приложение Java).

Рис. 11.12. Настройки параметров проекта


После создания проекта следует создать новую веб-службу. Для этого
выберите в главном меню пункт File | New File (Файл | Создать файл)
и затем в категории Web Services (Веб-службы) выберите тип файлов
Web Service Client (Клиент веб-службы), как показано на рис. 11.13.
На следующем шаге (см. рис. 11.14) установите переключатель
Project (Проект), если он еще не установлен, затем щелкните на
кнопке Browse (Обзор) и выберите веб-службу, созданную выше в
этой главе. Поле с адресом URL сгенерированного файла WSDL веб-
службы заполнится автоматически.
Обратите внимание, что можно создавать клиентов для веб-служб,
созданных сторонними разработчиками. Для этого нужно просто уста­
новить переключатель Local File (Локальный файл), чтобы использо­
вать WSDL-файл, находящийся на жестком диске, или переключатель
328 Глава 11. Веб-службы SOAP на основе JAX-WS

WSDL URL, чтобы использовать WSDL-файл из Интернета. Также в


NetBeans имеются готовые настройки для некоторых общедоступных
веб-служб. Чтобы создать клиента для одной из них, выберите пере­
ключатель IDE Registered (Зарегистрированная среда).

Рис. 11.13. Выбор типа файлов Web Service Client


(Клиент веб-службы)

Рис. 11.14. Настройка параметров клиента веб-службы


Создание простой веб-службы 329

После щелчка на кнопке Finish (Готово) в проекте появится новый


узел Web Service References (Ссылки на веб-службы). Если распах­
нуть этот узел, можно увидеть все операции, которые были определе­
ны в проекте веб-службы (см. рис. 11.15).

Рис. 11.15. Операции, которые были определены


в проекте веб-службы

Как правило, при создании клиентов веб-служб приходит­


ся писать массу типового кода. Однако в NetBeans можно просто
перетащить мышью операцию веб-службы, которую требуется вы­
звать, в окно редактора кода. В ответ NetBeans сгенерирует весь
необходимый типовой код и нам останется только определить, ка­
кие параметры отправить веб-службе. Если перетащить операцию
inchesToCentimeters из окна Projects (Проекты) в главный класс
проекта клиента веб-службы, NetBeans добавит в него код, как по­
казано на рис. 11.16.
Как видите, в код клиента был добавлен метод с именем
inchesToCentimeters() (имя операции веб-службы). Этот метод, в
свою очередь, вызывает несколько методов в классе UnitConversion_
Service. Этот класс (и несколько других) создан автоматически при
перетаскивании операции веб-службы в исходный код. Увидеть сге­
нерированные классы можно, распахнув узел Generated Sources
(jax-ws) (Созданные исходные файлы (jax-ws)) в окне проекта (см.
рис. 11.17).
330 Глава 11. Веб-службы SOAP на основе JAX-WS

Рис. 11.16. Сгенерированный код вызова веб-службы

Рис. 11.17. Сгенерированные классы


Метод getUnitConversionPort() класса UnitConversion_Service воз­
вращает экземпляр класса UnitConversion, сгенерированного на осно­
Создание простой веб-службы 331

ве WSDL и похожего на одноименный класс, который мы написали


в проекте веб-службы. Метод, сгенерированный в результате пере­
таскивания операции веб-службы в код, вызывает этот метод, затем
вызывает метод inchesToCentimeters() возвращаемого экземпляра
UnitConversion. Нам остается только вызвать сгенерированный ме­
тод из своего кода. После этой простой модификации наш код теперь
будет выглядеть, как показано на рис. 11.18.

Рис. 11.18. Вызов сгенерированного метода


Теперь мы готовы опробовать код клиента веб-службы. По­
сле запуска в консоли должен появиться вывод, как показано на
рис. 11.19.

Рис. 11.19. Вывод в консоли после запуска клиента веб-службы


332 Глава 11. Веб-службы SOAP на основе JAX-WS

Экспортирование компонентов
EJB в виде веб-служб
В предыдущем примере веб-службы мы видели, как экспортировать в
виде веб-службы простой объект Java (Plain Old Java Object, POJO),
упаковав его в веб-приложение и добавив несколько аннотаций. Это
существенно упрощает создание веб-служб, развертываемых в веб-
приложениях.
При работе с проектом модуля EJB, также можно экспортировать
в виде веб-службы сеансовый компонент без сохранения состояния
и тем самым открыть доступ к нему для клиентов, написанных на
других языках, отличных от Java. Экспортирование сеансовых ком­
понентов без сохранения состояния в виде веб-служб открывает
перед веб-службами возможность использовать функциональность,
доступную EJB, такую как управление транзакциями и аспектно-ори­
ентированное программирование.
Есть два способа экспортирования сеансовых компонентов в виде
веб-служб. При создании новой веб-службы в проекте модуля EJB,
новая веб-служба будет автоматически реализована в виде сеансово­
го компонента без сохранения состояния. Кроме того, есть возмож­
ность экспортировать в виде веб-служб сеансовые компоненты уже
существующие в проекте модуля EJB.

Реализация новых веб-служб в виде EJB


Чтобы реализовать новую веб-службу в виде EJB, нужно просто соз­
дать веб-службу в проекте модуля EJB, щелкнув правой кнопкой
мыши на проекте и выбрав в контекстном меню пункт New | Web
Service (Новый | Веб-служба).

При создании веб-службы в проекте веб-приложения предостав-


ляется на выбор возможность реализовать веб-службу в виде про-
стого объекта Java (POJO) или в виде сеансового компонента без
сохранения состояния. При создании веб-службы в проекте модуля
EJB реализовать веб-службу можно только в виде сеансового ком-
понента без сохранения состояния.

В окне мастера создания новой веб-службы нужно ввести всю не­


обходимую информацию, как показано на рис. рис. 11.20.
Здесь нужно указать имя будущей веб-службы, выбрать пакет для
размещения ее реализации, установить переключатель Create Web
Экспортирование компонентов EJB в виде веб-служб 333

Рис. 11.20. Окно мастера создания новой веб-службы

Service From Scratch (Создать веб-службу «с нуля») и затем щел­


кнуть на кнопке Finish (Готово), чтобы сгенерировать веб-службу.
После этого в окне редактора должен появиться исходный код веб-
службы (см. рис. 11.21).
Как видите, получившийся сеансовый компонент не реализует
ни локального, ни удаленного интерфейсов. Он просто декориро­
ван аннотацией @WebService, его методы декорированы аннотацией
@WebMethod, а каждый параметр – аннотацией @WebParam. Единствен­
ное отличие исходного кода этой веб-службы от исходного кода,
представленного в предыдущем примере, состоит в том, что на этот
раз сгенерированный класс является сеансовым компонентом без
сохранения состояния, поэтому он может пользоваться такими пре­
имуществами, как механизмы управления транзакциями EJB, аспек­
тно-ориентированное программирование и другие возможности EJB.
Так же как обычная веб-служба, веб-служба в виде сеансового ком­
понента может проектироваться с использованием визуального кон­
структора. В данном примере после удаления автоматически сгене­
рированной операции и добавления двух фактических операций окно
визуального конструктора будет выглядеть, как показано на рис. 11.22.
334 Глава 11. Веб-службы SOAP на основе JAX-WS

Рис. 11.21. Исходный код новой веб-службы

Рис. 11.22. Окно визуального конструктора после добавления


фактических операций
Экспортирование компонентов EJB в виде веб-служб 335

Щелкнув на вкладке Source (Источник), можно увидеть вновь сге­


нерированные методы вместе со всеми соответствующими аннотаци­
ями (см. рис. 11.23).

Рис. 11.23. Исходный код вновь созданных методов


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

Экспортирование существующих EJB


в виде веб-служб
Второй способ экспортирования компонентов EJB в виде веб-служб
заключается в экспортировании существующих компонентов EJB.
Для этого нужно создать веб-службу как обычно: щелкнуть правой
кнопкой мыши на проекте, выбрать в контекстном меню пункт New |
Web Service (Новый | Веб-служба), ввести имя веб-службы и пакета,
установить переключатель Create Web Service from Existing Session
Bean (Создать веб-службу на основе существующего сеансового ком­
понента), после чего выбрать компонент для экспортирования, щелк­
336 Глава 11. Веб-службы SOAP на основе JAX-WS

нув на кнопке Browse... (Обзор...) и выбрав соответствующий компо­


нент, как показано на рис. 11.24.

Рис. 11.24. Создание веб-службы на основе существующего


сеансового компонента
После щелчка на кнопке Finish (Готово) NetBeans создаст новую
веб-службу и откроет ее исходный код (см. рис. 11.25).
Как видите, создание веб-службы из существующего сеансового
компонента приводит к созданию нового сеансового компонента без
сохранения состояния. Этот новый компонент действует как клиент
существующего EJB (о чем свидетельствует переменная экземпляра
ejbRef в нашем примере, декорированная аннотацией @EJB).
Если щелкнуть на кнопке Design (Конструктор) вверху окна, от­
кроется визуальный конструктор веб-службы (см. рис. 11.26).
В проекте веб-приложения также имеется возможность экспор­
тировать компонент EJB в виде веб-службы, но в этом случае сгене­
рированная веб-служба будет представлена простым объектом Java,
декорированным аннотациями @WebService, @WebMethod и @WebParam, с
методами, вызывающими соответствующие методы экспортируемого
компонента EJB.
Экспортирование компонентов EJB в виде веб-служб 337

Рис. 11.25. Исходный код веб-службы на основе существующего


сеансового компонента

Рис. 11.26. Окно визуального конструктора веб-службы


338 Глава 11. Веб-службы SOAP на основе JAX-WS

Создание веб-службы из существующего


файла WSDL
Обычно создание веб-служб SOAP требует создания файла опреде­
ления веб-службы (WSDL). Процесс создания WSDL сложен и под­
вержен ошибкам, но, к счастью, Java EE освобождает от необходимо­
сти создавать файлы WSDL вручную, генерируя их автоматически
всякий раз, когда веб-служба развертывается на сервере приложений.
Однако иногда файл WSDL уже имеется в наличии и требуется
реализовать описанные в нем операции на Java. Для таких случаев
NetBeans предоставляет мастера, который создаст класс Java с мето­
дами-заглушками, опираясь на существующий файл WSDL.
Чтобы воспользоваться услугами этого мастера, нужно создать но­
вый файл, выбрав в главном меню пункт File | New File (Файл | Соз­
дать файл) и в открывшемся диалоге выбрать тип файлов Web Service
from WSDL (Веб-служба из WSDL) в категории Web Services (Веб-
службы), как показано на рис. 11.27.

Рис. 11.27. Выбор типа файлов Web Service from WSDL


(Веб-служба из WSDL)
Затем ввести имя веб-службы и пакета, и выбрать существующий
файл WSDL (см. рис. 11.28).
Резюме 339

Рис. 11.28. Настройка параметров создания веб-службы из WSDL


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

Резюме
В этой главе мы познакомились с поддержкой создания веб-служб
SOAP в NetBeans, включающей экспортирование методов POJO в
виде веб-служб и автоматическое добавление необходимых аннота­
ций в исходный код.
340 Глава 11. Веб-службы SOAP на основе JAX-WS

Мы узнали, как NetBeans помогает в создании клиентов веб-служб,


генерируя большую часть типового кода, оставляя нам инициализа­
цию любых параметров, которые будут переданы операциям веб-
службы.
Дополнительно мы посмотрели, как экспортировать методы EJB в
виде операций веб-служб, а также увидели, какую поддержку оказы­
вает NetBeans в упрощении экспортирования новых и существующих
EJB в виде веб-служб.
Наконец, мы рассмотрели, как NetBeans может помочь в реализа­
ции веб-службы из существующего файла WSDL, расположенного в
локальной файловой системе или развернутого на сервере, генерируя
методы-заглушки на основе WSDL.

Рис. 11.29. Исходный код новой веб-службы, созданной из WSDL


Предметный указатель
Символы аспектно-ориентированное програм-
мирование (Aspect-Oriented
: GNU Public License Version 2 (GPL),
Programming, AOP)
дицензия 26
интерцепторы 199
<ace:linkButton>, компонент 126
реализация 199
<ace:pushButton>, компонент 128
<ace:selectMenu>, компонент 128 В
<ace:sliderEntry>, компонент 128
@ApplicationScoped, аннотация 217 веб-служб
@ConversationScoped, аннотация 217 создание на основе WSDL 338
@Dependent, аннотация 217 веб-службы
@Interceptors, аннотация 202 введение 318
@RequestScoped, аннотация 216 создание 319
@Schedule, аннотация 205 создание клиента 327
@SessionScoped, аннотация 216 тестирование 325
@TransactionAtttibute, аннотация 197 экспортирование EJB 332
@WebMethod, аннотация 321 экспортирование существующих
@WebParam, аннотация 321 EJB 335
@WebService, аннотация 321 веб-службы RESTful 293
создание JavaScript-клиента 313
A создание Java-клиента 307
автозавершение кода 43 создание на основе существующей
автоматическое создание сущностей базы данных 294
JPA 153 тестирование 300
JPQL 162 веб-сокеты
именованные запросы 162 исследование программного кода на
проверка допустимости 164 Java 278
аннотации JPA исследование программного кода на
@Entity 144 JavaScript 279
@GeneratedValue 145 исследование программного кода
@Id 144 типовых примеров 275
аспектно-ориентированного програм- пример приложения Echo, опробова-
мирования (Aspect Oriented ние 277
Programming, AOP) 228 создание собственных приложений 281
342 Предметный указатель

пользовательский интерфейс 283 О


реализация клиента 288
объектно-реляционное отображение
серверная конечная точка 286
(Object-Relational Mapping,
визуальные индикаторы 53
ORM) 135
И объекты доступа к данным (Data
Access Object, DAO)
именованные запросы 162 создание 147
именованные параметры 163 объекты доступа к данным (Data
интегрированная среда разработки 19 Access Objects, DAO) 206
интерцепторы 199 одиночки, сеансовые компоненты 181
введение 199 отношения, сущностей JPA 164
К П
квалификаторы 220 передача репрезентативного состоя­
квалификаторы CDI ния (Representational State
создание 220 Transfer, REST) 293
клавиши быстрого вызова 49 потоки Faces Flows 101
клиент приложения
доступ к сеансовым компонентам 193 наблюдение за работой 254
запуск 196 приложения JSF
компоненты использование компонентов
проверка допустимости 164 ICEfaces 120
компоненты управляемые сообщениями использование компонентов
обзор 180 PrimeFaces 114
контекст использование компонентов
диалога 217 RichFaces 128
зависимый 217 создание из сущностей JPA 172
запроса 216 проверка допустимости в JSF 80
приложения 217 проверка допустимости, со стороны
сеанса 216 компонентов 164
контракты библиотек ресурсов простые плоские объекты Java (Plain
использование 92 Old Java Object, POJO) 136
описание 90 публикация/подписка, режим обмена
создание 91 сообщениями 237

М Р

механизм внедрения зависимостей развертывание приложений 40


(Contexts and Dependency С
Injection, CDI) 41
свойства активации компонента
Предметный укзатель 343

acknowledgeMode 251 сторонние серверы приложений


clientId 252 интегрирование с NetBeans 33
connectionFactoryLookup 252 стратегии генерации первичных
destinationLookup 252 ключей
destinationType 252 GenerationType.AUTO 145
messageSelector 252 GenerationType.IDENTITY 145
subscriptionDurability 252 GenerationType.SEQUENCE 145
subscriptionName 252 GenerationType.TABLE 145
сеансовые компоненты СУРБД 140
без сохранения состояния 181 сущности JPA
введение 181 автоматическое создание 153
доступ из клиента 193 добавление сохраняемых полей 145
обзор 180 именованные запросы 162
одиночки 181 объекты доступа к данным (Data
создание, в NetBeans 181 Access Object, DAO)
с сохранением состояния 181 создание 147
управление транзакциями 197 отношения 164
сквозные атрибуты 112 проверка допустимости 164
служба имен и каталогов Java (Java создание 136
Naming and Directory Interface, создание приложений JSF 172
JNDI) 182
служба таймеров EJB 203 Т
использование 203 типы привязки интерцепторов 228
собственные контексты 232 создание 228
советы по эффективной разработке 43 точка-точка, режим обмена сообще-
автозавершение кода 43 ниями 237
визуальные индикаторы 53 транзакции
клавиши быстрого вызова 49 управление в сеансовых компонен-
шаблоны кода 47 тах 197
составные компоненты
описание 96 У
создание 96 удаленный вызов методов (Remote
сохраняемые поля, сущностей JPA Method Invocation, RMI) 318
145 ускорение разработки HTML5 54
стереотипы 225 установка NetBeans
стереотипы CDI в Linux 24
создание 225 в Mac OS X 24
сторонние серверы баз данных в Windows 24
интегрирование с NetBeans 36 на другие платформы 25
соединение с NetBeans 38 описание 23
344 Предметный указатель

процедура 25 add(String name, JsonValue value) 260


add(String name, long value) 260
Ф add(String name, String value) 260
Фасад, шаблон проектирования Aspect Oriented Programming (AOP) 228
URL 296 Aspect-Oriented Programming (AOP)
интерцепторы 199
Ш реализация 199
шаблоны кода 47 C
fore 47
ifelse 47 CDI
Psf 47 введение 213
psvm 47 квалификаторы, создание 220
soutv 47 собственные контексты, создание 232
trycatch 47 стереотипы, создание 225
whileit 48 типичная разметка страниц JSF 213
шаблоны проектирования типы привязки интерцепторов 228
Фасад 296 создание 228
шаблоны фейслетов 83 Common Development and Distribution
добавление 84 License (CDDL), лицензия 26
использование 86 Contexts and Dependency Injection
(CDI) 41
Я cron, утилита
язык определения веб-служб (Web ссылка для справки 205
Services Definition Language, C, язык программирования 21
WSDL) 318 C++, язык программирования 21
D

A Data Access Objects (DAO) 206

ACE, компоненты 125 E


add, методы EAR, файлы 181
add(String name, BigDecimal value) 260 EJB
add(String name, BigInteger value) 260 существующие, экспортирование в
add(String name, boolean value) 260 виде веб-служб 335
add(String name, double value) 260 экспортирование в виде веб-служб
add(String name, int value) 260 332
add(String name, JsonArrayBuilder EJB Timer Service 203
builder) 260 использование 203
add(String name, JsonObjectBuilder Enterprise JavaBeans (EJB), компонен-
builder) 260 ты 180
Предметный укзатель 345

Event, значения экземпляра InvocationContext, класс 200


Event.END_OBJECT 273 getMethod(), метод 200
Event.KEY_NAME 273 getParameters(), метод 200
Event.START_OBJECT 273 getTarget(), метод 200
Event.VALUE_END_ARRAY 273 proceed(), метод 200
Event.VALUE_FALSE 273
Event.VALUE_NULL 273 J
Event.VALUE_NUMBER 273 JavaDB 140
Event.VALUE_START_ARRAY 273 Java EE 21
Event.VALUE_STRING 273 Java EE 5 135
Event.VALUE_TRUE 273 Java EE 7 257
F Java EE 7, документация
URL 145
faces-config.xml, файл 61 Java EE-приложения
настройка NetBeans для разработки
G
32
get, методы JAVA_HOME, переменная окружения
getBoolean(String name) 266 28
getInt(String name) 266 Java Naming and Directory Interface
getJsonArray(String name) 266 (JNDI) 182
getJsonNumber(String name) 266 Java Persistence API (JPA), прикладной
getJsonObject(String name) 266 интерфейс 20
getJsonString(String name) 266 Java SE 21
getString(String name) 266 JavaServer Faces (JSF) 60
JavaServer Faces (JSF), фреймворк 20
H
Java Transaction API (JTA) 143
HTML5 21 JDBC, драйверы
HTML5-подобная разметка 108 добавление в NetBeans 36
Jersey 307
I JMS 236
ICEfaces введение 236
адрес URL документации 128 продюсер, реализация 243
компоненты, использование создание ресурсов 237
в JSF-приложениях 120 JPA 135,  137
описание 120 JPA, сущности
ICE, компоненты 125 автоматическое создание 153
ICSoft добавление сохраняемых полей 145
адрес URL 121 именованные запросы 162
IDE, (Integrated Development объекты доступа к данным (Data
Environment) 19 Access Object, DAO)
346 Предметный указатель

создание 147 интегрирование со сторонним серве-


отношения 164 ром приложений 33
проверка допустимости 164 интегрированная среда разработки 19
создание 136 клавиши быстрого вызова 49
создание приложений JSF 172 настройка для разработки Java EE 32
JPQL 135,  162 первый запуск 31
JSF-приложения поддерживаемые платформы 22
запуск 78 получение 20
разработка 61 развертывание приложений 40
JSF-проекты советы по эффективной разработке 43
изменение страниц 66 соединение со сторонней СУРБД 38
именованные компоненты CDI, создание ресурсов JMS 237
создание 73 установка 23
создание 61 в Linux 24
страница подтверждения, создание 77 в Mac OS X 24
JSON 257 в Windows 24
парсинг данных 265,  271 на другие платформы 25
создание данных 258,  269 процедура 25
JSON-P 257 функция ускорения разработки
объектная модель HTML5 54
введение 257 шаблоны кода 47
парсинг данных 265 NetBeans Connector, расширение 57
пример 261 установка 58
создание данных 258 NetBeans, комплектность
потоковая модель All 21
введение 268 C/C++ 21
парсинг данных 271 HTML5 & PHP 21
создание данных 269 Java EE 21
JUnit, фреймворк 26 Java SE 21
N O
NetBeans Open Source Initiative (OSI), организа-
IDE 19 ция 26
URL 19
автозавершение кода 43 P
введение 19 PHP 21
визуальные индикаторы 53 PrimeFaces
добавление драйверов JDBC 36 компоненты, использование
интегрирование со сторонней в JSF-приложениях 114
СУРБД 36 описание 114
Предметный укзатель 347

R write (String name, BigInteger value)


271
REST 293 write (String name, boolean value) 271
RESTful, веб-службы 293 write (String name, double value) 271
создание JavaScript-клиента 313 write (String name, int value) 271
создание Java-клиента 307 write (String name, JsonValue value)
создание на основе существующей 271
базы данных 294 write (String name, long value) 271
тестирование 300 write (String name, String value) 271
RichFaces WSDL
адрес URL документации 133 создание веб-служб на основе 338
внешние зависимости 129 WSDL (Web Services Definition
компоненты, использование в JSF- Language - язык определения
приложениях 128 веб-служб) 318
описание 128
ссылка для загрузки последней
версии 129
RMI (Remote Method Invocation - уда-
ленный вызов методов) 318
W
WAR, файлы 181
WebSocket
исследование программного кода на
Java 278
исследование программного кода на
JavaScript 279
исследование программного кода
типовых примеров 275
пример приложения Echo, опробова-
ние 277
создание собственных приложений
281
пользовательский интерфейс 283
реализация клиента 288
серверная конечная точка 286
welcomePrimefaces.xhtml, файл 116
write, методы
write (String name, BigDecimal value)
270
Книги издательства «ДМК Пресс» можно заказать в торгово-издательском
холдинге «Планета Альянс» наложенным платежом, выслав открытку или
письмо по почтовому адресу: 115487, г. Москва, 2-й Нагатинский пр-д, д. 6А.
При оформлении заказа следует указать адрес (полностью), по которому
должны быть высланы книги; фамилию, имя и отчество получателя. Жела­
тельно также указать свой телефон и электронный адрес.
Эти книги вы можете заказать и в интернет-магазине: www.alians-kniga.ru.
Оптовые закупки: тел. +7 (499) 782-38-89.
Электронный адрес: books@alians-kniga.ru.

Дэвид Хеффельфингер

Разработка приложений Java EE 7 в NetBeans 8

Главный редактор Мовчан Д. А.


dmkpress@gmail.com
Перевод с английского Киселев А. Н.
Корректор Синяева Г. И.
Верстка Паранская Н. В.
Дизайн обложки Мовчан А. Г.

Формат 60×90 1/16. Гарнитура «Петербург».


Печать офсетная. Усл. печ. л. 21,75.
Тираж 200 экз.

Веб-сайт издательства: www.дмк.рф

Powered by TCPDF (www.tcpdf.org)

Вам также может понравиться