Text
                    Дэвид Хеффельфингер
Разработка
приложений
Java EE 7
в NetBeans 8
1/35


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 2/35
Разработка приложений Java EE 7 в NetBeans 8 Простая и быстрая разработка корпоративных приложений Java EE с помощью среды разработки NetBeans Дэвид Хеффельфингер Москва, 2016 3/35
УДК 004.438Java EE ББК 32.973.26-018.2 Разработка приложений 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­329­ ДМК Пресс, 2016 Х41 Х41 Дэвид Хеффельфингер 1 (рус.) © Оформление, перевод на русский язык, 4/35
Оглавление Об авторе ........................................................ 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 5/35
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 6/35
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 7/35
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 8/35
9 Оглавление Создание веб-службы из существующего файла WSDL ....................... 338 Резюме ....................................................................................... 339 предметный указатель .................................... 341 9/35
Об автОре Дэвид Хеффельфингер (David R. Heffelfinger) – технический ди­ ректор Ensode Technology LLC – консалтинговой компании, спе­ циализирующейся на разработке программного обеспечения и расположенной в районе большого Вашингтона, округ Колумбия. Дэ­ вид – профессиональный архитектор, проектировщик и разработчик программного обеспечения с 1995 года и использует Java в качестве основного языка программирования с 1996 года. Работал во многих крупных проектах для ряда клиентов, в числе которых департамент США по Национальной безопасности, Freddie Mac, Fannie Mae и Министерство обороны США. Имеет степень магистра в области раз­ работки программного обеспечения Южного методического универ­ ситета. Также является главным редактором Ensode.net (http://www. ensode.net), веб­сайта, посвященного Java, Linux и другим технологи­ ям. Часто выступает на конференциях Java­разработчиков, таких как JavaOne. Вы можете следовать за Дэвидом в Твиттере, его учетная за­ пись: @ensode. 10/35
О рецензентах Саурабх Чхаджед (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) с квали­ 11/35
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. Обучаясь в аспирантуре, он О рецензентах 12/35
13 также работал репетитором в одном из 100 лучших университетов в мире, где преподавал Java студентам начальных курсов и привле­ кался к приему экзаменов и оценке дипломных проектов. Свой про­ фессиональный опыт Манжит приобрел в работе над несколькими ответственными проектами ПО для обслуживания клиентов в сфере финансов, телекоммуникационных услуг, розничной торговли и в го­ сударственных учреждениях. Я очень благодарен своим родителям; моей жене Джаспал (Jaspal); моему сыну Кохинуру (Kohinoor); и моей дочери Прабхнур (Prabhnoor), за их поддержку и терпение, когда я, занимаясь рецен- зированием этой книги, оторвал от семьи несколько моих вечеров и выходных. О рецензентах 13/35
предислОвие 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 . 14/35
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). 15/35
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; 16/35
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.дмк. рф в разделе «Читателям – Файлы к книгам». 17/35
18 Предисловие Список опечаток Хотя мы приняли все возможные меры для того, чтобы удостоверить­ ся в качестве наших текстов, ошибки всё равно случаются. Если вы найдёте ошибку в одной из наших книг – возможно, ошибку в тексте или в коде – мы будем очень благодарны, если вы сообщите нам о ней. Сделав это, вы избавите других читателей от расстройств и поможете нам улучшить последующие версии этой книги. Если вы найдёте какие­либо ошибки в коде, пожалуйста, сообщите о них главному редактору по адресу dmkpress@gmail.com, и мы ис­ правим это в следующих тиражах. Нарушение авторских прав Пиратство в Интернете по­прежнему остается насущной проблемой. Издательства «ДМК Пресс» и «Packt» очень серьезно относятся к вопросам защиты авторских прав и лицензирования. Если вы стол­ кнетесь в Интернете с незаконно выполненной копией любой нашей книги, пожалуйста, сообщите нам адрес копии или веб­сайта, чтобы мы могли принять меры. Пожалуйста, свяжитесь с нами по адресу электронной почты dmkpress@gmail.com со ссылкой на подозрительные материалы. Мы высоко ценим любую помощь по защите наших авторов, и по­ могающую нам предоставлять вам качественные материалы. Вопросы Вы можете присылать любые вопросы, касающиеся данной книги, по адресу dm@dmk­press.ru или questions@packtpub.com . Мы постараем­ ся разрешить возникшие проблемы. 18/35
глава 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. 19/35
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 перечислены 20/35
21 Получение NetBeans некоторые комплекты 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 21/35
22 Глава 1. Знакомство с NetBeans Рис. 1.2. Страница со списком дистрибутивов NetBeans Для опробования примеров, приведенных в этой книге, необходим комплект Java EE или All. Все снимки экрана в этой книге были сделаны при использовании комплекта Java EE. В комплектации All NetBeans может выглядеть немного иначе, в частности можно заметить появление некоторых дополнительных пунктов меню. Официально поддерживаются следующие платформы: • Windows; • Linux (x86/x64); • MacOSX. Дополнительно NetBeans может выполняться на любой платфор­ ме, где установлена версия Java 7 или выше. Также доступна для за­ грузки версия NetBeans, не зависящая от операционной системы, ко­ торая будет выполняться на любой из этих платформ. 22/35
23 Установка NetBeans Даже при том, что версия 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. 23/35
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. 24/35
25 Установка NetBeans Другие платформы NetBeans для других платформ можно загрузить в виде независи­ мого от платформы ZIP­файла с именем, похожим на: netbeans-8.0- 201403101706-javaee.zip (точное имя файла может измениться в зависимости от конкретной версии и комплектности NetBeans, вы­ бранной для загрузки). Чтобы установить NetBeans на одной из этих платформ, извлеките файлы из ZIP­архива в любой подходящий каталог. Процедура установки Несмотря на то, что на разных платформах установка запускается по­ разному, сам процесс установки мало чем отличается. Исключением из этого правила является установка из ZIP-файла, в котором, по сути, отсутствует программа-установщик. Установка этой версии NetBeans заключается в простом извлечении файлов из архива в любой подходящий каталог. После запуска программы установки NetBeans на экране должно появиться окно, как показано на рис. 1 .3 . Рис. 1 .3. Начальное окно мастера установки NetBeans 25/35
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). 26/35
27 Установка NetBeans Рис. 1 .5 . Диалог соглашения с условиями лицензирования JUnit Рис. 1 .6 . Выбор каталога установки NetBeans и каталога JDK 27/35
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. – П рим. перев. 28/35
29 Установка NetBeans установки. И снова здесь можно указать свой каталог или принять значение по умолчанию и щелкнуть на кнопке Next> (Далее>). Рис. 1.8. Выбор каталога установки Tomcat Рис. 1 .9. Сводная информация о выбранных компонентах для установки 29/35
30 Глава 1. Знакомство с NetBeans На этом этапе мастер выведет на экран сводную информацию о вы­ бранных компонентах для установки (см. рис. 1.9). После ознакомле­ ния со списком щелкните на кнопке Install (Установить), чтобы на­ чать собственно установку. Рис. 1 .10. Процесс установки NetBeans и дополнительных компонентов После этого начнется установка. Мастер выведет на экран индика­ тор выполнения (см. рис. 1 .10), указывающий, как далеко продвинул­ ся процесс установки. Рис. 1.11 . Предложение ввести данные об использовании 30/35
31 Первый запуск NetBeans После того как NetBeans и все выбранные компоненты будут уста­ новлены, мастер сообщит об успешной установке и предоставит воз­ можность ввести анонимные данные об использовании продукта (рис. 1 .11). После того как мы сделаем наш вы­ бор, можно просто щелкнуть на кнопке Finish (Готово), чтобы завершить работу мастера. В большинстве платформ мастер установки поместит ярлык NetBeans на рабочий стол (см. рис. 1 .12). Выполнив двойной щелчок на этом ярлыке можно запустить NetBeans. первый запуск NetBeans Запустить NetBeans можно двойным щелчком на его ярлыке на ра­ бочем столе, после чего появится экранная заставка, которая будет отображаться в течение всего времени запуска (см. рис. 1 .13). Рис. 1 .13. Экранная заставка, появляющаяся в момент запуска NetBeans Как только NetBeans запустится, появится страница со ссылками на демонстрационные примеры, учебные пособия, образцы проектов и т. д. (см. рис. 1.14). Рис. 1 .12. Ярлык NetBeans на рабочем столе 31/35
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, что в общем­то означает – с любой СУРБД. 32/35
33 Настройка NetBeans для разработки Java EE-приложений Интегрирование NetBeans со сторонним сервером приложений Интегрировать NetBeans с сервером приложений очень просто. Для этого нужно выполнить следующие действия: В этом разделе демонстрируется, как интегрировать NetBeans с JBoss, однако интеграция с другими серверами приложений или контейнерами сервлетов выполняется аналогично. 1. Прежде всего выберите элемент меню Window | Services (Окно | Службы) (см. рис. 1 .15). Рис. 1 .15. Элемент меню Window | Services (Окно | Службы) 2. Затем щелкните правой кнопкой мыши на узле Servers (Сер­ веры) в дереве окна Services (Службы) и выберите в контекст­ ном меню пункт Add Server... (Добавить сервер...) . Рис. 1.16. Элемент контекстного меню Add Server... (Добавить сервер...) 33/35
34 Глава 1. Знакомство с NetBeans 3. В открывшемся окне выберите из списка сервер для установки и щелкните на кнопке Next> (Далее>). Рис. 1 .17 . Выбор сервера из списка 4. Введите путь к каталогу установки сервера приложений (см. рис. 1 .18) и щелкните на кнопке Next> (Далее>). Рис. 1.18. Ввод каталога для установки сервера 34/35
35 Настройка NetBeans для разработки Java EE-приложений 5. Наконец, укажите домен, имя хоста и номер порта для серве­ ра приложений (см. рис. 1 .19), после чего щелкните на кнопке Finish (Готово). Рис. 1 .19. Ввод домена, имени хоста и номера порта для сервера приложений В окне Services (Службы) теперь должен отображаться вновь добавленный сервер приложений (см. рис. 1 .20). Рис. 1.20. Вновь добавленный сервер приложений в окне Services (Службы) Вот и все! Мы успешно интегрировали NetBeans со сторонним сервером приложений. P owe red by T CP DF (www.tcp df.o rg) 35/35
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 «угадывает» имя класса 1/35
37 Настройка NetBeans для разработки Java EE-приложений драйвера, содержащего драйвер JDBC. Если в JAR­файле находит­ ся более чем один класс драйвера, правильный драйвер может быть выбран из раскрывающегося списке с названием Driver Class (Класс драйвера). Сделав выбор, щелкните на кнопке OK, чтобы добавить драйвер к NetBeans. Рис. 1 .22. Выбор драйвера СУРБД После выполнения вышеописанной процедуры, новый драй­ вер JDBC появится в списке зарегистрированных драйверов (см. рис. 1 .23). Рис. 1 .23. Новый драйвер в списке зарегистрированных драйверов 2/35
38 Глава 1. Знакомство с NetBeans Соединение со сторонней СУРБД После добавления драйвера JDBC в NetBeans все готово к установ­ ке соединения со сторонней СУРБД. Чтобы установить соединение с СУРБД стороннего производите­ ля, щелкните правой кнопкой мыши на драйвере во вкладке Services (Службы), в открывшемся контекстном меню выберите элемент Connect Using... (Установить соединение с использованием...) . Рис. 1 .24. Элемент контекстного меню Connect Using... (Установить соединение с использованием...) Введите URL JDBC, имя пользователя и пароль для базы данных (см. рис. 1 .25). Рис. 1 .25. Настройка параметров соединения 3/35
39 Настройка NetBeans для разработки Java EE-приложений После щелчка на кнопке Next> (Далее>) NetBeans может попро­ сить выбрать схему базы данных. В данном случае (рис. 1.26) в рас­ крывающемся списке была выбрана схема PUBLIC. Рис. 1.26. Выбор схемы базы данных На следующем шаге (см. рис. 1 .27) мастер настройки предложит ввести строку подключения к базе данных или принять значение по умолчанию. Рис. 1 .27. Ввод строки подключения к базе данных 4/35
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­компонент платфор­ 5/35
41 мы JavaServer Faces (Java EE 7)) (см. рис. 1 .29). Этот проект являет­ ся простым примером использования фреймворка JSF и механизма внедрения зависимостей Contexts and Dependency Injection (CDI). Рис. 1 .29. Выбор проекта примера После щелчка на кнопке Next> (Далее>) будет предложено ввести местоположение проекта (см. рис. 1 .30). Значения по умолчанию в данном случае нас вполне устраивают и их можно оставить. Рис. 1.30 . Выбор местоположения для сохранения проекта Развертывание нашего первого приложения 6/35
42 Глава 1. Знакомство с NetBeans После щелчка на кнопке Finish (Готово) наш новый проект появит­ ся (см. рис. 1 .31) в окне Projects (Проекты). Рис. 1 .31. Новый проект появился в окне Projects (Проекты) Мы можем скомпилировать, упаковать и развернуть проект в одно действие, щелкнув на нем правой кнопкой мыши и выбрав пункт Run (Выполнить) в открывшемся контекстном меню (см. рис. 1.32). Рис. 1.32. Запуск компиляции, упаковки и развертывания проекта в одно действие На этом этапе мы должны увидеть результат работы сценария сборки (см. рис. 1 .33). Кроме того, автоматически должны запустить­ ся интегрированный сервер приложений GlassFish и интегрирован­ ная СУРБД JavaDB. Рис. 1 .33. Результаты работы сценария сборки 7/35
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 еще раз. Подсказки NetBeans для эффективной разработки 8/35
44 Глава 1. Знакомство с NetBeans Рис. 1 .35. Страница по умолчанию для приложения-примера Как видно на рис. 1 .35, для выбранного варианта автозавершения NetBeans выводит на экран его описание JavaDoc. Другой экономя­ щей время функцией является автоматическое импортирование вы­ бранного класса. После выбора типа переменной можно снова нажать комбинацию Ctrl+Space, прямо после имени типа, и NetBeans предложит имена переменных (см. рис. 1.36). Рис. 1.36. Функция автозавершения может предложить на выбор имена переменных 9/35
45 Чтобы инициализировать переменную новым значением, можно снова нажать комбинацию Ctrl+Space и на экране появится список допустимых типов в качестве вариантов для завершения кода, как по­ казано на рис. 1 .37. Рис. 1.37. Список допустимых типов для инициализации переменной В нашем примере тип (java.util.List) является интерфейсом, по­ этому в качестве возможных кандидатов на автозавершение кода бу­ дут показаны все классы, реализующие этот интерфейс. Если бы тип нашей переменной был классом, в качестве кандидатов на автозавер­ шение были бы показаны и наш класс, и все его подклассы. Когда далее в коде потребуется использовать эту переменную, можно просто ввести первые несколько символов ее имени и нажать комбинацию Ctrl+Space, как показано на рис. 1 .38 . Чтобы вызвать метод объекта, достаточно ввести точку в конце имени переменной и все доступные методы будут выведены на экран, как варианты завершения кода, как показано на рис. 1 .39 . Обратите внимание, что для выбранного метода на экран автома­ тически выводится описание JavaDoc. Подсказки NetBeans для эффективной разработки 10/35
46 Глава 1. Знакомство с NetBeans Рис. 1 .38 . Список подходящих имен переменных Рис. 1 .39 . Список доступных методов 11/35
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) { }else{ } Генерирует условный оператор if-else. psvm public static void main(String[] args) { } Генерирует метод main для класса. soutv System.out.println("boolVar = " + boolVar); Генерирует инструк- цию System.out. println(), выводя- щую на экран значение переменной trycatch try { } catch (Exception exception) { } Генерирует блок try/ catch. Подсказки NetBeans для эффективной разработки 12/35
48 Глава 1. Знакомство с NetBeans Сокра- щение пример развернутого текста Описание whileit while (iterator.hasNext()) { Object object = iterator.next(); } Генерирует цикл while для перебора итерато- ра (Iterator) Чтобы увидеть полный список шаблонов кода, выберите в главном меню пункт Tools | Options (Сервис | Параметры), затем щелкните на значке Editor (Редактор) и выберите вкладку Code Templates (Ша­ блоны кода), как показано на рис. 1 .40. Рис. 1 .40. Список шаблонов кода Мы можем добавлять свои шаблоны, щелкая на кнопке New (Но­ вый). После щелчка будет предложено ввести сокращение (аббревиа­ туру) шаблона, после чего новый шаблон будет добавлен в список шаблонов и автоматически выбран. Теперь можно ввести текст рас­ ширения для шаблона во вкладке Expanded Text (Раскрытый текст). Не мешает упомянуть о том, что шаблоны кода поддерживаются не только для Java, но также для HTML, CSS и всех других языков, под­ держиваемых в NetBeans. Чтобы увидеть/отредактировать шаблоны 13/35
49 для других языков, просто выберите нужный язык в раскрывающемся меню Language (Язык) во вкладке Code Templates (Шаблоны кода), как показано на рис. 1 .41 . Рис. 1 .41 . Выбор языка для получения списка шаблонов кода Клавиши быстрого вызова NetBeans предлагает несколько комбинаций клавиш для быстрой на­ вигации между файлами исходного кода. Эти комбинации позволяют разрабатывать код намного эффективнее, чем в случае, когда мы це­ ликом полагаемся на мышь. Некоторые из самых полезных комбинаций клавиш NetBeans пере- числены в этом разделе, но этот список не является исчерпываю- щим, полный список комбинаций клавиш NetBeans можно найти, выбрав пункт меню Help | Keyboard Shortcuts Card (Справка | Таб- лица сочетаний клавиш). Одной из полезных комбинаций клавиш, позволяющей быстро пе­ ремещаться в пределах большого файла Java, является Ctrl+F12. Эта Подсказки NetBeans для эффективной разработки 14/35
50 Глава 1. Знакомство с NetBeans комбинация передает фокус ввода в окно Navigator (Навигатор), где отображается схема текущего файла Java со всеми мето­ дами в нем и переменными­членами (см. рис. 1 .42). Когда фокус ввода находится в окне Navigator (Навигатор), можно просто начать ввод искомого имени, чтобы со­ кратить список методов и переменных­ членов. Эта комбинация клавиш работает очень быстро и довольно удобна для пере­ мещения по большим файлам. Нажатие на Alt+F12 откроет окно Hierarhy (Иерархия) представляющее по­ ложение текущего класса Java в иерархии классов, как показано на рис. 1 .43. Рис. 1 .43. Окно Hierarhy (Иерархия) с иерархией классов Эту комбинацию можно использовать для быстрого перемещения к суперклас­ су или подклассу текущего класса. Другой полезной комбинацией яв­ ляется Alt+Insert, она может исполь­ зоваться для вставки часто использу­ емого кода, такого как определение конструктора, методов get и set и т. д. (см. рис. 1 .44). Код будет сгенерирован, начиная с те­ кущей позиции текстового курсора. Кроме того, когда курсор находится непосредственно за от открывающей или закрывающей фигурной скобкой, комбинация Ctrl+[ переместит резуль­ Рис. 1 .42. Выбор языка для получения списка шаблонов кода Рис. 1 .44. Окно Generate (Создать) для выбора соз- даваемого элемента 15/35
51 тат ввода внутрь соответствующей пары скобок. Эта комбинация ра­ ботает для фигурных, круглых и квадратных скобок. Нажатие комби­ нации Ctrl+Shift+[ имеет подобный эффект, но не только помещает ввод в соответствующую пару фигурных скобок, а еще и выбирает вставленный код (см. рис. 1 .45). Рис. 1 .45. После вставки код автоматически выбирается Иногда нужно узнать все точки в проекте, где вызывается опреде­ ленный метод. Мы легко можем получить эту информацию, выделив метод и нажав комбинацию Alt+F7 (см. рис. 1 .46). Рис. 1.46. Список мест в проекте, где вызывается интересующий метод Данная комбинация работает также с переменными. NetBeans указывает на ошибки компиляции в коде, подчеркивая неправильные строки волнистой красной линией. Установив курсор в пределы неправильного кода, и нажав комбинацию Alt+Enter, можно выбрать подходящий вариант исправления проблемы из списка (см. рис. 1 .47). Рис. 1 .47. Ошибочный код и варианты его исправления Иногда навигация по всем файлам в проекте может быть затруд­ нена, особенно если известно имя файла, который нужно открыть, но Подсказки NetBeans для эффективной разработки 16/35
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 превра­ тит метод или переменную в гиперссылку. Щелчок на этой гипер­ ссылке откроет объявление метода или переменной. 17/35
53 Изучение визуальных индикаторов NetBeans В дополнение к комбинациям «быстрых» клавиш, шаблонов и функ­ ции автозавершения кода, NetBeans предлагает много визуальных индикаторов, которые позволяют лучше понять код с первого взгля­ да. Некоторые из наиболее полезных показаны на рис. 1 .49. Рис. 1 .49. Визуальные индикаторы в редакторе NetBeans О предупреждениях компилятора NetBeans сообщает двумя спо­ собами: подчеркивает строку волнистой желтой линией и помещает значок в поле слева от неправильной строки. Лампочка в значке указывает, что у NetBeans есть предложения относительно решения проблемы. Если установить текстовый кур­ сор в пределы неправильного кода и нажать комбинацию Alt+Enter, это, как было описано в предыдущем разделе, приведет к тому, что NetBeans предложит один или более вариантов решения. Точно так же, при обнаружении ошибки компиляции, NetBeans подчеркнет неправильную строку красной волнистой линией и по­ местит значок в поле слева от строки с ошибкой. И вновь лампочка указывает, что у NetBeans есть предложения относительно решения проблемы, нажатие комбинации клавиш Alt+Enter в этом случае позволит увидеть эти предложения. Подсказки NetBeans для эффективной разработки 18/35
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. Для 19/35
55 ее проверки выберите встроенный браузер WebKit и запустите при­ ложение, развернутое нами выше в этой главе в разделе «Разверты­ вание нашего первого приложения». Приложение запустится в окне NetBeans, если используется встроенный браузер (см. рис. 1 .51). Рис. 1.50. Выбор браузера с коннектором NetBeans Рис. 1.51. При использовании встроенного браузера, приложение запустится в окне NetBeans Функция ускорения разработки HTML5 20/35
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> 21/35
57 Сохраните файл и, не выполняя повторного развертывания прило­ жения или обновления страницы, вернитесь в окно встроенного бра­ узера. Изменения должны отобразиться на странице (см. рис. 1.52). Рис. 1 .52 . Изменения автоматически отобразились встроенным браузером Чтобы функция ускорения разработки HTML5 заработала в Chrome, нужно в этом браузере установить расширение NetBeans Connector. Если выбрать Chrome, как веб­браузер (в разделе With NetBeans Connector (С коннектором NetBeans)) и попытаться запус­ тить приложение, NetBeans предложит установить упомянутое рас­ ширение (см. рис. 1 .53). Рис. 1.53. NetBeans предложит установить расширение NetBeans Connector Функция ускорения разработки HTML5 22/35
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 23/35
59 Щелчок на кнопке Add (Добавить) автоматически установит рас­ ширение. После этого можно запустить проект в браузере Chrome и любые изменения в разметке будут немедленно отражаться в браузе­ ре (см.рис. 1 .56). Рис. 1 .56. После установки расширения Chrome будет немедленно отображать любые изменения в разметке Как показано на рис. 1.56, когда приложение запущено через кон­ нектор NetBeans, браузер Chrome отображает сообщение, предупреж­ дающее об этом факте. Резюме В этой главе мы узнали, как установить NetBeans. Мы также узнали, как настроить NetBeans на работу со сторонни­ ми серверами приложений Java EE и с системами реляционных баз данных от сторонних производителей, включая регистрацию драйве­ ра JDBC для рассматриваемой СУРБД (RDBMS). Также мы создали и развернули наше первое приложение Java EE, использовав один из демонстрационных проектов, поставляемых вместе с NetBeans. Наконец мы рассмотрели некоторые возможности NetBeans, такие как автозавершение кода, шаблоны кода, комбинации клавиш и визу­ альные индикаторы, позволяющие нам, разработчикам программного обеспечения, выполнять свою работу более эффективно. Резюме 24/35
глава 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. 25/35
61 Разработка нашего первого приложения JSF Большое разнообразие веб­фреймворков часто приводит к «ана­ литическому параличу», то есть разработчики нередко тратят уйму времени на оценку фреймворков для своих приложений. В результате введения 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) можно выбрать фреймворки для использования в веб­приложении. 26/35
62 Глава 2. Разработка веб-приложений с использованием JavaServer. . . Рис. 2.1 . Ввод названия проекта и другой информации (если необходимо) Рис. 2.2 . Выбор сервера, версии Java EE и пути контекста приложения Неудивительно, что для приложений JSF мы выбрали фреймворк JavaServer Faces. 27/35
63 Разработка нашего первого приложения JSF Рис. 2 .3. Выбор используемых фреймворков После щелчка на кнопке Finish (Готово) мастер сгенерирует скелет JSF­проекта, состоящий из единственного файла фейс­ лета с именем index.xhtml и конфигураци­ онного файла web.xml (см. рис. 2 .4). Файл web.xml является стандартным конфигурационным файлом для веб­ приложений на Java. Этот файл стал не­ обязательным в версии 3.0 Servlet API, ко­ торый был введен в 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> Рис. 2 .4. Содержимое скелета нового проекта 28/35
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 (модульное тестирование). 29/35
65 Разработка нашего первого приложения JSF Как упоминалось выше, этап 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> 30/35
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 . Браузер с открытой главной страницей приложения Добавление в страницу возможности ввода данных Сгенерированное приложение, конечно, является всего лишь отправ­ ной точкой для создания нового приложения. Далее мы изменим сге­ 31/35
67 Разработка нашего первого приложения JSF нерированный файл 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> 32/35
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 показано, как будет выглядеть страница во время вы­ полнения приложения. 33/35
69 Разработка нашего первого приложения JSF Рис. 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, к пятому столбцу – второй, и т. д . 34/35
70 Глава 2. Разработка веб-приложений с использованием JavaServer. . . Если нужно определить стили для строк, сделать это можно с по­ мощью его атрибута rowClasses, который работает точно так же, как columnClasses для столбцов. Обратите внимание на тег <h:outputStylesheet> внутри <h:head> в начале страницы. Этот тег был введен в JSF 2.0 . Еще одна новая воз­ можность, которая появилась в JSF 2.0, предоставляет таблице доступ к ката­ логу стандартных ресурсов. Ресурсы (такие как таблицы стилей CSS, файлы JavaScript и изображения) можно поме­ стить в каталог верхнего уровня с име­ нем resources, и теги JSF автоматически получат доступ к этим ресурсам. В дан­ ном проекте каталог resources следует поместить в папку 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 со ­ ответствующего поля ввода. Рис. 2 .8. Каталог resources в папке Web Pages (Веб-страницы) P owe red by T CP DF (www.tcp df.o rg) 35/35
71 Разработка нашего первого приложения JSF Первая строка в нашей сетке содержит тег <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 будет содержать значение, отправлен­ ное серверу с формой. 1/35
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, воз­ вращающий строку. Данный метод может возвращать разные значе­ ния, исходя из определенных условий. То есть, переход будет выпол­ нен к странице, имя которой возвращается методом. 2/35
73 Разработка нашего первого приложения JSF Метод именованного компонента 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). Если потребуется ис­ пользовать другое имя, это можно сделать, указав новое имя в атри­ 3/35
74 Глава 2. Разработка веб-приложений с использованием JavaServer. . . буте value аннотации @Named. Вообще говоря, при следовании согла­ шениям по умолчанию получается более читаемый и более простой в сопровождении код; поэтому не следует отклоняться от них, если на то нет веских причин. Рис. 2 .9. Пункт контекстного меню New | Java Class... (Создать | Класс Java...) Именованные компоненты CDI могут иметь разные контексты. Контекст запроса означает, что компонент доступен только в преде­ лах одного HTTP­запроса. Контекст сеанса означает, что компонент доступен в пределах HTTP­сеанса для одного пользователя. Контекст диалога (conversation) означает, что компонент доступен в границах последовательности HTTP­запросов. Именованный компонент CDI может также иметь контекст при­ ложения, когда он доступен всем пользователям приложения. Также именованный компонент CDI может иметь зависимый псевдокон­ текст – такие компоненты создаются по мере необходимости. Наконец, именованный компонент CDI может иметь контекст по­ тока. В этом случае компонент доступен только в рамках определен­ ного JSF­потока (обсуждаются далее в этой главе). Чтобы определить контекст именованного компонента CDI, необходимо добавить соот­ ветствующую аннотацию. 4/35
75 Разработка нашего первого приложения JSF Рис. 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; 5/35
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; // методы получения и установки для краткости опущены } Обратите внимание, что имена всех свойств компонента (перемен­ ные экземпляра) соответствуют именам, которые использовались в 6/35
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), чтобы статическая навигация работала должным образом. Разработка нашего первого приложения JSF 7/35
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 страницы. 8/35
79 После ввода некоторых данных на странице она должна будет вы­ глядеть, как показано на рис. 2 .12 . Рис. 2 .12. Страница ввода данных После щелчка на кнопке Register (Зарегистрировать) наш име­ нованный компонент RegistrationBean заполнится значениями, вве­ денными в форму. Каждое свойство компонента получит значение из соответствующего поля ввода, как определено выражением связыва­ ния. Здесь же «сработает» механизм навигации JSF и браузер будет от­ правлен к странице подтверждения (см. рис. 2 .13). Рис. 2 .13. Страница подтверждения Значения, отображаемые на странице подтверждения, получены из именованного компонента, чем подтверждается, что его свойства были заполнены правильно. Разработка нашего первого приложения JSF 9/35
80 Глава 2. Разработка веб-приложений с использованием JavaServer. . . Проверка допустимости в JSF Ранее в этой главе рассказывалось, как атрибут required полей ввода JSF позволяет объявлять их обязательными для заполнения. Если пользователь пытается отправить форму, не заполнив одно или более обязательных полей, автоматически будет сгенерировано сообщение об ошибке (см. рис. 2 .14). Рис. 2.14 . Если не заполнить одно или более обязательных полей, автоматически генерируется сообщение об ошибке Сообщение об ошибке генерируется тегом <h:message>, соответ­ ствующим недопустимому полю. Слова «First Name» в сообщении об ошибке соответствуют значению атрибута label поля, если бы мы опустили атрибут label, вместо него в текст сообщения было бы встав­ лено значение атрибута id. Как видите, атрибут required существенно упрощает реализацию функциональности обязательного поля. Напомним, что поле age (возраст) связано со свойством типа Integer нашего именованного компонента. Если пользователь вве­ дет в это поле значение, не являющееся допустимым целым числом, автоматически будет сгенерировано сообщение об ошибке проверки допустимости (см. рис. 2 .15). Рис. 2.15. Сообщение об ошибке, выявленной при проверке допустимости значения 10/35
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(); Разработка нашего первого приложения JSF 11/35
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. Закончив реализацию валидатора, его можно использовать в на­ ших страницах. В данном конкретном случае, чтобы задействовать наш нестан­ дартный валидатор, нужно изменить поле электронной почты: 12/35
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­страницами является наличие собственного механизма обработки шаблонов. Шаблоны по­ зволяют определить макет страницы в одном месте и использовать его во множестве клиентских страниц. Поскольку в веб­приложениях часто используется некий единый макет страниц, использование ша­ Шаблоны фейслетов 13/35
84 Глава 2. Разработка веб-приложений с использованием JavaServer. . . блонов делает приложения намного более удобными в сопровожде­ нии, поскольку изменения в макете должны производиться лишь в одном месте. Если вдруг понадобится изменить макет (например, до­ бавить нижний колонтитул, или переместить столбец с левой сторо­ ны страницы в правую), достаточно будет изменить только шаблон, и произведенные изменения будут отражены во всех клиентах шаблона. Добавление шаблона фейслетов Чтобы добавить новый шаблон в проект, нужно выбрать пункт File | New File... (Файл | Создать файл...) в главном меню, затем в от­ крывшемся диалоге выбрать категорию JavaServer Faces (Приложе­ ние JavaServer Faces) и затем выбрать тип файлов Facelets Template (Шаблон Facelets), как показано на рис. 2 .17 . Рис. 2.17 . Создание шаблона фейслета NetBeans предоставляет замечательную поддержку шаблонов фейслетов и включает несколько готовых шаблонов типовых маке­ тов веб­страниц. Мы можем выбрать один предопределенных шаблонов (см. рис. 2 .18) и использовать его в качестве основы для своего шаблона или как «готовый продукт» («out of the box»). 14/35
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> Шаблоны фейслетов 15/35
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), чтобы эти разделы не генерировались в кли­ енте шаблона. 16/35
87 Рис. 2 .19. Создание клиента шаблона фейслета Рис. 2.20. Выбор шаблона для использования клиентом После щелчка на кнопке Finish (Готово) будет создан клиент ша­ блона: Шаблоны фейслетов 17/35
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" 18/35
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> Шаблоны фейслетов 19/35
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 . Она основана на шабло­ нах фейслетов и позволяет создавать веб­приложения со «сменным» оформлением. Например, приложение, обслуживающее множество клиентов, можно написать так, что после регистрации каждый клиент будет видеть собственное оформление, с логотипом своей компании. Как вариант, можно дать пользователям возможность выбирать из 20/35
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 «как есть» (снимки экрана не показаны). Контракты библиотек ресурсов 21/35
92 Глава 2. Разработка веб-приложений с использованием JavaServer. . . Рис. 2 .23. Создание контракта библиотеки ресурсов Рис. 2.24. Настройка нового контракта библиотеки ресурсов После создания контрактов библиотеки ресурсов NetBeans создаст соответствующие файлы в каталоге contracts (см. рис. 2 .25). 22/35
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. Контракты библиотек ресурсов 23/35
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. Так выглядит темное оформление 24/35
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" Контракты библиотек ресурсов 25/35
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 (Файл | Создать файл), затем в открывшемся ди­ 26/35
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> Составные компоненты 27/35
98 Глава 2. Разработка веб-приложений с использованием JavaServer. . . </cc:interface> <!-- РЕАЛИЗАЦИЯ --> <cc:implementation> </cc:implementation> </html> Каждый составной компонент JSF 2 содержит два раздела: интер­ фейс и реализацию. Рис. 2.30 . Настройка составного компонента Раздел интерфейса должен быть заключен в тег <cc:interface>. В интерфейсе определяются любые атрибуты компонента. Раздел ре­ ализации должен содержать разметку для отображения при исполь­ зовании составного компонента. В данном примере мы создадим простой компонент, который мож­ но использовать для ввода адресов. То есть, если в приложении по­ требуется организовать ввод адресов, логику и/или представление можно инкапсулировать в составном компоненте. Если позднее по­ надобится изменить ввод адреса (например, для поддержки междуна­ родных адресов), достаточно будет изменить только сам компонент, а все формы ввода адреса в приложении будут обновлены автомати­ чески. После «устранения белых пятен» наш составной компонент теперь выглядит так: 28/35
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 компонента должен ссылаться Составные компоненты 29/35
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" – это зна­ чение будет отображаться в заголовке компонента ввода адреса. 30/35
101 После развертывания и запуска приложения можно увидеть ком­ понент в действии (см. рис. 2 .31) Рис. 2.31. Составной компонент ввода адреса в действии Как видите, создание составных компонентов JSF 2 в NetBeans яв­ ляется очень простым делом. потоки Faces Flow Традиционные веб­приложения не хранят информацию о состоянии, то есть, страница, только что загруженная браузером, не имеет ни малейшего представления о том, с какими данными работал пользо­ ватель на предыдущих страницах. Веб­фреймворки на Java решают эту врожденную проблему веб­приложений, сохраняя информацию о состоянии на сервере и связывая разные классы Java с разными кон­ текстами приложения. В JSF это делается путем применения соот­ ветствующих аннотаций к именованным компонентам CDI, как опи­ сывалось выше в этой главе. Если потребуется организовать совместное использование данных исключительно в двух страницах, следующих друг за другом, доста­ точно задействовать контекст запроса. Если потребуется обеспечить доступность данных во всех страницах в приложении, можно вос­ пользоваться контекстом сеанса. Но, как быть, если данные должны быть доступны трем или более страницам, но не всем страницам в приложении? Прежде у нас не было подходящего для этого контек­ ста, но в версии JSF 2.2 появился контекст потока (flow scope). Так как страницы в потоке (или последовательности) связаны друг с другом, все они должны быть помещены в один подкаталог. В соот­ ветствии с соглашениями, имя подкаталога служит именем потока. Потоки Faces Flow 31/35
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. 32/35
103 Рис. 2.33. Выбор типа Folder (Папка) в категории Other (Прочее) Рис. 2.34. Назначение имени папке для хранения страниц, составляющих поток Потоки Faces Flow 33/35
104 Глава 2. Разработка веб-приложений с использованием JavaServer. . . Рис. 2 .35. Выбор типа Empty File (Пустой файл) в категории Other (Прочее) Далее нужно дать файлу правильное имя, гарантировав его разме­ щение в каталоге потока (см. рис. 2 .36). Рис. 2.36 . Назначение имени файлу конфигурационному 34/35
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, которые обе­ спечивают вызов отмеченных ими методов сразу после создания ком­ понента и непосредственно перед его удалением, соответственно. Мы добавили их, чтобы подтвердить, что именованный компонент с кон­ Потоки Faces Flow P owe red by T CP DF (www.tcp df.o rg) 35/35
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). 1/35
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"/> Потоки Faces Flow 2/35
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 в категории We b (Веб), как показано на рис. 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" 3/35
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"> Поддержка HTML5 4/35
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 (Веб) 5/35
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. Вмес­ то того, чтобы реализовать поддержку новых атрибутов, команда Поддержка HTML5 6/35
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" 7/35
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. Резюме 8/35
глава 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...»). Подождите не- сколько секунд и сообщение должно исчезнуть. 9/35
115 Использование компонентов PrimeFaces в JSF-приложениях Рис. 3 .1 . Выбор библиотеки компонентов PrimaFaces NetBeans немедленно сгенерирует приложение PrimeFaces, кото­ рое можно использовать как отправную точку. Вновь созданное при­ ложение можно запустить, чтобы посмотреть, как оно выглядит (см. рис. 3 .2 .). Рис. 3 .2 . Вновь созданное приложение PrimaFaces 10/35
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" 11/35
117 Использование компонентов PrimeFaces в JSF-приложениях 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 (центр), раздел будет отобра­ жаться в центре страницы. По высоте и ширине раздел авто­ матически будет охватывать все доступное пространство. 12/35
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­аналогами. Обратите внимание, что к надписям рядом с полями, обязательными для заполнения, автома­ тически добавляется символ звездочки. 13/35
119 Использование компонентов PrimeFaces в JSF-приложениях Рис. 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" 14/35
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­ 15/35
121 Использование компонентов ICEfaces в JSF-приложениях приложении, нужно создать проект обычного веб­приложения. При выборе фреймворка 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 16/35
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. Дайте библиотеке подходящее имя 17/35
123 Использование компонентов ICEfaces в JSF-приложениях После щелчка на кнопке OK нужно найти JAR­файлы ICEfaces в файловой системе, щелкнув на кнопке Add JAR/Folder... (Добавить JAR/папку...), и добавить их в библиотеку (см. рис. 3 .9). Рис. 3.9 . Добавление в библиотеку JAR-файлов ICEfaces После щелчка на кнопке OK NetBeans создаст библиотеку ICEfaces. Рис. 3.10. Завершение создания библиотеки ICEfaces Теперь щелкните на кнопке OK и Finish (Готово) в мастере нового проекта, чтобы завершить его создание. 18/35
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" 19/35
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. Использование компонентов ICEfaces в JSF-приложениях 20/35
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"/> 21/35
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. Оформление сообщений об ошибках Использование компонентов ICEfaces в JSF-приложениях 22/35
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 23/35
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> Использование компонентов RichFaces в JSF-приложениях 24/35
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> 25/35
131 </ul> </rich:panel> </h:body> </html> Единственный тег RichFaces, использованный здесь, – это <rich:panel>, который создает панель с текстом и ссылками на стра­ ницы. Рис. 3.16. Диалог создания библиотеки RichFaces Рис. 3.17 . Вновь созданное приложение RichFaces Использование компонентов RichFaces в JSF-приложениях 26/35
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" 27/35
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. Резюме 28/35
134 Глава 3. Библиотеки компонентов JSF Мы узнали, как создавать приложения с применением библиотеки PrimeFaces, входящей в состав NetBeans, а также обсудили порядок настройки NetBeans, чтобы получить возможность создавать JSF­ приложения с использованием библиотек компонентов ICEfaces и RichFaces. Наконец, мы рассмотрели, как NetBeans генерирует заго­ товки, которые можно использовать в качестве отправной точки для приложений на основе PrimeFaces, ICEfaces и RichFaces. Рис. 3 .19. Компоненты <rich:messages> и <rich:message> выводят отформатированные и хорошо видимые сообщения 29/35
глава 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. 30/35
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 31/35
137 Создание первой сущности JPA После этого 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 (Поставщик и база данных) предло­ жит имя для нашего модуля персистентности. В большинстве случаев вполне безопасно принять предложенное значение по умолчанию. J PA является спецификацией, для которой существует много реа­ лизаций. NetBeans поддерживает несколько реализаций JPA, вклю­ чая EclipseLink, Toplink Essentials, Hibernate, KODO и OpenJPA; поскольку поставляемый с дистрибутивом NetBeans сервер приложе­ ний GlassFish по умолчанию включает реализацию EclipseLink, имеет смысл принять это значение по умолчанию для поля Persistence Pro- vider (Поставщик персистентности), если предполагается разверты­ вание приложения на сервере GlassFish. 32/35
138 Глава 4. Взаимодействие с базами данных через Java Persistence API Рис. 4.3 . Настройка модуля персистентности Прежде чем взаимодействовать с базой данных из любого прило­ жения Java EE, на сервере приложений должны быть созданы пул со­ единений с базой данных и источник данных. Пул соединений содержит информацию, позволяющую соеди­ няться с базой данных, такую как имя сервера, порт и учетные дан­ ные пользователя. В сравнении с непосредственным соединением через JDBC, пул соединений имеет одно преимущество: соединения в пуле соединений никогда не закрываются, они просто выделяют­ ся приложениям, которые в них нуждаются. Это улучшает произво­ дительность, поскольку отсутствуют операции открытия и закрытия соединения с базой данных, которые обходятся дорого с точки зрения производительности. Источники данных дают возможность получить объект соедине­ ния из пула, для чего сначала приобретается экземпляр источника данных, а затем, вызовом его метода getConnection(), приобретается подключение к базе данных. Имея дело с JPA, не нужно непосред­ ственно получать ссылку на источник данных, это все автоматически делает JPA API, но мы все еще должны указать источник данных для использования в модуле персистентности приложения. NetBeans поставляется с несколькими предварительно настро­ енными источниками данных и пулами соединений, мы можем ис­ пользовать в своих приложениях один из этих ресурсов. Кроме того, NetBeans позволяет также создавать эти ресурсы «на лету», что мы и будем делать в нашем примере. 33/35
139 Создание первой сущности JPA Чтобы создать новый источник данных, нужно выбрать элемент 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 для нашего примера, чтобы избежать необходимости установки внешней СУРБД. 34/35
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 . P owe red by T CP DF (www.tcp df.o rg) 35/35
141 Рис. 4 .7. Настройка свойств соединения На следующем шаге (см. рис. 4 .8) нужно выбрать схему для ис­ пользования в приложении. В приложениях, взаимодействующих с СУРБД через драйвер JavaDB, часто используется схема APP, по­ этому выберем ее. Рис. 4.8. Выбор схемы базы данных для использования в приложении Создание первой сущности JPA 1/35
142 Глава 4. Взаимодействие с базами данных через Java Persistence API Далее NetBeans предложит ввести описательное имя соединения (см. рис. 4 .9). Рис. 4 .9. Ввод описательного имени соединения Здесь можно ввести свое название или принять название, предло­ женное по умолчанию. После создания нового источника данных и пула соединений можно продолжать настройку модуля персистент­ ности (см. рис. 4 .10). Рис. 4.10. Продолжение настройки модуля персистентности 2/35
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 имеет несколько отличительных особенностей (вы­ делены в следующем фрагменте), о которых стоит поговорить отдель­ но: Создание первой сущности JPA 3/35
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 под­ держивает автоматическую генерацию первичных ключей. Чтобы 4/35
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 содержит единственное поле – первичный ключ. По общему мнению, поле, не очень полезное с точки Создание первой сущности JPA 5/35
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 будут 6/35
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). Создание первой сущности JPA 7/35
148 Глава 4. Взаимодействие с базами данных через Java Persistence API Рис. 4.11 . Создание класса контроллера JPA Рис. 4 .12. Выбор классов сущностей для создания классов контроллеров JPA 8/35
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(); Создание первой сущности JPA 9/35
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(); } 10/35
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(); Создание первой сущности JPA 11/35
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() имеет несколько перегруженных версий, позволяющих получить бо­ 12/35
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 на основе имеющейся схемы базы данных, обе­ регая нас от большой и потенциально утомительной работы. Автоматическое создание сущностей JPA 13/35
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. Он включен в пакет примеров с исходными кодами кода для этой главы. 14/35
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 практически для любых проектов. В нашем примере мы будем использовать проект веб-приложения. Автоматическое создание сущностей JPA 15/35
156 Глава 4. Взаимодействие с базами данных через Java Persistence API Рис. 4.18. Таблицы, созданные сценарием create_populate_tables.sql Рис. 4.19. Создание сущностей из базы данных 16/35
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). Именованные запросы подробно описываются в следующем под- разделе. Автоматическое создание сущностей JPA 17/35
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 и 18/35
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 Автоматическое создание сущностей JPA 19/35
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"), 20/35
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. Заметьте, что не указана никакая стратегия генерации первичного ключа, мы должны или заполнить первичный ключ самостоятельно, или добавить анно­ Автоматическое создание сущностей JPA 21/35
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 для каждого поля сущности. Результатом выполнения запроса явля­ ется список, содержащий все экземпляры сущности, соответствую­ 22/35
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­мире. Автоматическое создание сущностей JPA 23/35
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, показанной выше, видно, что мастер обнаружил несколько 24/35
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"), Автоматическое создание сущностей JPA 25/35
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. В большинстве случаев снятие флажка позволяет получить более ясные имена полей, определяющих отношения между сущностями, как в данном примере. 26/35
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", Автоматическое создание сущностей JPA 27/35
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, эта аннотация со­ вершенно необходима, поскольку всякий раз, когда имеется отноше­ ние «многие ко многим», в схеме базы данных должна присутствовать 28/35
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"), Автоматическое создание сущностей JPA 29/35
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 поддерживает отношения «один к одному» для случаев, когда это окажется необходимым. 30/35
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; Автоматическое создание сущностей JPA 31/35
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. Мастер создаст два типа 32/35
173 классов: контроллеры JPA (JPA Controllers) и классы JSF (JSF Classes). Для каждого из них можно определить отдельный пакет. Рис. 4.24. Создание страниц JSF на основе имеющихся сущностей JPA Рис. 4 .25 . Выбор всех имеющихся сущностей JPA Создание приложений JSF из сущностей JPA 33/35
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 (Выберите шаблоны). 34/35
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 реали­ зует возможность создания новых сущностей; страница Edit позволяет изменять информацию в конкретной сущности, страница List выводит список всех экземпляров кон­ кретной сущности в базе данных, страница View отображает все свойства сущности. Сгенерированное приложение является обычным приложением JSF. Его можно запустить, просто щелкнув правой кнопкой мыши на проекте и выбрав в контекстном меню пункт Run (Выполнить). Далее все происходит как обычно – запускается сервер приложений, если он еще не был запущен, развертывается приложение и открывается окно браузера с начальной страницей приложения (см. рис. 4 .28). Как видите, начальная страница содержит ссылки, соответствую­ щие каждой из сущностей JPA. При переходе по любой ссылке откры­ вается страница с таблицей, содержащей список всех экземпляров выбранной сущности, хранящихся в базе данных. Если щелкнуть на ссылке Show All Customer Items (Показать список всех заказчиков), откроется страница, изображенная на рис. 4 .29. Поскольку база данных пока не содержит никакой информации, на страницу выводится сообщение No Customer Items Found (Заказчи­ Рис. 4.27. Законченное веб-приложение, поддержи- вающее операции CRUD Создание приложений JSF из сущностей JPA P owe red by T CP DF (www.tcp df.o rg) 35/35
176 Глава 4. Взаимодействие с базами данных через Java Persistence API ки не найдены). Добавим заказчика в базу данных, щелкнув на ссылке Create New Customer (Создать нового заказчика). Рис. 4.28. Окно браузера с начальной страницей приложения Рис. 4 .29. Страница со списком всех заказчиков Обратите внимание, что для каждого свойства сущности, которое, в свою очередь, соответствует столбцу в таблице базы данных, созда­ ется свое поле ввода. Как видите, для первичного ключа сущности также было сгенериро- вано поле ввода. Это поле генерируется, только если сущность JPA не использует стратегию генерации первичного ключа. 1/35
177 Рис. 4.30. Диалог создания нового заказчика После ввода информации и щелчка на кнопке Save (Сохранить) в таблицу добавляется новая запись и выводится сообщение Customer was successfully created (Заказчик успешно создан), как показано на рис. 4 .31. Рис. 4.31. В таблицу добавляется новая запись и выводится сообщение об успехе операции Обратите внимание, что на странице присутствуют кнопки View (Посмотреть), Edit (Изменить) и Delete (Удалить) для выполнения операций с экземплярами сущности. Чтобы перейти к выполнению операций с другими сущностями, можно выбрать требуемую сущность в сгенерированном раскры­ Создание приложений JSF из сущностей JPA 2/35
178 Глава 4. Взаимодействие с базами данных через Java Persistence API вающемся списке Maintenance (Обслуживание), как показано на рис. 4 .32. Рис. 4.32. В списке Maintenance (Обслуживание) можно выбрать другую сущность Предположим, что нам нужно добавить адрес заказчика. Сделать это можно, выбрав пункт Address (Адрес) в раскрывающемся спи­ ске Maintenance (Обслуживание) и затем щелкнув на кнопке Create (Создать). Рис. 4.33. Диалог создания нового адреса 3/35
179 Сущность Address находится на стороне «один» для нескольких от­ ношений «один ко многим». Обратите внимание, что для каждой из сущностей на стороне «ко многим» сгенерировано поле в виде рас­ крывающегося списка. Поскольку этот адрес требуется присвоить за­ казчику, который только что был добавлен, можно просто выбрать его в раскрывающемся списке Customer (Заказчик). Если щелкнуть на раскрывающемся списке, появится загадочное, практически неразборчивое (с точки зрения пользователя) наимено­ вание нашего заказчика. Причина, в том, что эти наименования гене­ рируются для каждого элемента в раскрывающемся списке вызовом метода toString() сущности, использованной для заполнения этого поля. Эту проблему можно решить, изменив реализацию метода to- String() так, чтобы он возвращал удобочитаемую строку, подходя­ щую для использования в списке. Как видите, в код, сгенерированный мастерами NetBeans, можно внести некоторые улучшения, например: изменить методы toString() всех сущностей JPA, чтобы их результаты можно было использовать в качестве элементов списков, или изменить надписи в сгенерирован­ ных страницах JSF, чтобы они были более удобны для пользователя. Но, как бы то ни было, мы получили законченное рабочее приложе­ ние, созданное лишь несколькими щелчками мыши. Эта функцио­ нальность, безусловно, позволяет сэкономить уйму времени и усилий (только не говорите об этом вашему боссу). Резюме В этой главе мы узнали, как NetBeans может помочь ускорить разра­ ботку приложений, использующих возможности JPA. Мы увидели, как NetBeans генерирует новые классы JPA сразу со всеми необходимыми аннотациями в положенном для них месте. Также мы познакомились с возможностью автоматического создания кода для сохранения сущности JPA в таблице базы данных. Изучили, как NetBeans генерирует сущности JPA на основе существующей схе­ мы базы данных, включая автоматическое создание запросов JPQL, именованных запросов и проверку допустимости. Наконец, мы уз­ нали, как NetBeans может создать законченное приложение JSF из существующих сущностей JPA. Резюме 4/35
глава 5. реализация уровня бизнес-логики на сеансовых компонентах EJB К большинству корпоративных приложений выдвигается множество общих требований, таких как поддержка транзакций, безопасность, масштабируемость и т. д . Компоненты Enterprise JavaBeans (EJB) позволяют разработчикам приложений уделять основное внимание реализации бизнес­логики и не беспокоиться о реализации упомяну­ тых выше общих требований. Имеются два типа компонентов EJB: сеансовые компоненты (Session Beans) и компоненты, управляемые сообщениями (Message­Driven Beans). В этой главе мы будем об­ суждать сеансовые компоненты, которые значительно упрощает ре­ ализацию бизнес­логики на стороне сервера. В главе 7 мы обсудим компоненты, управляемые сообщениями, позволяющие без труда ре­ ализовать обмен сообщениями в приложениях. Предыдущие версии J2EE включали также объектные компоненты (Entity Beans), начиная с версии Java EE 5, объектные компоненты были признаны устаревшими и в настоящее время рекомендуется использовать JPA. В этой главе будут затронуты следующие темы: введение в сеансовые компоненты; создание сеансовых компонентов в NetBeans; управление транзакциями EJB; реализация аспектно­ориентированного программирования с интерцепторами; служба таймеров EJB; автоматическое создание сеансовых компонентов из сущнос­ тей JPA. 5/35
181 Создание сеансового компонента в NetBeans Введение в сеансовые компоненты Сеансовые компоненты инкапсулируют бизнес­логику корпоратив­ ных приложений. Использование сеансовых компонентов при раз­ работке корпоративных приложений выглядит предпочтительнее, потому что они позволяют нам, как разработчикам приложений, сосредоточиться на разработке бизнес­логики и не беспокоиться по поводу иных требований к корпоративным приложениям, таким как масштабируемость, безопасность, поддержка транзакций и т. д . Даже при том, что мы не реализуем непосредственно общие тре- бования к корпоративным приложениям, такие как поддержка тран- закций и безопасность, мы можем настраивать эти службы с помо- щью аннотаций. Имеется два типа сеансовых компонентов: сеансовые компонен- ты без сохранения состояния (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 (архив корпоративного приложения). 6/35
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). 7/35
183 Создание сеансового компонента в NetBeans Рис. 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). 8/35
184 Глава 5. Реализация уровня бизнес-логики на сеансовых компонентах EJB Рис. 5 .3. Дополнительные настройки проекта Рис. 5.4 . Два новых проекта Рис. 5 .5. Создание клиента корпоративного приложения 9/35
185 Создание сеансового компонента в NetBeans Ввести имя проекта в поле Project Name (Имя проекта) и, при желании, изменить значение поля Project Location (Расположение проекта), как Рис. 5 .6. Ввод имени и местоположения проекта клиентского приложения На следующем шаге следует выбрать наш проект корпоративно­ го приложения в поле раскрывающегося списка Add to Enterprise Application (Добавить в приложение J2EE) и затем определить желае­ мое имя в поле Main Class (Основной класс), как показано на рис. 5.7 . Рис. 5 .7 . Выбор корпоративного приложения и имени основного класса 10/35
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). 11/35
187 Создание сеансового компонента в NetBeans Рис. 5.10. Ввод имени и местоположения проекта библиотеки классов Рис. 5.11 . Появился проект библиотеки классов Java Теперь необходимо добавить проект библиотеки классов Java как библиотеку в проект клиентского приложения. Сделать это можно, щелкнув правой кнопкой мыши на узле Libraries (Библиотеки) и вы­ брав пункт контекстного меню Add Project... (Добавить проект...), как показано на рис. 5 .12 . Рис. 5 .12. Пункт Add Project... (Добавить проект...) в контекстном меню 12/35
188 Глава 5. Реализация уровня бизнес-логики на сеансовых компонентах EJB Далее, в появившемся окне, следует выбрать наш проект библиоте­ ки классов Java (см. рис. 5 .13). Рис. 5.13. Выбор проекта библиотеки классов Java Теперь, после создания всех необходимых проектов, можно при­ ступать к созданию нашего первого сеансового компонента. Для этого можно щелкнуть правой кнопкой мыши на модуле EJB, выбрать пункт New | Other (Новый | Другое...) и в категории Enterprise JavaBeans выбрать тип файлов Session Bean (Сеансовый компонент), как по­ казано на рис. 5 .14 . Рис. 5 .14. Создание сеансового компонента 13/35
189 Теперь нужно настроить некоторые параметры компонента (см. рис. 5 .15): • переопределить имя сеансового компонента, присвоенное по умолчанию; • определить пакет для нашего сеансового компонента; • определить тип сеансового компонента: без сохранения со­ стояния, с сохранением состояния или компонент­одиночка (singleton): сеансовые компоненты c сохранением состояния поддер­ живают состояние диалога с клиентом (то есть, значения любых задействованных переменных находятся в непро­ тиворечивом состоянии между вызовами метода); сеансовые компоненты без сохранения состояние не под­ держивают состояние диалога, по этой причине они вы­ полняются быстрее, чем сеансовые компоненты с сохра­ нением состояния; сеансовые компоненты­одиночки (Singleton) появились в Java EE 6. При развертывании приложения создается единственный экземпляр каждого такого компонента­ одиночки. Сеансовые компоненты­одиночки удобно ис­ пользовать для кэширования данных, часто читаемых из базы данных. • определить, будет ли сеансовый компонент иметь удаленный интерфейс для использования клиентами, выполняющимися в иной JVM, нежели сам компонент, локальный интерфейс, предназначенный для клиентов, работающих в той же самой JVM, что и сам компонент, или оба интерфейса сразу. В ранних версиях Java EE локальные интерфейсы были обязатель- ными, если компоненты EJB и их клиенты выполнялись в одной JVM. В Java EE 6 это требование было смягчено и теперь нет необходи- мости создавать какие-либо интерфейсы для сеансовых компонен- тов, если к ним получают доступ только клиенты, выполняющиеся в той же самой JVM. В нашем примере компонент не должен поддерживать состояние диалога со своими клиентами, поэтому его следует сделать сеансовым компонентом без сохранения состояния. Единственный клиент ком­ понента будет выполняться в другой JVM, поэтому требуется создать удаленный интерфейс и не создавать локального. Создание сеансового компонента в NetBeans 14/35
190 Глава 5. Реализация уровня бизнес-логики на сеансовых компонентах EJB Рис. 5 .15. Настройка параметров сеансового компонента При создании удаленного ин­ терфейса NetBeans потребует указать библиотеку клиента, куда будет добавлен удаленный интер­ фейс. Именно для этого нам по­ требовалось создать библиотеку классов Java ранее. Библиотека клиента выбирается по умолча­ нию. После настройки всех соответ­ ствующих параметров и щелчка на кнопке Finish (Готово), сеан­ совый компонент будет создан в проекте модуля EJB, а удаленный интерфейс – в проекте библиоте­ ки клиента (см. рис. 5 .16). Сгенерированный код для се­ ансового компонента является просто пустым классом с аннотацией @Stateless и реализованным удаленным интерфейсом (см. рис. 5 .17). Обратите внимание, что наш компонент реализует удаленный ин­ терфейс, который на этом этапе является пустым интерфейсом с ан­ Рис. 5.16. Созданы сеансовый компонент и удаленный интерфейс к нему 15/35
191 нотацией @Remote (см. рис. 5 .18). Эта аннотация была добавлена, по­ тому что был отмечен флажок создания удаленного интерфейса. Рис. 5.17 . Сгенерированный код для сеансового компонента Рис. 5.18. Удаленный интерфейс с аннотацией @Remote Причина, почему нам потребовался удаленный и/или необяза­ тельный локальный интерфейс, в том, что клиенты сеансового ком­ понента никогда не вызывают методов компонента непосредственно, вместо этого они получают ссылку на класс, реализующий удаленный и/или локальный интерфейс, и вызывают методы этого класса. Начи­ ная с Java EE 6 больше нет необходимости создавать локальный ин­ терфейс; сервер приложений может генерировать его автоматически при развертывании приложения. Создание сеансового компонента в NetBeans 16/35
192 Глава 5. Реализация уровня бизнес-логики на сеансовых компонентах EJB Реализация удаленного и/или локаль­ ного интерфейса создается автоматически контейнером EJB при развертывании ком­ понента. Эта реализация выполняет не­ которые операции перед вызовом метода сеансового компонента. Поскольку методы должны быть определены и в интерфейсе, и в компоненте, следует добавить сигна­ туру метода и к компонент, и в его удален­ ный и/или локальный интерфейс. Однако при работе с сеансовыми компонентами в NetBeans можно просто щелкнуть правой кнопкой на исходном коде компонента и в контекстном меню выбрать пункт Insert Code | Add Business Method (Вставка кода | Добавить бизнес­метод...), как показано на рис. 5 .19, и добавить метод одновременно в компонент и его удаленный/локальный интерфейс. В результате появится диалог (см. рис. 5 .20), где можно определить имя метода, тип возвращаемого значения, параметры и интерфейс(ы) для добавления метода (удаленный и/или локальный). Рис. 5.20. Диалог настройки добавляемого метода Рис. 5.19. Пункт меню Add Business Method (Добавить бизнес- метод...) 17/35
193 В данном примере мы добавим метод с именем echo, который при­ нимает и возвращает строку (значение типа String). Поскольку ком­ понент имеет только удаленный интерфейс, переключатели Local (Локальный) и Both (Оба) отображаются как неактивные. После ввода соответствующей информации метод будет добавлен и в компонент, и в его удаленный интерфейс, как показано на рис. 5 .21 . Рис. 5.21. Новый метод echo По умолчанию метод просто возвращает null. Давайте изменим его, чтобы он возвращал строку, начинающуюся со слова «echoing:» и заканчивающуюся значением входного параметра, как показано на рис. 5.22. Рис. 5.22. Измененная реализация метода echo Теперь у нас есть простой, но полноценный сеансовый компонент без сохранения состояния, готовый обслуживать клиентов. Доступ к компонентам из клиента Теперь пора обратить внимание на клиента. Для поддержки уда­ ленных компонентов, проект клиента должен использовать проект Доступ к компонентам из клиента 18/35
194 Глава 5. Реализация уровня бизнес-логики на сеансовых компонентах EJB библиотеки классов Java с удаленным интерфейсом. Именно для этого нам по­ требовалось создать библиотеку классов Java ранее. После добавления в проект клиентско­ го приложения библиотеки с удаленным интерфейсом компонента мы готовы вы­ звать метод EJB. Клиентский код должен получить ссылку на экземпляр класса, реализующего удаленный интерфейс ком­ понента. В NetBeans это сделать очень просто – нужно щелкнуть правой кнопкой мыши на клиентском коде (com.ensode. sessionbeanintro.Main в проекте клиентс­ кого приложения) и выбрать Insert Code... | Call Enterprise Bean (Вставка кода... | Вызов компонента EJB...), как показано на рис. 5 .23. В результате появится диалог со списком всех открытых проектов, где имеются компоненты EJB (см. рис. 5 .24). Мы должны выбрать компонент, к которому хотим получить доступ. Рис. 5.24. Диалог со списком компонентов EJB Рис. 5.23. Пункт меню Call Enterprise Bean (Вызов компонента EJB...) 19/35
195 Если бы компонент имел оба интерфейса, локальный и удаленный, нам была бы предоставлена возможность выбрать требуемый интер­ фейс. Однако, поскольку в данном случае имеется только удаленный интерфейс, переключатель выбора локального интерфейса неакти­ вен. Но, даже если бы у нас была возможность выбрать локальный интерфейс, мы все равно должны были бы выбрать удаленный ин­ терфейс, потому что клиент будет выполняться в другой JVM, а взаи­ модействия по локальным интерфейсам через границы JVM невоз­ можны. Когда выбор будет сделан, в код клиента добавится переменная­ член типа EchoRemote (удаленный интерфейс компонента), декориро­ ванная аннотацией @EJB. Эта аннотация используется для внедрения экземпляра удаленного интерфейса во время выполнения. В предыдущих версиях J2EE было необходимо выполнить поиск в JNDI, чтобы получить ссылку на домашний интерфейс компонента и затем использовать его для получения ссылки на удаленный или локальный интерфейс. Как видите, процедура получения ссылки на EJB была значительно упрощена в Java EE. Получившийся программный код показан на рис. 5 .25. Рис. 5.25. Новая переменная-член типа EchoRemote Доступ к компонентам из клиента 20/35
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. 21/35
197 Управление транзакциями в сеансовых компонентах Как уже говорилось выше, одним из преимуществ компонентов EJB является автоматическая поддержка транзакций. Тем не менее, есть некоторые настройки, которые нужно сделать, чтобы улучшить управление транзакциями. Транзакции позволяют выполнить все шаги в методе, либо, если на одном из шагов возникнет сбой (например, будет возбуждено ис­ ключение), произвести откат изменений, выполненных в этом ме­ тоде. Прежде всего нужно настроить поведение компонента в случае, если один из его методов вызывается во время выполнения транзакции. Должен ли метод продолжить выполняться в рамках существующей транзакции? Следует ли приостановить существующую транзакцию и создать новую только для этого метода? Выполнить необходимые настройки можно с помощью аннотации @TransactionAttribute. Аннотация @TransactionAttribute позволяет управлять поведе­ нием методов EJB при вызове во время выполнения транзакции и в отсутствие транзакций. Эта аннотация имеет единственный атрибут value, который можно использовать, чтобы указать, как метод компо­ нента будет вести себя в обоих перечисленных случаях. В табл. 5 .1 перечислены все допустимые значения, которые можно присвоить аннотации @TransactionAtttibute. Таблица 5.1 . Допустимые значения атрибута value аннотации @TransactionAtttibute Значения @TransactionAttribute Метод вызывается во время выполнения транзакции Метод вызывается в отсутствие транзакции TransactionAttributeType. MANDATORY Метод становится частью существующей транзакции. Возбуждается исключение Transaction- RequiredEx- ception. TransactionAttributeType. NEVER Возбуждается исключение RemoteException. Продолжает выполнение без поддержки транзакций. Управление транзакциями в сеансовых компонентах 22/35
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 23/35
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 появилась Реализация аспектно-ориентированного программирования с помощью. .. 24/35
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() Вызывает перехваченный метод. В следующем примере демонстрируется простой класс интерцеп­ тора: 25/35
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(). Значение, возвращаемое этим методом, сохраняется в переменной Реализация аспектно-ориентированного программирования с помощью.. . 26/35
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(). 27/35
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). Служба таймеров EJB 28/35
204 Глава 5. Реализация уровня бизнес-логики на сеансовых компонентах EJB Рис. 5.29. Включение службы таймеров в проект На следующем шаге (см. рис. 5 .30) мастер дает возможность вы­ полнить некоторые настройки. Рис. 5.30. Настройка службы таймеров 29/35
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. – Прим. перев. Служба таймеров EJB 30/35
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 31/35
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 (Готово) сеансовые компоненты бу­ дут созданы и помещены в указанный пакет. Автоматическое создание сеансовых компонентов из сущностей JPA 32/35
208 Глава 5. Реализация уровня бизнес-логики на сеансовых компонентах EJB Рис. 5 .33. Создание сеансовых компонентов для всех классов сущностей JPA Рис. 5.34. Последний этап создания сеансовых компонентов на основе сущностей JPA Все сгенерированные сеансовые компоненты наследуют абстракт­ ный класс AbstractFacade, который также генерируется мастером создания сеансовых компонентов для классов сущностей. Этот аб­ 33/35
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(); Автоматическое создание сеансовых компонентов из сущностей JPA 34/35
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; P owe red by T CP DF (www.tcp df.o rg) 35/35
211 } public CustomerFacade() { super(Customer.class); } } Как видите, сеансовые компоненты получились очень простыми. Они просто включают переменную экземпляра типа EntityManager и используют возможности механизма внедрения ресурсов для ее инициализации. Они также включают метод getEntityManager(), предназначенный для вызова родительским классом, благодаря чему он получает доступ к экземпляру EntityManager данного сеансового компонента. Дополнительно конструктор компонента вызывает кон­ структор родительского класса, который через обобщения инициали­ зирует переменную экземпляра entityClass родительского класса. Безусловно, ничто не мешает добавить дополнительные методы для в сгенерированные сеансовые компоненты. Например, иногда бывает желательно добавить метод для поиска всех сущностей, со­ ответствующих определенным критериям, например, для выявления всех заказчиков с одинаковой фамилией. Прием добавления методов в сгенерированные сеансовые компо- ненты имеет один недостаток: если по какой-то причине сеансовые компоненты нужно будет сгенерировать повторно, все дополни- тельные методы будут потеряны и их придётся добавлять заново. Чтобы избежать подобной ситуации правильнее будет унаследо- вать сгенерированные сеансовые компоненты и добавить допол- нительные методы в дочерние классы, обезопасит нас от потери методов. Резюме В этой главе мы познакомились с сеансовыми компонентами и узна­ ли, как NetBeans может помочь ускорить их разработку. Мы узнали, как компоненты Enterprise JavaBeans вообще и сеансо­ вые компоненты в частности позволяют реализовать разные страте­ гии поддержки транзакций в корпоративных приложениях. Мы также посмотрели, как использовать приемы аспектно­ориентированного программирования и создавать свои интерцепторы. Дополнительно мы узнали, что сеансовые компоненты могут определять методы, ко­ торые периодически вызывается контейнером EJB с помощью служ­ Резюме 1/35
212 Глава 5. Реализация уровня бизнес-логики на сеансовых компонентах EJB бы таймеров EJB (EJB Timer Service). Наконец, мы изучили, как с по­ мощью NetBeans можно существенно ускорить реализацию уровней доступа к данным в приложениях путем автоматического создания сеансовых компонентов, реализующих шаблон проектирования Объ- ект доступа к данным (Data Access Object, DAO). 2/35
глава 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: 3/35
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; 4/35
215 Введение в CDI Рис. 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; 5/35
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- сеанса. В контексте сеанса каждый пользователь приложения получает собственный экземпляр компонента. 6/35
217 Введение в CDI Контекст Аннотация Описание Диалога @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 7/35
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"/> 8/35
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 имеются квалификато­ 9/35
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; 10/35
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; 11/35
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}", 12/35
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" 13/35
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. Страница, подтверждающая создание премиального заказчика 14/35
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; 15/35
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; 16/35
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 17/35
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; 18/35
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; 19/35
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> 20/35
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 21/35
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, а не для прикладных программи­ 22/35
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 23/35
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 и исследовали некоторые дополнительные возможности, 24/35
235 Резюме которые предоставляет CDI API поверх стандартного JSF. Мы также посмотрели, как устранить неоднозначность при внедрении компо­ нентов с помощью квалификаторов CDI. Дополнительно познакоми­ лись с возможностью группировки аннотаций CDI через стереотипы. Посмотрели, как CDI помогает в аспектно­ориентированном про­ граммировании через типы привязки интерцепторов. Наконец, мы узнали, что есть возможность создавать свои собственные контексты CDI. 25/35
глава 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 получают со­ общения оттуда. 26/35
237 Создание ресурсов JMS из NetBeans Механизмом 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). 27/35
238 Глава 7. Обмен сообщениями с применением JMS и компонентов.. . Рис. 7 .1 . Создание нового проекта веб-приложения Рис. 7 .2. Выбор имени проекта 28/35
239 Создание ресурсов JMS из NetBeans Рис. 7.3. Настройки по умолчанию, предложенные мастером Рис. 7.4 . Выбор фреймворка JavaServer Faces 29/35
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. 30/35
241 Создание ресурсов JMS из NetBeans Рис. 7.6. Выбор имени и типа ресурса JMS Рис. 7.7. Определение значения свойства Name При развертывании проекта на сервере GlassFish сервер прочита­ ет этот файл и создаст ресурсы, определенные в файле. Увидеть со­ держимое этого файла (см. рис. 7 .9) можно, распахнув узел Server Resources (Ресурсы сервера) на вкладке Projects (Проекты) и дваж­ ды щелкнув на его имени. 31/35
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 32/35
243 Реализация продюсера сообщений JMS но распахнуть узел JMS Resources (Ресурсы JMS), слева, затем распахнуть узел Destination Resources (Ресурсы приемников) и проверить присутствие только что созданной очереди в списке (см. рис. 7 .12). Рис. 7.11 . Веб-консоль администратора GlassFish Теперь, убедившись, что очередь была успешно создана, можно приступать к реализации функций обмена сообщениями JMS. Реализация продюсера сообщений JMS В этом разделе будет создано простое JSF­приложение. Один из компонентов CDI в этом приложении будет произво­ дить сообщения JMS и отправлять их в очередь, настроенную в предыдущем раз­ деле. Создадим новый класс Java с именем JmsMessageModel (см. рис. 7 .13). Этот класс будет хранить текст сообщения для отправки в очередь. Рис. 7 .12. Очередь действительно присутствует в списке 33/35
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; 34/35
245 Реализация продюсера сообщений JMS } 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. Здесь нужно щел­ P owe red by T CP DF (www.tcp df.o rg) 35/35
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); 1/35
247 Реализация продюсера сообщений JMS } } 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 автоматически создает экземпляр клас­ са, реализующего данный интерфейс, когда в качестве сообщения отправляется строка; это существенно упростило труд прикладных программистов. Теперь нужно добавить несколько изменений в код, сгенерирован­ ный несколькими щелчками мыши: добавим свой метод, который 2/35
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"> 3/35
249 Реализация продюсера сообщений JMS <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­сообщение было успешно отправлено. Теперь, закончив с реализацией отправки сообщений, можно при­ ступать к разработке кода, который будет извлекать сообщения из очереди. 4/35
250 Глава 7. Обмен сообщениями с применением JMS и компонентов.. . Обработка сообщений компонентами, управляемыми сообщениями Наиболее общий способ организации приема и обработки сообщений JMS заключается в создании компонентов, управляемых сообщения­ ми. Компоненты, управляемые сообщениями, – это особый тип ком­ понентов Enterprise JavaBean (EJB), цель которых ждать появления сообщений JMS в очереди или в теме. Компоненты, управляемые со­ общениями, обладают всеми возможностями EJB, такими как управ­ ление транзакциями и способность к масштабированию. В действующих системах продюсеры и потребители сообще- ний JMS создаются в отдельных проектах NetBeans, так как часто они выполняются в совершенно разных системах. Для простоты, мы создадим продюсера и потребителя в рамках одного проекта NetBeans. Чтобы создать компонент, управляемый сообщениями, нужно вы­ брать в главном меню пункт File | New File (Файл | Создать файл) и в категории Enterprise JavaBeans выбрать тип Message-Driven Bean (Компонент, управляемый сообщениями), как показано на рис. 7 .16. Рис. 7.16. Создание компонента, управляемого сообщениями 5/35
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_ ACKNOWLEDGE или DUPS_OK_ ACKNOWLEDGE Когда установлено значение AUTO_ ACKNOWLEDGE, сервер приложений бу- дет подтверждать сообщения сразу после их приема. Когда установлено значение DUPS_OK_ACKNOWLEDGE, сер- вер приложений будет подтверждать сообщения некоторое время спустя, после их приема. 6/35
252 Глава 7. Обмен сообщениями с применением JMS и компонентов.. . Свойство Допустимые значения Описание clientId В свободной форме Идентификатор клиента для подписчи- ков с длительной подпиской. Использу- ется только в режиме обмена сообщени- ями издатель/подписчик (то есть, когда вместо очередей используются темы). connectionFacto- ryLookup В свободной форме Имя фабрики соединений в JNDI. По умолчанию хранит имя фабрики соеди- нений по умолчанию. destinationType QUEUE или TOPIC Определяет тип приемника – очередь или тема. destinationLookup В свободной форме Имя приемника (очереди или темы) в JNDI. messageSelector В свободной форме Позволяет компонентам, управляемым сообщениями, обрабатывать сообще- ния избирательно. subscriptionDura- bility DURABLE или 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() { 7/35
253 Обработка сообщений компонентами, управляемыми сообщениями } @Override public void onMessage(Message message) { } } Рис. 7.18. Настройка свойств активации компонента, управляемого сообщениями Аннотация @MessageDriven превращает класс в компонент, управ­ ляемый сообщениями. Ее атрибут activationConfig принимает массив аннотаций @ActivationConfigProperty, каждая из которых определяет имя свойства JMS и его значение. Обе аннотации – @MessageDriven и соответствующая ей @ActivationConfig – создаются автоматически на основе значений, выбранных на последнем шаге мастера создания компонента, управляемого сообщениями (см. рис. 7 .18). Отметьте, что сгенерированный класс реализует интерфейс javax. jms.MessageListener. Это обязательное требование для компонентов, управляемых сообщениями. Данный интерфейс определяет единст­ венный метод onMessage(), принимающий экземпляр класса, который реализует интерфейс javax.jms.Message, и ничего не возвращающий. Этот метод будет вызываться автоматически, когда в приемнике по­ явится сообщение для данного компонента. Нам остается только до­ бавить в этот метод обработку сообщения: 8/35
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. Запуск проекта 9/35
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 Используется для отправки сообщений в виде массивов бай- тов. 10/35
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. 11/35
глава 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 в памяти. С этой 12/35
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); } 13/35
259 Объектная модель JSON-P 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­свойства, а во втором – соответствующее значение. 14/35
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 builder) Добавляет в объект JSON массив объектов JSON. add(String name, JsonObjectBuilder builder) Добавляет в объект JSON другой объект 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, использовав­ 15/35
261 Объектная модель JSON-P шийся для создании интерфейса 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: 16/35
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) { 17/35
263 Объектная модель JSON-P this.age = age; } } И снова в классе Person нет ничего необычного для нас – это про­ стой именованный компонент CDI с приватными свойствами и соот­ ветствующими им методами set() и get(). Приложение можно запустить как обычно, щелкнув правой кноп­ кой мыши на проекте и выбрав в контекстном меню пункт Run (Вы­ полнение), как показано на рис. 8 .1 . Рис. 8 .1 . Запуск приложения Через несколько секунд должно появиться окно браузера с откры­ той страницей JSF (см. рис. 8 .2). Рис. 8.2. Страница JSF приложения Если щелкнуть на кнопке Submit (Отправить), управление будет передано контроллеру, о котором рассказывалось в предыдущем раз­ 18/35
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. 19/35
265 Объектная модель JSON-P Парсинг данных в формате 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; } } 20/35
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, эта строка будет использована для перехода 21/35
267 Объектная модель JSON-P к странице 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> 22/35
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 – она не дает возможности обращаться к свойствам 23/35
269 Потоковая модель JSON-P 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()). 24/35
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 value) Добавляет в данные JSON свойство типа BigDecimal. 25/35
271 Потоковая модель JSON-P Метод write() Описание write (String name, BigInteger value) Добавляет в данные JSON свойство типа BigInteger. write (String name, JsonValue value) Добавляет в данные JSON свойство типа реализующее интерфейс JsonValue или один из его подынтерфейсов (JsonArray, JsonObject, JsonString или JsonStructure). write (String name, String value) Добавляет в данные JSON свойство типа String. write (String name, boolean value) Добавляет в данные JSON свойство типа boolean. write (String name, double value) Добавляет в данные JSON свойство типа double. write (String name, int value) Добавляет в данные JSON свойство типа int. write (String name, long value) Добавляет в данные JSON свойство типа 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 26/35
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. 27/35
273 Потоковая модель JSON-P После получения ссылки на экземпляр 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 Прочитан конечный символ массива. 28/35
274 Глава 8. Прикладной интерфейс JSON Processing Резюме В этой главе мы познакомились с поддержкой обработки данных в формате JSON­P, недавно ставшей частью спецификации Java EE. Мы рассмотрели создание и чтение данных с использованием про­ стой объектной модели JSON­P, а затем переключились на более производительную и экономную (в отношении использования памя­ ти) потоковую модель, рассмотрев приемы манипуляции данными JSON и с применением этой модели. 29/35
глава 9. прикладной интерфейс WebSocket Традиционно веб­приложения следуют модели запрос/ответ. То есть, браузер посылает HTTP­запрос серверу, а сервер возвращает HTTP­ ответ. Веб­сокеты (WebSocket) – это новая технология HTML5, обе­ спечивающая поддержку двустороннего, дуплексного обмена между клиентом (обычно веб­браузером) и сервером. Иными словами, она позволяет серверу посылать данные браузеру в масштабе реального времени, не ожидая, пока поступит HTTP­запрос. Java EE 7 включает все необходимое для разработки приложений на основе веб­сокетов, а NetBeans включает некоторые удобства, делающие разработку таких приложений Java EE еще проще. В этой главе мы: исследуем приемы использования веб­сокетов на типовых примерах, включенных в состав NetBeans; создадим приложение Java EE, использующее веб­сокеты. Исследование приемов использования веб-сокетов на типовых примерах В состав NetBeans входит множество примеров проектов, которые можно использовать в качестве основы для своих проектов. В их чис­ ло входит проект приложения Echo, использующее веб­сокеты для передачи некоторых данных со стороны сервера браузеру. Прежде чем приступить к исследованиям, нужно создать типовой проект. Для этого выберите пункт главного меню File | New Project (Файл | Создать проект...) и затем в категории Samples | Java EE (При­ 30/35
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. Выбор папки для размещения проекта 31/35
277 Исследование приемов использования веб-сокетов на типовых примерах Щелкните на кнопке Finish (Готово), чтобы закончить создание проекта. Опробование примера приложения Echo Прежде чем приступать к исследованию исходного кода, давайте посмотрим, что делает приложение Echo. Запустить его можно как обычно, щелкнув правой кнопкой на проекте и выбрав в контекстном меню пункт Run (Выполнение), как показано на рис. 9 .3 . Рис. 9 .3. Запуск проекта Спустя несколько секунд откроется окно браузера и автоматически запустится приложение. Текстовое поле ввода автоматически заполняется текстом «Hello WebSocket!». Если щелкнуть на кнопке Press me (Нажми меня), этот текст будет отправлен серверу веб­сокетов, который просто отправит текст обратно клиенту. Результаты этой операции можно видеть на рис. 9.4. Рис. 9 .4. Вид главной страницы приложения 32/35
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, но в данном примере возвращается та же самая стро­ ка, что была получена в виде аргумента. 33/35
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) { 34/35
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", записывая в него сообщение получено от сервера. Де­ P owe red by T CP DF (www.tcp df.o rg) 35/35
281 Создание собственных приложений с веб-сокетами лается это в функции writeToScreen(), которая вызывается из onMes- sage() при получении сообщения от сервера. Пример приложения Echo может служить отличным пособием по созданию собственных приложений, использующих Java WebSocket API. В следующем разделе мы напишем собственное приложение, без стеснения заимствуя из примера Echo все, что только возможно. Создание собственных приложений с веб-сокетами В предыдущем разделе был показан пример приложения на основе веб­сокетов, входящий в состав NetBeans, который можно исполь­ зовать как основу для своих проектов. В этом разделе мы создадим такое веб­приложение, включающее серверную конечную точку WebSocket, с помощью которой поля формы будут заполняться зна­ чениями по умолчанию. Чтобы создать проект, выберите пункт главного меню File | New Project (Файл | Создать проект...) и затем в категории Java Web вы­ берите тип проекта Web Application (Веб­приложение), как показано на рис. 9.5. Рис. 9.5. Создание проекта веб-приложения, включающего серверную конечную точку WebSocket 1/35
282 Глава 9. Прикладной интерфейс WebSocket Затем определите имя проекта и его местоположение (см. рис. 9 .6). Рис. 9.6. Выбор имени проекта и папки для его размещения Прикладной Java WebSocket API впервые появился в Java EE 7, поэтому для разработки веб­приложений, использующих веб­сокеты, следует выбрать эту версию Java EE (см. рис. 9 .7). Мастер нового про­ екта предлагает на этом шаге вполне разумные значения по умолча­ нию, поэтому их можно оставить «как есть». Рис. 9.7 . Достаточно разумным будет оставить значения по умолчанию 2/35
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> 3/35
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> 4/35
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; 5/35
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 (Файл | Создать файл) и в катего­ рии We b (Веб) выберите тип WebSocket Endpoint (Конечная точка WebSocket), как показано на рис. 9 .9 . Рис. 9.9. Выбор типа WebSocket Endpoint (Конечная точка WebSocket) 6/35
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(): Создание собственных приложений с веб-сокетами 7/35
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" 8/35
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); Создание собственных приложений с веб-сокетами 9/35
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. 10/35
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, изучили Резюме 11/35
292 Глава 9. Прикладной интерфейс WebSocket его исходный код, чтобы лучше понять, как можно написать соб­ ственное приложение. Затем мы использовали полученные знания для создания собственного приложения, использующего новый Java WebSocket API, введенный в Java EE 7, воспользовавшись всеми пре­ имуществами мастеров NetBeans, оказывающими помощь в создании приложений. Рис. 9.11 . Приложение в действии 12/35
глава 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. 13/35
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, предлагаемое по умолчанию. Правиль­ 14/35
295 Создание веб-службы RESTful на основе существующей базы данных нее будет ввести имя пакета, которое следует стандартному соглаше­ нию об именовании пакетов (см. рис. 10.4). Рис. 10.2. Выбор источника данных и таблицы Рис. 10.3 . Ввод имени пакета 15/35
296 Глава 10. Веб-службы RESTful на основе JAX-RS Рис. 10.4. Ввод имени пакета ресурса После щелчка на кнопке Finish (Готово) NetBeans сгенерирует код веб­службы. Анализ сгенерированного кода Мастер, обсуждавшийся в предыдущем разделе, создаст сущность JPA для каждой выбранной таблицы плюс класс AbstractFacade и класс фасада для каждой сгенерированной сущности JPA. Программ­ ный код генерируется в соответствии с шаблоном проектирования Фасад (Facade). По сути, каждый класс­фасад является оберткой для кода JPA. Дополнительную информацию о шаблоне проектирования Фасад можно найти по адресу: https://ru.wikipedia.org/wiki/Фасад_(ша- блон_проектирования). Сгенерированные классы фасада развертываются как веб­службы RESTful и доступны как таковые (см. рис. 10.5). Класс AbstractFacade служит родительским классом для всех дру­ гих классов фасада: 16/35
297 Создание веб-службы RESTful на основе существующей базы данных 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 = 17/35
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; 18/35
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(); Создание веб-службы RESTful на основе существующей базы данных 19/35
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) в проекте, щел­ 20/35
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. Тестирование веб-службы RESTful 21/35
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. 22/35
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 (Протестировать). Тестирование веб-службы RESTful 23/35
304 Глава 10. Веб-службы RESTful на основе JAX-RS Рис. 10.11 . Ответ веб-службы в формате JSON Рис. 10.12. Ответ веб-службы в формате XML 24/35
305 Рис. 10.13.Тестирование передачи данных в формате JSON Теперь, убедившись, что веб­служба RESTful успешно развернута и действует, можно приступать к реализации клиентского приложе­ ния, которое будет пользоваться веб­службой. Но прежде давайте рассмотрим класс ApplicationConfig, который сгенерировала среда разработки NetBeans (см. рис. 10.14). Рис. 10.14. Местоположение класса ApplicationConfig Ниже приводится исходный код этого класса: Тестирование веб-службы RESTful 25/35
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(). 26/35
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 (Имя класса). Это имя класса по умолчанию нас вполне устраивает в дан- ном примере. Создание Java-клиента веб-службы RESTful 27/35
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 28/35
309 Создание Java-клиента веб-службы RESTful Рис. 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 { 29/35
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); 30/35
311 Создание Java-клиента веб-службы RESTful } 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 создает две версии для 31/35
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\":" 32/35
313 Создание JavaScript-клиента веб-службы RESTful + "{\"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­приложение. Получившееся веб­приложение использует 33/35
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 в исходные коды проекта 34/35
315 Создание JavaScript-клиента веб-службы RESTful Теперь осталось только выбрать REST­ресурс щелчком на кнопке Browse... (Обзор...), как показано на рис. 10.20. Рис. 10.20. Выбор REST-ресурса В этом примере мы выбрали веб­службу CustomerFacadeREST, соз­ данную в начале главы. На следующем шаге нужно выбрать имя для HTML­файла, кото­ рый должен быть сгенерирован (см. рис. 10.21). Рис. 10.21. Выбор имени для HTML-файла И затем останется только развернуть приложение и ввести в адресной строке браузера адрес сгенерированной страницы (см. рис. 10.22). P owe red by T CP DF (www.tcp df.o rg) 35/35
316 Глава 10. Веб-службы RESTful на основе JAX-RS Рис. 10.22 . Сгенерированная страница в браузере Щелчком на кнопке Create (Создать) в верхнем левом углу можно добавить новую запись в базу данных. Поля ввода будут отображены в нижней части страницы. Кроме того, если щелкнуть на значении в столбце ID, будет предоставлена возможность изменить значения полей в выбранной записи, как показано на рис. 10.23. Рис. 10.23. Вид страницы после щелчка на значении в столбце ID 1/33
317 Резюме Как видите, NetBeans способна генерировать полноценные клиент­ ские приложения на JavaScript, пользующиеся услугами веб­служб RESTful, не заставляя разработчика написать хотя бы строчку кода. Резюме В этой главе мы познакомились с некоторыми мощными возможно­ стями создания веб­служб RESTful, предлагаемых средой разработки NetBeans. Мы видели, как NetBeans запросто генерирует веб­службу RESTful из существующей схемы базы данных. Мы также видели, как легко и просто можно протестировать веб­службы, используя ин­ струменты, предоставляемые NetBeans и GlassFish. Кроме того, мы узнали, как несколькими щелчками мыши создать клиента веб­службы и как создать JavaScript­клиента с минимумом усилий. 2/33
глава 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). После создания 3/33
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 (Веб-служба) 4/33
320 Глава 11. Веб-службы SOAP на основе JAX-WS После щелчка по кнопке Next > (Далее >) нужно ввести имя и па­ кет для веб­службы (см. рис. 11 .2). Рис. 11 .2 . Ввод имени веб-службы и пакета После щелчка на кнопке Finish (Завершить) NetBeans создаст веб­ службу и автоматически откроет исходный код (см. рис. 11 .3). Рис. 11 .3. Исходный код веб-службы 5/33
321 Создание простой веб-службы Как видите, NetBeans автоматически сгенерировала простую веб­ службу «Привет, мир». Аннотация @WebService на уровне класса пре­ вращает этот класс в веб­службу. Аннотация @WebMethod на уровне метода превращает аннотируемый метод в операцию веб­службы; атрибут operationName этой аннотации определяет имя операции веб­службы. Это имя будет использоваться клиентами. Аннотация @WebParam определяет свойства параметров операции веб­службы. Атрибут name этой аннотации определяет имя параметра в WSDL, ко­ торый генерируется при развертывании веб­службы. NetBeans позволяет изменять веб­службы посредством графиче­ ского интерфейса – добавлять и/или удалять операции веб­службы и их параметры, щелкая на них мышью, после чего в код будут автома­ тически добавляться соответствующие методы­заглушки и аннота­ ции. Чтобы получить доступ к графическому дизайнеру веб­службы, достаточно просто щелкнуть на кнопке Design (Конструктор) в верх­ нем левом углу окна с исходным кодом веб­службы (см. рис. 11 .4). Рис. 11 .4. Внешний вид конструктора веб-службы с графическим интерфейсом Первое, что следует сделать, – удалить автоматически сгенериро­ ванную операцию. Для этого просто щелкните на кнопке Remove Op- eration (Удалить операцию), как показано на рис. 11 .5 . Чтобы добавить в веб­службу новую операцию, щелкните на кноп­ ке Add Operation... (Добавить операцию...) и заполните пустые поля в открывшемся окне. 6/33
322 Глава 11. Веб-службы SOAP на основе JAX-WS Рис. 11 .5. Первый шаг – удаление операции hello Наша веб­служба должна иметь две операции: одна для преоб­ разования дюймов в сантиметры, и другая – сантиметров в дюймы. Обе операции принимают единственный параметр типа double и воз­ вращают значение также типа double. После щелчка на кнопке Add Operation... (Добавить операцию...) нужно ввести информацию, не­ обходимую для создания операции inchesToCentimeters, как показано на рис. 11.6. Рис. 11 .6 . Информация, необходимая для создания операции inchesToCentimeters 7/33
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 (Надежная достав­ ка сообщений) можно указать, что сообщения должны передаваться 8/33
324 Глава 11. Веб-службы SOAP на основе JAX-WS по крайней мере один раз и не более одного раза. Использование ме­ ханизма надежной доставки сообщений позволяет приложениям вос­ станавливаться после ситуаций, когда сообщения могут быть потеря­ ны в процессе передачи. Установка флажка Secure Service (Служба безопасности) акти­ вирует средства защиты информации, такие как шифрование со­ общений и требование аутентификации клиента для доступа к веб­ службе. Увидеть сгенерированные методы­заглушки можно на вкладке Source (Источник), как показано на рис. 11 .8 . Рис. 11 .8. Исходный код веб-службы Теперь нам осталось заменить сгенерированные реализации ме­ тодов в классе «настоящими», развернуть приложение и веб­служба будет готова к работе. В данном случае нужно лишь разделить дюймы на коэффициент 2.54, чтобы преобразовать дюймы в сантиметры, и умножить на коэффициент 2.54, чтобы преобразовать сантименты в дюймы. После замены реализаций методов фактически необходимыми, все готово к развертыванию веб­службы, что может быть выполнить щелчком правой кнопки мыши на проекте и выбором в контекстном меню пункта Deploy (Развернуть). 9/33
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, с результатом преоб­ разования. 10/33
326 Глава 11. Веб-службы SOAP на основе JAX-WS Рис. 11 .10. Страница тестирования веб-службы Рис. 11.11. Результат преобразования сантиметров в дюймы Вверху страницы отображаются параметры, переданные мето­ ду, и возвращаемое значение. Внизу – фактические запрос и ответ SOAP. 11/33
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­файл, находящийся на жестком диске, или переключатель 12/33
328 Глава 11. Веб-службы SOAP на основе JAX-WS WSDL URL, чтобы использовать WSDL­файл из Интернета. Также в NetBeans имеются готовые настройки для некоторых общедоступных веб­служб. Чтобы создать клиента для одной из них, выберите пере­ ключатель IDE Registered (Зарегистрированная среда). Рис. 11 .13. Выбор типа файлов Web Service Client (Клиент веб-службы) Рис. 11 .14. Настройка параметров клиента веб-службы 13/33
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). 14/33
330 Глава 11. Веб-службы SOAP на основе JAX-WS Рис. 11.16. Сгенерированный код вызова веб-службы Рис. 11 .17 . Сгенерированные классы Метод getUnitConversionPort() класса UnitConversion_Service воз­ вращает экземпляр класса UnitConversion, сгенерированного на осно­ 15/33
331 Создание простой веб-службы ве WSDL и похожего на одноименный класс, который мы написали в проекте веб­службы. Метод, сгенерированный в результате пере­ таскивания операции веб­службы в код, вызывает этот метод, затем вызывает метод inchesToCentimeters() возвращаемого экземпляра UnitConversion. Нам остается только вызвать сгенерированный ме­ тод из своего кода. После этой простой модификации наш код теперь будет выглядеть, как показано на рис. 11 .18. Рис. 11 .18. Вызов сгенерированного метода Теперь мы готовы опробовать код клиента веб­службы. По­ сле запуска в консоли должен появиться вывод, как показано на рис. 11.19. Рис. 11 .19. Вывод в консоли после запуска клиента веб-службы 16/33
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 17/33
333 Service From Scratch (Создать веб­службу «с нуля») и затем щел­ кнуть на кнопке Finish (Готово), чтобы сгенерировать веб­службу. После этого в окне редактора должен появиться исходный код веб­ службы (см. рис. 11 .21). Как видите, получившийся сеансовый компонент не реализует ни локального, ни удаленного интерфейсов. Он просто декориро­ ван аннотацией @WebService, его методы декорированы аннотацией @WebMethod, а каждый параметр – аннотацией @WebParam. Единствен­ ное отличие исходного кода этой веб­службы от исходного кода, представленного в предыдущем примере, состоит в том, что на этот раз сгенерированный класс является сеансовым компонентом без сохранения состояния, поэтому он может пользоваться такими пре­ имуществами, как механизмы управления транзакциями EJB, аспек­ тно­ориентированное программирование и другие возможности EJB. Так же как обычная веб­служба, веб­служба в виде сеансового ком­ понента может проектироваться с использованием визуального кон­ структора. В данном примере после удаления автоматически сгене­ рированной операции и добавления двух фактических операций окно визуального конструктора будет выглядеть, как показано на рис. 11.22. Рис. 11 .20. Окно мастера создания новой веб-службы Экспортирование компонентов EJB в виде веб-служб 18/33
334 Глава 11. Веб-службы SOAP на основе JAX-WS Рис. 11 .21. Исходный код новой веб-службы Рис. 11.22. Окно визуального конструктора после добавления фактических операций 19/33
335 Щелкнув на вкладке Source (Источник), можно увидеть вновь сге­ нерированные методы вместе со всеми соответствующими аннотаци­ ями (см. рис. 11 .23). Рис. 11 .23. Исходный код вновь созданных методов После развертывания проекта клиенты смогут подключаться к на­ шей веб­службе точно так же, как к любой другой веб­службе. Для клиента не имеет значения, что веб­служба реализована в виде сеан­ сового компонента. Экспортирование существующих EJB в виде веб-служб Второй способ экспортирования компонентов EJB в виде веб­служб заключается в экспортировании существующих компонентов EJB. Для этого нужно создать веб­службу как обычно: щелкнуть правой кнопкой мыши на проекте, выбрать в контекстном меню пункт New | Web Service (Новый | Веб­служба), ввести имя веб­службы и пакета, установить переключатель Create Web Service from Existing Session Bean (Создать веб­службу на основе существующего сеансового ком­ понента), после чего выбрать компонент для экспортирования, щелк­ Экспортирование компонентов EJB в виде веб-служб 20/33
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. 21/33
337 Рис. 11.25. Исходный код веб-службы на основе существующего сеансового компонента Рис. 11 .26. Окно визуального конструктора веб-службы Экспортирование компонентов EJB в виде веб-служб 22/33
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). 23/33
339 Рис. 11 .28. Настройка параметров создания веб-службы из WSDL После этого NetBeans сгенерирует веб­службу с методами­заглуш­ ками для всех операций, определенных в WSDL (см. рис. 11 .29). После этого остается только добавить реализацию в сгенерирован­ ные методы. В данном примере мы использовали WSDL, сгенерированный из предыдущего примера, что кажется излишним, потому что у нас уже есть реализации для всех операций. Однако процедура, показанная здесь, применима к любому файлу WSDL, находящемуся в локаль­ ной файловой системе или развернутому на сервере. Резюме В этой главе мы познакомились с поддержкой создания веб­служб SOAP в NetBeans, включающей экспортирование методов POJO в виде веб­служб и автоматическое добавление необходимых аннота­ ций в исходный код. Резюме 24/33
340 Глава 11. Веб-службы SOAP на основе JAX-WS Мы узнали, как NetBeans помогает в создании клиентов веб­служб, генерируя большую часть типового кода, оставляя нам инициализа­ цию любых параметров, которые будут переданы операциям веб­ службы. Дополнительно мы посмотрели, как экспортировать методы EJB в виде операций веб­служб, а также увидели, какую поддержку оказы­ вает NetBeans в упрощении экспортирования новых и существующих EJB в виде веб­служб. Наконец, мы рассмотрели, как NetBeans может помочь в реализа­ ции веб­службы из существующего файла WSDL, расположенного в локальной файловой системе или развернутого на сервере, генерируя методы­заглушки на основе WSDL. Рис. 11 .29. Исходный код новой веб-службы, созданной из WSDL 25/33
предметный уКазатель Символы : GNU Public License Version 2 (GPL), дицензия 26 <ace:linkButton>, компонент 126 <ace:pushButton>, компонент 128 <ace:selectMenu>, компонент 128 <ace:sliderEntry>, компонент 128 @ApplicationScoped, аннотация 217 @ConversationScoped, аннотация 217 @Dependent, аннотация 217 @Interceptors, аннотация 202 @RequestScoped, аннотация 216 @Schedule, аннотация 205 @SessionScoped, аннотация 216 @TransactionAtttibute, аннотация 197 @WebMethod, аннотация 321 @WebParam, аннотация 321 @WebService, аннотация 321 A автозавершение кода 43 автоматическое создание сущностей JPA 153 JPQL 162 именованные запросы 162 проверка допустимости 164 аннотации JPA @Entity 144 @GeneratedValue 145 @Id 144 аспектно-ориентированного програм- мирования (Aspect Oriented Programming, AOP) 228 аспектно-ориентированное програм- мирование (Aspect-Oriented Programming, AOP) интерцепторы 199 реализация 199 В веб-служб создание на основе WSDL 338 веб-службы введение 318 создание 319 создание клиента 327 тестирование 325 экспортирование EJB 332 экспортирование существующих EJB 335 веб-службы RESTful 293 создание JavaScript-клиента 313 создание Java-клиента 307 создание на основе существующей базы данных 294 тестирование 300 веб-сокеты исследование программного кода на Java 278 исследование программного кода на JavaScript 279 исследование программного кода типовых примеров 275 пример приложения Echo, опробова- ние 277 создание собственных приложений 281 26/33
342 Предметный указатель пользовательский интерфейс 283 реализация клиента 288 серверная конечная точка 286 визуальные индикаторы 53 И именованные запросы 162 именованные параметры 163 интегрированная среда разработки 19 интерцепторы 199 введение 199 К квалификаторы 220 квалификаторы CDI создание 220 клавиши быстрого вызова 49 клиент доступ к сеансовым компонентам 193 запуск 196 компоненты проверка допустимости 164 компоненты управляемые сообщениями обзор 180 контекст диалога 217 зависимый 217 запроса 216 приложения 217 сеанса 216 контракты библиотек ресурсов использование 92 описание 90 создание 91 М механизм внедрения зависимостей (Contexts and Dependency Injection, CDI) 41 О объектно-реляционное отображение (Object-Relational Mapping, ORM) 135 объекты доступа к данным (Data Access Object, DAO) создание 147 объекты доступа к данным (Data Access Objects, DAO) 206 одиночки, сеансовые компоненты 181 отношения, сущностей JPA 164 П передача репрезентативного состоя- ния (Representational State Transfer, REST) 293 потоки Faces Flows 101 приложения наблюдение за работой 254 приложения JSF использование компонентов ICEfaces 120 использование компонентов PrimeFaces 114 использование компонентов RichFaces 128 создание из сущностей JPA 172 проверка допустимости в JSF 80 проверка допустимости, со стороны компонентов 164 простые плоские объекты Java (Plain Old Java Object, POJO) 136 публикация/подписка, режим обмена сообщениями 237 Р развертывание приложений 40 С свойства активации компонента 27/33
343 Предметный укзатель acknowledgeMode 251 clientId 252 connectionFactoryLookup 252 destinationLookup 252 destinationType 252 messageSelector 252 subscriptionDurability 252 subscriptionName 252 сеансовые компоненты без сохранения состояния 181 введение 181 доступ из клиента 193 обзор 180 одиночки 181 создание, в NetBeans 181 с сохранением состояния 181 управление транзакциями 197 сквозные атрибуты 112 служба имен и каталогов Java (Java Naming and Directory Interface, JNDI) 182 служба таймеров EJB 203 использование 203 собственные контексты 232 советы по эффективной разработке 43 автозавершение кода 43 визуальные индикаторы 53 клавиши быстрого вызова 49 шаблоны кода 47 составные компоненты описание 96 создание 96 сохраняемые поля, сущностей JPA 145 стереотипы 225 стереотипы CDI создание 225 сторонние серверы баз данных интегрирование с NetBeans 36 соединение с NetBeans 38 сторонние серверы приложений интегрирование с NetBeans 33 стратегии генерации первичных ключей GenerationType.AUTO 145 GenerationType.IDENTITY 145 GenerationType.SEQUENCE 145 GenerationType.TABLE 145 СУРБД 140 сущности JPA автоматическое создание 153 добавление сохраняемых полей 145 именованные запросы 162 объекты доступа к данным (Data Access Object, DAO) создание 147 отношения 164 проверка допустимости 164 создание 136 создание приложений JSF 172 Т типы привязки интерцепторов 228 создание 228 точка-точка, режим обмена сообще- ниями 237 транзакции управление в сеансовых компонен- тах 197 У удаленный вызов методов (Remote Method Invocation, RMI) 318 ускорение разработки HTML5 54 установка NetBeans в Linux 24 вMacOSX24 в Windows 24 на другие платформы 25 описание 23 28/33
344 Предметный указатель процедура 25 Ф Фасад, шаблон проектирования URL 296 Ш шаблоны кода 47 fore 47 ifelse 47 Psf 47 psvm 47 soutv 47 trycatch 47 whileit 48 шаблоны проектирования Фасад 296 шаблоны фейслетов 83 добавление 84 использование 86 Я язык определения веб-служб (Web Services Definition Language, WSDL) 318 A ACE, компоненты 125 add, методы add(String name, BigDecimal value) 260 add(String name, BigInteger value) 260 add(String name, boolean value) 260 add(String name, double value) 260 add(String name, int value) 260 add(String name, JsonArrayBuilder builder) 260 add(String name, JsonObjectBuilder builder) 260 add(String name, JsonValue value) 260 add(String name, long value) 260 add(String name, String value) 260 Aspect Oriented Programming (AOP) 228 Aspect-Oriented Programming (AOP) интерцепторы 199 реализация 199 C CDI введение 213 квалификаторы, создание 220 собственные контексты, создание 232 стереотипы, создание 225 типичная разметка страниц JSF 213 типы привязки интерцепторов 228 создание 228 Common Development and Distribution License (CDDL), лицензия 26 Contexts and Dependency Injection (CDI) 41 cron, утилита ссылка для справки 205 C, язык программирования 21 C++, язык программирования 21 D Data Access Objects (DAO) 206 E EAR, файлы 181 EJB существующие, экспортирование в виде веб-служб 335 экспортирование в виде веб-служб 332 EJB Timer Service 203 использование 203 Enterprise JavaBeans (EJB), компонен- ты 180 29/33
345 Предметный укзатель Event, значения экземпляра Event.END _OBJECT 273 Event.KEY _NAME 273 Event.START_OBJECT 273 Event.VALUE_END_ARRAY 273 Event.VALUE_FALSE 273 Event.VALUE_NULL 273 Event.VALUE_NUMBER 273 Event.VALUE_START_ARRAY 273 Event.VALUE_STRING 273 Event.VALUE_TRUE 273 F faces-config.xml, файл 61 G get, методы getBoolean(String name) 266 getInt(String name) 266 getJsonArray(String name) 266 getJsonNumber(String name) 266 getJsonObject(String name) 266 getJsonString(String name) 266 getString(String name) 266 H HTML5 21 HTML5-подобная разметка 108 I ICEfaces адрес URL документации 128 компоненты, использование в JSF-приложениях 120 описание 120 ICE, компоненты 125 ICSoft адрес URL 121 IDE, (Integrated Development Environment) 19 InvocationContext, класс 200 getMethod(), метод 200 getParameters(), метод 200 getTarget(), метод 200 proceed(), метод 200 J JavaDB 140 Java EE 21 Java EE 5 135 Java EE 7 257 Java EE 7, документация URL 145 Java EE-приложения настройка NetBeans для разработки 32 JAVA_HOME, переменная окружения 28 Java Naming and Directory Interface (JNDI) 182 Java Persistence API (JPA), прикладной интерфейс 20 Java SE 21 JavaServer Faces (JSF) 60 JavaServer Faces (JSF), фреймворк 20 Java Transaction API (JTA) 143 JDBC, драйверы добавление в NetBeans 36 Jersey 307 JMS 236 введение 236 продюсер, реализация 243 создание ресурсов 237 JPA 135, 137 JPA, сущности автоматическое создание 153 добавление сохраняемых полей 145 именованные запросы 162 объекты доступа к данным (Data Access Object, DAO) 30/33
346 Предметный указатель создание 147 отношения 164 проверка допустимости 164 создание 136 создание приложений JSF 172 JPQL 135, 162 JSF-приложения запуск 78 разработка 61 JSF-проекты изменение страниц 66 именованные компоненты CDI, создание 73 создание 61 страница подтверждения, создание 77 JSON 257 парсинг данных 265, 271 создание данных 258, 269 JSON-P 257 объектная модель введение 257 парсинг данных 265 пример 261 создание данных 258 потоковая модель введение 268 парсинг данных 271 создание данных 269 JUnit, фреймворк 26 N NetBeans IDE 19 URL 19 автозавершение кода 43 введение 19 визуальные индикаторы 53 добавление драйверов JDBC 36 интегрирование со сторонней СУРБД 36 интегрирование со сторонним серве- ром приложений 33 интегрированная среда разработки 19 клавиши быстрого вызова 49 настройка для разработки Java EE 32 первый запуск 31 поддерживаемые платформы 22 получение 20 развертывание приложений 40 советы по эффективной разработке 43 соединение со сторонней СУРБД 38 создание ресурсов JMS 237 установка 23 в Linux 24 вMacOSX24 в Windows 24 на другие платформы 25 процедура 25 функция ускорения разработки HTML5 54 шаблоны кода 47 NetBeans Connector, расширение 57 установка 58 NetBeans, комплектность All 21 C/C++ 21 HTML5 & PHP 21 Java EE 21 Java SE 21 O Open Source Initiative (OSI), организа- ция 26 P PHP 21 PrimeFaces компоненты, использование в JSF-приложениях 114 описание 114 31/33
347 Предметный укзатель R REST 293 RESTful, веб-службы 293 создание JavaScript-клиента 313 создание Java-клиента 307 создание на основе существующей базы данных 294 тестирование 300 RichFaces адрес URL документации 133 внешние зависимости 129 компоненты, использование в JSF- приложениях 128 описание 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 write (String name, BigInteger value) 271 write (String name, boolean value) 271 write (String name, double value) 271 write (String name, int value) 271 write (String name, JsonValue value) 271 write (String name, long value) 271 write (String name, String value) 271 WSDL создание веб-служб на основе 338 WSDL (Web Services Definition Language - язык определения веб-служб) 318 32/33
Дэвид Хеффельфингер Разработка приложений Java EE 7 в NetBeans 8 Главный редактор Мовчан Д. А. dmkpress@gmail.com Перевод с английского Киселев А. Н . Корректор Синяева Г. И. Верстка Паранская Н. В . Дизайн обложки Мовчан А. Г. Формат 60×90 1/16. Гарнитура «Петербург». Печать офсетная. Усл. печ. л . 21,75. Тираж 200 экз. Веб­сайт издательства: www.дмк.рф Книги издательства «ДМК Пресс» можно заказать в торгово­издательском холдинге «Планета Альянс» наложенным платежом, выслав открытку или письмо по почтовому адресу: 115487, г. Москва, 2-й Нагатинский пр-д, д. 6А. При оформлении заказа следует указать адрес (полностью), по которому должны быть высланы книги; фамилию, имя и отчество получателя. Жела­ тельно также указать свой телефон и электронный адрес. Эти книги вы можете заказать и в интернет­магазине: www.alians-kniga.ru. Оптовые закупки: тел. +7 (499) 782-38-89 . Электронный адрес: books@alians-kniga.ru . P owe red by T CP DF (www.tcp df.o rg) 33/33