<?xml version="1.0"?>
<?xml-stylesheet type="text/css" href="http://wiki.linuxformat.ru/wiki/skins/common/feed.css?303"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="ru">
		<id>http://wiki.linuxformat.ru/wiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Dionysius</id>
		<title>Linuxformat - Вклад участника [ru]</title>
		<link rel="self" type="application/atom+xml" href="http://wiki.linuxformat.ru/wiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Dionysius"/>
		<link rel="alternate" type="text/html" href="http://wiki.linuxformat.ru/wiki/%D0%A1%D0%BB%D1%83%D0%B6%D0%B5%D0%B1%D0%BD%D0%B0%D1%8F:Contributions/Dionysius"/>
		<updated>2026-05-13T01:31:02Z</updated>
		<subtitle>Вклад участника</subtitle>
		<generator>MediaWiki 1.19.20+dfsg-0+deb7u3</generator>

	<entry>
		<id>http://wiki.linuxformat.ru/wiki/LXF95:%D0%A7%D1%82%D0%BE_%D0%B7%D0%B0_%D1%88%D1%82%D1%83%D0%BA%D0%B0</id>
		<title>LXF95:Что за штука</title>
		<link rel="alternate" type="text/html" href="http://wiki.linuxformat.ru/wiki/LXF95:%D0%A7%D1%82%D0%BE_%D0%B7%D0%B0_%D1%88%D1%82%D1%83%D0%BA%D0%B0"/>
				<updated>2009-02-06T06:43:32Z</updated>
		
		<summary type="html">&lt;p&gt;Dionysius: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;div style=&amp;quot;text-align: justify;&amp;quot;&amp;gt;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;'''''Пол Хадсон''' проливает свет на новое зелье от Sun, соперника интерактивных web-приложений Flash, Ajax и Silverlight...''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''В позапрошлом номере на этих страницах был D. Месяц назад – E. Думаете, мне охота слушать о языках программирования три месяца подряд? Я уж лучше пойду…'''&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
Нет, нет – погодите! Не захлопывайте страницу! &lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
'''Что?'''&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
Никакого программирования.&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
'''Ладно, у вас пять минут для того, чтобы заинтересовать меня. Время пошло.'''&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
Хорошо: JavaFX – новая крутая платформа от Sun для создания полноценных Интернет-приложений, конкурент Ajax. Это, кстати, та самая приправа, которой Google Mail обязан интеллектом. Ajax базируется на JavaScript, и это порождает целый спектр проблем совместимости с браузерами. Например, Google Docs прекрасно обрабатываются Firefox, но отказываются работать на Safari. Если вы пользователь Opera, то, как я слышал, OpenOffice.org хорош для редактирования документов. Но даже на Firefox Ajax не станет работать ни минуты, пока вы в оффлайне. &lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
'''Значит JavaFX призван заменить Ajax?'''&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
Я ещё не закончил. На ряду с Ajax для строительства привелекательных web-приложений всё шире применяется технология Flash от Adobe, но, хотя он и работает оффлайн, Flash Player – все-таки не открытое ПО. Возникают проблемы, если у вас неподдерживаемое оборудование, или вам некогда ждать, пока Adobe наконец выпустит новую версию для Linux. И, конечно, если вы идейный противник проприетарного ПО, Flash тоже не для вас. &lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
'''Подождите – так JavaFX заменит и Ajax, и Flash? Как бы не откусить больше, чем можешь прожевать…'''&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
Вот тут в дело вступает часть ‘Java’ от ‘JavaFX’: тут все основано на Java-платформе, то есть вся функциональность уже готова – Sun лишь надстраивает немного сверху. Например, есть Java 2D – библиотека, задуманная для высокоскоростной отрисовки графики, и она превосходна для создания блестящих графических интерфейсов. JavaFX располагает также всеми сетевыми и XML-библиотеками, необходимыми для отправки и получения данных по проводам, а также их кэширования оффлайн. Фактически, онлайн-приложения работают и в оффлайн-режиме.&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
'''Значит, экономится трафик?'''&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt; &lt;br /&gt;
Ну, объем передаваемого кода прежний, а браузер кэширует столько JavaScript, сколько можно, так что особой экономии ждать не приходится. Но JavaFX имеет определенные преимущества в вопросах безопасности, так как весь код исполняется внутри сверхбезопасной «песочницы» Java. Даже если у самого браузера проблемы с безопасностью, Java сохранит данные в неприкосновенности.&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
'''Значит, JavaFX работает оффлайн, поддерживается многими браузерами, да еще и безопаснее. Мне начинает нравиться.'''&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
Я ж говорил, это здорово! На самом деле, предлагается много больше. Помните старый слоган Java: «Написано однажды, работает везде»?&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
'''Мне помнится, было немного иначе: «Написано однажды, ломается везде».'''&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
Не спорю, раньше так и было. Но ведь Java с тех пор здорово усовершенствована, а это значит, что JavaFX будет работать на любом Java-совместимом устройстве. Да-да, и на вашем мобильном телефоне тоже. JavaScript очень медленно обрабатывается, потому и Google Doc на мобильных устройствах с их слабыми процессорами идут со скрипом, да и то если у браузера есть все необходимые средства для обработки JavaScript!&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
'''Но ведь и Java не из скороходов…'''&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
Напротив – язык Java для телефонов и других мобильных устройств существенно оптимизирован с целью извлечь максимум возможного из весьма ограниченных ресурсов. На телефонах не только слабые процессоры, но и весьма ограниченные объемы оперативной памяти, поэтому оптимизация Java пришлась как нельзя кстати – не нужны никакие хаки и прочая заумь, с помощью которых Ajax-приложения пытаются заставить работать на настольных ПК: все-таки Java – хорошо изученная платформа. Слой JavaFX просто ставится поверх виртуальной Java-машины (как на настольном ПК, так и на мобильнике), а это означает, что любой компьютер, инсталлировавший тонкий слой JavaFX, способен пользоваться подлинно кроссплатформенными JavaFX-приложениями.&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
'''А не выйдет ли так, что использование Java затруднит изучение JavaFX рядовыми программистами?'''&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
Это вполне могло случиться, если бы Sun одновременно не представила JavaFX Script.&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
'''Это что-то вроде JavaScript?'''&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
Не забывайте о том, что JavaScript – всего лишь претендент на трон Java. JavaScript не имеет ничего общего с Java, он даже изобретен не в Sun! JavaFX Script – который, будучи ядром JavaFX, вполне может называться просто «JavaFX» – тоже совершенно не похож на Java. JavaFX Script предназначен для упрощенного создания пользовательских интерфейсов с Java-библиотеками Swing, но он обладает полным доступом к основным библиотекам Java, если в том возникает потребность. &lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
'''Мне смутно вспоминается что-то из прошлых лет. Grove? Groove?'''&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
Верно. Вы вспомнили Groovy, мы об этом писали в LXF67. Конечно, между Groovy и JavaFX есть определенное сходство, но JavaFX предназначен для создания web-приложений, и инструментарий у него соответствующий. Groovy был более общим, то есть недостающую функциональность нужно было достраивать самому. &lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
'''Понимаю, как это нудно. Как же Sun умудрилась так быстро все обстряпать?'''&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
На самом деле проект разрабатывался довольно долго, под условным названием F3 (Form Follows Function). Недавно Microsoft представила нечто очень похожее на JavaFX, под названием Silverlight. В Microsoft-варианте в web-браузер встраивается .NET вместе с графическим холстом, поэтому можно писать код на C# или JavaScript и получать примерно те же вещи, что с JavaFX. &lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
'''Подождите, зачем тогда JavaFX?''' &lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
Видите ли, несмотря на все наши надежды, Microsoft не сделала ощутимого поворота в своей политике по открытому ПО, а Sun уже открыла общедоступный сайт, на котором выложила GPL-лицензированный код JavaFX. Разработчики Mono тоже ухватились за идею Silverlight и начали работу над свободным клоном на основе Mono и Cairo. Но все это – дело будущего, а JavaFX есть уже здесь и сейчас, хотя и в альфа-версии, и образцы его кода уже можно «потрогать руками».&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
'''То есть, JavaFX можно попробовать прямо сейчас?'''&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
Конечно! Sun выпустила даже JavaFX Mobile, встраиваемый в мобильные операционные платформы на основе Linux. На выходе и другие продукты семейства JavaFX, но в наличии пока только JavaFX Mobile и JavaFX Script. В настоящее время код довольно сырой, поэтому не ждите от него чудес – но с помощью JavaFX уже можно строить и связывать между собой интерфейсы Swing. &lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
'''Советуете немного подождать?'''&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
Наоборот, я рекомендовал бы включиться прямо сейчас, ведь первозданное состояние JavaFX позволяет вылепить из него все, что угодно, и на этой стадии ваши подсказки и пожелания очень бы пригодились.&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
'''Вы убедили меня как минимум загрузить и попробовать JavaFX. Назовите, пожалуйста, несколько URL, пока я в теме…'''&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
Вот они: домашняя страница JavaFX [http://www.sun.com/software/javafx], там вы найдёте ссылки на информацию, пресс-релизы и прочие маркетинговые детали. Если вы действительно решили ознакомиться с образцами кода, демо и руководствами, то вам сюда: [http://www.openjfx.dev.java.net], это домашняя страница кода. Полезно посетить и такую страничку: [http://www.blogs.sun.com/chrisoliver] – блог того парня, который создал F3 и довел его до JavaFX. Там находится немало примеров кода, готовых к работе. Удачи вам! ''LXF''&amp;lt;/div&amp;gt;&lt;/div&gt;</summary>
		<author><name>Dionysius</name></author>	</entry>

	<entry>
		<id>http://wiki.linuxformat.ru/wiki/LXF80:Seapine_Surround_SCM_4.1</id>
		<title>LXF80:Seapine Surround SCM 4.1</title>
		<link rel="alternate" type="text/html" href="http://wiki.linuxformat.ru/wiki/LXF80:Seapine_Surround_SCM_4.1"/>
				<updated>2009-02-06T06:42:58Z</updated>
		
		<summary type="html">&lt;p&gt;Dionysius: /* Большая разница */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Система управления версиями&lt;br /&gt;
&lt;br /&gt;
== Seapine Surround SCM 4.1 ==&lt;br /&gt;
&lt;br /&gt;
''Кажется, кто-то забыл сделать check-in нововведений в этом выпуске, подозревает '''Пол Хадсон'''.''&lt;br /&gt;
&lt;br /&gt;
{{Врезка&lt;br /&gt;
|Заголовок = '''Самое главное'''&lt;br /&gt;
|Содержание = Проприетарная система управления версиями с приятным GUI. Аналоги:&lt;br /&gt;
''Subversion'', ''BitKeeper'', ''Bazaar-NG''.&lt;br /&gt;
*'''Разработчик''': Seapine&lt;br /&gt;
*'''Сайт''': www.seapine.co.uk&lt;br /&gt;
*'''Цена''': 395 фунтов стерлингов за именную или 995 за плавающую лицензию&lt;br /&gt;
|Ширина=300px}}&lt;br /&gt;
&lt;br /&gt;
SourceForge недавно перешла от CVS к ''Subversion ''для управления своим репозитарием исходных текстов. Этот шаг сделал ''Subversion н''омером один среди систем управления версиями. Конечно, есть другие системы: B''itKeeper ''сохранил популярность даже после фиаско с ядром Linux; новичок ''BazaarNG ''уже задействован в нескольких проектах; и конечно же, проект Git, детище команды разработчиков ядра, достиг зрелого состояния.&lt;br /&gt;
&lt;br /&gt;
{{Врезка&lt;br /&gt;
|Заголовок= '''ЗАЧЕМ НУЖЕН SUBVERSION?'''&lt;br /&gt;
|Содержание= Subversion использует особую систему транзакций: файлы, которые выгружаются в репозитарий, недоступны другим пользователям до тех пор, пока не будут зафиксированы изменения в последнем из них. Например, мы фиксируем три файла, а в это время кто-то другой пытается получить доступ к одному из них — тогда Subversion отправит ему старую версию файла без внесенных нами изменений. Таким образом обеспечивается целостность проекта, независимо от числа разработчиков и их месторасположения.&lt;br /&gt;
|Ширина=300px}}&lt;br /&gt;
&lt;br /&gt;
Однако рынок систем контроля версий не ограничен одними открытыми программами, о которых мы слышим каждый день. Мы следим за развитием проприетарной системы контроля версий ''Seapine Surround SCM ''с ее первого выпуска: многообещающее приложение, которое было бы превосходным, если бы разработчики добавили в него пару ключевых возможностей, расцвело в... опять-таки многообещающее приложение, которому не хватает все тех же возможностей.&lt;br /&gt;
&lt;br /&gt;
=== Большая разница ===&lt;br /&gt;
Последний раз мы обсуждали ''Surround SCM ''версии 3.0 в ''LXF61''. С тех пор было добавлено несколько интересных возможностей, включая поддержку WebDAV (чтение и запись репозитория через webсервер), поддержка Unicode и усиленная интеграция с различными IDE. Особенно хороша поддержка WebDAV, так как Windows, Mac OS X и Linux могут читать и осуществлять запись на серверы WebDAV, что во многих случаях устраняет необходимость устанавливать ''Surround''-клиент.&lt;br /&gt;
&lt;br /&gt;
[[Изображение:Img_80_14_1.jpg|frame| ]]&lt;br /&gt;
&lt;br /&gt;
Поддержка IDE пригодится вам лишь в случае, если ваша среда поддерживается программой. В список поддерживаемых IDE входят ''Visual Studio'', ''Dreamweaver, WebSphere'', ''CodeWarrior'', ''IntelliJ IDEA, JBuilder ''и ''Eclipse'', из которых для нас важнее всего последние две, потому что они и в самом деле являются интегрированными средами разработки – мы любим, когда все наши инструменты доступны из одного приложения.&lt;br /&gt;
&lt;br /&gt;
=== Провал Seapine ===&lt;br /&gt;
Новые возможности неплохи, но как насчет старых проблем? Для начала, ''Surround SCM ''все еще поддерживает атомарность выгрузки в репозитарий отдельных файлов, но не задания в целом, так что ктото другой вполне может загрузить некорректную версию ПО, пока вы фиксируете свои изменения. (''см. врезку Зачем нужен Subversion?)''&lt;br /&gt;
&lt;br /&gt;
{{Врезка&lt;br /&gt;
|Заголовок= '''Вердикт Linux Format'''&lt;br /&gt;
|Содержание= * Функциональность 9/10&lt;br /&gt;
* Возможности 5/10&lt;br /&gt;
* Простота использования 6/10&lt;br /&gt;
* Скорость 4/10&lt;br /&gt;
* Оправданность цены 1/10&lt;br /&gt;
* Рейтинг 4/10&lt;br /&gt;
То, что оно работает под Linux, еще не повод его использовать&lt;br /&gt;
|Ширина=200px}}&lt;br /&gt;
&lt;br /&gt;
''П''рограмма продолжает игнорировать потребность в работе без подключения к сети: постоянное общение с сервером идет даже при выполнении простейших операций. Например, если вы хотите зафиксировать несколько файлов, причем изменения произошли только в малой их части, то S''urround SCM ''будет неэффективно использовать ваш канал: перекинет все файлы на сервер, а потом заявит, что в большинстве файлов изменений нет. Реализации примитивнее и придумать нельзя, поэтому вы вправе удивляться, чего ради надо приплачивать к 395 фунтам за честь ею попользоваться.&lt;br /&gt;
&lt;br /&gt;
Кстати о цене. Да, программа стоит 395 фунтов. Прибавьте к этому НДС, а также обязательный единовременный платеж в виде 20% от цены за дальнейшие обновления и поддержку, и все это только за именную лицензию (то есть выданную конкретному разработчику). Если хотите плавающую лицензию, то готовьте 995 фунтов + НДС + единовременный платеж. Для сравнения, пользователям Windows две полных копии ''Microsoft Visual Studio 2005 ''обойдутся дешевле, чем одна копия ''Seapine Surround ''с именной лицензией – причем у MS не самые низкие цены.&lt;br /&gt;
&lt;br /&gt;
''Surround SCM ''имеет приятный пользовательский интерфейс, но ничего более.&lt;br /&gt;
&lt;br /&gt;
Однако новизна GUI меркнет, как только вы осознаете, что никому не нужно отдельное приложение для задач, которые и без того выполняются в вашей IDE: ''Eclipse'', например, уже имеет первоклассную систему S''ubversion''. Во времена, когда царил ''CVS, Surround ''явно тянулась в лидеры. Но с приходом ''Subversion ''остался единственный выбор, и это не ''Seapine Surround''.&lt;/div&gt;</summary>
		<author><name>Dionysius</name></author>	</entry>

	<entry>
		<id>http://wiki.linuxformat.ru/wiki/LXF87-88:%D0%91%D0%B5%D0%B7%D0%BE%D0%BF%D0%B0%D1%81%D0%BD%D0%BE%D1%81%D1%82%D1%8C</id>
		<title>LXF87-88:Безопасность</title>
		<link rel="alternate" type="text/html" href="http://wiki.linuxformat.ru/wiki/LXF87-88:%D0%91%D0%B5%D0%B7%D0%BE%D0%BF%D0%B0%D1%81%D0%BD%D0%BE%D1%81%D1%82%D1%8C"/>
				<updated>2009-02-06T06:42:21Z</updated>
		
		<summary type="html">&lt;p&gt;Dionysius: /* Настроим индивидуальные фильтры */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{цикл/Безопасность}}&lt;br /&gt;
&lt;br /&gt;
== Строим межсетевой экран на базе Linux ==&lt;br /&gt;
''ЧАСТЬ 4 Любой подключенный к сети компьютер открыт для атаки. Д-р '''Крис Браун''' научит, как уменьшить вашу уязвимость, создав заслон при помощи стандартных утилит Linux.''&lt;br /&gt;
&lt;br /&gt;
В речи по поводу Дюнкеркской эвакуации, перед тем, как сказать&lt;br /&gt;
«Мы будем сражаться на пляжах», Уинстон Черчилль изрек&lt;br /&gt;
«Мы защитим наш остров любой ценой». Компьютеры — наши&lt;br /&gt;
прекрасные маленькие островки, но они всегда находятся под обстрелом, о чем свидетельствуют журналы любого Linux-сервера, имеющего&lt;br /&gt;
внешний IP-адрес.&lt;br /&gt;
&lt;br /&gt;
Межсетевые экраны (брандмауэры) — один из наиболее важных&lt;br /&gt;
рубежей обороны вашего компьютера. Это сетевые устройства (как&lt;br /&gt;
аппаратные, так и программные, запускаемые на обычном компьютере), контролирующие трафик между внутренней сетью и Интернетом.&lt;br /&gt;
Есть несколько технологий создания таких экранов. Например, запросы от клиента по протоколам прикладного уровня (HTTP, FTP или&lt;br /&gt;
DNS) перенаправляются через прокси-сервер; таким образом, прямое&lt;br /&gt;
соединение между узлом локальной сети и Интернетом становится&lt;br /&gt;
ненужным. Трансляция сетевых адресов (NAT или маскарадинг) также&lt;br /&gt;
делает вашу сеть невидимой снаружи, позволяя еще и использовать&lt;br /&gt;
свой диапазон адресов внутри сети. Однако наш сегодняшний урок&lt;br /&gt;
посвящен классическому пакетному фильтру, работающему на сетевом уровне (IP). Я покажу вам, как настроить его в Linux.&lt;br /&gt;
&lt;br /&gt;
=== Часть 1: Разбираем iptables ===&lt;br /&gt;
[[Изображение:Img 87-88 60 2.png|250px|thumb|Рис. 1. Три стандартных цепочки позволяют фильтровать входящие, исходящие и проходящие пакеты.]]&lt;br /&gt;
В недрах ядра скрывается кусок кода под названием netfilter (сетевой&lt;br /&gt;
фильтр) — он выполняет фильтрацию IP-пакетов: проверяет каждый&lt;br /&gt;
исходящий и входящий пакет и решает его судьбу, основываясь на&lt;br /&gt;
его параметрах, таких, как исходные и конечные адреса и порты или&lt;br /&gt;
флаги заголовка TCP.&lt;br /&gt;
&lt;br /&gt;
Два главных действия, выполняемых netfilter — принять пакет&lt;br /&gt;
или, соответственно, отклонить его. Он также может вести журнал с&lt;br /&gt;
помощью сервиса syslogd. Используя netfilter, можно настроить Linux-машину как межсетевой экран с фильтрацией пакетов.&lt;br /&gt;
&lt;br /&gt;
Обычно в роли межсетевого экрана выступает машина, работающая&lt;br /&gt;
между внутренней сетью предприятия и большим и злым Интернетом&lt;br /&gt;
и защищающая данные корпоративных компьютеров от взломщиков,&lt;br /&gt;
коварных скриптописцев и вирусов. В данном случае фильтрация применяется к пакетам, приходящим извне и переправляемым во внутреннюю сеть. Можно также фильтровать пакеты, идущие на межсетевой&lt;br /&gt;
экран изнутри или исходящие из него; то есть использовать netfilter как&lt;br /&gt;
персональный брандмауэр, пригодный даже для одиночной домашней&lt;br /&gt;
машины, подключенной к сети через ADSL или модем.&lt;br /&gt;
&lt;br /&gt;
Netfilter настраивается с помощью специальных правил, задаваемых из консоли командой iptables. Ее синтаксис — полноценный язык&lt;br /&gt;
со своими правилами. Типичное правило включает в себя компоненты&lt;br /&gt;
для распознавания определенных пакетов и заканчивается директивой&lt;br /&gt;
принятия или отклонения подобного пакета. Вот пример того, как это&lt;br /&gt;
выглядит:&lt;br /&gt;
&amp;lt;pre&amp;gt;iptables -A INPUT -i eth0 -p udp -s $NAMESERVER --sport 53 -d 140.116.5.1 --dport 53 -J ACCEPT&amp;lt;/pre&amp;gt;&lt;br /&gt;
Чтобы разобраться в этом, нужно представить, какими путями пакеты приходят, проходят и уходят с вашей машины. Есть три варианта,&lt;br /&gt;
показанные на рис. 1. Входящий пакет проверяется пакетным фильтром, и следует его решению. Если адрес доставки пакета соответствует данной машине, пакет проходит через входную цепочку (Input) и&lt;br /&gt;
идет вверх по стеку протоколов. Если пакет предназначен для другой&lt;br /&gt;
машины, реализация протокола сетевого уровня опрашивает таблицу&lt;br /&gt;
маршрутизации, чтобы узнать, на какой сетевой интерфейс его отправить. Затем он идет по цепочке Forward и возвращается в сеть. Наконец,&lt;br /&gt;
пакеты, формируемые на данной машине, проходят через выходную&lt;br /&gt;
цепочку (Output) и попадают в сеть. Каждая из этих цепочек, в сущности, является простым набором условий, через проверку которых и&lt;br /&gt;
проходит пакет.&lt;br /&gt;
&lt;br /&gt;
{{Врезка|center|&lt;br /&gt;
|Заголовок=Зачем вам брандмауэр?&lt;br /&gt;
|Содержание=Интернет, увы, становится враждебным окружением, к которому уже&lt;br /&gt;
нельзя подключаться непосредственно, без брандмауэра (он же&lt;br /&gt;
firewall). Однако домашние пользователи с широкополосным доступом должны различать две вещи. Если ADSL-модем воткнут в вашу&lt;br /&gt;
машину напрямую (ну, или через USB), то у нее есть реальный IP-адрес, к которому можно подключиться извне, и она нуждается в&lt;br /&gt;
защите.&lt;br /&gt;
&lt;br /&gt;
А если вы подключаетесь к внешнему ADSL-модему через Ethernet,&lt;br /&gt;
то в такой модем уже встроен маршрутизатор, выполняющий преобразование адресов (Network address translation – NAT). Большое преимущество NAT состоит в том, что машинам из внешнего мира запрещено подключение к внутренним – т.е. к вашей домашней машине [по&lt;br /&gt;
такому принципу работают многие российские интернет-провайдеры,&lt;br /&gt;
однако, имейте в виду, что в этом случае ваши соседи по сегменту также потенциально опасны – кое-кто из них, например, не побрезгует&lt;br /&gt;
воспользоваться «дырой» в чужой системе, чтобы посидеть в&lt;br /&gt;
Интернете за ваш счет, – прим. ред.].&lt;br /&gt;
|Ширина=}}&lt;br /&gt;
&lt;br /&gt;
==== Настроим индивидуальные фильтры ====&lt;br /&gt;
{{Врезка&lt;br /&gt;
|Заголовок=Скорая помощь&lt;br /&gt;
|Содержание=Будьте осторожны,&lt;br /&gt;
когда настраиваете&lt;br /&gt;
брандмауэр на&lt;br /&gt;
машине, к которой у&lt;br /&gt;
вас нет физического доступа. Очень&lt;br /&gt;
легко заблокировать все входящие&lt;br /&gt;
соединения, задавая&lt;br /&gt;
политику по умолчанию: вы и охнуть не&lt;br /&gt;
успеете. Верьте мне!&lt;br /&gt;
Я уже пробовал…&lt;br /&gt;
|Ширина=150px}}&lt;br /&gt;
Теперь вы понимаете, что в примере выше мы добавляем правило во&lt;br /&gt;
входную цепочку (-A INPUT). Условие гласит, что пакет должен придти&lt;br /&gt;
на сетевой интерфейс eth0, и это должен быть UDP-пакет. Оно также&lt;br /&gt;
указывает исходные адрес и порт, а также адрес и порт назначения,&lt;br /&gt;
которым должен соответствовать пакет. Эта информация передается&lt;br /&gt;
в заголовке пакета. Завершающая часть (-J ACCEPT) говорит netfilter,&lt;br /&gt;
что делать с тем пакетом, который соответствует всем указанным в&lt;br /&gt;
правиле параметрам. Эта часть называется целью условия. Возможные&lt;br /&gt;
цели таковы:&lt;br /&gt;
* ACCEPT — принять пакет;&lt;br /&gt;
* DROP — молча отклонить пакет;&lt;br /&gt;
* REJECT — отклонить пакет и сказать об этом отправителю;&lt;br /&gt;
* LOG — записать прибытие пакета (и позволить ему пройти к следующему условию).&lt;br /&gt;
Списки условий для цепочки могут быть весьма длинными: 50 — обычнейшее количество. Для каждого пакета проверяется каждое&lt;br /&gt;
условие. Как только произойдет совпадение, будет предпринято соответствующее решение (цель), и следующие условия проверяться уже не&lt;br /&gt;
будут. Если пакет доходит до конца цепочки, не удовлетворив ни одному условию, его судьба зависит от «политики» цепочки. Например,&lt;br /&gt;
&amp;lt;pre&amp;gt;iptables -P INPUT DROP&amp;lt;/pre&amp;gt;&lt;br /&gt;
говорит, что все такие пакеты будут отклонены. Простейший способ&lt;br /&gt;
создания набора условий — установить политику по умолчанию в DROP,&lt;br /&gt;
а затем добавлять условия для нужных пакетов. Это подход с «презумпцией виновности». Другой подход (установка политики по умолчанию в ACCEPT, а затем создание правил для блокировки ненужных&lt;br /&gt;
пакетов) гораздо сложнее, менее безопасен и не рекомендуется.&lt;br /&gt;
Вы можете улучшить организацию ваших правил, определив собственные цепочки и присвоив им имена. Например, я могу определить&lt;br /&gt;
цепочки TCP_RULES и UDP_RULES. Затем, в главной входной цепочке&lt;br /&gt;
я могу задать всего два предопределенных правила:&lt;br /&gt;
&amp;lt;pre&amp;gt;iptables -A INPUT -p tcp -J TCP_RULES&lt;br /&gt;
iptables -A INPUT -p udp -J UDP_RULES&amp;lt;/pre&amp;gt;&lt;br /&gt;
Это позволяет более гибко управлять наборами правил, да и более&lt;br /&gt;
эффективно; например, UDP-пакет никогда не будет сравниваться&lt;br /&gt;
с правилами из цепочки TCP_RULES. Я предпочитаю думать, что прыжки по цепочке аналогичны проходам по процедурам (подпрограммам).&lt;br /&gt;
&lt;br /&gt;
Механизм «попакетной» проверки IP-трафика — лишь половина&lt;br /&gt;
умений iptables. Возможно еще записывать, когда происходит TCP или&lt;br /&gt;
UDP-транзакция, и проверять пакеты не только по их заголовкам, но и&lt;br /&gt;
в контексте совершаемого соединения… Борюсь с искушением вдаться&lt;br /&gt;
в подробности, иначе урок разросся бы до размеров журнала.&lt;br /&gt;
&lt;br /&gt;
Можно настроить межсетевой экран путем создания полного набора правил и команды iptables, как было показано. Начните с политики&lt;br /&gt;
безопасности, определяющей, какие сервисы должны быть доступны,&lt;br /&gt;
вычислите, какой трафик они генерируют, поместите соответствующие&lt;br /&gt;
правила в цепочки, а все остальное запретите. Все это требует отличного знания TCP/IP и большой внимательности. Построение межсетевого экрана таким способом аналогично созданию большого web-сайта путем ручной верстки в vi или написанию программ на Ассемблере.&lt;br /&gt;
Другими словами, это лучше оставить экспертам, которым нужен полный контроль над каждой настройкой.&lt;br /&gt;
&lt;br /&gt;
Но если вы все-таки решите дерзнуть, ознакомьтесь с материалом&lt;br /&gt;
по iptables из LXF47. Мы положили его на диск в формате PDF.&lt;br /&gt;
&lt;br /&gt;
=== Часть 2: Простой путь ===&lt;br /&gt;
{{Врезка&lt;br /&gt;
|Заголовок=Скорая помощь&lt;br /&gt;
|Содержание=Рискуя быть&lt;br /&gt;
навязчивым,&lt;br /&gt;
я хочу подчеркнуть&lt;br /&gt;
важность политики&lt;br /&gt;
безопасности.&lt;br /&gt;
Пока вы не сядете&lt;br /&gt;
и не зададитесь&lt;br /&gt;
вопросом «Кто&lt;br /&gt;
и что может делать&lt;br /&gt;
с моей машиной?»,&lt;br /&gt;
вы не готовы&lt;br /&gt;
настраивать правила межсетевого&lt;br /&gt;
экрана, отключать&lt;br /&gt;
ненужные сервисы&lt;br /&gt;
и повышать&lt;br /&gt;
безопасность&lt;br /&gt;
компьютера.&lt;br /&gt;
|Ширина=150px}}&lt;br /&gt;
Я показал вам продвинутый путь настройки межсетевого экрана, однако большинство из нас (включая меня), возможно, предпочтут использование утилиты, которая позволит указать политику безопасности на&lt;br /&gt;
более высоком уровне и сгенерирует команды iptables сама. А потом их&lt;br /&gt;
можно будет подредактировать, как описано в первой части.&lt;br /&gt;
&lt;br /&gt;
[[Изображение:Img 87-88 62 2.png|400px|left|thumb|Рис. 2. Межсетевой экран с тремя интерфейсами защищает DMZ и внутреннюю сеть от внешнего мира. Для каждой сети можно выбрать свой уровень фильтрации.]]&lt;br /&gt;
Мне нравится модуль конфигурации брандмауэра YaST в SUSE.&lt;br /&gt;
Он позволит вам настроить брандмауэр примерно на том же уровне,&lt;br /&gt;
на котором вы задаете политику безопасности, в противоположность&lt;br /&gt;
уже рассмотренному нудному процессу ручного ввода команд. Модуль&lt;br /&gt;
требует указать для каждого сетевого интерфейса три зоны: внешнюю,&lt;br /&gt;
внутреннюю или демилитаризованную. Эти термины иллюстрируются&lt;br /&gt;
рис. 2, где показана архитектура классического межсетевого экрана в&lt;br /&gt;
корпоративной сети.&lt;br /&gt;
&lt;br /&gt;
Внешние интерфейсы — те, что подключены к огромному злому Интернету; в домашних условиях это обычно ADSL или простой&lt;br /&gt;
модем. Если у вас всего одна машина, ваш сетевой интерфейс будет&lt;br /&gt;
внешним.&lt;br /&gt;
&lt;br /&gt;
Внутренние сетевые интерфейсы — те, что подключены к доверенным узлам. В небольшом офисе, где как шлюз используется Linux-машина, это будет интерфейс, подключенный к локальной сети.&lt;br /&gt;
&lt;br /&gt;
Демилитаризованная зона (DMZ) — это сеть, в которой находятся видимые снаружи машины, например, Web/FTP/почтовые-серверы. Сказать по правде, если вы настраиваете межсетевой экран для&lt;br /&gt;
использования в корпоративной среде с DMZ, вы обязаны изучить&lt;br /&gt;
более глубокие материалы по данной тематике. Тот же модуль YaST, к&lt;br /&gt;
примеру, недостаточно гибок для настройки направления пакетов между внутренней сетью и DMZ; он всего лишь определяет правила ограничения доступа к машине из всех трех зон.&lt;br /&gt;
&lt;br /&gt;
Если интерфейс у вас на компьютере только один, не имеет значения, считаете ли вы его внешней или внутренней зоной. Назначьте&lt;br /&gt;
зону произвольно и определите доступные сервисы, как мы покажем&lt;br /&gt;
далее.&lt;br /&gt;
&lt;br /&gt;
[[Изображение:Img 87-88 62 1.png|thumb|Рис. 3 Модуль YaST для настройки брандмауэра. Здесь указывается, какие сервисы доступны в каждой из трех зон.]]&lt;br /&gt;
Определив, какие сетевые интерфейсы соответствуют нужным&lt;br /&gt;
зонам, переходите на экран доступных сервисов для каждой из зон&lt;br /&gt;
(рис. 3). Слева вы видите перечень семи экранов модуля YaST, справа — выпадающие списки зон и сервисов (DNS, IMAP, HTTP и т. д.).&lt;br /&gt;
Выберите зону и определите доступные ей сервисы.&lt;br /&gt;
&lt;br /&gt;
Для разрешения доступа к сервису, выберите его из списка и&lt;br /&gt;
нажмите Add (Добавить). Чтобы запретить сервис, выделите его и&lt;br /&gt;
нажмите Remove (Удалить). Для сервисов, которых нет в списке,&lt;br /&gt;
вы должны будете указать номер порта. Например, для разрешения&lt;br /&gt;
Telnet-сеансов (возможно, вы захотите разрешить их только для внутренней сети, поскольку Telnet небезопасен) нажмите на Advanced… и&lt;br /&gt;
введите номер порта (23) в поле TCP Ports. Внутренняя зона должна обрабатываться по-особому. Внизу вы найдете флажок Protect&lt;br /&gt;
Firewall From Internal Zone. Пока он не отмечен, к пакетам, исходящим&lt;br /&gt;
из внутренней сети, не будут применяться никакие правила.&lt;br /&gt;
&lt;br /&gt;
==== За кулисами ====&lt;br /&gt;
{{Врезка|left|&lt;br /&gt;
|Заголовок=Скорая помощь&lt;br /&gt;
|Содержание=Сканер портов,&lt;br /&gt;
типа Nmap,&lt;br /&gt;
рассмотренного&lt;br /&gt;
на прошлом&lt;br /&gt;
уроке — отличная&lt;br /&gt;
утилита для проверки корректной&lt;br /&gt;
работы правил вашего межсетевого&lt;br /&gt;
экрана.&lt;br /&gt;
|Ширина=150px}}&lt;br /&gt;
Модуль YaST не генерирует правила iptables напрямую. Вместо этого&lt;br /&gt;
он редактирует файл /etc/sysconfig/SuSEfirewall2. Если у вас SUSE,&lt;br /&gt;
рекомендую изучить этот файл. Он очень хорошо прокомментирован и&lt;br /&gt;
углубит ваше понимание действий YaST, а также предоставит синтаксически более сложные примеры.&lt;br /&gt;
&lt;br /&gt;
Сам межсетевой экран настраивается на&lt;br /&gt;
раннем этапе загрузки через два скрипта,&lt;br /&gt;
SuSEfirewall2_init и SuSEfirewall2_setup, находящиеся в директории /etc/init.d. Первый&lt;br /&gt;
запирает брандмауэр (пропуская только трафик bootp и ping), а второй, запускающийся&lt;br /&gt;
несколько позже, устанавливает цепочки правил при включении брандмауэра и очищает&lt;br /&gt;
их при отключении. Оба скрипта в конечном&lt;br /&gt;
итоге вызывают /sbin/SuSEfirewall2: это движок механизма межсетевого экрана в SUSE, и&lt;br /&gt;
в нем генерируются команды iptables. Я бы не&lt;br /&gt;
рекомендовал вам в нем копаться (особенно&lt;br /&gt;
после плотного обеда), если вы не любитель&lt;br /&gt;
скриптоужастиков.&lt;br /&gt;
&lt;br /&gt;
==== Брандмауэр Fedora ====&lt;br /&gt;
{{Врезка&lt;br /&gt;
|Заголовок=Скорая помощь&lt;br /&gt;
|Содержание=Если вы столкнетесь с проблемами,&lt;br /&gt;
заставляя какой-либо сетевой&lt;br /&gt;
сервис работать,&lt;br /&gt;
стоит проверить,&lt;br /&gt;
не стоит ли на его&lt;br /&gt;
пути netfilter. Мне&lt;br /&gt;
случалось потерять&lt;br /&gt;
много времени,&lt;br /&gt;
прежде чем я обнаруживал, что все&lt;br /&gt;
дело в брандмауэре. Его отключение&lt;br /&gt;
(ненадолго!) значительно упростит&lt;br /&gt;
настройку.&lt;br /&gt;
|Ширина=150px}}&lt;br /&gt;
[[Изображение:Img 87-88 63 1.jpg|thumb|Рис. 4 Утилита настройки брандмауэра в Fedora сгодится при создании личного брандмауэра, но не более того.]]&lt;br /&gt;
Не только в SUSE есть графические утилиты&lt;br /&gt;
для настройки брандмауэра. На рис. 4 показана утилита system-config-securitylevel, входящая в Fedora. Это более простой инструмент,&lt;br /&gt;
чем модуль YaST. Она не позволяет определять&lt;br /&gt;
зоны, а просто закрывает и открывает нужные&lt;br /&gt;
порты, и подходит только для настройки личного брандмауэра на одиночной машине.&lt;br /&gt;
&lt;br /&gt;
[[Изображение:Img 87-88 63 2.png|thumb|Рис. 5 Просмотр действующих правил командой iptables-save. Если сохранить их в файле, можно будет их восстановить командой iptables-restore.]]&lt;br /&gt;
Вы можете просмотреть ваши правила, выполнив команду iptablessave&lt;br /&gt;
(ее вывод показан на рис. 5). Здесь вы видите пример определенной пользователем цепочки (RH-Firewall-1-INPUT), устанавливающей&lt;br /&gt;
правила как для входящей, так и для исходящей цепочек. Если сохранить вывод iptables-save в файле, из него можно будет восстановить&lt;br /&gt;
правила командой iptables-restore.&lt;br /&gt;
&lt;br /&gt;
Netfilter имеет и другие возможности (например, NAT и фильтрацию&lt;br /&gt;
по состояниям), я о них успел только намекнуть. Есть и другие методы,&lt;br /&gt;
например, прокси-серверы уровня приложений, они тоже полезны при&lt;br /&gt;
настройке межсетевого экрана.&lt;br /&gt;
&lt;br /&gt;
{{Врезка|center|&lt;br /&gt;
|Заголовок=Предотвращение скрытого сканирования&lt;br /&gt;
|Содержание=В прошлом месяце я рассказывал о скрытом сканировании с помощью Nmap. Этот вид сканирования использует нештатные комбинации флагов TCP-пакетов, а скрытым называется, поскольку маловероятно, что системный журнал его зафиксирует. Однако, используя&lt;br /&gt;
способность netfilter проверять флаги в заголовке TCP-пакета и&lt;br /&gt;
записывать события в журнал, можно не только блокировать подобные попытки, но и регистрировать факт их наличия. Подробности&lt;br /&gt;
довольно запутанны, но пример пары правил против FIN-сканирования прояснит суть этой идеи:&lt;br /&gt;
&amp;lt;pre&amp;gt;iptables -A INPUT -p tcp --tcp-flags ACK,FIN FIN -j LOG --log-prefix &amp;quot;Stealth scan&amp;quot;&lt;br /&gt;
iptables -A INPUT -p tcp --tcp-flags ACK,FIN FIN -j DROP&amp;lt;/pre&amp;gt;&lt;br /&gt;
Первое правило служит для обязательной записи события в журнал. После цели LOG пакет продолжает движение по цепочке условий (в отличие от целей DROP и ACCEPT. Принятые или отклоненные&lt;br /&gt;
пакеты на дальнейшую проверку не пойдут). В данном случае пакет,&lt;br /&gt;
удовлетворяющий первому условию, удовлетворит и второму,&lt;br /&gt;
согласно которому он будет отклонен. Параметры --tcp-flags ACK,FIN&lt;br /&gt;
FIN описывают комбинацию TCP-флагов. Первый список состояний&lt;br /&gt;
(ACK,FIN) перечисляет тестируемые флаги, второй (FIN) – те из них,&lt;br /&gt;
что установлены. Таким образом, условие соответствует тем пакетам, в которых есть FIN-флаг, но нет ACK. При нормальном TCP-соединении эта комбинация невозможна, зато типична для скрытого&lt;br /&gt;
сканирования.&lt;br /&gt;
&lt;br /&gt;
Проведите эксперимент: если у вас две Linux-системы, выберите&lt;br /&gt;
одну из них мишенью, а на второй запустите нечто вроде&lt;br /&gt;
&amp;lt;pre&amp;gt;nmap -sF -p1-50 192.168.0.3&amp;lt;/pre&amp;gt;&lt;br /&gt;
(подставьте нужный IP-адрес). Nmap сообщит вам об открытых портах. Если вы проследите судьбу пакетов через Ethereal, то увидите,&lt;br /&gt;
что FIN-пакеты достигли цели, а в ответ были отправлены пакеты&lt;br /&gt;
RST,ACK. Теперь добавьте на системе-мишени два правила, показанных выше, и повторите попытку. Вы увидите, что Nmap больше не&lt;br /&gt;
обнаруживает открытые порты, а в журнале (у меня это /var/log/firewall) появились новые сообщения. Ethereal покажет, что FIN-пакеты по-прежнему доходят, но не получают ответа. На подобных экспериментах можно научиться многому. Вот вам развлечение для&lt;br /&gt;
дождливого вечера!&lt;br /&gt;
|Ширина=}}&lt;br /&gt;
&lt;br /&gt;
{{Врезка|center|&lt;br /&gt;
|Заголовок=Рекомендуется прочесть&lt;br /&gt;
|Содержание=Лучшая книга по брандмауэрам в Linux, конечно же, Linux Firewalls Стива Сьюринга [Steve&lt;br /&gt;
Sturing] и Роберта Циглера [Robert Ziegler], третье издание (и, будете смеяться, третий издатель,&lt;br /&gt;
Novell Press). Эта книга не только подробнейше описывает использование iptables, но и рассматривает внутреннюю защиту, SELinux и мониторинг сети.&lt;br /&gt;
|Ширина=}}&lt;/div&gt;</summary>
		<author><name>Dionysius</name></author>	</entry>

	<entry>
		<id>http://wiki.linuxformat.ru/wiki/LXF92:Rosegarden</id>
		<title>LXF92:Rosegarden</title>
		<link rel="alternate" type="text/html" href="http://wiki.linuxformat.ru/wiki/LXF92:Rosegarden"/>
				<updated>2009-02-06T06:39:25Z</updated>
		
		<summary type="html">&lt;p&gt;Dionysius: /* Мутная аудиоконверсия */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__TOC__&lt;br /&gt;
&lt;br /&gt;
= Rosegarden 1.5 =&lt;br /&gt;
&lt;br /&gt;
''Система нумерации версий более разумная, кодовая база переработана, но розы-то в этом саду остались? '''Грэм Моррисон''' принюхивается.''&lt;br /&gt;
&lt;br /&gt;
{{Врезка|left|&lt;br /&gt;
Заголовок = Вкратце… |&lt;br /&gt;
Содержание = MIDI- и аудиосеквенсор с поддержкой программного синтезатора и эффектов реального времени. См. также: ''Ardour'' и ''Muse''. |&lt;br /&gt;
Ширина=250px}}&lt;br /&gt;
&lt;br /&gt;
За два года, прошедшие со времени выхода последней версии Rosegarden (почему-то названной версией 4), разработчики хорошо потрудились над добавлением функций и оптимизацией кода. Заодно они создали Fervent Software и выпустили коммерческий Linux-дистрибутив под названием Studio To Go. Rosegarden составляет сердцевину Studio To Go, и по функциональности это&lt;br /&gt;
открытое приложение ближе всего подходит к уровню программ, используемых мировыми лидерами звукозаписи: Steinberg Cubase, Logic Pro от Apple и Digital Performer от MOTU.&lt;br /&gt;
&lt;br /&gt;
Rosegarden использует окно аранжировки: вертикальные дорожки, содержащие аудио- или MIDI-данные, располагаются слева, а сегменты данных каждой дорожки растягиваются вправо. Каждая дорожка обычно – отдельный инструмент или запись, а каждый сегмент – музыкальный фрагмент. Окном аранжировки оно называется потому, что в нем можно свободно перетаскивать блоки влево и вправо по времени, вверх и вниз между инструментами, эффективно меняя «аранжировку» музыкального фрагмента. Cubase, Logic и Digital Performer встраивают в такое окно функции правки, позволяющие редактировать MIDI-ноты и менять громкость фрагментов. Rosegarden этого не умеет (пока), но в новой версии переключение между дорожками и управление инструментами значительно упростилось.&lt;br /&gt;
[[Изображение:Img_92_13_2.jpg|left|thumb|Интерфейс Rosegarden]]&lt;br /&gt;
&lt;br /&gt;
Особенно впечатляет программный синтезатор. Мы впервые видим DSSI-инструменты, так хорошо интегрированные в музыкальное&lt;br /&gt;
приложение для Linux. Избранные инструменты и настройки хранятся вместе с проектом, поэтому обмениваться материалами с&lt;br /&gt;
другими пользователями теперь можно без долгих переговоров о настройке Jack-соединений. Параметры инструментов устанавливаются с помощью точных органов управления, а звуковые дорожки и треки синтезатора могут использовать LADSPA-эффекты с панели параметров инструментов в реальном времени. Работа со звуком по-прежнему перекладывается на какой-либо внешний редактор, например Audacity, но матричное отображение для редактирования MIDI-нот заметно улучшилось. Теперь работать с нотами в Rosegarden – одно удовольствие.&lt;br /&gt;
&lt;br /&gt;
=== Нету розы без шипов ===&lt;br /&gt;
Главных дополнений два. Первое – автоматическое преобразование аудио-фрагментов. Идея такая: если есть фрагмент длиной в один такт, а его нужно растянуть на два, нажатие Ctrl с перетаскиванием границы фрагмента автоматически подгонит звук внутри фрагмента для соответствия новой продолжительности. Масштабируя фрагмент, Rosegarden перерабатывает внешним редактором (sox или sndfile-resample) весь аудиофайл. В зависимости от размеров клипа, это может занять от нескольких секунд до убийственных для производительности нескольких минут. Лучше было бы масштабировать звук только в пределах фрагмента, именно так поступают коммерческие соперники Rosegarden.&lt;br /&gt;
&lt;br /&gt;
=== Мутная аудиоконверсия ===&lt;br /&gt;
Второе заметное дополнение – прозрачная аудиоконверсия. К несчастью, импорт звука неинтуитивен, для него нет пункта меню.&lt;br /&gt;
Необходимо перетащить аудиофайл в окно аранжировки, и Rosegarden запустит фоновый процесс для его конвертации с помощью внешнего скрипта. Если у вас нет подходящего инструмента – скажем, oggdec – Rosegarden безмолвно провалит импорт.&lt;br /&gt;
&lt;br /&gt;
Рассматривая Rosegarden в LXF65, мы заключили, что, при всех недостатках, это лучшая аудио/секвенсорная прогамма на Linux. Теперь мы вдвойне в этом уверены, ведь некоторые из недостатков уже исправлены. Внешние звуковые редакторы и программные синтезаторы отныне выглядят «полноправными гражданами», а не примитивными дополнениями к MIDI-секвенсору. С учетом нововведений 1.5, и если разработчики сохранят прицел на интуитивность – мы ждем не дождемся версии 2.0!&lt;br /&gt;
&lt;br /&gt;
=== Вердикт ===&lt;br /&gt;
&lt;br /&gt;
'''Rosegarden 1.5'''&lt;br /&gt;
&lt;br /&gt;
Разработчик: Chris Cannam, Richard&lt;br /&gt;
&lt;br /&gt;
Сайт: www.rosegardenmusic.com&lt;br /&gt;
&lt;br /&gt;
Цена: Бесплатно под GPL&lt;br /&gt;
&lt;br /&gt;
* Функциональность                   9/10&lt;br /&gt;
* Производительность                 6/10&lt;br /&gt;
* Простота использования             7/10&lt;br /&gt;
* Документация                       7/10&lt;br /&gt;
&lt;br /&gt;
Интуитивно и достаточно гибко для мира современной музыки.&lt;br /&gt;
&lt;br /&gt;
'''Рейтинг 8/10'''&lt;br /&gt;
&lt;br /&gt;
[[Категория:Обзоры]]&lt;/div&gt;</summary>
		<author><name>Dionysius</name></author>	</entry>

	<entry>
		<id>http://wiki.linuxformat.ru/wiki/LXF80:Libre_Graphics</id>
		<title>LXF80:Libre Graphics</title>
		<link rel="alternate" type="text/html" href="http://wiki.linuxformat.ru/wiki/LXF80:Libre_Graphics"/>
				<updated>2009-02-06T06:38:03Z</updated>
		
		<summary type="html">&lt;p&gt;Dionysius: /* Scribus */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== LIBRE GRAPHICS ==&lt;br /&gt;
Adobe, Corel, Quark и другим стоит оглянуться – свободное программное обеспечение вот-вот догонит их продукцию. Ник Вейч посетил первую конференцию Libre Graphics, знаковое событие для всего мира open source.&lt;br /&gt;
&lt;br /&gt;
Linux. У большинства людей это слово ассоциируется с безумно высокотехнологичным миром Интернета. В голову немедля приходят образы «лезвийных» серверов, кластеров, Apache и потоков кода. И пингвины. В конце девяностых доминирование свободного ПО среди технологий, определяющих развитие Интернета, стало почти неизбежным – не только Linux, но весь массив технологий, обеспечивающих развитие сети, полагается на открытый код и открытые стандарты.&lt;br /&gt;
&lt;br /&gt;
Но Linux – вовсе не платформа, ориентированная всего на одну задачу, и вводная конференция Libre Graphics, проходившая на юге Франции, в Лионе, в школе Superieur Chimie Physique Electronique (CPE), это отчётливо продемонстрировала.&lt;br /&gt;
&lt;br /&gt;
Организовал конференцию разработчик Gimp Дейв Нири (Dave Neary). Он затевал просто встречу с другими Gimp-программистами, но конференция довольно быстро переросла в рассмотрение графических инструментов вообще – разработчики других проектов ухватились за возможность повидать коллег, обменяться идеями и обсудить общие проблемы.&lt;br /&gt;
&lt;br /&gt;
Три дня место проведения мероприятия было забито программистами, пользователями и просто зеваками. Презентации в главном зале прошли с аншлагом и затронули новые технологии и обновление проектов и тренинги на тему, как можно помочь проектам.&lt;br /&gt;
&lt;br /&gt;
'''Прорыв'''&lt;br /&gt;
&lt;br /&gt;
Хотя полезным было все, неформальное общение посетителей между заседаниями, вероятно, в наибольшей степени способствовало успеху. В коридорах, во время перекуров на улице, в обед и, конечно же, за ужином шли разговоры об углублении сотрудничества, объединении знаний и ресурсов для решения общих проблем, обмен дружескими советами в диапазоне от того, какие библиотеки использовать, до эффективных приёмов работы.&lt;br /&gt;
&lt;br /&gt;
Впрочем, это был не просто «слёт умников»: многие из представленных проектов – действительно серьёзные графических инструменты. И большинство из них – кроссплатформенные: Inkscape, Gimp, Scribus, Blender… Далее мы подробнее рассмотрим некоторые из этих приложений, продвигающих Open Source как выбор для профессиональной графики и дизайна. Сложившийся рынок принадлежит таким фирмам, как Adobe, но молодёжь, несомненно, наступает на пятки. И вполне возможно, когда-нибудь, в не столь далёком будущем, эта конференция будет рассматриваться как поворотная точка, когда графические инструменты с открытым кодом стали взрослыми…&lt;br /&gt;
----&lt;br /&gt;
'''''LinuxFormat:''' ''Итак, почему вы организовали LGM?&lt;br /&gt;
&lt;br /&gt;
'''Дейв Нири (ДН)''': Ну, есть и другие конференции, но они фокусируются не на том. Это мероприятия вроде Fosdem &amp;lt;nowiki&amp;gt;[Free and Open Source Software Developers’ Meeting, &amp;lt;/nowiki&amp;gt;встреча разработчиков свободного ПО, – прим. пер.], слишком общего, или Akademy &amp;lt;nowiki&amp;gt;[всемирная конференция сообщества KDE, – прим. пер.]&amp;lt;/nowiki&amp;gt;. Но ''Gimp'', да и все графические приложения несколько выпадают из этого – Blender не использует GTK или Qt, а ''Gimp ''– хотя и приложение Gnome, но фактически не связан с этим проектом.&lt;br /&gt;
&lt;br /&gt;
''Gimp ''используется и в KDE, Scribus работает на Gnome. Приложения, представленные здесь, кросс-платформенные – нам нужно большее, чем специализированная конференция Linux. К тому же, у графики свои причуды: мы должны думать о «людях в оранжевых очках». Люди, в которых мы заинтересованы, наши пользователи – графические дизайнеры и арт-директора, они далеки от сферы открытого ПО. Нам нужно делать что-то конкретное, чтобы «зацепить» этот сорт людей.&lt;br /&gt;
&lt;br /&gt;
'''''LXF:''' ''То есть конференция задумана, чтобы убедить людей использовать открытые инструменты?&lt;br /&gt;
&lt;br /&gt;
'''ДН:''' Мы преследуем две цели: первая – собрать вместе участников этих проектов и наметить наше общее будущее – согласованное направление развития для всех этих приложений.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;Вторая – продемонстрировать открытые программы публике, которая, возможно, вообще о них не знает, людям, которые работают с графикой. Нам нужно показать, что [даже если] мы не удовлетворим все их нужды прямо сейчас, у нас есть функциональные, стабильные и многообещающие приложения. И что у нас есть сообщество вокруг этих приложений, очень дружественное. Нам нужно больше художников, вовлечённых в свободное ПО.&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''''LXF:''' ''Что мешает людям использовать свободные программы для серьёзной графики?&lt;br /&gt;
&lt;br /&gt;
'''ДН:''' Причин несколько. Есть неписаный кодекс поведения, порой не совсем понятный. Это барьер сообщества.&lt;br /&gt;
&lt;br /&gt;
Есть технологические барьеры. Есть вещи, на которые распространяется понятие «интеллектуальная собственность». Это такая штука, которая мне ненавистна, но под действие авторских прав, торговых марок, патентов много чего подпадает. То есть вещи вроде каталога цветов Pantone, защищенного авторскими правами.&lt;br /&gt;
&lt;br /&gt;
Третий барьер – рабочая сила. Кое-чего мы просто не делаем. Мы говорим о художниках – почему художники не так охотно пользуются нашими программами.Есть вещи, которые ''Gimp ''не делает, но они нужны художникам, кинопродюсерам и людям, занимающимся допечатной подготовкой.&lt;br /&gt;
&lt;br /&gt;
'''''LXF:''' ''Некоторые считают, что кросс-платформенная природа этих инструментов – это плохо; что свободное ПО должно оставаться на свободной платформе.&lt;br /&gt;
&lt;br /&gt;
'''ДН:''' Да, есть такие люди. Но я не из их числа. Я думаю, что наша цель – чтобы все использовали свободные программы, включая операционную систему. Тут вы можете спросить: «А не идёт ли портирование приложений GNU на Windows вразрез с этой целью?» А по-моему, будь я пользователем Windows, я не стал бы ничего менять одним махом.&lt;br /&gt;
&lt;br /&gt;
Если кто-то даст мне CD с текстовым процессором, звуковым редактором, графическими инструментами и web-браузером, я могу использовать всё это в Windows и не беспокоиться об откате назад, если мне что-то не понравится. И вот вы вдруг получаете кого-то, кто использует свободные программы, но работает под Windows. Всё, что ему остаётся сделать – сменить операционную систему. Вы помогаете людям становиться на путь просвещения.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Scribus ==&lt;br /&gt;
&lt;br /&gt;
'''Открытая настольная издательская система становится профессиональной.'''&lt;br /&gt;
&lt;br /&gt;
Во многом, Scribus – это стремительная история успеха открытого графического инструмента. Да, ''Gimp в''осхитителен, ''Inkscape о''бладает потенциалом – но ''Scribus ''поставил перед собой задачу, появившись ниоткуда, стать серьёзным, профессиональным решением для настольных издательских систем (ИС), и, следуя традиционным стандартам разработки программ, в два счёта этого достиг.&lt;br /&gt;
&lt;br /&gt;
Фактически, последний выпуск ''Scribus ''(1.3.3) предоставляет возможности, превосходящие стандарты профессионального программного обеспечения для ИС, например, генерацию штрихкода без привлечения стороннего ПО. Для коммерческой ИС такое немыслимо – за пять лет пройти путь от первой строки кода до надёжного, профессионального инструмента.&lt;br /&gt;
&lt;br /&gt;
'''Типы печати'''&lt;br /&gt;
&lt;br /&gt;
[[Изображение:Img_80_40_3.jpg|frame|Да, Scribus умеет выполнять цветоделение, используя последние версии ghostscript]]&lt;br /&gt;
&lt;br /&gt;
[[Изображение:Img_80_40_1.jpg|frame|В Scribus 1.3.3 уже есть ряд простых эффектов изображения, но будущие версии могут включить GEGL]]&lt;br /&gt;
&lt;br /&gt;
Как и со всеми освещёнными здесь проектами, похоже, что выбор открытой модели разработки способствовал изрядной прозорливости ''Scribus''. Достаточно стандартная история – нужный человек оказался в нужное время в нужном месте. Везение это или нет, но команда ''Scribus с''умела собрать вокруг себя очень талантливых и компетентных людей из мира печатных изданий. S''cribus ''&amp;lt;nowiki&amp;gt;увидел свет как проект немецкого программиста-самоучки Франца Шмида [Franz Schmid]. Его первоначальная гениальная идея была создать программу вёрстки, базирующуюся на технологическом процессе PDF. Еще в 2001 году было видно, что PDF может стать &amp;lt;/nowiki&amp;gt;''lingua franca ''&amp;lt;nowiki&amp;gt;[принятый язык межнационального общения, – прим. пер.] &amp;lt;/nowiki&amp;gt;для печати, и так оно и вышло. Почти все крупные издательства, а также и принтеры, теперь работают преимущественно в формате PDF, который генерируется, проектируется и выводится прямо на пластины для типографии. Шмид, очевидно, прекрасно разбирался в спецификации PDF, потому что S''cribus ''выполняет фантастическую работу по генерации этих файлов –даже обрабатывает ряд интерактивных и презентационных функций, которые не используются для печати и практически не генерируются другими программами, за исключением полной версии Adobe Reader. Мы надеемся, что и LinuxFormat помог популяризации ''Scribus'', поскольку мы включали его в наш раздел HotPicks''' '''(это один из немногих проектов, фигурировавших там неоднократно, в ''LXF36'' и ''LXF50''). Однако по мере взросления программы маленький проект Шмида становился всё сложнее и запутаннее. Это само по себе привело к множеству изменений – значительную часть кода пришлось переписать или, по крайней мере, переместить. То, что имело смысл для небольшого проекта, работающего с маленькими документами, может не годиться, если пользователи намерены «отстукивать» 200-страничные книги. &lt;br /&gt;
&lt;br /&gt;
'''Усложнение'''&lt;br /&gt;
&lt;br /&gt;
[[Изображение:Img_80_40_2.jpg|frame|&amp;quot;Предстартовая&amp;quot; проверка означает, что от ваших PDF вам гарантирован превосходный результат ]]&lt;br /&gt;
&lt;br /&gt;
С последней серией 1.3 пользователи ''Scribus ''теперь могут насладиться цветовой заливкой, управлением цветом, правильным цветоделением и т.п. В общем, ''Scribus ''вырастает в новую систему обработки текста; тут значительно увеличена скорость обновления изображений и добавлена поддержка Unicode. Полезные возможности простираются аж до назначения поднаборов глифов &amp;lt;nowiki&amp;gt;[glyph – визуальный образ символа шрифта, – прим. пер.]&amp;lt;/nowiki&amp;gt;. Много народу страдало по версии ''Scribus ''для командной строки, чтобы конвертировать документы ''Scribus ''в PDF без посредства графического интерфейса, и что вы думаете? Это не такая уж далёкая перспектива!&lt;br /&gt;
&lt;br /&gt;
Чувствуется, что по крайней мере в этом сегменте настольных систем программы с открытым кодом постепенно меняют позицию «как ни странно полезных» на звание профессиональных инструментов. На Libre Graphics команда продемонстрировала Le Tigre – созданную в ''Scribus ''еженедельную французскую газету, выходящую тиражом более 12 тыс. экземпляров. Она не первая и, конечно, не последняя.&lt;br /&gt;
----&lt;br /&gt;
'''ИНТЕРЬЮ LinuxFormat:'''&lt;br /&gt;
&lt;br /&gt;
В чём причина успеха ''Scribus''?&lt;br /&gt;
&lt;br /&gt;
'''&amp;lt;nowiki&amp;gt;Питер Линнелл [Peter Linnell] (ПЛ):&amp;lt;/nowiki&amp;gt;''' Думаю, в том, что на командном уровне мы работаем очень хорошо… &lt;br /&gt;
&lt;br /&gt;
'''К&amp;lt;nowiki&amp;gt;рейг Ринджер [Craig Ringer] (КР):&amp;lt;/nowiki&amp;gt;''' ''Scribus ''– довольно закрытая система разработки. Есть основная команда, поддержанная добровольцами и переводчиками; некоторые добровольцы, достигшие определённого уровня профессионализма, приглашаются в команду. Отбор довольно жёсткий. Анонимный доступ к внутреннему CVS не предоставляется.&lt;br /&gt;
&lt;br /&gt;
'''ПЛ:''' У нас есть внутренний CVS для команды. Насчет кода мы прямо-таки параноики. Внутренний CVS – тот, с которым мы можем экспериментировать, чтобы люди могли доверять коду, выполняя реальную работу. Мы пытаемся поддерживать систему, где «слепок» CVS всегда можно собрать и использовать. Конечно, ошибки всё равно проскальзывают, но это уже совсем другое, чем пригодность для работы вообще.&lt;br /&gt;
&lt;br /&gt;
'''LXF: '''Всё-таки Scribus прошёл длинный путь за короткое время – намного быстрее многих других проектов.&lt;br /&gt;
&lt;br /&gt;
ПЛ: Мы немного старше, чем обычные хакеры, работающие с открытым кодом. У Крейга значительный производственный опыт, Льюис много лет проработал в профессиональной допечатной индустрии. У нас довольно много людей, занимающихся издательской деятельностью, которые способны протестировать различные функции программы – либо пользовательский интерфейс, либо качество результата, либо цвета, либо PDF. Также у нас есть пользователи, располагающие высококачественном печатным оборудованием, и либо мы даём им файлы, либо они печатают свои собственные тесты. Это помогает нам сохранять высокий &amp;lt;nowiki&amp;gt;уровень качества. Другая причина – команда разработки очень гармонична. Требуется некоторый опыт в допечатной подготовке, чтобы знать, что делать. Например, мы не допустим, чтобы вы получили «ложный» курсив [когда буквы преобразуются в курсив на экране, вместо того чтобы использовать специально разработанную версию шрифта] – мы не допустим этого, потому что это уродство. Этого не будет. &amp;lt;/nowiki&amp;gt;''Scribus ''очень прилежно относится к проверке используемых шрифтов – что они действительно присутствуют и что глифы работают.&lt;br /&gt;
&lt;br /&gt;
'''LXF: '''То есть вы не терпите ошибок, по крайней мере, серьёзных? КР: Вывод PDF у нас очень надёжный. Мы исключили проблемы, приводящие к созданию плохого PDF. Если такое случится, мы сосредоточим на этом всё внимание, всё остановим и исправим.&lt;br /&gt;
&lt;br /&gt;
'''ПЛ:''' Была у нас подобная проблема в 1.2, очень странная ошибка: когда вы устанавливали градиент определённым способом, все просто чернело. Обнаружив ошибку, мы исправили её за шесть часов. В 1.4 нам «посчастливилось» получить некоторую разновидность автоматизированной регрессии. По мере усложнения кода становится всё труднее по-настоящему тестировать все функции, потому что их слишком много. В прежние времена я мог пробежаться по ним за выходные. Теперь этого уже не сделать. Теперь над ошибками работает много людей – ищут их, проверяют исправления, чтобы убедиться, что всё откорректировано правильно.&lt;br /&gt;
&lt;br /&gt;
'''КР:''' В процессе очистки всплывают проблемы, скрытые за двойным кодом, или код, который исполняется дважды. Я переместил буквально тысячи строк кода. При переходе на другую модель – будь то возможность конвертировать в PDF из командной строки, или запускать как сервер, или использовать меньше памяти – обнаружилось, что уйма кода располагалось там, где его было удобно прописать когда-то. Почистив код, мы можем сделать его быстрее и эффективнее.&lt;br /&gt;
&lt;br /&gt;
'''LXF:''''' ''Над чем вы сейчас работаете?&lt;br /&gt;
&lt;br /&gt;
'''КР:''' &amp;lt;nowiki&amp;gt;Над очисткой и перемещением кода… будет значительное повышение скорости. Пока что мы сделали новую систему отмены последнего действия [undo], предстартовую проверку, цветовое колесо... Кое-что пытаемся сделать более&lt;br /&gt;
дружественным к пользователю. Сейчас мы используем отдельные стили параграфов и стили строк. Но скоро появится новый менеджер стилей, который их объединит, плюс у нас будут стили символов.&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''ПЛ:''' Над крупным улучшением поддержки EPS &amp;lt;nowiki&amp;gt;[Encapsulated PostScript; формат для печати на PostScipt-устройствах, – прим. пер.]&amp;lt;/nowiki&amp;gt;. Версия 1.3 – это путь к 1.4, и ''Scribus ''1.4 будет инструментом профессионального класса; в нём будет всё необходимое для выполнения вёрстки практически любого вида. Я думаю, ''Scribus ''некоторые планки взял. Считалось, что открытое приложение никогда не сможет работать с заливкой цветом, но мы этого добились. На подходе ещё одна вещь – улучшенная поддержка сносок в больших документах, и т.п. Верите или нет, но сейчас ''LaTeX ''или ''OpenOffice.org'', похоже, лучше всех поддерживают большие документы. Ещё у нас готовитсяизменение размера страницы на лету, и разнородные размеры страниц в документе. Также предусмотрены спуски &amp;lt;nowiki&amp;gt;[imposition; расстановка полос на печатной форме в нужной для брошюрования последовательности – прим. пер.]&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Спуски сделать будет не сложнее, чем распечатать A3: согнуть, скрепить, и готово. Следующий шаг – добраться до книг, тонкой переплётной работы и прочего: это совершенно другой мир, с весьма специфическими инструментами, стоящими от 5000 долларов. Его мы трогать не собираемся. Пока не собираемся&lt;br /&gt;
&lt;br /&gt;
== Inkscape ==&lt;br /&gt;
'''Задаёт стандарты будущего графического дизайна.'''&lt;br /&gt;
&lt;br /&gt;
Иногда жесткие решения себя оправдывают. Стандарт SVG, предназначение которого, будем надеяться, – стать повсеместным для векторной графики, как JPEG для фотографий, никогда не разрабатывался как внутренний формат. Базирование структуры на XML делает его очень гибким и пригодным для самых разнообразных целей. Но на внутреннем уровне работа с XML – мучение, заметно снижающее эффективность. Поэтому вполне естественно, что многие разработчики графических пакетов реализуют поддержку SVG только как функцию окончательной обработки. Некоторые, со скрипом, могут и импортировать SVG, но это, как правило, немногим больше, чем поверхностная трансляция – попробуйте сохранить тот же файл, и, вполне возможно, заметите искажение.&lt;br /&gt;
&lt;br /&gt;
[[Изображение:Img_80_42_3.jpg|frame|Экспериментальные эффекты, управляемые скриптами, можно получить, разрешив их использование в настройках приложения]]&lt;br /&gt;
&lt;br /&gt;
[[Изображение:Img_80_42_1.jpg|frame|Трассировка растра в Inkscape  в настоящее время - лучшее из имеющегося в Linux, благодаря использованию другого открытого кода]]&lt;br /&gt;
&lt;br /&gt;
Группа разработчиков, однако, решила, что миру нужно слияние традиционных SVGредакторов в «программистском» стиле (которые непосредственно воздействуют на XML и позволяют программистам изменять свойства узлов и т.д.) и мира инструментов рисования. Результат – ''Inkscape ''– добавил еще одну яркую историю успеха открытых графических программ и подтвердил идею, что открытые стандарты и свободная мысль могут изменить мир. По крайней мере, мир графики.&lt;br /&gt;
&lt;br /&gt;
Проект стартовал под именем ''Sodipodi''.&lt;br /&gt;
&lt;br /&gt;
Критической точкой явилось несовпадение мнений о том, в каком направлении его продолжать. Это вполне обычное явление: интернет-феномен под названием ''Firefox в''озник, когда двое разработчиков покинули проект Mozilla. А в данном случае четверо из основных участников ''Sodipodi ''решили отделить код и создать проект, ныне известный как ''Inkscape''.&lt;br /&gt;
&lt;br /&gt;
'''Чистовой чертёж'''&lt;br /&gt;
&lt;br /&gt;
Проект придерживался этого направления, и в вопросах редактирования SVG он настолько вырвался вперёд на любой платформе, что вряд ли не сможет стать основным инструментом создания векторной графики в этом стандарте.&lt;br /&gt;
&lt;br /&gt;
Как и большинство других рассмотренных здесь инструментов, I''nkscape ''является кроссплатформенным. Одна и та же версия доступна под Linux, Windows и Mac, а в мире графики это даёт огромные преимущества. Действующее сообщество художников, несомненно, для профессионального дизайна использует Mac.&lt;br /&gt;
&lt;br /&gt;
[[Изображение:Img_80_40_2.jpg|frame|Inkscape вкючает мощный XML-редактор для прямых манипуляций с элементами]]&lt;br /&gt;
&lt;br /&gt;
Доступность ''Inkscape н''а этой платформе не только открывает программе рынок, но также обеспечивает полезнейшую обратную связь от пользователей-профессионалов. Кое-кто еще цепляется за идею, что ''Inkscape и''спользуют только потому, что он бесплатный, но это становится всё меньше похоже на правду.&lt;br /&gt;
&lt;br /&gt;
Профессиональные дизайнеры не отказывают себе в самоновейших специализированных инструментах, если они им нужны для работы – и выбор ими открытых инструментов вроде ''Inkscape ''говорит скорее о надёжности, открытости и высоких стандартах, чем об отсутствии ценника.&lt;br /&gt;
&lt;br /&gt;
Отсюда также следует, что ряд людей, интересующихся проектом, включая и часть основных разработчиков, считают себя скорее художниками, чем программистами. И хотя можно подумать, что они просто пишут то, что им интересно, команда ''Inkscape о''тлично понимает различных пользователей, которых хочет привлечь, и различные их нужды.&lt;br /&gt;
&lt;br /&gt;
Полезность – основное направление текущей разработки: сделать программу пригодной для широкого круга дизайнеров, а также подходящей для всех задач, использующих SVG. С распространением формата SVG в сети, на рабочих столах и в мобильных устройствах ''Inkscape ''готов стать следующим поколением Illustrator’а.&lt;br /&gt;
----&lt;br /&gt;
'''ИНТЕРЬВЮ'''&lt;br /&gt;
&lt;br /&gt;
'''''LXF:''' ''Что привело вас в ''Inkscape''?&lt;br /&gt;
&lt;br /&gt;
'''Джон Круз (ДК):''' Просто раскол ''Sodipodi ''произошёл, как раз когда я хотел активнее включиться в движение open source; я уже давал им советы насчет всяких трюков в Win32. Они сказали, что будут создавать самый лучший SVG-редактор – и в техническом, и в художественном смысле.&lt;br /&gt;
&lt;br /&gt;
Хотелось привлечь сообщество и разработчиков открытого кода, и хотелось так все организовать, чтобы не было единственной точки управления. Ну, а это, по моему мнению, имело смысл, с учётом моего опыта профессионального разработчика ПО.&lt;br /&gt;
&lt;br /&gt;
На мой взгляд, разработчики ядра ''Inkscape ''выбрали правильные методы. Если у вас есть хорошая идея, то реализуйте её и добавьте в проект. Если есть проблемы, люди за ними присмотрят. То есть получается очень простая организация. Это проект, и каждый работает на проект – это не код одного человека.&lt;br /&gt;
&lt;br /&gt;
'''''LXF:''' ''А может, люди просто хотят заниматься тем, что им интересно?&lt;br /&gt;
&lt;br /&gt;
'''ДК:''' Основная масса участников проекта старается сделать довольно серьёзные части программы. Мы пишем для пользователей. Вначале пользователями были одни разработчики, но со временем пользовательская база разрослась.&lt;br /&gt;
&lt;br /&gt;
Вот в чём у меня есть некоторый опыт – так это в изучении требований. Люди приносят идеи о том, как программу можно использовать. Порой они просят о нужных с их точки зрения вещах, но не могут объяснить, что в конечном итоге достигается.&lt;br /&gt;
&lt;br /&gt;
'''''LXF:''' ''То есть вы руководствуетесь тем, как пользователи в действительности используют программу, а не просто вставляете функции? Нетипично!&lt;br /&gt;
&lt;br /&gt;
'''ДК:''' Да, и я думаю, это одна из вещей, за которые команду критикуют. Я думаю, это критика из разряда «Они – простые инженеры, что они понимают?». Но некоторые из наших разработчиков – художники, и они очень хорошо ладят с интерфейсом.&lt;br /&gt;
&lt;br /&gt;
'''''LXF:''' ''Какие функции появятся в следующем году?&lt;br /&gt;
&lt;br /&gt;
'''ДК:''' Мы думаем о просмотре свойств – преобразовании его в нечто большее, чем просмотрщик объектов, а также об улучшении диалога слоёв. Это очень нужно. Люди продолжают работать над этим и тратят много времени, чтобы сделать всё как следует. Будет здорово, но мы не хотим выпускать это в недоделанном виде.&lt;br /&gt;
&lt;br /&gt;
'''''LXF:''' ''Займетесь ли вы слоями? Сейчас ''Inkscape'', похоже, использует тот же подход, что и O''penOffice''.org. Слои – это такая вещь, важность которых понимаешьне раньше, чем начнешь их использовать.&lt;br /&gt;
&lt;br /&gt;
'''ДК:''' ''OpenOffice.org ''и подобные приложения должны делать хорошо каждый фрагмент. А мы фокусируемся на SVG, и мы должны уметь «вылизать» эту единственную вещь, причем превосходно. Мы также занимается переделкой пользовательского интерфейса и целевыми профилями. Например, если вы хотите создать SVG Tiny, для телефонов и мобильных устройств, мы можем упростить вашу задачу: вы выполните экспорт и получите совместимый SVG. Firefox выпустил новую версию с поддержкой SVG. И стал качественно иным.&lt;br /&gt;
&lt;br /&gt;
'''''LXF:''' ''Здесь явно выплывает много сопутствующих идей, например, Cairo.&lt;br /&gt;
&lt;br /&gt;
'''ДК:''' Ну, их может быть намного больше. Например, вы могли бы нацелить ''Inkscape ''&amp;lt;nowiki&amp;gt;на следующую версию рабочего стола Gnome и всюду использовать векторы. Чем легче создавать [векторные изображения], тем шире использование.&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Уже сейчас, создав XHTML- страницу со встроенным SVG, вы сможете добиться, чтобы это работало в Firefox со скриптами и DOM, и можно делать довольно сложные вещи. Сейчас делается много интересного.&lt;br /&gt;
&lt;br /&gt;
'''''LXF:''' ''Всё же не так много инструментов, ориентированных на SVG. Программные средства есть, но не такие артистичные, как ''Inkscape''.&lt;br /&gt;
&lt;br /&gt;
'''ДК:''' Да, и у нас есть цель и опыт. У нас хороший круг людей… SVG – очень понятный стандарт, и в его правильности очень легко убедиться. Я знаю людей, постоянно работающих с ''Inkscape ''– если что-то не так, мы тут же услышим об этом.&lt;br /&gt;
&lt;br /&gt;
== Gimp ==&lt;br /&gt;
'''Номер один в своей области – серьёзная графическая мощь.'''&lt;br /&gt;
&lt;br /&gt;
Попросите сотню среднестатистических пользователей Linux назвать графическое приложение – и смело можно держать пари, что в ответ они выпалят одно слово: ''Gimp''.&lt;br /&gt;
&lt;br /&gt;
Прошло уже более десяти лет с тех пор, когда Спенсер Кимболл (Spencer Kimball) объявил о проекте создания открытого приложения, способного соперничать с профессиональными (и дорогими) альтернативами для Mac и Windows. За это время появились бесчисленные дополнительные модули, обогатившие функции, и пользовательский интерфейс был полностью перестроен.&lt;br /&gt;
&lt;br /&gt;
[[Изображение:Img_80_44_3.jpg|frame|Митч Наттерер (Mitch Natterer) объясняет, чем так хороша GEGL! (фото Романа Йооста)]]&lt;br /&gt;
&lt;br /&gt;
[[Изображение:Img_80_44_1.jpg|frame|SIOX позволит вырезать контуры намного проще - только используйте бета-версию Gimp 2.3, а не расширения версии 2.2]]&lt;br /&gt;
&lt;br /&gt;
''Gimp ''прошёл длинный путь, но его всё ещё критикуют за недостаток ряда ключевых функций и медленный темп разработки. Заявляют даже, что ''Gimp ''фактически тормозит развитие: его разительное первенство в редактировании растровой графики под Linux означает, что соперников ему бояться нечего – а следовательно, нет выбора и конкуренции. Хотя на самом деле причина в чрезвычайной сложности ''Gimp: д''остижение заметных улучшений требует уймы времени и усилий.&lt;br /&gt;
&lt;br /&gt;
'''День GEGL'''&lt;br /&gt;
&lt;br /&gt;
Хотя это и не часть собственно ''Gimp'', ''GEGL (''Generic Graphics Library – общая графическая библиотека) оказывает значительное влияние на направление будущего развития G''imp''. ''GEGL ''пока что находится на этапе разработки релиз-кандидата, и проектируется как автономная библиотека для работы с графикой «по запросу».&lt;br /&gt;
&lt;br /&gt;
Управляемость по запросу – наиболее заметная особенность системы. Она означает, что окончательная обработка выполняется именно в нужный момент (т.е.&lt;br /&gt;
непосредственно перед выводом на экран), а не последовательно через ряд шагов.&lt;br /&gt;
&lt;br /&gt;
Преимущество здесь в том, что на этом этапе можно достичь очень хорошей оптимизации – вместо того, чтобы обрабатывать каждый пиксел и затем отбрасывать ненужные, трансформация коснется только конкретного участка.&lt;br /&gt;
&lt;br /&gt;
''GEGL ''также умеет управляться с различными глубинами цвета, так что она пригодна для широкого круга задач по обработке изображений. Конечно, G''EGL ''будет очень полезна и для других приложений. Команда Scribus, например, очень заинтересовалась потенциалом ''GEGL ''и тем, что она может сделать для обработки изображений в издательском приложении.&lt;br /&gt;
&lt;br /&gt;
'''Всё включено'''&lt;br /&gt;
&lt;br /&gt;
[[Изображение:Img_80_44_2.jpg|frame|Пока не вышел Gimp 2.4, за большинство новых хитростей отвечают фильтры]]&lt;br /&gt;
&lt;br /&gt;
Основная работа, идущая в выпускаемых версиях ''Gimp, з''аключается в чистке и приборке перед долгожданным релизом 2.4 – так что мы займемся дополнениями, обеспечивающими внедрение интересных новых разработок.&lt;br /&gt;
&lt;br /&gt;
Мы рассматривали SIOX (Simple Interactive Object Extraction – простое интерактивное извлечение объектов) в ''[[LXF74-75:Технологии Linux-2006|LXF75]]'', но стоит остановиться на нем ещё раз. С этим замечательным инструментом можно отделить изображение переднего плана от фона, всего пару раз шаркнув мышкой. Каждый бит сгенерированного контура столь хорош, как будто вы кропотливо отслеживали каждую точку очертания самостоятельно.&lt;br /&gt;
&lt;br /&gt;
SIOX будет интегрирован в финальный релиз ''Gimp ''2.4, но уже сейчас доступен как дополнительный модуль. Из-за ограничений ''Gimp ''2.2 он не столь эффективен, как будет в финальном релизе – если вы серьезно намерены его попробовать, лучше использовать разрабатываемую версию G''imp ''(имеется на нашем диске), пока не выйдет версия 2.4. Дополнительная информация по SIOX – на сайте '''www.siox.org'''.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
'''ИНТЕРВЬЮ'''&lt;br /&gt;
&lt;br /&gt;
'''''LinuxFormat''': ''Вы довольны нынешним положением дел в Gimp?&lt;br /&gt;
&lt;br /&gt;
'''Свен Нойманн (СН):''' Вообще-то я рад был бы видеть его прошедшим больший путь. В прошлом у нас были грандиозные планы, но пока завершить их не удалось. Есть много вещей, о которых просят люди – скажем, поддержка большей глубины цвета или других цветовых схем, например, CMYK – мы их уже планировали. Но у нас недостаточно ресурсов, чтобы всё это реализовать.&lt;br /&gt;
&lt;br /&gt;
Проделана огромная работа по исправлению ошибок и реализации мелких улучшений. Мы хотим добиться, чтобы функции, уже доступные пользователям ''Gimp'', можно было найти и использовать, не читая руководства – его и так никто не читает.&lt;br /&gt;
&lt;br /&gt;
'''''LXF:''' ''Многих удивит, что такой важный проект имеет всего 30–40 участников. Это всегда так было, или раньше у вас было больше людей?&lt;br /&gt;
&lt;br /&gt;
'''СН:''' ''Gimp ''всегда был проектом, на который тратят свои силы всего несколько человек. Хотя эти люди всё время меняются.&lt;br /&gt;
&lt;br /&gt;
Одна из проблем – то, что многие из участников-долгожителей, отлично знающих код, повзрослели: они уже не ст уденты, и у них есть работа и личная жизнь. Они просто не могут посвящать проекту так много времени, как пару лет назад. Возможно, необходимо вливание новой крови.&lt;br /&gt;
&lt;br /&gt;
'''''LXF:''' ''Новым участникам потребуется немало времени, чтобы освоиться со столь сложным кодом.&lt;br /&gt;
&lt;br /&gt;
'''СН:''' Ну, если людям это интересно, они могу т поработать с дополнительными модулями. В большинстве из них легко разобраться – они в основном оформлены в одном C-файле. Трудности особой нет – и есть похожие модули и примеры для иллюстрации.&lt;br /&gt;
&lt;br /&gt;
Конечно, работа с ядром и внесение фундаментальных изменений в пользовательский интерфейс или ввод новой концепции – довольно сложная задача, если имеешь дело с большим объёмом кода. С другой стороны, за последние несколько лет мы значительно улучшили кодовую базу. Выполнили большую работу по очистке и, так сказать, вынесению за скобки. То, что было несколькими сотнями файлов в одном каталоге, теперь разделено и лучше организовано.&lt;br /&gt;
&lt;br /&gt;
'''''LXF:''' ''Кое-кто мог бы предположить, что вы всей командой чуть ли не год отдыхали, потому что набор функций за этот год почти не изменился.&lt;br /&gt;
&lt;br /&gt;
'''СН:''' &amp;lt;nowiki&amp;gt;Мы хотели [давным-давно] выпустить релиз 2.4. Мы пообещали добавить в него некоторые функции и приступили к работе над ними. А теперь мы в той ситуации, что есть пара инструментов, просто не готовых к выпуску.&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Мы должны или выбросить их, или доделать. Одна из таких вещей – управление цветом. Мы решили, что действительно хотим, чтобы эти функции вошли в следующий релиз, так что будем работать до победного конца.&lt;br /&gt;
&lt;br /&gt;
'''''LXF:''' ''Вы упомянули управление цветом и CMYK – какие ещё функции вы могли бы сделать доступными в ближайшее время?&lt;br /&gt;
&lt;br /&gt;
'''СН:''' CMYK не будет использоваться для хранения данных о пикселах. Но как только мы как следует реализуем управление цветом, люди получат многое из того, что они просили. Я также думаю, что наиболее важная из отсутствующих в ''Gimp ''функция в данный момент – поддержка высокой глубины цвета. Всё больше и больше камер предоставляют такие данные, и мы не должны просто отбрасывать их при импорте.&lt;br /&gt;
&lt;br /&gt;
'''''LXF:''' ''Думаете ли вы, что Gimp помогла бы спонсорская помощь или включение в в какую-нибудь из имеющихся премиальных схем?&lt;br /&gt;
&lt;br /&gt;
'''СН:''' Вот этим, в частности, я хотел бы заняться в следующих месяцах. Составить список того, что действительно требуется сделать в ''Gimp'', и прикинуть, куда обращаться и с кем говорить. Мы хотели бы его опубликовать, и не только в Bugzilla. Я хотел бы, чтобы все знали: мы ищем разработчиков, и видели, что нам нужно доделать. Это могло бы привлечь больше людей.&lt;br /&gt;
&lt;br /&gt;
С другой стороны, премии порой создают трудности, потому что для разработчиков, которые с нами уже давно, сотрудничество с людьми, которые работают за плату, может стать демотивирующим фактором. Мы хотели бы, чтобы довольны были все. Я уверен, что это возможно.&lt;br /&gt;
&lt;br /&gt;
== Xara ==&lt;br /&gt;
'''Профессиональный инструмент дизайна становится свободным.'''&lt;br /&gt;
&lt;br /&gt;
Не больше ли радует движение Open Source одно проприетарное приложение, раскаявшееся и оставившее путь закрытого кода, чем 99 приложенийправедников? Если да, то на ''Xara ''должны изливаться потоки любви. Хотя эта программа всю жизнь разрабатываемая частной британской фирмой, она много лет лицензировалась компанией ''Corel'', распространявшей её под названием ''CorelXara''.&lt;br /&gt;
&lt;br /&gt;
[[Изображение:Img_80_46_3.jpg|frame|Открытие кода Xara может привести в Linux множество новых художников]]&lt;br /&gt;
&lt;br /&gt;
[[Изображение:Img_80_46_1.jpg|frame|&amp;quot;Живые&amp;quot; эффекты в Xara экономят время и очень популярны среди дизайнеров]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Вы можете подумать, что для «освобождения» приложения достаточно опубликовать исходный код и позволить каждому им пользоваться. Для некоторых приложений так оно и есть, но проприетарное ПО – в частности, настольные приложения – очень часто используют сторонний инструментарий и библиотеки, и связанный с ними код открыть нельзя. Исключение этого кода может быть весьма кропотливой задачей, и, очевидно, оставит приложение неполным: тут какие-то подпрограммы загрузки и сохранения, там куски алгоритмов – не говоря уж о целых инструментариях GUI.&lt;br /&gt;
&lt;br /&gt;
Одна из самых знаменитых функций X''ara ''– движок предпросмотра в режиме реального времени. Протащите прозрачный градиент по вашему рисунку, и экран обновится в реальном времени по мере протаскивания – никаких проб и ошибок при получении желаемого эффекта!&lt;br /&gt;
&lt;br /&gt;
Обратная связь в режиме реального времени уберегает от массы трудностей и делает приложение более отзывчивым на требования пользователей. Это одна из областей программы, которая пока ещё не открыта.&lt;br /&gt;
&lt;br /&gt;
Понятно, что разработчики ''Xara ''делают всё неспешно и аккуратно.&lt;br /&gt;
&lt;br /&gt;
'''Стык культур?'''&lt;br /&gt;
&lt;br /&gt;
Когда ''Xara ''решилась на переход, некоторые роптали, что это может закончиться конкуренцией с ''Inkscape ''за лидерство среди дизайнерских приложений для Linux, но они упустили главное: хотя общего у проектов много, они, по большому счёту, ориентированы на разные вещи.&lt;br /&gt;
&lt;br /&gt;
[[Изображение:Img_80_46_2.jpg|frame|Открытая Xara Extreme готова к использованию - хотя многие из ее функций пока недоработаны. Включая &amp;quot;Save&amp;quot;!]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
У ''Xara ''длинная история новаторского и разностороннего инструмента для иллюстраций и работ для Интернета, но она не нацелена исключительно на работу с SVG, как ''Inkscape''. Не похоже, что в ''Xara ''когда-нибудь будет встроен XML-редактор, как это сделано в ''Inkscape – х''отя сейчас это открытая программа, и как знать? Очевидно одно: разработчики ''Inkscape и'' ''Xara ''стремятся к сотрудничеству. На конференции LGM они охотно обменивались идеями и работали над способом облегчить передачу данных между своими приложениями.&lt;br /&gt;
&lt;br /&gt;
Чарльз Мойр (Charles Moir), владелец ''Xara'', также спонсирует разработку&lt;br /&gt;
«суперконвертора». Идея знакома сторонникам открытого кода – создать промежуточный формат для графики и набор инструментов для преобразования всевозможных форматов в промежуточный и промежуточного – в другие форматы. Сказать это проще, чем сделать, потому что для достижения настоящей эффективности требуются способы сохранения или, по крайней мере, корректной интерпретации характеристик каждого формата, зачастую уникальных. Проект получил название Chromista, и первый его разработчик – Эрик Вильгельм (Eric Wilhelm). Вы можете узнать больше о целях проекта на его сайте: '''http://scratchcomputing.com/projects/uber-converter'''.&lt;br /&gt;
&lt;br /&gt;
Что же касается самой ''Xara'', продолжается работа по реализации всех функций проприетарной версии в открытом коде. Обещаны регулярные выпуски кода и двоичных пакетов, и команда тратит уйму усилий, чтобы включить функцию «Save» (Сохранить). Дело хорошее!&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
'''ИНТЕРВЬЮ'''&lt;br /&gt;
&lt;br /&gt;
'''''LinuxFormat:''' ''Почему, спустя более 10 лет, вы открыли код X''ara''?&lt;br /&gt;
&lt;br /&gt;
'''Нейл Хоу (НХ):''' Основная причина – хочется видеть, что проект способен достичь намного большего, чем есть сейчас. Проблема у нас в том, что мы – очень маленькая фирма с ограниченными ресурсами. Нам все говорят: какой ''Xara Extreme ''хороший инструмент для рисования! – и мы просто хотим выпускать его побольше. Я думаю, что придав ему кросс-платформность, обеспечив доступ и пользователям Linux, и пользователям Mac, а также сделав код открытым, мы сможем этого добиться.&lt;br /&gt;
&lt;br /&gt;
Еще один фактор – мы недостаточно быстро развивали продукт. Я уверен, что каждая фирма-разработчик ПО планирует намного больше, чем в состоянии когда-либо осуществить. Мы не исключение – разве что, как я подозреваю, наш список ещё длиннее – и с имеющимися ресурсами мы не чувствовали сил для достаточно быстрого движения.&lt;br /&gt;
&lt;br /&gt;
'''''LXF:''' ''То есть выходит, что даже будь у вас превосходные идеи, вы не смогли бы их реализовать?&lt;br /&gt;
&lt;br /&gt;
'''НХ:''' Да, мы можем сделать не так уж много. Мы хотим видеть инструмент, добившийся определённого успеха по сравнению с подобными от Adobe и Microsoft. Если мы получим поддержку сообщества Open Source, думается, мы сможем достичь более быстрого прогресса.&lt;br /&gt;
&lt;br /&gt;
'''''LXF:''' ''Вы говорите об открытии кода ''Xara''?&lt;br /&gt;
&lt;br /&gt;
'''НХ:''' Да! Это было трудным решением для такой фирмы, как наша, – взять инструмент, с нашими технологиями, который мы собирали так много лет – годы усилий были потрачены на него; это значительный шаг. Тут и причина, почему мы не приняли подход «большого взрыва» – мы не просто говорим «Всё это теперь открыто». Мы осуществляем открытие в два этапа, чтобы посмотреть, как проходит первый этап, сформировать сообщество, продвинуть его вперёд, а затем полностью выпустить код.&lt;br /&gt;
&lt;br /&gt;
'''''LXF:''' ''Технология, оставшаяся закрытой – движок рендеринга. Есть ли ещё компоненты, которые вы пока придержите?&lt;br /&gt;
&lt;br /&gt;
'''НХ:''' Среди них, код комбинирования фигур – код для пересечения конт уров – это всё в одной и той же библиотеке. Так что закрыты пока движок и эта часть кода, они находятся в одной двоичной библиотеке, и мы поставляем ее вместе с исходным кодом, который должен с ней собираться. Точно сказать не могу, но в терминах объёма исходного кода, сейчас открыто примерно 90%.&lt;br /&gt;
&lt;br /&gt;
'''''LXF:''' ''Что, как вы ожидаете, будет первым эффектом от открытиякода, и чего бы вам хотелось?&lt;br /&gt;
&lt;br /&gt;
'''НХ:''' &amp;lt;nowiki&amp;gt;Мы хотели бы получить сообщество, и оно у нас уже есть. В последние месяцы мы даже частным порядком приглашали разработчиков «со стороны», чтобы сделать порт [для Linux] – мы просто открыли им доступ к коду еще до публичного выпуска. И это дало старт сообществу, если хотите, и мы рады наблюдать, как сообщество растёт, и видим, что разработчики открытого кода помогают нам завершить порт как можно быстрее. Это первая цель – сделать его столь же функциональным и надёжным, как версия для Windows, с которой мы начали.&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''''LXF:''' ''Сколько времени, по вашей оценке, на это потребуется?&lt;br /&gt;
&lt;br /&gt;
'''НХ:''' Сложно сказать. Первоначально прогресс был медленнее, чем я ожидал. Но это в значительной степени потому, что портирование – основа всего, само оно ничего не выполняет, а просто предоставляет доступ к другим функциям.&lt;br /&gt;
&lt;br /&gt;
Примерно с середины января начался действительный взлёт. Например, на этой неделе Алекс (один из разработчиков) включил два инструмента за два дня – инструмент «свободное выделение» и инструмент смешивания. Так что мы надеемся, что темпы развития сохранятся или даже возрастут.&lt;br /&gt;
&lt;br /&gt;
'''''LXF:''' ''Вы сказали, что у вас огромные планы. Как вы думаете, на этом этапе вы в состоянии реализовать новые функции помимо собственно портирования?&lt;br /&gt;
&lt;br /&gt;
'''НХ:''' Мы установили первую веху в функциональности ''Xtreme'', пока без того, что мы не можем открыть, как я уже упоминал. Но открытый код есть открытый код, и мы не собираемся указывать людям, что можно делать, а что нельзя. Если кто-то приходит с желанием работать над продуктом, пишет, например, новый инструмент и хочет внести его в код, мы не скажем ему «Извините, мы принимаем только заплатки».&lt;/div&gt;</summary>
		<author><name>Dionysius</name></author>	</entry>

	<entry>
		<id>http://wiki.linuxformat.ru/wiki/LXF91:Java_EE</id>
		<title>LXF91:Java EE</title>
		<link rel="alternate" type="text/html" href="http://wiki.linuxformat.ru/wiki/LXF91:Java_EE"/>
				<updated>2009-02-06T06:33:14Z</updated>
		
		<summary type="html">&lt;p&gt;Dionysius: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Цикл/Java EE}}&lt;br /&gt;
[[Категория:Учебники]]&lt;br /&gt;
__TOC__&lt;br /&gt;
:В прошлый раз вы, под чутким руководством '''Александра''', разобрались с JSP и переписали &amp;quot;Телефонную книгу&amp;quot; с учетом этой технологии. Сегодня мы двинемся дальше и поговорим об авторизации, сессиях, фильтрах и использовании разделяемых объектов в web-приложениях.&lt;br /&gt;
&lt;br /&gt;
:''{{oncolor||red|ЧАСТЬ 3}} Каждый линуксоид знает, что система уровня предприятия должна обеспечивать разграничение доступа. '''Антон Черноусов''' расскажет, что может предложить здесь Java EE.&lt;br /&gt;
[[Media:AddressBook2.tar.bz2|Скачать исходный код примера]]&lt;br /&gt;
&lt;br /&gt;
==Процесс передачи информации==&lt;br /&gt;
Давайте рассмотрим стандартную ситуацию: пользователь вызывает различные страницы одного web-приложения. Если бы этот &amp;quot;диалог&amp;quot; происходил по телефону, он был бы примерно таким:&lt;br /&gt;
*'''Пользователь (набирает номер): Алло, это приложение?'''&lt;br /&gt;
*Приложение: Да, это приложение.&lt;br /&gt;
*(Пользователь кладет трубку)&lt;br /&gt;
*'''Пользователь (набирает номер): Меня зовут Георг.'''&lt;br /&gt;
*Приложение: Да, Георг, мы вас внимательно слушаем.&lt;br /&gt;
*(Пользователь кладет трубку)&lt;br /&gt;
*'''Пользователь (набирает номер): Дайте мне, пожалуйста, всю информацию.'''&lt;br /&gt;
*Приложение: Возьмите...&lt;br /&gt;
Отметим, что после каждого обмена репликами пользователь кладет трубку, разрывая соединение - именно таким образом в большинстве случаев и происходит обмен данными между браузером пользователя и сервером, на котором работает web-приложение. Возникает резонный вопрос: как передавать информацию между соединениями, открываемые в рамках одной сессии? Для этого могут использоваться самые различные методы:&lt;br /&gt;
*перезапись URL;&lt;br /&gt;
*скрытые поля;&lt;br /&gt;
*cookie;&lt;br /&gt;
*сессионный объект.&lt;br /&gt;
Из всех представленных методов наиболее простым и в тоже время можным является сессионный объект (Session), реализованный в Java посредством интерфейса {{oncolor||red|javax.servlet.http.Session}}.&lt;br /&gt;
Ранее, чтобы получить данные от пользователя или обеспечить возможность их транспортировки внутри приложения, мы использовали объект типа &lt;br /&gt;
{{oncolor||red|HttpServletRequest}}. Основное отличие {{oncolor||red|HttpSession}} заключается во времени жизни, схематично изображенном на '''Рис.1'''. Объект типа {{oncolor||red|HttpServletRequest}} предназначен для передачи запроса (и необходимых данных) от браузера к web-приложению и существует только между запросом и ответом, в то время как объект {{oncolor||red|HttpSession}} обеспечивает средства для хранения и доступа к данным пользователя на протяжениии всего периода работы с приложением.&lt;br /&gt;
)(Рис.1) Время жизни объектов HttpServletRequest и HttpSession.&lt;br /&gt;
Следует, однако, иметь в виду, что отследить момент, когда пользователь перестает работать с приложением, не всегда возможно. Поэтому в настройках сервера устанавливается некоторое предельное время существования объекта {{oncolor||red|HttpSession}} после получения последнего запроса.&lt;br /&gt;
&lt;br /&gt;
[[Изображение:LXF91_javaee1.png|Рис. 1]]&lt;br /&gt;
&lt;br /&gt;
'''Время жизни объектов ''HttpServletRequest'' и ''HttpSession'''''&lt;br /&gt;
&lt;br /&gt;
==Сессия==&lt;br /&gt;
Объект session в JSP является предопределеным, то есть с ним можно сразу же начинать работать. Чтобы создать сессионный объект в сервлете, воспользуемся следующим методом:&lt;br /&gt;
&lt;br /&gt;
 HttpSession session = aRequest.getSession(true);&lt;br /&gt;
&lt;br /&gt;
Здесь '''aRequest''' – это экземпляр объекта '''HttpServletRequest''', то есть&lt;br /&gt;
запрос переданный сервлету.&lt;br /&gt;
&lt;br /&gt;
Классы, реализующие интерфейс '''HttpSession''', имеют два замечательных метода: '''setAttribute(String, Object)''' и '''getAttribute(String)'''. Метод&lt;br /&gt;
'''setAttribute(String, Object)''' применяется для добавления объекта в сессию, а '''getAttribute(String)''' – для извлечения объекта из нее. Например:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
 session.setAttribute(“sameKey”,sameObject);&lt;br /&gt;
 SameObject aObject = (SameObject) session.getAttribute(“sameKey”);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Фактически, объект '''session''' – это таблица '''Hashtable''', в которой можно хранить любое количество пар типа «ключ – объект». При использовании сессионного объекта данные приложения не отправляются&lt;br /&gt;
пользователю так, как это происходит с cookie. Сессионный объект&lt;br /&gt;
хранится на сервере персонально для каждого клиента. Сервер различает сессии с помощью маркера, который передается пользователю.&lt;br /&gt;
Маркер хранится в cookies браузера до конца сессии, что накладывает&lt;br /&gt;
некоторые ограничения на клиентское рабочее место (использование&lt;br /&gt;
cookies для вашего приложения должно быть разрешено). В этом можно легко убедиться, просмотрев сохраненные cookies в вашем любимом браузере.&lt;br /&gt;
&lt;br /&gt;
[[Изображение:LXF91_javaee2.png|Рис. 2]]&lt;br /&gt;
&lt;br /&gt;
'''Маркер сессии. Герои романа «''Лабиринт отражений''» пытались повесить друг другу маркер под видом невинного поцелуя, а наше web-приложение действует еще хитрее и незаметнее.'''&lt;br /&gt;
&lt;br /&gt;
==Авторизация==&lt;br /&gt;
&lt;br /&gt;
Логично предположить, что чаще всего сессии применяются там, где&lt;br /&gt;
необходимо авторизовать пользователя и предоставлять доступ к&lt;br /&gt;
определенным функциям приложения только при наличии соответствующих привилегий. Давайте обратимся к нашей телефонной книге:&lt;br /&gt;
ее просмотр разрешен всем желающим, а для добавления нового телефона, редактирования и удаления записи необходима авторизация.&lt;br /&gt;
&lt;br /&gt;
Давайте создадим JSP-страницу '''auth.jsp''', которая будет запрашивать у пользователя имя и пароль. Сохраните файл в каталоге '''jsps''' и введите в него следующий текст:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
&amp;lt;span style=”color: green;”&amp;gt;&amp;lt;%=request.getAttribute(“message”)%&amp;gt;&lt;br /&gt;
&amp;lt;/span&amp;gt;&lt;br /&gt;
&amp;lt;form action=”&amp;lt;%=request.getContextPath()%&amp;gt;/auth” method=”post”&amp;gt;&lt;br /&gt;
        &amp;lt;table&amp;gt;&lt;br /&gt;
               &amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;Логин: &amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&amp;lt;input type=”text” name=”login”/&amp;gt;&lt;br /&gt;
&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&lt;br /&gt;
               &amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;Пароль: &amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&lt;br /&gt;
                         &amp;lt;input type=”password” name=”password”/&amp;gt;&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
               &amp;lt;tr&amp;gt;&amp;lt;td colspan=”2” align=”center”&amp;gt;&lt;br /&gt;
                        &amp;lt;input type=”submit” value=”Авторизоваться”/&amp;gt;&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
           &amp;lt;/table&amp;gt;&lt;br /&gt;
&amp;lt;/form&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Легко видеть, что в форме имеется два поля ввода и одна кнопка.&lt;br /&gt;
Заметим также, что поле для ввода пароля имеет тип '''password''', так что&lt;br /&gt;
вместо нажатых клавиш в окне браузера будут отображаться до боли&lt;br /&gt;
знакомые «звездочки» ('''‘*’''').&lt;br /&gt;
&lt;br /&gt;
Теперь внесем изменения в '''AddressBookServlet''': расширим ветвление в методе '''handle''' следующим образом:&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
 }else if (“/auth”.equals(target)) {&lt;br /&gt;
           handleAuth(aRequest, aResponse);&lt;br /&gt;
       }&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Дело за малым – осталось написать метод '''handleAuth(aRequest,aResponse)''', который и будет обрабатывать данные, введенные в форму, то есть производить авторизацию пользователя.&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
    private void handleAuth(HttpServletRequest aRequest,&lt;br /&gt;
               HttpServletResponse aResponse) throws IOException,&lt;br /&gt;
 ServletException {&lt;br /&gt;
       String login = aRequest.getParameter(“login”);&lt;br /&gt;
       String password = aRequest.getParameter(“password”);&lt;br /&gt;
       if ((login != null) &amp;amp;&amp;amp; (password != null)) {&lt;br /&gt;
           // here is auth process&lt;br /&gt;
           if ((login.equals(“user”)) &amp;amp;&amp;amp; (password.equals(“userPass”))) {&lt;br /&gt;
               //here is success auth&lt;br /&gt;
               HttpSession session = aRequest.getSession(true);&lt;br /&gt;
               session.setAttribute(“auth”,aRequest.getParameter(“login”));&lt;br /&gt;
               aRequest.setAttribute(“message”, null);&lt;br /&gt;
               outputPage(“index.jsp”, aRequest, aResponse);&lt;br /&gt;
           } else {&lt;br /&gt;
               //here is failed auth&lt;br /&gt;
               aRequest.setAttribute(“message”,&lt;br /&gt;
                            “Неверный логин или пароль, повторите ввод&lt;br /&gt;
 данных”);&lt;br /&gt;
               outputPage(“auth.jsp”, aRequest, aResponse);&lt;br /&gt;
           }&lt;br /&gt;
       } else {&lt;br /&gt;
           //here is no data to auth&lt;br /&gt;
           aRequest.setAttribute(“message”,&lt;br /&gt;
                        “Логин и пароль не могут быть пустыми, повторите&lt;br /&gt;
 ввод данных”);&lt;br /&gt;
           outputPage(“auth.jsp”, aRequest, aResponse);&lt;br /&gt;
      }&lt;br /&gt;
   }&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Чтобы не усложнять приложение, мы используем простейший&lt;br /&gt;
способ авторизации – имя пользователя и пароль жестко зашиты в&lt;br /&gt;
теле метода. С точки зрения безопасности и масштабируемости лучше&lt;br /&gt;
использовать для хранения этих данных БД.&lt;br /&gt;
&lt;br /&gt;
В случае удачной авторизации мы помещаем имя пользователя в&lt;br /&gt;
объект '''session''' и перенаправляем его на главную страницу. Если же&lt;br /&gt;
авторизация не удалась (введено неверное имя пользователя и/или&lt;br /&gt;
пароль), пользователю будет предложено повторить попытку.&lt;br /&gt;
&lt;br /&gt;
Таким образом, если авторизация прошла успешно, в сессии пользователя будет сохранен объект, содержащий его имя. Используя этот&lt;br /&gt;
факт, можно изменить '''index.jsp''' и спрятать ссылку на добавление нового телефона от посторонних глаз:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
        &amp;lt;%String name = (String)session.getAttribute(“auth”);&lt;br /&gt;
         if (name != null){ %&amp;gt;&lt;br /&gt;
              &amp;lt;p&amp;gt;Вы авторизованны как: &amp;lt;%=name%&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
              &amp;lt;a href=”&amp;lt;%=request.getContextPath()%&amp;gt;/add”&amp;gt;Добавить&lt;br /&gt;
 запись&amp;lt;/a&amp;gt;&amp;lt;br/&amp;gt;&lt;br /&gt;
         &amp;lt;%} else {%&amp;gt;&lt;br /&gt;
             &amp;lt;a href=”&amp;lt;%=request.getContextPath()%&amp;gt;/auth”&amp;gt;&lt;br /&gt;
 Авторизоваться&amp;lt;/a&amp;gt;&amp;lt;br/&amp;gt;&lt;br /&gt;
         &amp;lt;%} %&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Мы приветствуем пользователя, используя значение атрибута '''auth'''&lt;br /&gt;
и предоставляем ему доступ к функции добавления нового контакта.&lt;br /&gt;
Чтобы спрятать удаление и редактирование записей от неавторизованного пользователя, необходимо внести аналогичные изменения в&lt;br /&gt;
файл '''view.jsp'''.&lt;br /&gt;
&lt;br /&gt;
==Фильтры==&lt;br /&gt;
&lt;br /&gt;
Наше приложение, к сожалению, страдает проблемами безопасности:&lt;br /&gt;
злонамеренный пользователь может обойти процедуру авторизации,&lt;br /&gt;
обратившись к функциям редактирования/удаления напрямую, по&lt;br /&gt;
адресу '''request.getContextPath() + действие (/add; /edit; /remove)'''; можно также непосредственно вызывать JSP-страницы, расположенные в каталоге '''/jsps''' – нужно только узнать их имена.&lt;br /&gt;
&lt;br /&gt;
Что же делать? Проверять, авторизован ли пользователь перед&lt;br /&gt;
выполнением привилегированного действия? Это неудобно – если&lt;br /&gt;
количество JSP будет расти, это заставит вас написать много строк&lt;br /&gt;
однотипного кода, поддержание которых будет отнимать ваше драгоценное время. Мы пойдем другим путем и воспользуемся так называемыми фильтрами.&lt;br /&gt;
&lt;br /&gt;
Схема работы фильтров представлена на Рис. 3. Если для адреса,&lt;br /&gt;
на который отображен сервлет, осуществляется фильтрация, запрос&lt;br /&gt;
будет передан сервлету только после того, как пройдет через каждый&lt;br /&gt;
установленный фильтр.&lt;br /&gt;
&lt;br /&gt;
[[Изображение:LXF91_javaee3.png|Рис. 3]]&lt;br /&gt;
&lt;br /&gt;
'''Схема прохождения запроса через фильтры.'''&lt;br /&gt;
&lt;br /&gt;
На самом деле, фильтр представляет из себя обыкновенный Java-&lt;br /&gt;
класс, реализующий интерфейс '''javax.servlet.Filter'''. Например:&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
 public class FirstFilter implements Filter {&lt;br /&gt;
   private FilterConfig filterConfig;&lt;br /&gt;
   public void init(FilterConfig filterConfig) throws ServletException {&lt;br /&gt;
       System.out.println(“Filter init”);&lt;br /&gt;
       this.filterConfig = filterConfig;&lt;br /&gt;
   }&lt;br /&gt;
   public void doFilter(ServletRequest aRequest, ServletResponse&lt;br /&gt;
 aResponse, FilterChain filterChain)&lt;br /&gt;
   throws IOException, ServletException {&lt;br /&gt;
       System.out.println(“Filter used”);&lt;br /&gt;
       filterChain.doFilter(aRequest, aResponse);&lt;br /&gt;
   }&lt;br /&gt;
   public void destroy() {&lt;br /&gt;
       System.out.println(“Filter dead”);&lt;br /&gt;
       this.filterConfig = null;&lt;br /&gt;
   }&lt;br /&gt;
 }&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Реализовав всего три метода: '''init''' (инициализация фильтра в&lt;br /&gt;
момент старта приложения), '''destroy''' (освобождение ресурсов перед&lt;br /&gt;
завершением работы приложения) и '''doFilter''' (собственно метод, выполняющий фильтрацию), вы получаете класс, способный существенным образом повлиять на работу вашего приложения.&lt;br /&gt;
&lt;br /&gt;
Обратите внимание, что в приведенном выше примере метод&lt;br /&gt;
'''doFilter''' заканчивается вызовом метода '''doFilter(aRequest, aResponse)'''&lt;br /&gt;
объекта '''filterChain''' – это обеспечивает вызов следующего фильтра в&lt;br /&gt;
цепочке (естественно, если фильтров несколько). Если фильтров больше нет, то управление будет передано следующему ресурсу, например, сервлету.&lt;br /&gt;
&lt;br /&gt;
Остановимся подробнее на методе '''init''', а точнее на объекте, реализующем интерфейс '''FilterConfig'''. Этот объект имеет четыре замечательных метода:&lt;br /&gt;
* '''getFilterName()''' – возвращает имя фильтра;&lt;br /&gt;
* '''getInitParameterNames()''' – возвращает объект '''Enumeration''', который содержит в своем теле имена параметров текущего фильтра;&lt;br /&gt;
* '''getInitParameter(String)''' – возвращает значение параметра, имя которого было передано в качестве аргумента;&lt;br /&gt;
* '''getServletContext()''' – возвращает объект '''ServletContext''', о котором мы поговорим ниже.&lt;br /&gt;
&lt;br /&gt;
Как вы уже, наверное, поняли, объект '''FilterConfig''' позволяет получить доступ к конфигурации фильтра, которая была задана при его объявлении в дескрипторе развертывания ('''web.xml''').&lt;br /&gt;
&lt;br /&gt;
==Ограничение доступа==&lt;br /&gt;
&lt;br /&gt;
Итак, воспользуемся фильтрами для ограничения доступа к ресурсам&lt;br /&gt;
нашего приложения. Прежде всего, запретим пользователю обращаться напрямую к JSP, и, при попытке запросить ресурс из каталога '''/jsps''',&lt;br /&gt;
заставим его перейти на индексную (первую) страницу. Для этого&lt;br /&gt;
напишем простой фильтр '''AccessFilter'''. Метод '''doFilter''' будет выглядеть&lt;br /&gt;
следующим образом:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
   public void doFilter(ServletRequest aRequest,&lt;br /&gt;
           ServletResponse aResponse, FilterChain filterChain)&lt;br /&gt;
           throws IOException, ServletException {&lt;br /&gt;
              ((HttpServletResponse)aResponse).&lt;br /&gt;
            sendRedirect(((HttpServletRequest)aRequest).getContextPath());&lt;br /&gt;
   }&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Благодаря методу '''sendRedirect''', вместо ожидаемого ресурса неавторизованный пользователь увидит '''index.jsp'''. Чтобы фильтр заработал, его необходимо подключить в дескрипторе развертывания. Это делается при помощи следующих строк:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
   &amp;lt;filter&amp;gt;&lt;br /&gt;
       &amp;lt;filter-name&amp;gt;AccessFilterName&amp;lt;/filter-name&amp;gt;&lt;br /&gt;
       &amp;lt;filter-class&amp;gt;AccessFilter&amp;lt;/filter-class&amp;gt;&lt;br /&gt;
   &amp;lt;/filter&amp;gt;&lt;br /&gt;
   &amp;lt;filter-mapping&amp;gt;&lt;br /&gt;
      &amp;lt;filter-name&amp;gt;AccessFilterName&amp;lt;/filter-name&amp;gt;&lt;br /&gt;
      &amp;lt;url-pattern&amp;gt;/jsps/*&amp;lt;/url-pattern&amp;gt;&lt;br /&gt;
   &amp;lt;/filter-mapping&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В секции '''filter''' мы объявляем, что имени фильтра '''AccessFilterName'''&lt;br /&gt;
соответствует класс '''AccessFilter''', а в секции '''filter-mapping''' указываем,&lt;br /&gt;
на какие объекты распространяется зона действия фильтра. В данном случае фильтр работает по шаблону, то есть используется для&lt;br /&gt;
всех адресов типа '''/jsps/*''', где вместо звездочки может быть все, что&lt;br /&gt;
угодно.&lt;br /&gt;
&lt;br /&gt;
Теперь давайте создадим класс '''AuthFilter''', который будет ограничивать доступ к некоторым действиям для неавторизованных пользователей. Метод '''doFilter''' будет выглядеть следующим образом:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
   public void doFilter(ServletRequest aRequest,&lt;br /&gt;
          ServletResponse aResponse, FilterChain filterChain)&lt;br /&gt;
          throws IOException, ServletException {&lt;br /&gt;
      HttpSession session = ((HttpServletRequest) aRequest).&lt;br /&gt;
 getSession();&lt;br /&gt;
      String name = (String) session.getAttribute(“auth”);&lt;br /&gt;
      if (name != null) {&lt;br /&gt;
          filterChain.doFilter(aRequest, aResponse);&lt;br /&gt;
      } else {&lt;br /&gt;
          aRequest.setAttribute(“action”, “auth”);&lt;br /&gt;
          RequestDispatcher dispatcher = aRequest.getRequestDispatcher(“/&lt;br /&gt;
 auth”);&lt;br /&gt;
          dispatcher.forward(aRequest, aResponse);&lt;br /&gt;
      }&lt;br /&gt;
   }&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Если в текущей сессии не задан атрибут '''auth''', пользователь будет&lt;br /&gt;
отправлен на страничку авторизации. Добавим в дескриптор развертывания следующие строки:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
   &amp;lt;filter&amp;gt;&lt;br /&gt;
      &amp;lt;filter-name&amp;gt;AuthEditFilter&amp;lt;/filter-name&amp;gt;&lt;br /&gt;
      &amp;lt;filter-class&amp;gt;AuthFilter&amp;lt;/filter-class&amp;gt;&lt;br /&gt;
   &amp;lt;/filter&amp;gt;&lt;br /&gt;
   &amp;lt;filter-mapping&amp;gt;&lt;br /&gt;
      &amp;lt;filter-name&amp;gt;AuthEditFilter&amp;lt;/filter-name&amp;gt;&lt;br /&gt;
      &amp;lt;url-pattern&amp;gt;/edit&amp;lt;/url-pattern&amp;gt;&lt;br /&gt;
   &amp;lt;/filter-mapping&amp;gt;&lt;br /&gt;
   &amp;lt;filter&amp;gt;&lt;br /&gt;
      &amp;lt;filter-name&amp;gt;AuthAddFilter&amp;lt;/filter-name&amp;gt;&lt;br /&gt;
      &amp;lt;filter-class&amp;gt;AuthFilter&amp;lt;/filter-class&amp;gt;&lt;br /&gt;
   &amp;lt;/filter&amp;gt;&lt;br /&gt;
   &amp;lt;filter-mapping&amp;gt;&lt;br /&gt;
      &amp;lt;filter-name&amp;gt;AuthAddFilter&amp;lt;/filter-name&amp;gt;&lt;br /&gt;
      &amp;lt;url-pattern&amp;gt;/add&amp;lt;/url-pattern&amp;gt;&lt;br /&gt;
   &amp;lt;/filter-mapping&amp;gt;&lt;br /&gt;
   &amp;lt;filter&amp;gt;&lt;br /&gt;
      &amp;lt;filter-name&amp;gt;AuthRemFilter&amp;lt;/filter-name&amp;gt;&lt;br /&gt;
      &amp;lt;filter-class&amp;gt;AuthFilter&amp;lt;/filter-class&amp;gt;&lt;br /&gt;
   &amp;lt;/filter&amp;gt;&lt;br /&gt;
   &amp;lt;filter-mapping&amp;gt;&lt;br /&gt;
      &amp;lt;filter-name&amp;gt;AuthRemFilter&amp;lt;/filter-name&amp;gt;&lt;br /&gt;
      &amp;lt;url-pattern&amp;gt;/remove&amp;lt;/url-pattern&amp;gt;&lt;br /&gt;
   &amp;lt;/filter-mapping&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Обратите внимание, что хотя наш фильтр реализован одним-единственным классом, он может быть доступен по нескольким именам (в&lt;br /&gt;
нашем случае: '''AuthEditFilter, AuthAddFilter, AuthRemFilter''') и работать&lt;br /&gt;
для разных URL.&lt;br /&gt;
&lt;br /&gt;
Чтобы закрыть тему развертывания фильтров, рассмотрим пример&lt;br /&gt;
настройки параметров фильтра в дескрипторе:&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
      &amp;lt;init-param&amp;gt;&lt;br /&gt;
          &amp;lt;description&amp;gt;&lt;br /&gt;
              That is description&lt;br /&gt;
          &amp;lt;/description&amp;gt;&lt;br /&gt;
          &amp;lt;param-name&amp;gt;&lt;br /&gt;
              sameParamName&lt;br /&gt;
          &amp;lt;/param-name&amp;gt;&lt;br /&gt;
          &amp;lt;param-value&amp;gt;&lt;br /&gt;
              sameParamValue&lt;br /&gt;
          &amp;lt;/param-value&amp;gt;&lt;br /&gt;
       &amp;lt;/init-param&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Секцию '''init-param''' необходимо создавать отдельно для каждого&lt;br /&gt;
параметра, при этом она должна быть размещена внутри секции '''filter'''.&lt;br /&gt;
Обязательными являются подсекции '''param-name''' и '''param-value''', которые задают название параметра и его значение, соответственно.&lt;br /&gt;
&lt;br /&gt;
==Доступ к общим объектам==&lt;br /&gt;
&lt;br /&gt;
Наше приложение уже вполне работоспособно, однако рано или поздно нам захочется расширить его функциональность, и, возможно, для&lt;br /&gt;
этого потребуется уже не один сервлет, а несколько, причем все они&lt;br /&gt;
будут обращаться к некому общему ресурсу (информации или объектам). Например, вы можете захотеть узнать, сколько пользователей&lt;br /&gt;
в данный момент авторизовано в приложении или получить из двух&lt;br /&gt;
несвязанных сервлетов доступ к информации, хранящейся в одном&lt;br /&gt;
файле.&lt;br /&gt;
&lt;br /&gt;
Подобно сессионным объектам, существует и контекстный объект, который обеспечивает доступ к общим ресурсам из разных мест&lt;br /&gt;
приложения. Использование контекстного объекта очень похоже на&lt;br /&gt;
использование сессионного, и получить его экземпляр в сервлете можно следующим образом:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
 ServletContext sc = this.getServletContext();&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Этот объект обладает следующими методами:&lt;br /&gt;
&lt;br /&gt;
* '''getAttribute(String)''' – получение общего объекта по ключу;&lt;br /&gt;
* '''getAttributeNames()''' – получение списка ключей общих объектов;&lt;br /&gt;
* '''setAttribute(String, Object)''' – добавление нового объекта и соответствующего ему ключа;&lt;br /&gt;
* '''removeAttribute(String)''' – удаление объекта, соответствующего ключу, из списка общедоступных объектов;&lt;br /&gt;
&lt;br /&gt;
Несмотря на то, что в этой статье контекстному объекту уделено&lt;br /&gt;
немного внимания, он является одним из мощнейших инструментов&lt;br /&gt;
для обеспечения работы web-приложения.&lt;br /&gt;
&lt;br /&gt;
==Вместо заключения==&lt;br /&gt;
&lt;br /&gt;
Сегодня мы поговорили о сессионных и контекстных объектах, узнали&lt;br /&gt;
как создавать и использовать фильтры, рассмотрели простейший пример авторизации пользователя (конечно, надо сделать оговорку, что&lt;br /&gt;
было представлено весьма небезопасное и не промышленное решение,&lt;br /&gt;
хотя для простого офисного приложения его может быть и достаточно).&lt;br /&gt;
Наконец, мы модифицировали наше web-приложение «Телефонная&lt;br /&gt;
книга», применяя практически все изученные возможности.&lt;br /&gt;
&lt;br /&gt;
В следующий раз мы рассмотрим паттерн ''MVC'' и его вариацию для&lt;br /&gt;
создания web-приложений: ''Model2''. '''LXF'''&lt;/div&gt;</summary>
		<author><name>Dionysius</name></author>	</entry>

	<entry>
		<id>http://wiki.linuxformat.ru/wiki/LXF99:Java_EE</id>
		<title>LXF99:Java EE</title>
		<link rel="alternate" type="text/html" href="http://wiki.linuxformat.ru/wiki/LXF99:Java_EE"/>
				<updated>2009-01-01T07:45:18Z</updated>
		
		<summary type="html">&lt;p&gt;Dionysius: /* Экстракт кофе */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Цикл/Java EE}}&lt;br /&gt;
[[Категория:Учебники]]&lt;br /&gt;
== Экстракт кофе ==&lt;br /&gt;
&lt;br /&gt;
[[Media:JavaEE_LXF99.tar.gz‎|Скачать исходный код примера]]&lt;br /&gt;
&lt;br /&gt;
'''ЧАСТЬ 11''' Наша серия, увы, подходит к концу, но на десерт '''Александр Бабаев''' припас нечто особенное — ароматные зерна ''Enterprise Java Beans''&lt;br /&gt;
&lt;br /&gt;
Все предыдущие статьи содержали огромное количество полезной информации. Мы рассмотрели алгоритмы, структуры, принципы работы, множество библиотек, так или иначе связанных с созданием серьезных приложений на Java. И пусть вас не смущает то, что примеры были простые. В основе больших, серьезных, важных и сложных приложений лежит именно то, о чем мы говорили.&lt;br /&gt;
&lt;br /&gt;
Теперь вы уже готовы узнать, что обозначает загадочная аббревиатура EJB3. Расшифровывается она как «Enterprise Java Beans, version 3» и содержит внутри огромный мир, окошко в который мы сегодня приоткроем.&lt;br /&gt;
&lt;br /&gt;
=== Почему именно EJB3? ===&lt;br /&gt;
&lt;br /&gt;
Дело в том, что это стандарт. Тройка в названии указывает, что были еще версии один и два, также была версия два-точка-один… Но только текущая, третья версия действительно является великолепным инструментов для борьбы с хаосом корпоративных систем. Все предыдущие версии строились по такому принципу: «Мы ('''Sun/IBM/''' и пр.) тут собрались, посовещались и решили, что вы (разработчики) будете использовать вот это… (EJB2.1)». Было круто, но разработчики хоть и использовали, но плевались (странно, правда?). EJB3 создавалась иначе. Те же люди собрались, посмотрели на то, как работают программисты, какие есть библиотеки, удачные решения, технологии… И, выбрав лучшее и добавив свой (огромный) опыт, выдали третью версию спецификации.&lt;br /&gt;
&lt;br /&gt;
Получившийся стандарт хорош. Хорош и простотой (можно обойтись без специфических XML, которых в предыдущих вариантах были сотни), и привычностью (''Hibernate'' использовали? Нет? Ну, это теперь reference implementation, то есть стандартная реализация для ''JPA1'', части EJB3), и заменой старых неудобных частей на новые, «блестящие и шелковистые».&lt;br /&gt;
&lt;br /&gt;
=== Для чего оно? ===&lt;br /&gt;
&lt;br /&gt;
Большие системы никогда не создаются в одиночку. Существуют люди, которые зовутся архитекторами: они придумывают систему. Система обычно состоит из блоков. Блоки, в свою очередь, состоят из других блоков… Блоки отвечают за совершенно разные вещи: за хранение бизнес-объектов, за просчет алгоритмов, за управление элементами системы, и так далее. Разрабатываются эти составные части системы разными людьми, часто совершенно не связанными друг с другом.&lt;br /&gt;
&lt;br /&gt;
В таких условиях нужен стандарт, который обеспечивал бы, чтобы блок, написанный в Индии, и блок, написанный в России, заработали вместе. Можно этот стандарт каждый раз придумывать заново, но на это никогда нет времени. Лучше использовать EJB3.&lt;br /&gt;
&lt;br /&gt;
=== Общая структура EJB-проекта ===&lt;br /&gt;
&lt;br /&gt;
Упрощенная схема проекта приведена на Рис. 1.&lt;br /&gt;
&lt;br /&gt;
[[Изображение:LXF99_Java1.jpg|Рис. 1. Трехзвенная структура EJB-проекта.]]&lt;br /&gt;
&lt;br /&gt;
Это так называемая трехзвенная структура. Она проста, и для более сложных систем может разрастись до четырех-, пяти-, n-звенной. Серверов приложений может быть кластер, СУБД тоже может представлять кластер с распределением нагрузки и резервированием, и так далее.&lt;br /&gt;
&lt;br /&gt;
В качестве СУБД сгодится практически любая: у нас это будет ''MySQL'', но, грамотно используя JPA (об этом чуть дальше), СУБД можно сменить хотя и за ненулевое, но вполне приемлемое время. Клиенты могут быть либо тонкими (браузер), либо полноценными приложениями (и даже не обязательно на Java).&lt;br /&gt;
&lt;br /&gt;
А вот про сервер приложений поговорим подробнее. Это именно то место, где соединяются те самые блоки, написанные в Индии, России, Китае, США и так далее. Чтобы все они работали вместе, написаны специальные приложения, которые обеспечивают связь с СУБД, предоставляют огромное количество стандартных API для работы блоков, дают возможность эти блоки выгружать и загружать без перезапуска сервера, контролировать их исполнение… и много чего еще. Сервера приложений есть как коммерческие (''IBM  WebSphere'', ''BEA  WebLogic''), так и бесплатные (''GlassFish'', ''JBoss'', ''IBM WebSphere Community Edition''). Мы посмотрим поближе на ''JBoss'', который, помимо прочего, продвигается Red Hat и распространяется в составе Red Hat Enterprise Linux.&lt;br /&gt;
&lt;br /&gt;
=== JBoss ===&lt;br /&gt;
&lt;br /&gt;
Итак, сервер приложений. Он состоит из огромного количества блоков, интегрированных вместе. Некоторые нам уже так или иначе знакомы: например, Tomcat или подсистема RMI-подключений. Но есть и множество других: Hibernate, который отвечает за «связь с СУБД», система кэширования, кластеризации, распространения сообщений, транзакций (уровня приложения), …&lt;br /&gt;
&lt;br /&gt;
Короче, система большая. Мы рассмотрим относительно небольшую часть, которая работает с EJB3, задержимся на Hibernate (и JPA) и немного поглядим на транзакции (JTA). Все рассмотреть, конечно, не успеем, поэтому после статьи приведен список литературы — выберите книжку по вкусу, чтобы заняться подробным изучением.&lt;br /&gt;
&lt;br /&gt;
=== Так что же такое EJB? ===&lt;br /&gt;
&lt;br /&gt;
Ну, во-первых, это технология, это уже понятно. Во-вторых, это Enterprise Java Bean (без «s») — то самое «зерно», то есть блок, из множества которых строится приложение. Блок стандартным образом упаковывается, и помещается в каталог ''JBoss'', после чего (если все сделано правильно), ''JBoss'' подключает блок к системе.&lt;br /&gt;
&lt;br /&gt;
То есть блок — это бин (bean). И наоборот. Бин в простейшем случае — это всего лишь класс, описанный и объявленный специальным образом. Например, пусть он считает площадь круга.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
 @Remote&lt;br /&gt;
 public interface Calculator {&lt;br /&gt;
   public double getSquare(double aRadius);&lt;br /&gt;
 }&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Пока это только объявление, то есть интерфейс — то, что видит клиент. Причем от обычного интерфейса он отличается только словом '''@Remote''' (так называемой аннотацией). Оно обозначает, что клиенты, которые используют этот бин, могут находиться как на серверном компьютере, так и на любых других узлах сети. Если доступ извне локального компьютера не предполагается, то можно использовать аннотацию '''@Local''', или вообще ничего не писать, так как интерфейсы считаются локальными по умолчанию. Умолчания — это одно из огромных достоинств EJB3, так как не нужно прописывать банальности, которые в крупных проектах превращаются в мегабайты ненужного кода.&lt;br /&gt;
&lt;br /&gt;
Но где же считается сама площадь? В классе, который реализует интерфейс:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=java&amp;gt; &lt;br /&gt;
 @Stateless&lt;br /&gt;
 public class CalculatorBean implements Calculator {&lt;br /&gt;
   public double getSquare(double aRadius) {&lt;br /&gt;
      return Math.PI*aRadius*aRadius;&lt;br /&gt;
   }&lt;br /&gt;
 }&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Тут опять появилась аннотация, которая обозначает, что этот бин (да, это настоящий Enterprise Java Bean; да, больше — кроме упаковки в jar — не нужно вообще ничего) является Stateless-бином, то есть не сохраняет состояние в процессе работы. Клиент вызывает метод, метод выполняется, и следующий метод ничего не будет знать о предыдущем исполнении.&lt;br /&gt;
&lt;br /&gt;
Теперь давайте посмотрим на клиент и, наконец, поставим ''JBoss'', запустив наш бин.&lt;br /&gt;
&lt;br /&gt;
Клиент будет немного «не в стиле EJB3». Правильный EJB3-клиент выглядит примерно так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
 public class CalculatorClient {&lt;br /&gt;
   @EJB&lt;br /&gt;
   private Calculator _calculator;&lt;br /&gt;
   private void start() {&lt;br /&gt;
      System.out.println(“Square for circle with radius 2.345 = “ +&lt;br /&gt;
            _calculator.getSquare(2.345));&lt;br /&gt;
   }&lt;br /&gt;
   public static void main(String[] args) {&lt;br /&gt;
      new CalculatorClient().start();&lt;br /&gt;
   }&lt;br /&gt;
 }&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Но ''JBoss'' (который мы будем использовать для демонстрации) такого стиля (пока) не понимает, поэтому выйдет чуть-чуть подлиннее:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
 public class CalculatorClient {&lt;br /&gt;
    private Calculator _calculator;&lt;br /&gt;
    private void start() throws NamingException {&lt;br /&gt;
        _calculator = (Calculator) getInitialContext().&lt;br /&gt;
              lookup(“CalculatorBean/remote”);&lt;br /&gt;
        System.out.println(“Square for circle with radius 2.345 = “ +&lt;br /&gt;
              _calculator.getSquare(2.345));&lt;br /&gt;
    }&lt;br /&gt;
    private InitialContext getInitialContext() throws NamingException {&lt;br /&gt;
        Properties properties = new Properties();&lt;br /&gt;
        properties.put(Context.INITIAL_CONTEXT_FACTORY,&lt;br /&gt;
              “org.jnp.interfaces.NamingContextFactory”);&lt;br /&gt;
        properties.put(Context.PROVIDER_URL, “localhost:1099”);&lt;br /&gt;
        return new InitialContext(properties);&lt;br /&gt;
    }&lt;br /&gt;
    public static void main(String[] args) throws NamingException {&lt;br /&gt;
        new CalculatorClient().start();&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Разница, как можно заметить, в отсутствии аннотации '''@EJB'''. А заменяет ее тот код, который мы написали (получение контекста, а из него — ссылки на нужный нам бин).&lt;br /&gt;
&lt;br /&gt;
Нужно сделать небольшое отступление. При поиске объекта мы использовали его имя: '''«CalculatorBean/remote»'''. Почему именно так? И как вообще ищутся объекты? Для этого используется так называемая служба имен Java (JNDI) — нечто похожее на реестр RMI, который использовался для регистрации и поиска RMI-серверов. А имя присваивается автоматически и по умолчанию. В принципе, можно дать указания серверу приложений, как должен называться бин в системе.&lt;br /&gt;
&lt;br /&gt;
Именно служба имен гарантирует, что бин, даже если его выгрузить и загрузить обновленный, будет найден остальными частями системы. А если при этом не изменять его интерфейс, то можно безболезненно улучшать реализацию, при этом не куроча всю систему и не навлекая гнев соратников по клавиатуре.&lt;br /&gt;
&lt;br /&gt;
Осталось упаковать классы, скинуть их серверу приложений и посмотреть, что же из этого выйдет.&lt;br /&gt;
&lt;br /&gt;
=== Первый бин ===&lt;br /&gt;
  &lt;br /&gt;
Для начала скачаем ''JBoss''. Это можно сделать на страничке http://labs.jboss.com/jbossas/downloads/; берите стабильную версию (4.2.2.GA). Ее также можно найти на диске, прилагаемом к журналу.&lt;br /&gt;
&lt;br /&gt;
Установка ''JBoss''’а заключается в разархивировании его куданибудь. После этого он готов к работе (предполагается, что пути к '''/bin/java''' уже находятся в '''PATH''').&lt;br /&gt;
&lt;br /&gt;
Теперь сделаем бин. Для этого нужно скомпилировать наш интерфейс и класс (путь к ''JBoss''’у, конечно, подставьте свой):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
  javac -cp ~/bin/jboss/server/default/lib/jboss-ejb3x.jar Calculator.java&lt;br /&gt;
  javac -cp ~/bin/jboss/server/default/lib/jboss-ejb3x.jar CalculatorBean.&lt;br /&gt;
  java&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
после чего запаковать все в jar-файл:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
  jar -c CalculatorBean.class Calculator.class &amp;gt; CalculatorEJB.jar&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Теперь начинается волшебство. Запускаем ''JBoss'':&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
  jboss/bin/run.sh -c default &amp;amp;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(понятно, что путь к ''JBoss''’овскому '''run.sh''' будет другой, понятно, что если требуется запустить его с возможностью закрыть терминал, нужно использовать '''nohup'''… это самый простой вариант) и кидаем jar-файл в каталог '''/jboss/server/default/deploy'''.&lt;br /&gt;
&lt;br /&gt;
Все, через секунду бин «воткнут» в ''JBoss''. Теперь можно запускать клиента. Компилируем его:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
  javac -cp ~/bin/jboss/server/default/lib/jboss-ejb3x.jar:. CalculatorClient.&lt;br /&gt;
  java&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Запускаем клиент, при этом подключая нужные библиотеки (все они есть в ''JBoss''’е):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
  java -cp ~/bin/jboss/client/jbossall-client.jar:. client.CalculatorClient&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Получаем:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
  Square for circle with radius 2.345 = 17.275696541906616&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Кто не верит, пусть пересчитает на калькуляторе.&lt;br /&gt;
&lt;br /&gt;
=== Второй бин. JPA ===&lt;br /&gt;
&lt;br /&gt;
Почувствовали, насколько это просто и быстро? Теперь попробуем что-нибудь сохранить в СУБД. Для этого немного настроимся. Подключать будем ''MySQL'', которая запущена на локальной машине, с настройками по умолчанию (учетная запись root без пароля). Чтобы подключить СУБД, нужно положить специальный XML-файл в тот же каталог '''jboss/server/default/deploy'''. Файл будет называться '''mysql-ds.xml''', а содержать будет следующее:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
  &amp;lt;?xml version=”1.0” encoding=”UTF-8”?&amp;gt;&lt;br /&gt;
  &amp;lt;datasources&amp;gt;&lt;br /&gt;
    &amp;lt;local-tx-datasource&amp;gt;&lt;br /&gt;
              &amp;lt;jndi-name&amp;gt;jdbc/jpaPool&amp;lt;/jndi-name&amp;gt;&lt;br /&gt;
              &amp;lt;connection-url&amp;gt;jdbc:mysql://localhost:3306/nisp&amp;lt;/connection-url&amp;gt;&lt;br /&gt;
              &amp;lt;driver-class&amp;gt;com.mysql.jdbc.Driver&amp;lt;/driver-class&amp;gt;&lt;br /&gt;
              &amp;lt;user-name&amp;gt;root&amp;lt;/user-name&amp;gt;&lt;br /&gt;
              &amp;lt;password&amp;gt;&amp;lt;/password&amp;gt;&lt;br /&gt;
              &amp;lt;exception-sorter-class-name&amp;gt;org.jboss.resource.adapter.jdbc. vendor.MySQLExceptionSorter&amp;lt;/exception-sorter-class-name&amp;gt;&lt;br /&gt;
    &amp;lt;/local-tx-datasource&amp;gt;&lt;br /&gt;
 &amp;lt;/datasources&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Тут все просто: строка соединения нам попадалась уже при раcсмотрении JDBC в [[LXF93:Java|LXF93]] (можно ее усложнить, введя при необходимости всякие параметры), имя учетной записи и пароль — тоже вполне самоочевидные вещи. Еще указывается '''jndi-name''', то есть имя, по которому этот пул соединений можно будет найти в недрах ''JBoss''.&lt;br /&gt;
&lt;br /&gt;
Положили? Теперь перезапустите сервер приложений:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
  jboss/bin/shutdown.sh -s jnp://127.0.0.1:1099 -S&lt;br /&gt;
  jboss/bin/run.sh -c default &amp;amp;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Теперь мы готовы; начинаем писать '''EntityBean'''. Так называются классы-сущности, экземпляры которых хранятся в СУБД.&lt;br /&gt;
&lt;br /&gt;
JPA, или Java Persistence API — это стандартный API, предназначенный для того, чтобы в удобном виде хранить Java-объекты в БД. При этом создается структура БД, создаются классы, в которых при помощи аннотаций прописываются связи между полями классов и полями таблиц БД, а остальное (преобразования, проверки, …) делает конкретная реализация JPA. В ''JBoss'' это '''Hibernate'''.&lt;br /&gt;
&lt;br /&gt;
Итак, сделаем микробиблиотеку. Вот класс, который хранит в БД информацию о книге:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
  @Entity&lt;br /&gt;
  public class Book implements Serializable {&lt;br /&gt;
     @Id&lt;br /&gt;
   @GeneratedValue(strategy = GenerationType.AUTO)&lt;br /&gt;
   public Long Id;&lt;br /&gt;
   public String Title;&lt;br /&gt;
   public String Author;&lt;br /&gt;
 }&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
И все. Мы вновь использовали аннотацию, на сей раз '''@Entity'''. Все остальное — умолчания, а значит, поля класса '''Title''' и '''Author''' будут полями в таблице '''Book'''. '''Id''' — это идентификатор объекта, он уникален, и аннотацией мы указываем, чтобы JPA сам его создавал, когда будет нужно. Можно прописать и подробности: самостоятельно дать имена полям и таблице, указать типы полей и т. п., но пока оставим все это на усмотрение JPA.&lt;br /&gt;
&lt;br /&gt;
Расширим микробиблиотеку, введя туда микрочитателей. Предположим, что один читатель может читать только одну книгу.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
 @Entity&lt;br /&gt;
 public class Reader implements Serializable {&lt;br /&gt;
   @Id&lt;br /&gt;
   @GeneratedValue(strategy = GenerationType.AUTO)&lt;br /&gt;
   public Long Id;&lt;br /&gt;
   public String Name;&lt;br /&gt;
   @OneToOne&lt;br /&gt;
   public Book Book;&lt;br /&gt;
 }&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Аннотация '''@OneToOne''' обозначает, что связь между '''Reader''' и '''Book''' — один к одному. Физически эта связь будет реализована посредством внешнего ключа в таблице.&lt;br /&gt;
&lt;br /&gt;
Возникает вопрос: а в какой таблице будет прописана связь (создан внешний ключ)? В '''Book'''? Или в '''Reader'''? Нужно как-то определиться, чтобы более четко понимать, что происходит в системе — иначе как ошибки-то искать? Для этого пропишем ссылку на '''Reader'''’а в книге, плюс укажем аннотацией, с какой стороны должна быть ссылка в БД. Теперь книга выглядит так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
 @Entity&lt;br /&gt;
 public class Book implements Serializable {&lt;br /&gt;
   @Id&lt;br /&gt;
   @GeneratedValue(strategy = GenerationType.AUTO)&lt;br /&gt;
   public Long Id;&lt;br /&gt;
   public String Title;&lt;br /&gt;
   public String Author;&lt;br /&gt;
   @OneToOne(mappedBy = “Book”)&lt;br /&gt;
   public Reader Reader;&lt;br /&gt;
 }&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Параметр аннотации '''@OneToOne mappedBy''' показывает, что указанное поле в БД (в таблице Book) не существует, а извлекается из таблицы, на которую указывает связь, то есть '''Reader'''.&lt;br /&gt;
&lt;br /&gt;
{{Врезка&lt;br /&gt;
    |Заголовок=Как рекомендуется создавать бины&lt;br /&gt;
    |Содержание=После деплоймента (установки и развертывания) бина микробиблиотеки, JPA автоматически создал все необходимые таблицы, связи между ними и так далее. Казалось бы, это счастье. Пишем код, JPA делает таблицы – все довольны... Но не так все просто.&lt;br /&gt;
Во-первых, зачастую таблицы уже есть. Во-вторых, далеко не всегда разработчики, которые создают бины, умеют хорошо создавать таблицы. В третьих, иногда таблицы и бины создают разные люди.&lt;br /&gt;
&lt;br /&gt;
И это правильно. Нехорошо, если разработчик «и швец, и жнец, и на дуде игрец» (то есть хорошо, конечно, но где ж их взять в достаточном для пропитания количестве?). Правильный путь разработки структуры данных – когда сначала создается структура таблиц в СУБД, правильно прописываются связи, при необходимости реализуются триггеры и другие особенности, связанные с СУБД, и только потом на этой основе создаются бины, объекты, связи между ними. JPA рассчитан именно на такое применение и, в принципе, умеет не только создавать структуру СУБД по Java-файлам, но и наоборот, создавать классы по базе. Стоит это учитывать. Иначе часто получается, что «хотели как лучше, а получилось как всегда», или «что тут этот дурак JPA насоздавал – я совсем не то имел в виду; да и тормозит всё».&lt;br /&gt;
    |Ширина=200px}}&lt;br /&gt;
&lt;br /&gt;
=== Третий бин ===&lt;br /&gt;
&lt;br /&gt;
Создав бины для хранимых сущностей (Entity Beans), нужно сделать бин для работы с ними: извлечения из СУБД, сохранения, выдачи книги и возврата ее обратно в библиотеку.&lt;br /&gt;
&lt;br /&gt;
Для экономии места не будем увлекаться всевозможными проверками (например, выдана ли книга повторно), сосредоточимся на JPA и EJB3.&lt;br /&gt;
&lt;br /&gt;
Как и в предыдущий раз, сначала интерфейс:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
 @Remote&lt;br /&gt;
 public interface Library {&lt;br /&gt;
   public void addBook(Book aBook);&lt;br /&gt;
   public void addReader(Reader aReader);&lt;br /&gt;
   public void giveBook(String aReaderName, Book aBook);&lt;br /&gt;
   public void returnBook(String aReaderName);&lt;br /&gt;
    public Reader getReader(String aReaderName);&lt;br /&gt;
    public Book getBook(String aBookTitle);&lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Потом его реализация (и на ней остановимся поподробнее):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
 @Stateless&lt;br /&gt;
 public class LibraryBean implements Library {&lt;br /&gt;
    @PersistenceContext&lt;br /&gt;
    private EntityManager _entityManager;&lt;br /&gt;
    public Book addBook(Book aBook) {&lt;br /&gt;
       _entityManager.persist(aBook);&lt;br /&gt;
       return aBook;&lt;br /&gt;
    }&lt;br /&gt;
    public Reader addReader(Reader aReader) {&lt;br /&gt;
       _entityManager.persist(aReader);&lt;br /&gt;
       return aReader;&lt;br /&gt;
    }&lt;br /&gt;
    public void giveBook(String aReaderName, Book aBook) {&lt;br /&gt;
       getReader(aReaderName).Book = aBook;&lt;br /&gt;
    }&lt;br /&gt;
    public void returnBook(String aReaderName) {&lt;br /&gt;
       getReader(aReaderName).Book = null;&lt;br /&gt;
    }&lt;br /&gt;
    public Reader getReader(String aReaderName) {&lt;br /&gt;
        return (Reader) _entityManager.createQuery(&lt;br /&gt;
             “SELECT r FROM Reader AS r WHERE r.Name = :name”).&lt;br /&gt;
             setParameter(“name”, aReaderName).getSingleResult();&lt;br /&gt;
    }&lt;br /&gt;
    public Book getBook(String aBookTitle) {&lt;br /&gt;
       return (Book) _entityManager.createQuery(&lt;br /&gt;
             “SELECT b FROM Book AS b WHERE b.Title = :title”).&lt;br /&gt;
             setParameter(“title”, aBookTitle).getSingleResult();&lt;br /&gt;
    }&lt;br /&gt;
 }&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Тут есть два интересных момента. Первый — '''@PersistenceContext'''. Это так называемый контекст сохранения объектов, в котором обретается все то, что должно «жить» между перезапусками приложения (то есть записываемое в БД). Он нигде не инициализируется явно, так как эту работу берет на себя JPA.&lt;br /&gt;
&lt;br /&gt;
У этого контекста есть несколько (не так много, как могло бы быть) методов, которые позволяют сохранять ('''persist''') и удалять ('''remove''') объекты. Но нет метода, который позволял бы обновлять объекты… Почему? Посмотрим более внимательно на метод '''giveBook''', который «выдает книгу» читателю. Мы просто извлекаем читателя, присваиваем ему ссылку на книгу… и все. Контекст сохранения сам обновит объект в БД. Не правда ли, просто?&lt;br /&gt;
&lt;br /&gt;
И последний интересный момент: запросы. Несмотря на то, что они очень похожи на ''SQL'', это совсем не ''SQL''. Это ''JPQL'' (Java Persistence Query Language), который оперирует не столбцами, но объектами. Например, предположим на минутку, что разные экземпляры одной и той же книги могут читать сразу несколько читателей (связь '''OneToMany'''). Тогда найти всех людей, которые держат книгу с определенным названием, можно так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
  SELECT r FROM Reader AS r WHERE r.Book.Title=:title&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
На ''SQL'' нам пришлось бы выполнить '''JOIN''', учесть внешние ключи и так далее. Здесь все это будет сделано автоматически.&lt;br /&gt;
&lt;br /&gt;
Мало того, контекст заботится и о транзакциях. Он сам открываеттранзакцию перед входом в методы и сам же закрывает ее после выхода. Естественно, используя JTA (Java Transaction API), это можно контролировать вручную, если есть необходимость.&lt;br /&gt;
&lt;br /&gt;
=== Собираем, запускаем ===&lt;br /&gt;
&lt;br /&gt;
Собрать jar-файл, чтобы положить его в ''JBoss'', можно аналогичным образом: скомпилировать, собрать jar и поместить его в каталог '''deploy'''. Но при этом нужно сделать пару вещей, которые позволят JPA автомтически создать структуру таблиц при запуске бина. Для этого необхо димо перейти в каталог '''jboss/server/default/deploy/ejb3.deployer/META-INF''' и в файле '''persistence.properties''' убрать комментарий (решетку) перед строкой&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
 hibernate.hbm2ddl.auto=create.&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
После чего перезапустить ''JBoss'', скинуть jar… бины встали на место. Попробуем простенький клиент для проверки. Основной код остался тем же, что и у клиента калькулятора, поменялся только метод '''start'''.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
 private void start() throws NamingException {&lt;br /&gt;
        _library = (Library) getInitialContext().&lt;br /&gt;
               lookup(“LibraryBean/remote”);&lt;br /&gt;
        Book book = new Book();&lt;br /&gt;
        book.Title = “Linux strikes back”;&lt;br /&gt;
        book.Author = “Community org.”;&lt;br /&gt;
        book = _library.addBook(book);&lt;br /&gt;
        Reader reader = new Reader();&lt;br /&gt;
        reader.Name = “Hacker I.A.”;&lt;br /&gt;
        _library.addReader(reader);&lt;br /&gt;
        _library.giveBook(“Hacker I.A.”, book);&lt;br /&gt;
        reader = _library.getReader(“Hacker I.A.”);&lt;br /&gt;
        System.out.println(“У читателя \”” + reader.Name + “\” “ +&lt;br /&gt;
               “есть книга \”” + reader.Book.Title + “\””);&lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Запустите его и убедитесь, что все работает, как надо.&lt;br /&gt;
&lt;br /&gt;
=== Вместо заключения ===&lt;br /&gt;
&lt;br /&gt;
Заключения тут не получится. Мы рассмотрели только камень на вершине айсберга, который называется EJB3. Чтобы только перечислить, что же оно может, нужно раз в 5-10 больше места. А чтобы разобраться, нужно потратить не одну неделю. Поэтому хочется просто отметить, что, в отличие от стандарта EJB2, который использовать очень и очень трудно, EJB3 использовать можно и нужно. Есть надежда, что скоро все основные сервера приложений будут поддерживать EJB3 в полном объеме, что позволит быстро и просто разрабатывать сложные, надежные и производительные приложения, рассчитанные на работу в системах уровня предприятия. Если вы заинтересовались данной темой, и хотите узнать про EJB3 побольше, обратите внимание на врезку '''Литература'''. Удачного освоения! '''LXF'''&lt;br /&gt;
&lt;br /&gt;
=== Литература ===&lt;br /&gt;
&lt;br /&gt;
К сожалению, про EJB3 пока очень мало что написано на русском языке. Но на английском есть несколько книг, из которых хочется порекомендовать следующие:&lt;br /&gt;
&lt;br /&gt;
* Raghu R. Kodali, Jonathan R. Wetherbee, Peter Zadrozny. Beginning EJB 3 Application Development: From Novice to Professional (ISBN 1590596714).&lt;br /&gt;
* Mike Keith, Merrick Schincariol. Pro EJB 3: Java Persistence API (ISBN 1590596455).&lt;br /&gt;
&lt;br /&gt;
Книги написаны приличным языком, со знанием дела (авторы участвовали в разработке стандартов и работают в компаниях, которые сами используют EJB3), читать их достаточно легко.&lt;br /&gt;
&lt;br /&gt;
Ну, и, конечно, нельзя не указать на сами стандарты:&lt;br /&gt;
* http://java.sun.com/products/ejb/&lt;br /&gt;
* http://java.sun.com/products/ejb/docs.html&lt;br /&gt;
* http://java.sun.com/javaee/technologies/persistence.jsp&lt;/div&gt;</summary>
		<author><name>Dionysius</name></author>	</entry>

	<entry>
		<id>http://wiki.linuxformat.ru/wiki/%D0%A4%D0%B0%D0%B9%D0%BB:JavaEE_LXF99.tar.gz</id>
		<title>Файл:JavaEE LXF99.tar.gz</title>
		<link rel="alternate" type="text/html" href="http://wiki.linuxformat.ru/wiki/%D0%A4%D0%B0%D0%B9%D0%BB:JavaEE_LXF99.tar.gz"/>
				<updated>2009-01-01T07:43:37Z</updated>
		
		<summary type="html">&lt;p&gt;Dionysius: Код урока JavaEE LXF99&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Код урока JavaEE LXF99&lt;/div&gt;</summary>
		<author><name>Dionysius</name></author>	</entry>

	<entry>
		<id>http://wiki.linuxformat.ru/wiki/LXF98:Java_EE</id>
		<title>LXF98:Java EE</title>
		<link rel="alternate" type="text/html" href="http://wiki.linuxformat.ru/wiki/LXF98:Java_EE"/>
				<updated>2009-01-01T07:40:17Z</updated>
		
		<summary type="html">&lt;p&gt;Dionysius: /* &amp;lt;font color=darkred&amp;gt;Struts&amp;lt;/font&amp;gt;, великий и ужасный */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Цикл/Java EE}}&lt;br /&gt;
==&amp;lt;font color=darkred&amp;gt;Struts&amp;lt;/font&amp;gt;, великий и ужасный==&lt;br /&gt;
&lt;br /&gt;
[[Media:JavaEE_LXF98.tar.gz‎|Скачать исходный код примера]]&lt;br /&gt;
&lt;br /&gt;
''&amp;lt;font color=darkred&amp;gt;'''ЧАСТЬ 10'''&amp;lt;/font&amp;gt; Компьютеры были придуманы, чтобы избавить человечество от рутины – так зачем делать вручную то, что можно сгенерировать автоматически? '''Александр Бабаев''' покажет, как Struts позволяет избежать монотонного кодирования приложений J2EE.''&lt;br /&gt;
&lt;br /&gt;
Вручную делать простые вещи хорошо: быстро, просто, понятно. Но что делать, если нужно так же быстро и просто создать нечто большое? Сайт-портал, например? Не тот портал, который Яндекс, а корпоративный – где интегрирована система хранения документов, информационная система, наша адресная книга?&lt;br /&gt;
&lt;br /&gt;
В PHP в этом случае приходит на помощь ''CMS''. Сладкие слова, которые обещают «в два клика» сделать вам все что угодно. Маркетинг, конечно, страшная сила, но почему так много ''CMS'' на PHP, и нет на ''Java''?&lt;br /&gt;
&lt;br /&gt;
Возможно, просто потому что не нужно. Место ''CMS'' в ''Java'' занимают разнообразные инструментарии разработчика, которые помогают обходить сложные и рутинные работы. В результате можно небольшими силами сделать систему, по сложности намного превосходящую то, что можно сделать «вручную».&lt;br /&gt;
&lt;br /&gt;
===Что включается в Struts?===&lt;br /&gt;
&lt;br /&gt;
''Struts'' не изобретает велосипедов. В его основе лежит шаблон «MODEl-View-Controller», который мы рассматривали в [[LXF92:Java EE|LXF92]], но с его помощью проще создать грамотное приложение, так как четко определены задачи по его созданию; проще разобраться, что необходимо написать для получения результата.&lt;br /&gt;
&lt;br /&gt;
Итак, ''Struts'' (будем рассматривать более простую, первую версию) содержит:&lt;br /&gt;
&lt;br /&gt;
*''API'' для создания обработчика запросов (менеджер, распределяющий запросы по действиям) и для создания самих действий (&amp;lt;font color=darkred&amp;gt;Actions&amp;lt;/font&amp;gt;).&lt;br /&gt;
*''API'' для создания обработчиков форм.&lt;br /&gt;
*''API'' для работы с проверкой корректности заполнения (валидации) форм.&lt;br /&gt;
*''Tiles''. Расширение для создания модульных страниц (что-то «вроде SSI»).&lt;br /&gt;
*''JSP-taglib'', библиотека ''JPS''-тэгов для упрощения написания JSP-страниц.&lt;br /&gt;
*''XML''-конфигурационные файлы, для простой и быстрой настройки всего вышеперечисленного и связи его друг с другом.&lt;br /&gt;
&lt;br /&gt;
Все это в предыдущих статьях мы делали вручную. Теперь настало время проделать то же самое более «технологично».&lt;br /&gt;
&lt;br /&gt;
===Как этим пользоваться?===&lt;br /&gt;
&lt;br /&gt;
Во-первых, библиотеку нужно скачать. Это можно сделать со странички http://struts.apache.org/download.cgi#struts138. После чего следует распаковать полученный файл и вытащить оттуда все JAR-архивы.&lt;br /&gt;
&lt;br /&gt;
В качестве примера, создадим уже знакомую телефонную книгу. Сперва каталог; в нем, как всегда, организуем подкаталоги для исходных текстов, скомпилированного кода, библиотек и JSP-файлов. Получится что-то такое:&lt;br /&gt;
&lt;br /&gt;
*'''libs'''&lt;br /&gt;
**''antlr-2.7.2.jar''&lt;br /&gt;
**''bsf-2.3.0.jar''&lt;br /&gt;
**''commons-beanutils-1.7.0.jar''&lt;br /&gt;
**''commonschain-1.1.jar''&lt;br /&gt;
**''commons-digester-1.8.jar''&lt;br /&gt;
**''commons-fileipload-1.1.1.jar''&lt;br /&gt;
**''commons-io-1.1.jar''&lt;br /&gt;
**''commons-logging-1.0.4.jar''&lt;br /&gt;
**''commons-validator-1.3.1.jar''&lt;br /&gt;
**''jstl-1.0.2.jar''&lt;br /&gt;
**''oro-2.0.8.jar''&lt;br /&gt;
**''standart-1.0.2.jar''&lt;br /&gt;
**''struts-core-1.3.8.jar''&lt;br /&gt;
**''struts-el-1.3.8.jar''&lt;br /&gt;
**''struts-extras-1.3.8.jar''&lt;br /&gt;
**''struts-faces-1.3.8.jar''&lt;br /&gt;
**''struts-mailreader-dao-1.3.8.jar''&lt;br /&gt;
**''struts-scripting-1.3.8.jar''&lt;br /&gt;
**''struts-taglib-1.3.8.jar''&lt;br /&gt;
**''struts-tiles-1.3.8.jar''&lt;br /&gt;
*'''out'''&lt;br /&gt;
*'''src'''&lt;br /&gt;
**''MessageResources_en.properties''&lt;br /&gt;
**''MessageResources_ru.properties''&lt;br /&gt;
**'''ru'''&lt;br /&gt;
*'''web'''&lt;br /&gt;
**''index.jsp''&lt;br /&gt;
**'''pages'''&lt;br /&gt;
**'''WEB-INF'''&lt;br /&gt;
&lt;br /&gt;
Затем в каталог библиотек нужно положить JAR-файлы ''Struts''. Готово? Тогда можно приступать к кодированию.&lt;br /&gt;
&lt;br /&gt;
Когда запрос приходит в сервлет, он первым делом попадает в ''Struts'', который перенаправляет его в менеджер запросов (&amp;lt;font color=darkred&amp;gt;ActionServet&amp;lt;/font&amp;gt;) и далее в нужное действие (&amp;lt;font color=darkred&amp;gt;Action&amp;lt;/font&amp;gt;). Это происходит примерно так:&lt;br /&gt;
&lt;br /&gt;
Как видно, схема здорово напоминает примененную нами при создании адресной книги. Зачем тогда ''Struts''? А затем, чтобы не писать много-много однотипного кода, который повторяется из проекта в проект.&lt;br /&gt;
&lt;br /&gt;
===Конфигурационные файлы===&lt;br /&gt;
&lt;br /&gt;
Вначале научимся запускать ''Struts''. Для этого нужно перенаправить все запросы сервлету-обработчику и написать файл конфигурации. Вот&lt;br /&gt;
простой дескриптор для простого ''Struts''-приложения:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
  &amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
  &amp;lt;web-app&amp;gt;&lt;br /&gt;
    &amp;lt;servlet&amp;gt;&lt;br /&gt;
        &amp;lt;servlet-name&amp;gt;action&amp;lt;/servlet-name&amp;gt;&lt;br /&gt;
        &amp;lt;servlet-class&amp;gt;org.apache.struts.action.ActionServlet&amp;lt;/servlet-class&amp;gt;&lt;br /&gt;
        &amp;lt;init-param&amp;gt;&lt;br /&gt;
           &amp;lt;param-name&amp;gt;config&amp;lt;/param-name&amp;gt;&lt;br /&gt;
           &amp;lt;param-value&amp;gt;/WEB-INF/struts-config.xml&amp;lt;/param-value&amp;gt;&lt;br /&gt;
        &amp;lt;/init-param&amp;gt;&lt;br /&gt;
    &amp;lt;/servlet&amp;gt;&lt;br /&gt;
    &amp;lt;servlet-mapping&amp;gt;&lt;br /&gt;
        &amp;lt;servlet-name&amp;gt;action&amp;lt;/servlet-name&amp;gt;&lt;br /&gt;
        &amp;lt;url-pattern&amp;gt;*.do&amp;lt;/url-pattern&amp;gt;&lt;br /&gt;
    &amp;lt;/servlet-mapping&amp;gt;&lt;br /&gt;
  &amp;lt;/web-app&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Видно, что все запросы &amp;lt;font color=darkred&amp;gt;*.do&amp;lt;/font&amp;gt; передаются сервлету &amp;lt;font color=darkred&amp;gt;action&amp;lt;/font&amp;gt;, обрабатываемому классом &amp;lt;font color=darkred&amp;gt;ActionServlet&amp;lt;/font&amp;gt;. Это стандартный класс Struts, который перенаправляет запросы в действия. Ему передается конфигурационный файл '''struts-config.xml'''. Вот он:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
   &amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; ?&amp;gt;&lt;br /&gt;
  &amp;lt;struts-config&amp;gt;&lt;br /&gt;
    &amp;lt;form-beans&amp;gt;&lt;br /&gt;
        &amp;lt;form-bean name=&amp;quot;addForm&amp;quot; type=&amp;quot;org.apache.struts.validator.DynaValidatorForm&amp;quot;&amp;gt;&lt;br /&gt;
           &amp;lt;form-property name=&amp;quot;name&amp;quot; type=&amp;quot;java.lang.String&amp;quot; initial=&amp;quot;Name&amp;quot;/&amp;gt;&lt;br /&gt;
           &amp;lt;form-property name=&amp;quot;phone&amp;quot; type=&amp;quot;java.lang.String&amp;quot; initial=&amp;quot;1234567&amp;quot;/&amp;gt;&lt;br /&gt;
          &amp;lt;form-property name=&amp;quot;age&amp;quot; type=&amp;quot;java.lang.Integer&amp;quot; initial=&amp;quot;20&amp;quot;/&amp;gt;&lt;br /&gt;
          &amp;lt;form-property name=&amp;quot;comment&amp;quot; type=&amp;quot;java.lang.String&amp;quot; initial=&amp;quot;NoComment&amp;quot;/&amp;gt;&lt;br /&gt;
       &amp;lt;/form-bean&amp;gt;&lt;br /&gt;
      &amp;lt;action-mappings&amp;gt;&lt;br /&gt;
       &amp;lt;action path=&amp;quot;/add&amp;quot; name=&amp;quot;addForm&amp;quot; validate=&amp;quot;true&amp;quot; type=&amp;quot;ru.linuxformat.actions.Add&amp;quot;&amp;gt;&lt;br /&gt;
          &amp;lt;forward name=&amp;quot;form&amp;quot; path=&amp;quot;/pages/Add.jsp&amp;quot;/&amp;gt;&lt;br /&gt;
          &amp;lt;forward name=&amp;quot;done&amp;quot; path=&amp;quot;/list.do&amp;quot;/&amp;gt;&lt;br /&gt;
       &amp;lt;/action&amp;gt;&lt;br /&gt;
       &amp;lt;action path=&amp;quot;/list&amp;quot; type=&amp;quot;ru.linuxformat.actions.ShowAll&amp;quot;&amp;gt;&lt;br /&gt;
          &amp;lt;forward name=&amp;quot;ok&amp;quot; path=&amp;quot;/pages/List.jsp&amp;quot;/&amp;gt;&lt;br /&gt;
       &amp;lt;/action&amp;gt;&lt;br /&gt;
   &amp;lt;/action-mappings&amp;gt;&lt;br /&gt;
   &amp;lt;message-resources parameter=&amp;quot;MessageResources&amp;quot;/&amp;gt;&lt;br /&gt;
   &amp;lt;plug-in className=&amp;quot;org.apache.struts.validator.ValidatorPlugIn&amp;quot;&amp;gt;&lt;br /&gt;
       &amp;lt;set-property property=&amp;quot;pathnames&amp;quot; value=&amp;quot;/org/apache/struts/validator/validator-rules.xml,/WEB-INF/validation.xml&amp;quot;/&amp;gt;&lt;br /&gt;
   &amp;lt;/plug-in&amp;gt;&lt;br /&gt;
 &amp;lt;/struts-config&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
В данном файле описана форма добавления контакта (&amp;lt;font color=darkred&amp;gt;form-bean&amp;lt;/font&amp;gt;), с использованием стандартного класса формы с поддержкой автоматической проверки полей (&amp;lt;font color=darkred&amp;gt;DynaValidatorForm&amp;lt;/font&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
После этого описано, какие запросы в какие классы перенаправляются.&lt;br /&gt;
&amp;lt;source lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;action path=&amp;quot;/list&amp;quot; type=&amp;quot;ru.linuxformat.actions.ShowAll&amp;quot;&amp;gt;&lt;br /&gt;
   &amp;lt;forward name=&amp;quot;ok&amp;quot; path=&amp;quot;/pages/List.jsp&amp;quot;/&amp;gt;&lt;br /&gt;
 &amp;lt;/action&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
В этом примере запрос &amp;lt;font color=darkred&amp;gt;/list.do&amp;lt;/font&amp;gt; пойдет в класс &amp;lt;font color=darkred&amp;gt;ShowAll.Forward&amp;lt;/font&amp;gt; используется внутри действия, чтобы упростить перенаправление вывода. Дальше будет понятно, как.&lt;br /&gt;
&lt;br /&gt;
После описания действий все становится совсем просто.Описывается файл, откуда будут браться локализованные строки, и&lt;br /&gt;
подключается модуль, который обеспечивает простую и мощную валидацию (проверку) форм.&lt;br /&gt;
&lt;br /&gt;
===Действия===&lt;br /&gt;
&lt;br /&gt;
Перейдем к классам. Все они должны быть унаследованы от класса &amp;lt;font color=darkred&amp;gt;org.apache.struts.action.Action&amp;lt;/font&amp;gt;. При этом в простейшем случае нужно переопределить только один метод, &amp;lt;font color=darkred&amp;gt;execute(…)&amp;lt;/font&amp;gt;. Например, вот действие, которое показывает список контактов:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
 public class ShowAll extends Action {&lt;br /&gt;
   public ActionForward execute(ActionMapping aActionMapping,&lt;br /&gt;
                        ActionForm aActionForm,&lt;br /&gt;
                        HttpServletRequest aHttpServletRequest,&lt;br /&gt;
                        HttpServletResponse aHttpServletResponse)&lt;br /&gt;
                                            throws Exception {&lt;br /&gt;
      aHttpServletRequest.setAttribute(&amp;quot;contacts&amp;quot;,&lt;br /&gt;
             Contacter.getInstance().getContactsSortedByName());&lt;br /&gt;
      return aActionMapping.findForward(&amp;quot;ok&amp;quot;);&lt;br /&gt;
   }&lt;br /&gt;
 }&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
В этом действии в атрибут запроса кладется список всех контактов, после чего вызывается форвард &amp;lt;font color=darkred&amp;gt;&amp;quot;ok&amp;quot;&amp;lt;/font&amp;gt; – именно он был описан чуть выше.&lt;br /&gt;
&amp;lt;source lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;action path=&amp;quot;/list&amp;quot; type=&amp;quot;ru.linuxformat.actions.ShowAll&amp;quot;&amp;gt;&lt;br /&gt;
   &amp;lt;forward name=&amp;quot;ok&amp;quot; path=&amp;quot;/pages/List.jsp&amp;quot;/&amp;gt;&lt;br /&gt;
 &amp;lt;/action&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Из описания видно, что форвард перенаправляет обработку запроса в '''List.jsp''. Посмотрим, что в нем написано:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;%@ page pageEncoding=&amp;quot;UTF-8&amp;quot; language=&amp;quot;java&amp;quot; contentType=&amp;quot;text/&lt;br /&gt;
 html; utf-8&amp;quot; %&amp;gt;&lt;br /&gt;
 &amp;lt;%@ taglib uri=&amp;quot;http://struts.apache.org/tags-html&amp;quot; prefix=&amp;quot;html&amp;quot; %&amp;gt;&lt;br /&gt;
 &amp;lt;%@ taglib uri=&amp;quot;http://struts.apache.org/tags-bean&amp;quot; prefix=&amp;quot;bean&amp;quot; %&amp;gt;&lt;br /&gt;
 &amp;lt;%@ taglib uri=&amp;quot;http://struts.apache.org/tags-logic&amp;quot; prefix=&amp;quot;logic&amp;quot; %&amp;gt;&lt;br /&gt;
 &amp;lt;html:html&amp;gt;&lt;br /&gt;
   &amp;lt;head&amp;gt;&lt;br /&gt;
      …&lt;br /&gt;
   &amp;lt;/head&amp;gt;&lt;br /&gt;
   &amp;lt;body&amp;gt;&lt;br /&gt;
      …&lt;br /&gt;
   &amp;lt;table border=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
          &amp;lt;td&amp;gt;&amp;lt;bean:message key=&amp;quot;AddressBook.list.name&amp;quot;/&amp;gt;&amp;lt;/td&amp;gt;&lt;br /&gt;
          &amp;lt;td&amp;gt;&amp;lt;bean:message key=&amp;quot;AddressBook.list.phone&amp;quot;/&amp;gt;&amp;lt;/td&amp;gt;&lt;br /&gt;
          &amp;lt;td&amp;gt;&amp;lt;bean:message key=&amp;quot;AddressBook.list.comment&amp;quot;/&amp;gt;&amp;lt;/td&amp;gt;&lt;br /&gt;
          &amp;lt;td&amp;gt;&amp;lt;bean:message key=&amp;quot;AddressBook.list.age&amp;quot;/&amp;gt;&amp;lt;/td&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
   &amp;lt;logic:iterate id=&amp;quot;contact&amp;quot; type=&amp;quot;ru.linuxformat.Contact&amp;quot;&lt;br /&gt;
 name=&amp;quot;contacts&amp;quot; scope=&amp;quot;request&amp;quot;&amp;gt;&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
          &amp;lt;td&amp;gt;&amp;lt;%=contact.getName()%&amp;gt;&amp;lt;/td&amp;gt;&lt;br /&gt;
          &amp;lt;td&amp;gt;&amp;lt;%=contact.getPhone()%&amp;gt;&amp;lt;/td&amp;gt;&lt;br /&gt;
          &amp;lt;td&amp;gt;&amp;lt;%=contact.getComment()%&amp;gt;&amp;lt;/td&amp;gt;&lt;br /&gt;
          &amp;lt;td&amp;gt;&amp;lt;%=contact.getAge()%&amp;gt;&amp;lt;/td&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
   &amp;lt;/logic:iterate&amp;gt;&lt;br /&gt;
   &amp;lt;/table&amp;gt;&lt;br /&gt;
   &amp;lt;/body&amp;gt;&lt;br /&gt;
 &amp;lt;/html:html&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
На многоточия заменены неинтересные куски кода, а интересное – в самом начале листинга (&amp;lt;font color=darkred&amp;gt;taglib&amp;lt;/font&amp;gt;). Это так называемые библиотеки тэгов. Примеры можно видеть здесь же. Скажем, &amp;lt;font color=darkred&amp;gt;&amp;lt;bean:messagekey=&amp;quot;AddressBook.list.name&amp;quot;/&amp;gt;&amp;lt;/font&amp;gt; вставляет локализованную строку, соответствующую данному ключу. А &amp;lt;font color=darkred&amp;gt;&amp;lt;logic:iterate&amp;gt;&amp;lt;/font&amp;gt; умеет итерировать по коллекциям (списки, ассоциативные массивы и так далее). В данном случае мы итерируем по атрибуту запроса &amp;lt;font color=darkred&amp;gt;contacts&amp;lt;/font&amp;gt;, который мы положили туда в действии.&lt;br /&gt;
&lt;br /&gt;
===Формы, проверка корректности форм===&lt;br /&gt;
&lt;br /&gt;
Другая интересная часть – формы. Второе действие, добавление контакта, выглядит следующим образом (приведен только код метода &amp;lt;font color=darkred&amp;gt;execute&amp;lt;/font&amp;gt;):&lt;br /&gt;
&amp;lt;source lang=&amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
       if (aHttpServletRequest.getParameter(&amp;quot;name&amp;quot;) == null) {&lt;br /&gt;
           return aActionMapping.findForward(&amp;quot;form&amp;quot;);&lt;br /&gt;
       } else {&lt;br /&gt;
           DynaActionForm form = (DynaActionForm) aActionForm;&lt;br /&gt;
           Contacter.getInstance().addContact(form.getString(&amp;quot;name&amp;quot;), form.&lt;br /&gt;
 getString(&amp;quot;phone&amp;quot;), form.getString(&amp;quot;comment&amp;quot;), (Integer) form.&lt;br /&gt;
 get(&amp;quot;age&amp;quot;));&lt;br /&gt;
           return aActionMapping.findForward(&amp;quot;done&amp;quot;);&lt;br /&gt;
       }&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Логика очень похожа на ту, что была в предыдущих статьях. Если форма не заполнена, переходим по форварду &amp;lt;font color=darkred&amp;gt;form&amp;lt;/font&amp;gt;, который показывает форму для ввода. Если она заполнена (и валидирована), то контакт добавляется в список, и мы переходим на форвард &amp;lt;font color=darkred&amp;gt;done&amp;lt;/font&amp;gt;. Вот и сама форма (точнее, ее основная часть):&lt;br /&gt;
&amp;lt;source lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;html:form action=&amp;quot;/add&amp;quot; method=&amp;quot;post&amp;quot; onsubmit=&amp;quot;return&lt;br /&gt;
 validateAddForm(this);&amp;quot;&amp;gt;&lt;br /&gt;
       &amp;lt;table&amp;gt;&lt;br /&gt;
           &amp;lt;tr&amp;gt;&lt;br /&gt;
              &amp;lt;td&amp;gt;&amp;lt;bean:message key=&amp;quot;AddressBook.add.name&amp;quot;/&amp;gt;:&amp;lt;/td&amp;gt;&lt;br /&gt;
              &amp;lt;td&amp;gt;&amp;lt;html:text property=&amp;quot;name&amp;quot;/&amp;gt;&amp;lt;/td&amp;gt;&lt;br /&gt;
           &amp;lt;/tr&amp;gt;&lt;br /&gt;
          …&lt;br /&gt;
           &amp;lt;tr&amp;gt;&lt;br /&gt;
              &amp;lt;td colspan=&amp;quot;2&amp;quot;&amp;gt;&amp;lt;html:submit titleKey=&amp;quot;AddressBook.add.&lt;br /&gt;
 submit&amp;quot;/&amp;gt;&amp;lt;/td&amp;gt;&lt;br /&gt;
           &amp;lt;/tr&amp;gt;&lt;br /&gt;
       &amp;lt;/table&amp;gt;&lt;br /&gt;
    &amp;lt;/html:form&amp;gt;&lt;br /&gt;
    &amp;lt;html:javascript formName=&amp;quot;addForm&amp;quot;/&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Тут интересны два момента. Во-первых, используются тэги ''Struts'' (&amp;lt;font color=darkred&amp;gt;html:…&amp;lt;/font&amp;gt;), упрощающие создание компонентов формы. Во-вторых, используется скрипт валидации (&amp;lt;font color=darkred&amp;gt;onsubmit=&amp;quot;…&amp;quot;&amp;lt;/font&amp;gt; и &amp;lt;font color=darkred&amp;gt;&amp;lt;html:Javascript …&amp;gt;&amp;lt;/font&amp;gt;). Он обеспечивает валидацию прямо в браузере, не отсылая запрос на сервер.&lt;br /&gt;
&lt;br /&gt;
Сами правила валидации задаются в файле '''validation.xml'''. Вот как это выглядит:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
  &amp;lt;form-validation&amp;gt;&lt;br /&gt;
    &amp;lt;formset&amp;gt;&lt;br /&gt;
       &amp;lt;form name=&amp;quot;addForm&amp;quot;&amp;gt;&lt;br /&gt;
           &amp;lt;field property=&amp;quot;age&amp;quot; depends=&amp;quot;required,integer,intRange&amp;quot;&amp;gt;&lt;br /&gt;
              &amp;lt;arg key=&amp;quot;AddressBook.add.age&amp;quot;/&amp;gt;&lt;br /&gt;
              &amp;lt;arg position=&amp;quot;1&amp;quot; name=&amp;quot;intRange&amp;quot; key=&amp;quot;10&amp;quot;&lt;br /&gt;
 resource=&amp;quot;false&amp;quot;/&amp;gt;&lt;br /&gt;
              &amp;lt;arg position=&amp;quot;2&amp;quot; name=&amp;quot;intRange&amp;quot; key=&amp;quot;20&amp;quot;&lt;br /&gt;
 resource=&amp;quot;false&amp;quot;/&amp;gt;&lt;br /&gt;
              &amp;lt;var&amp;gt;&amp;lt;var-name&amp;gt;min&amp;lt;/var-name&amp;gt;&amp;lt;var-value&amp;gt;10&amp;lt;/var-value&amp;gt;&amp;lt;/&lt;br /&gt;
 var&amp;gt;&lt;br /&gt;
              &amp;lt;var&amp;gt;&amp;lt;var-name&amp;gt;max&amp;lt;/var-name&amp;gt;&amp;lt;var-value&amp;gt;20&amp;lt;/var-value&amp;gt;&amp;lt;/&lt;br /&gt;
 var&amp;gt;&lt;br /&gt;
           &amp;lt;/field&amp;gt;&lt;br /&gt;
       &amp;lt;/form&amp;gt;&lt;br /&gt;
    &amp;lt;/formset&amp;gt;&lt;br /&gt;
  &amp;lt;/form-validation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Форму я создал в '''struts-config'''. Называться она должна так же. Для поля &amp;lt;font color=darkred&amp;gt;age&amp;lt;/font&amp;gt; задается три правила валидации: &amp;lt;font color=darkred&amp;gt;required&amp;lt;/font&amp;gt;, &amp;lt;font color=darkred&amp;gt;integer&amp;lt;/font&amp;gt;, &amp;lt;font color=darkred&amp;gt;intRange&amp;lt;/font&amp;gt;. Первое говорит, что поле обязательно, второе – что значение должно быть целочисленным, третье правило сообщает, что значение должно лежать в пределах от 10 до 20. В качестве параметров задаются аргументы сообщений, которые будут выводиться при ошибочном заполнении формы (&amp;lt;font color=darkred&amp;gt;arg&amp;lt;/font&amp;gt;), и параметры для правил валидации (&amp;lt;font color=darkred&amp;gt;var&amp;lt;/font&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
===Локализация===&lt;br /&gt;
&lt;br /&gt;
Последняя часть, пока не описанная – локализация. Сообщения хранятся в так называемых properties-файлах, причем если property-файл называется '''MessageResources''', то, например, файл русской локализации должен называться '''MessageResources_ru.properties''', а английской –  '''MessageResources_en.properties'''. Если нужно уточнить – например, английский язык, Америка – то получается так: '''MessageResources_en_US.properties'''.&lt;br /&gt;
&lt;br /&gt;
Структура файлов '''properties''' очень проста. Каждая строка (не пустая и не комментарий) состоит из двух частей, разделенных знаком равенства (&amp;lt;font color=darkred&amp;gt;=&amp;lt;/font&amp;gt;). Слева – ключ, справа – значение этого ключа.&lt;br /&gt;
&lt;br /&gt;
Эти файлы нужно положить в каталог '''src''', и проконтролировать, чтобы они переписались туда же, куда попадают class-файлы. Плюс, для неанглийских&lt;br /&gt;
файлов, их нужно преобразовать в ASCII-формат. Это делается утилитой ''native2ascii'' из поставки JDK. Инструкции по пользованию утилитой можно найти здесь: http://Java.sun.com/Javase/6/docs/technotes/tools/windows/native2ascii.html.&lt;br /&gt;
&lt;br /&gt;
После этого можно использовать в JSP вставки вида &amp;lt;font color=darkred&amp;gt;&amp;lt;bean:message key=&amp;quot;AddressBook.add.name&amp;quot;/&amp;gt;&amp;lt;/font&amp;gt;, вместо которых будет вставлена локализованная строка, соответствующая данному ключу (в примере – &amp;lt;font color=darkred&amp;gt;AddressBook.add.name&amp;lt;/font&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
===Что дальше?===&lt;br /&gt;
&lt;br /&gt;
''Struts'' – великолепная библиотека, позволяющая упростить разработку сложных приложений. Особенно хорошо такого рода библиотеки подходят для задач, в которых много монотонной работы: больших форм, большого количества простых действий, необходимость проверки данных, вводимых в форму, локализация.&lt;br /&gt;
&lt;br /&gt;
Также полезно, что используются стандартные средства: ''JSP'', ''Servlets'', да и сам ''Struts'' – самая распространенная библиотека для такого рода работ. В результате при приеме на работу, например, знание именно ''Struts'' позволяет набрать несколько дополнительных баллов.&lt;br /&gt;
&lt;br /&gt;
Правда, еще больше баллов дает знание ''EJB3''. Но об этом мы поговорим в следующей, заключительной статье.&lt;/div&gt;</summary>
		<author><name>Dionysius</name></author>	</entry>

	<entry>
		<id>http://wiki.linuxformat.ru/wiki/%D0%A4%D0%B0%D0%B9%D0%BB:JavaEE_LXF98.tar.gz</id>
		<title>Файл:JavaEE LXF98.tar.gz</title>
		<link rel="alternate" type="text/html" href="http://wiki.linuxformat.ru/wiki/%D0%A4%D0%B0%D0%B9%D0%BB:JavaEE_LXF98.tar.gz"/>
				<updated>2009-01-01T07:39:31Z</updated>
		
		<summary type="html">&lt;p&gt;Dionysius: Код урока JavaEE LXF98&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Код урока JavaEE LXF98&lt;/div&gt;</summary>
		<author><name>Dionysius</name></author>	</entry>

	<entry>
		<id>http://wiki.linuxformat.ru/wiki/LXF97:Java_EE</id>
		<title>LXF97:Java EE</title>
		<link rel="alternate" type="text/html" href="http://wiki.linuxformat.ru/wiki/LXF97:Java_EE"/>
				<updated>2009-01-01T06:46:19Z</updated>
		
		<summary type="html">&lt;p&gt;Dionysius: /* Почтовый сервис */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Цикл/Java EE}}&lt;br /&gt;
[[Категория:Учебники]]&lt;br /&gt;
==Почтовый сервис==&lt;br /&gt;
&lt;br /&gt;
[[Media:JavaEE_LXF97.tar.gz‎|Скачать исходный код примера]]&lt;br /&gt;
&lt;br /&gt;
: '''ЧАСТЬ 9''' Хотите оснастить свою программу возможностью писать «на деревню дедушке»? '''Александр Бабаев''' знает подходящее средство.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Несмотря на засилье браузера в серии, JEE им не ограничивается. Давайте попробуем посылать письма из Java (ведь каждая хорошая программа должна уметь сообщать разработчикам об ошибках), и сделаем это приложение не браузерным, а «обычным».&lt;br /&gt;
&lt;br /&gt;
===Коротко о почте===&lt;br /&gt;
&lt;br /&gt;
Чтобы работать с почтой, нужно уметь её отправлять и получать. При попытке разобраться в этом вы наткнетесь на следующие буквосочетания (с разными вариациями): '''SMTP, POP, IMAP'''. Рассмотрим кратко, что это такое и как этим пользоваться (а также где почитать поподробнее).&lt;br /&gt;
&lt;br /&gt;
* '''SMTP'''&lt;br /&gt;
При помощи Simple Mail Transfer Protocol (простого протокола передачи почты) почта передается с клиента на сервер. Отправляется то есть. Протокол текстовый, и если есть желание, можно отправлять письма прямо из telnet’а.&lt;br /&gt;
&lt;br /&gt;
* '''POP'''&lt;br /&gt;
Тоже текстовый протокол, но уже не для передачи, а для приема сообщений. Может выдать информацию по почтовому ящику (сколько&lt;br /&gt;
сообщений, какой их размер), загрузить сообщение по номеру и так далее.&lt;br /&gt;
&lt;br /&gt;
* '''IMAP'''&lt;br /&gt;
'''POP''' предполагает, что почта скачивается на клиент и там уже раскладывается по папкам, обрабатывается, группируется. При этом с сервера сообщения стираются. Это не всегда удобно. Как раз для хранения почты на сервере создан протокол '''IMAP'''. Как и предыдущие два, он текстовый. Но с его помощью можно не только получить сообщения, но и создать на сервере папку, переместить письмо куда-нибудь, подписаться на получение изменений (новых писем) в папке, и так далее.&lt;br /&gt;
&lt;br /&gt;
* '''GoogleMail/HotMail/…'''&lt;br /&gt;
Но и это не всё. Протоколы протоколами, но некоторые сервисы работают «по-своему». И если '''GoogleMail''' предоставляет '''POP'''-интерфейс, то '''Hotmail''', например, нет. В таком случае обычно есть какой-то свой, нестандартный протокол.&lt;br /&gt;
&lt;br /&gt;
===Итого===&lt;br /&gt;
&lt;br /&gt;
В итоге получается, что разных протоколов много-много (это не считая вариантов и нюансов, комбинаций которых сотни). И чтобы по-человечески все это обрабатывать, пришлось писать бы огромное количество кода. А потом его отлаживать… Поэтому обычно, рассматривая сетевые приложения, отправку/получение писем обходят стороной. Действительно, зачем? Кому нужно, и так разберется.&lt;br /&gt;
&lt;br /&gt;
Но в Java, как всегда, уже позаботились о том, чтобы упростить жизнь человеку, которому нужно рассылать письма – позаботились на самом высшем уровне (в Sun Microsystems) и достаточно качественно.&lt;br /&gt;
&lt;br /&gt;
===И где волшебная кнопка?===&lt;br /&gt;
&lt;br /&gt;
Есть такая замечательная библиотека, '''JavaMail'''. Она достаточно крупная (224 килобайта только JAR-файл), зато и умеет очень много. А чего не умеет – можно научить, благо архитектура настраиваемая. Давайте посмотрим, как с ней работать.&lt;br /&gt;
&lt;br /&gt;
===Подготовка===&lt;br /&gt;
&lt;br /&gt;
Для начала скачаем саму библиотеку. Страничка продукта находится по адресу http://java.sun.com/products/javamail/; скачивать нужно, как водится, последний релиз (1.4). Также понадобится JavaBeans Activation Framework (JAF), которую можно загрузить со странички рядом: http://java.sun.com/products/javabeans/jaf/index.jsp.&lt;br /&gt;
&lt;br /&gt;
После загрузки и разархивирования, получаем две библиотеки: '''mail.jar''' и '''activation.jar'''. Первая из них поддерживает все возможные протоколы, поэтому размер имеет достаточно внушительный. Если что-то из этого многообразия вам не нужно, можно воспользоваться урезанными версиями, они также содержатся в '''mail.jar'''.&lt;br /&gt;
&lt;br /&gt;
Создадим каталог для проекта ('''QuickMailer'''), в нем заведем подкаталог '''libs''' и положим туда эти два jar-файла. Потом заведем другой подкаталог ('''src'''), для записи исходных текстов.&lt;br /&gt;
&lt;br /&gt;
===Окошки===&lt;br /&gt;
&lt;br /&gt;
Сделаем окошко для отправки сообщения. Оно будет простое, как на рис. 1.&lt;br /&gt;
[[Изображение:LXF97_JAVA1.jpg|Рис. 1. Окно создания и отправки сообщений.]]&lt;br /&gt;
&lt;br /&gt;
Подробно рассказать про то, как создаются формы, не хватит места. Но привести код, создающий такое окошко – запросто.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
 import javax.swing.*;&lt;br /&gt;
 import javax.swing.border.EmptyBorder;&lt;br /&gt;
 import java.awt.*;&lt;br /&gt;
 import java.awt.event.ActionListener;&lt;br /&gt;
 import java.awt.event.ActionEvent;&lt;br /&gt;
 public class QuickMailerForm extends JFrame {&lt;br /&gt;
    private JTextField _fieldTo;&lt;br /&gt;
    private JTextField _fieldSubject;&lt;br /&gt;
    private JEditorPane _message;&lt;br /&gt;
    private JButton _buttonSend;&lt;br /&gt;
    public QuickMailerForm() throws HeadlessException {&lt;br /&gt;
       setTitle(“Быстро Мэйлер”);&lt;br /&gt;
       setDefaultCloseOperation(EXIT_ON_CLOSE);&lt;br /&gt;
       createLayout();&lt;br /&gt;
       createActions();&lt;br /&gt;
       pack();&lt;br /&gt;
       setSize(700, 560);&lt;br /&gt;
       Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();&lt;br /&gt;
       setLocation((int) (screenSize.getWidth() - 700)/2, (int) ((screenSize.getHeight() - 560)/2));&lt;br /&gt;
    }&lt;br /&gt;
 }&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Как видно, класс наследуется от '''JFrame''', это окно приложения. Имеется конструктор, где окну присваивается заголовок, устанавливается размер и положение в середине экрана. Также есть две функции: первая создает компоненты ('''createLayout'''), вторая «вешает» на кнопку Отправить обработчик события, который собирает информацию и вызывает метод отправки почты.&lt;br /&gt;
&lt;br /&gt;
Вот как создаются компоненты формы:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
 private void createLayout() {&lt;br /&gt;
       JPanel labelsPanel = new JPanel(new GridLayout(2, 1));&lt;br /&gt;
       labelsPanel.add(new JLabel(“EMail получателя:”, JLabel.RIGHT));&lt;br /&gt;
       labelsPanel.add(new JLabel(“Тема письма:”, JLabel.RIGHT));&lt;br /&gt;
       JPanel buttonsPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));&lt;br /&gt;
       _buttonSend = new JButton(“Отправить”);&lt;br /&gt;
       buttonsPanel.add(_buttonSend);&lt;br /&gt;
       JPanel fieldsPanel = new JPanel(new GridLayout(2, 1));&lt;br /&gt;
       _fieldTo = new JTextField();&lt;br /&gt;
       _fieldSubject = new JTextField();&lt;br /&gt;
       fieldsPanel.add(_fieldTo);&lt;br /&gt;
       fieldsPanel.add(_fieldSubject);&lt;br /&gt;
       JPanel controlsPanel = new JPanel(new BorderLayout(5, 5));&lt;br /&gt;
       controlsPanel.add(labelsPanel, BorderLayout.WEST);&lt;br /&gt;
       controlsPanel.add(fieldsPanel, BorderLayout.CENTER);&lt;br /&gt;
        JPanel mainPanel = new JPanel(new BorderLayout(5, 5));&lt;br /&gt;
        _message = new JEditorPane(“text/rtf”, “”);&lt;br /&gt;
        mainPanel.add(controlsPanel, BorderLayout.NORTH);&lt;br /&gt;
        mainPanel.add(new JScrollPane(_message), BorderLayout.CENTER);&lt;br /&gt;
        mainPanel.add(buttonsPanel, BorderLayout.SOUTH);&lt;br /&gt;
        mainPanel.setBorder(new EmptyBorder(5, 5, 5, 5));&lt;br /&gt;
        setContentPane(mainPanel);&lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Вкратце, здесь создается несколько панелей, вложенных друг в друга. Чтобы выглядело получше, задаются отступы и межкомпонентные расстояния.&lt;br /&gt;
&lt;br /&gt;
Последний метод – создание обработчика события нажатия на кнопку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
  private void createActions() {&lt;br /&gt;
        _buttonSend.addActionListener(new ActionListener() {&lt;br /&gt;
            public void actionPerformed(ActionEvent e) {&lt;br /&gt;
              try {&lt;br /&gt;
                QuickMailer.sendMessage(“alex@jdnevnik.com”, _fieldTo.getText(), _fieldSubject.getText(),&lt;br /&gt;
                     _message.getDocument().getText(0, _message.getDocument().getLength()));&lt;br /&gt;
              } catch (Exception e1) {&lt;br /&gt;
                  e1.printStackTrace();&lt;br /&gt;
              }&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Тут все достаточно просто. Вытаскиваются параметры письма из полей, после чего вызывается некий метод '''sendMessage''', который мы сейчас и рассмотрим подробнее.&lt;br /&gt;
&lt;br /&gt;
===Собственно отправка сообщения===&lt;br /&gt;
&lt;br /&gt;
Предполагается, что у вас на localhost’е настроен smtp-сервер (у меня стоит ''postfix''), либо есть доступ к какому-то другому (который не требует авторизации: с ней разбираться пока не будем).&lt;br /&gt;
&lt;br /&gt;
Для начала создадим адреса отправителя и получателя:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
 public static void sendMessage(String aFrom, String aTo, String aSubject,&lt;br /&gt;
 String aMessageText) throws Exception {&lt;br /&gt;
    InternetAddress from = new InternetAddress(aFrom, “From”);&lt;br /&gt;
    InternetAddress to = new InternetAddress(aTo, “To”);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Теперь нужно настроить так называемый транспорт, который будет заниматься отправкой сообщения.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
    Properties props = new Properties();&lt;br /&gt;
    props.put(“mail.transport.protocol”, “smtp”);&lt;br /&gt;
    props.put(“mail.smtp.host”, “localhost”);&lt;br /&gt;
    props.put(“mail.smtp.port”, “25”);&lt;br /&gt;
    Session session = Session.getDefaultInstance(props);&lt;br /&gt;
    Transport transport = session.getTransport();&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Теперь – создадим само сообщение.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
    MimeMessage message = new MimeMessage(session);&lt;br /&gt;
    message.setFrom(from);&lt;br /&gt;
    message.setRecipient(Message.RecipientType.TO, to);&lt;br /&gt;
    message.setSubject(aSubject, “utf-8”);&lt;br /&gt;
    message.setContent(aMessageText, “text/plain; charset=utf-8”);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
И, наконец, отошлем письмо.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
   transport.connect();&lt;br /&gt;
   transport.sendMessage(message, new Address[]{to});&lt;br /&gt;
   transport.close();&lt;br /&gt;
 }&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Как можно заметить, все предельно просто и понятно – но исключительно потому, что сам пример простой. Система достаточно мощная, чтобы справиться и с авторизацией, и с сообщениями на разныхязыках, и с вложениями файлов.&lt;br /&gt;
&lt;br /&gt;
===Собираем все вместе===&lt;br /&gt;
&lt;br /&gt;
Осталось только написать метод, который будет все это запускать.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
 public static void main(String[] args) {&lt;br /&gt;
   QuickMailerForm form = new QuickMailerForm();&lt;br /&gt;
   form.setVisible(true);&lt;br /&gt;
 }&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
У меня после написания кода получилось два файла, '''QuickMailer.java''' и '''QuickMailerForm.java'''. Скомпилируем их (выполнив, находясь в каталоге, в котором находится '''src''' и '''libs'''):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
 javac -cp libs/activation.jar:libs/commons-email-1.0.jar:libs/mail.jar -encoding utf-8 -d out src/*.java&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Обратите внимание на часть команды после '''-cp'''. Это указание компилятору, где искать используемые в коде классы, кроме стандартных. Часть команды после '''-d''' определяет каталог, куда будут складываться скомпилированные классы. И, наконец, так как мы создавали файлы в кодировке UTF-8 (в ней представлены исходные тексты на диске), то и в командной строке это нужно указать, иначе будет выбрана кодировка по умолчанию, а это не всегда верно.&lt;br /&gt;
&lt;br /&gt;
Запустим:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
 cd out&lt;br /&gt;
 java -cp .:../libs/activation.jar:../libs/commons-email-1.0.jar:../libs/mail.jar&lt;br /&gt;
 QuickMailer&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Можно отправлять письма.&lt;br /&gt;
&lt;br /&gt;
===А получить?===&lt;br /&gt;
&lt;br /&gt;
На новое окошко места уже не хватит. Ограничимся просмотром кода, который нужно написать для того, чтобы получить письмо, например, по протоколу '''POP'''.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
 Properties props = new Properties();&lt;br /&gt;
 Session session = Session.getDefaultInstance(props, null);&lt;br /&gt;
 Store store = session.getStore(“pop3”);&lt;br /&gt;
 store.connect(aHost, aUserName, aPassword);&lt;br /&gt;
 Folder folder = store.getFolder(“INBOX”);&lt;br /&gt;
 folder.open(Folder.READ_ONLY);&lt;br /&gt;
 Message message[] = folder.getMessages();&lt;br /&gt;
 for (int i = 0, n = message.length; i &amp;lt; n; i++) {&lt;br /&gt;
   System.out.println(i + “: “ + message[i].getFrom()[0] + “\t” + message[i].&lt;br /&gt;
 getSubject());&lt;br /&gt;
 }&lt;br /&gt;
 folder.close(false);&lt;br /&gt;
 store.close();&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Данный кусок кода просто выведет список всех писем на сервере.&lt;br /&gt;
&lt;br /&gt;
===Ну, а если не мудрить…===&lt;br /&gt;
&lt;br /&gt;
Есть вариант и попроще. Если программа работает с почтой активно, можно использовать библиотеку наших постоянных друзей из ''apache-commons''. Называется она '''commons-email''', и ее страничка располагается по адресу http://commons.apache.org/email/. Скачав библиотеку, положим её в '''libs''', к '''mail.jar''' и компании. Теперь попробуем отправить письмо с ее помощью:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
 public static void sendMessageCommonsEMail(String aFrom, String aTo,&lt;br /&gt;
 String aSubject, String aMessageText) throws EmailException {&lt;br /&gt;
    SimpleEmail email = new SimpleEmail();&lt;br /&gt;
    email.setHostName(“localhost”);&lt;br /&gt;
    email.setFrom(aFrom, “From”);&lt;br /&gt;
    email.addTo(aTo, “To”);&lt;br /&gt;
    email.setSubject(aSubject);&lt;br /&gt;
    email.setMsg(aMessageText);&lt;br /&gt;
    email.send();&lt;br /&gt;
 }&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Насколько все проще и понятнее сразу стало! А если нужно файл приложить? Да пожалуйста:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
 public static void sendMessageWithAttachment(String aFrom, String aTo,&lt;br /&gt;
 String aSubject, String aMessageText) throws EmailException {&lt;br /&gt;
    EmailAttachment attachment = new EmailAttachment();&lt;br /&gt;
    attachment.setPath(“attachments/attachment.zip”);&lt;br /&gt;
    attachment.setDisposition(EmailAttachment.ATTACHMENT);&lt;br /&gt;
    attachment.setDescription(“Файл-приложение к письму”);&lt;br /&gt;
    attachment.setName(“attachment.zip”);&lt;br /&gt;
    MultiPartEmail email = new MultiPartEmail();&lt;br /&gt;
    email.setHostName(“localhost”);&lt;br /&gt;
    email.setFrom(aFrom, “From”);&lt;br /&gt;
    email.addTo(aTo, “To”);&lt;br /&gt;
    email.setSubject(aSubject);&lt;br /&gt;
    email.setMsg(aMessageText);&lt;br /&gt;
    email.attach(attachment);&lt;br /&gt;
    email.send();&lt;br /&gt;
 }&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Здесь тоже ничего сложного нет. Создать приложение в письме можно и используя только '''JavaMail''', но там это получается достаточно непросто, и длиннее раза в три-четыре.&lt;br /&gt;
&lt;br /&gt;
===Про спам===&lt;br /&gt;
&lt;br /&gt;
Конечно, работа с почтой не так проста, как это отражено в статье. Есть и проблема спама (а для программно отправляемых сообщений – проблема того, что оно с большой вероятностью посчитается именно спамом), и проблема корректности. SMTP-протокол, в частности, достаточно старый, и там много неточностей, неявных правил и так далее. В общем, то, что есть '''JavaMail''' – это отлично, и она очень сильно помогает при работе с почтовыми сообщениями, но панацеей тем не менее не является. Все равно нужно представлять себе, как работает протокол, какие заголовки нужно ставить, как обрабатывают сообщения разные почтовые клиенты (чтобы письмо нормально там показывалось, а не крякозябрами) и много чего еще.&lt;br /&gt;
&lt;br /&gt;
Но все же, надеюсь, теперь можно не бояться страшных буквенных сочетаний, связанных с почтой, и спокойно встраивать в прорамму еще одно удобнейшее средство коммуникации: электронные письма. '''LXF'''&lt;/div&gt;</summary>
		<author><name>Dionysius</name></author>	</entry>

	<entry>
		<id>http://wiki.linuxformat.ru/wiki/%D0%A4%D0%B0%D0%B9%D0%BB:JavaEE_LXF97.tar.gz</id>
		<title>Файл:JavaEE LXF97.tar.gz</title>
		<link rel="alternate" type="text/html" href="http://wiki.linuxformat.ru/wiki/%D0%A4%D0%B0%D0%B9%D0%BB:JavaEE_LXF97.tar.gz"/>
				<updated>2009-01-01T06:45:00Z</updated>
		
		<summary type="html">&lt;p&gt;Dionysius: Код урока JavaEE LXF97&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Код урока JavaEE LXF97&lt;/div&gt;</summary>
		<author><name>Dionysius</name></author>	</entry>

	<entry>
		<id>http://wiki.linuxformat.ru/wiki/LXF96:Java_EE</id>
		<title>LXF96:Java EE</title>
		<link rel="alternate" type="text/html" href="http://wiki.linuxformat.ru/wiki/LXF96:Java_EE"/>
				<updated>2009-01-01T06:42:03Z</updated>
		
		<summary type="html">&lt;p&gt;Dionysius: /* Как докричаться? */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Цикл/Java EE}}&lt;br /&gt;
&lt;br /&gt;
== Java Enterprise Edition: Перекличка серверов ==&lt;br /&gt;
&lt;br /&gt;
''ЧАСТЬ 8, Отыскали пару лишних серверов и готовы написать настоящее распределенное приложение? '''Александр Бабаев''' подскажет подходящий способ.''&lt;br /&gt;
&lt;br /&gt;
На этом уроке наше приложение будет усложнено ровно в два раза. Раньше сервер был один (он обрабатывал запросы пользователей и менял список записей телефонной книги), теперь добавим ему напарника. Предположим, что это выделенный сервер, который хранит данные пользователей — имена, пароли, дополнительную информацию, и мы решили связать web-сервер с этим вторым, чтобы, например, можно было по имени пользователя получить дополнительную информацию о нем.&lt;br /&gt;
&lt;br /&gt;
Для удобства ссылок дадим серверам названия. Пускай наш старый называется Нео, а большой сервер с разной информацией — Матрица.&lt;br /&gt;
&lt;br /&gt;
Прикинем, как все устроить. Для тестов все программы (оба сервера) будут запускаться на одном компьютере. Но, поменяв соответствующие IP-адреса (ниже мы разберемся, какие именно), можно будет запускать Нео и Матрицу на разных компьютерах одной и той же подсети. Правда, если между ними есть всякие-разные устройства вроде маршрутизаторов, шлюзов и мостов, тесты могут и не заработать. Это предупреждение! Реализовать более сложную схему взаимодействия тоже можно, но на это нам, к сожалению, не хватит журнального места, которое вполне конечно.&lt;br /&gt;
&lt;br /&gt;
Итак, получается следующая топология сети:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Изображение:LXF96-JavaEE_1.png]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Как докричаться? ===&lt;br /&gt;
&lt;br /&gt;
[[Media:JavaEE_LXF96.tar.gz|Скачать исходный код примера]]&lt;br /&gt;
&lt;br /&gt;
Классический способ связи двух разных программ — через файлы:  одна пишет, другая читает. Например, можно сделать общий файл для записи команд, куда пишет Нео и откуда читает Матрица, и файл ответов, где запись/чтение происходят наоборот.&lt;br /&gt;
&lt;br /&gt;
Способ простой, но плохой: возникает огромное количество проблем, из которых падение производительности — самая маленькая. Рассматривать его не будем.&lt;br /&gt;
&lt;br /&gt;
Другой способ — связь через так называемый «сокет». Сокет — это такая штука, к которой можно присоединиться [англ. socket — «розетка»]. Сервер «открывает» сокет и «слушает» его. Клиент — подключается к серверному сокету, передает/получает данные, отключается. Затем подключается следующий клиент.&lt;br /&gt;
&lt;br /&gt;
Можно представить себе сокет как телефон. Вы получаете номер абонента (открываете сокет), подключаете телефон (теперь вы слушаете сокет), вам звонят — соединяются с вашим сокетом… Ну, вроде бы понятно? Примерно так же соединяются и компьютеры. А чтобы определить, к какому конкретно сокету присоединяться, нужно знать адрес IP-компьютера и номер порта, на котором открыт сокет. Номер порта необходим, потому что на одном и том же компьютере может быть запущено несколько «слушающих» программ (серверов), и нужно, чтобы они не мешали друг другу. А клиент, с другой стороны, должен точно указать, какой именно сервер ему нужен.&lt;br /&gt;
&lt;br /&gt;
Есть еще и третий способ, «каналы», но о нем мы поговорим несколько позже.&lt;br /&gt;
&lt;br /&gt;
=== Сокеты ===&lt;br /&gt;
Итак, общая схема работы с сокетами достаточно проста. Сервер создает сокет:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
 ServerSocket serverSocket = new ServerSocket(); &lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Дальше нужно открыть сокет для приема соединений. В Java это делается так:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
 serverSocket.bind(new InetSocketAddress(&amp;quot;localhost&amp;quot;, 10000)); &lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Здесь мы говорим, что сервер будет слушать соединения, в адресе назначения которых указано «localhost» и порт 10000. Сразу отметим: чтобы создать сервер на портах с номерами, меньшими 1024, обычно нужны права суперпользователя.&lt;br /&gt;
&lt;br /&gt;
Далее нужно ловить подключения. Обычно это делается в цикле (получили подключение, обработали, ждем следующее). Например, так:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
while (true) { &lt;br /&gt;
    Socket socket = serverSocket.accept(); &lt;br /&gt;
    int byteReceived = socket.getInputStream().read(); &lt;br /&gt;
    OutputStream outputStream = socket.getOutputStream(); &lt;br /&gt;
    outputStream.write(byteReceived + 1); &lt;br /&gt;
    socket.close(); &lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
serverSocket.accept() ждет подключения, блокируя текущий поток, и как только оно появится, создает так называемый клиентский сокет, продолжая выполнение потока. Я читаю из потока сокета один байт, записываю в сокет его же плюс единицу и закрываю сокет.&lt;br /&gt;
&lt;br /&gt;
Вот так все несложно. А как устроен клиент? Он создает сокет, пишет в него единицу, читает ответ (двойку) и выводит ее на экран. По Jav’овски это выглядит так:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
Socket socket = new Socket(&amp;quot;localhost&amp;quot;, 10000); &lt;br /&gt;
socket.getOutputStream().write(1); &lt;br /&gt;
int result = socket.getInputStream().read(); &lt;br /&gt;
System.out.println(&amp;quot;Result is: &amp;quot; + result); &lt;br /&gt;
socket.close(); &lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Здесь тоже все предельно просто, но тем не менее, работает. То есть, если создать два класса и в main первого поставить код сервера, запустить (он будет «висеть», слушая соединения), в main второго — код клиента, и запустить… Клиент выдаст 2.&lt;br /&gt;
&lt;br /&gt;
=== NIO ===&lt;br /&gt;
Проблемы начинаются, когда соединений больше одного, когда их одновременно больше одного, когда они достаточно длительные, когда нужно принимать/отсылать громадные файлы или протокол работы сложный. Если все делать вручную, как мы сейчас, то получается очень и очень трудоемко: огромное количество потоков (рекомендуется, собственно, по одному на каждый запрос), проблемы отсоединений клиентов, отслеживания этих событий, сериализация (запись в поток и обратно) объектов, и так далее.&lt;br /&gt;
&lt;br /&gt;
Еще один минус блокирующих сокетов в том, что они (простите за тафтологию) блокируют поток в ожидании подключения. А если под ключения нет? Значит, поток заблокирован навсегда. И дело не только в подключениях, но и в записи/чтении в/из сокета: случись что во время этих операций — заблокируют навсегда. Вдобавок при большом количестве соединений/потоков скорость работы сервера уменьшается гораздо быстрее, чем увеличивается количество соединений. То есть имеет место типичная ситуация плохой масштабируемости.&lt;br /&gt;
&lt;br /&gt;
И тут на помощь приходит новый способ обработки соединений — NIO (The new I/O API), хотя и более сложный, но гораздо более масштабируемый. Эта библиотека появилась в Java 1.4, и она позволяет создавать неблокирующие соединения. Рассмотрим тот же сервер и клиент в реализации NIO.. Сначала создадим так называемый «канал»:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
Selector acceptSelector = SelectorProvider.provider().openSelector(); &lt;br /&gt;
ServerSocketChannel socketChannel = ServerSocketChannel.open(); &lt;br /&gt;
socketChannel.configureBlocking(false); &lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Мы не просто создали канал, но еще и объявили его неблокирующим (работа с блокирующими каналами ничуть не лучше работы с блокирующими сокетами). Теперь нужно «объяснить» каналу, какие события нам интересны (присоединение клиента, чтение, запись, отсоединение). Займемся подсоединением клиента:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
socketChannel.socket().bind(new InetSocketAddress(&amp;quot;localhost&amp;quot;,  10000)); &lt;br /&gt;
socketChannel.register(acceptSelector, SelectionKey.OP_ACCEPT); &lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Теперь начинаем волшебный цикл обработки. В цикле проверяем, есть ли интересующие нас события в «канале», и если есть, итерируем по ним, получая клиентский сокет. Дальше с сокетом работаем так же, как было описано выше.&lt;br /&gt;
&amp;lt;source lang=&amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
while (acceptSelector.select() &amp;gt; 0) { &lt;br /&gt;
	Set readyKeys = acceptSelector.selectedKeys(); &lt;br /&gt;
	Iterator i = readyKeys.iterator(); &lt;br /&gt;
&lt;br /&gt;
	while (i.hasNext()) { &lt;br /&gt;
		SelectionKey key = (SelectionKey) i.next(); &lt;br /&gt;
		i.remove(); &lt;br /&gt;
		ServerSocketChannel nextReady = (ServerSocketChannel) key.  channel(); &lt;br /&gt;
		Socket socket = nextReady.accept().socket(); &lt;br /&gt;
		outputInputPlusOne(socket); &lt;br /&gt;
	}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Функция outputInputPlusOne(socket), как мы делали ранее, читает число, увеличивает его на 1 и записывает результат. При этом поток постоянно крутится в цикле, не блокируясь. Это позволяет использовать достаточно интересные методы обработки подключений: например, создавать только один поток для произволь ного количества подключений, и он один будет их все обрабатывать. Плюс, операции чтения и записи тоже можно сделать неблокирующими, и совсем будет сказка… Если бы не сложность. Переход на неблокирующую обработку подключений — увеличение объема программы в три раза. Неблокирующая запись и чтение — еще больше. В результате получается достаточно громоздкая схема. Увы, только так можно добиться хорошей производительности.&lt;br /&gt;
&lt;br /&gt;
=== Минное поле ===&lt;br /&gt;
Но есть замечательная библиотека, где все это уже учтено. Внутри она использует NIO, поэтому отлично масштабируется. Зато наружу выходят очень простые и понятные методы, которые можно использовать, получая превосходный результат. Библиотека называется Apache Mina, она достаточно широко распространена (например, Jetty, который мы рассматривали в первой статье цикла, использует именно ее) и неплохо отлажена. Иными словами, ею можно пользоваться в «промышленных» масштабах, не особо беспокоясь о проблемах.&lt;br /&gt;
Сервер делается так:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
SocketAcceptorConfig acceptorConfig = new SocketAcceptorConfig(); &lt;br /&gt;
acceptorConfig.getFilterChain().addLast(&amp;quot;Serializator&amp;quot;, new ProtocolCodecFilter(new ObjectSerializationCodecFactory())); &lt;br /&gt;
DataServerHandler handler = new DataServerHandler(); &lt;br /&gt;
SocketAcceptor acceptor = new SocketAcceptor(); &lt;br /&gt;
acceptor.bind(new InetSocketAddress(&amp;quot;localhost&amp;quot;, 10000), handler, acceptorConfig); &lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
В первой строчке создается конфигуратор (через него задаются разные параметры подключения), затем в него вставляется фильтр, через который пройдут все данные (фильтр распаковывает объекты Java из потока). В третьей строке создается обработчик событий (о нем чуть ниже), который обрабатывает события Mina, и дальше — инициализируется сервер (опять тот же bind).&lt;br /&gt;
&lt;br /&gt;
Поскольку Mina использует NIO, сервер получается событийный. При подключении клиента вызывается обработчик (Handler), который обслуживает запрос. При поступлении данных — тоже вызывается Handler (уже другой метод). И так далее. Вот как выглядит обработчик — (Handler) в нашем случае:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
public class DataServerHandler extends IoHandlerAdapter { &lt;br /&gt;
  public void messageReceived(IoSession session, Object message) &lt;br /&gt;
  throws Exception { &lt;br /&gt;
     if (message instanceof Integer) { &lt;br /&gt;
         session.write(((Integer) message) + 1); &lt;br /&gt;
         session.close(); &lt;br /&gt;
     } &lt;br /&gt;
  } &lt;br /&gt;
} &lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Мы переопредили только один метод, который отвечает за получе ние данных. Mina работает не с потоками, а с объектами (преобразованием занимается фильтр, который мы вставляли в конфигуратор). Поэтому наша задача упрощается: читаем Integer, прибавляем единицу, записываем обратно, закрываем сессию. Все.&lt;br /&gt;
&lt;br /&gt;
Предыдущий клиент, правда, не подойдет — нужно создавать другой. Но он не сложнее, чем был. Вот его код:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
SocketConnector connector = new SocketConnector(); &lt;br /&gt;
SocketConnectorConfig config = new SocketConnectorConfig(); &lt;br /&gt;
config.getFilterChain().addLast(&amp;quot;Serializator&amp;quot;, new ProtocolCodecFilter(new ObjectSerializationCodecFactory())); &lt;br /&gt;
ConnectFuture connectFuture = connector.connect(new InetSocketAddress(&amp;quot;localhost&amp;quot;, 10000), new DataClientHandler(), config); &lt;br /&gt;
connectFuture.join(); &lt;br /&gt;
IoSession session = connectFuture.getSession(); &lt;br /&gt;
session.write(new Integer(1)); &lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Код клиента — практически точное повторение кода сервера. Даже обработчик есть. Только вместо Acceptor’а создаем Connector. connectFuture — это объект, который позволяет, во-первых, дождаться, пока присоединимся (.join()), а во-вторых, от него получается сессия, куда можно писать всякие объекты (в нашем случае — Integer). Обработчик, как и на сервере, простой:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
public class DataClientHandler extends IoHandlerAdapter { &lt;br /&gt;
  public void messageReceived(IoSession session, Object message) &lt;br /&gt;
  throws Exception { &lt;br /&gt;
    System.out.println(&amp;quot;Result is: &amp;quot; + message.toString()); &lt;br /&gt;
  } &lt;br /&gt;
} &lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Все. Сложность в данном конкретном случае — четыре класса вместо двух. Но это оправданно. Mina позволяет гибко настраивать обработку данных, бегающих между клиентом и сервером: количество потоков, обрабатывающих каналы, и так далее. Все это позволяет создавать простые серверы буквально за минуты, а сложные — за считанные часы или за несколько дней.&lt;br /&gt;
&lt;br /&gt;
=== RMI ===&lt;br /&gt;
Еще один способ соединения двух Java-программ — RMI (Remote Method Invocation, удаленный вызов метода). Это несколько другой класс соединений, который скрывает сокеты и прочую внутреннюю механику от программиста, но требует понимания совершенно других принципов.&lt;br /&gt;
&lt;br /&gt;
Для работы RMI использует так называемый registry. Это специальная служебная программа, которая аккуратно регистрирует классы, желающие быть серверами. Например, там может зарегистрироваться наш сервер. После чего клиент, зная, где находится registry, подключается к нему и просит выдать ссылку на сервер — и получает ссылку на класс.&lt;br /&gt;
&lt;br /&gt;
После этих магических пассов методы сервера можно вызывать, как будто они находятся прямо в клиенте, в той же Java-машине. А RMI сам понимает, что вызван метод сервера, организует передачу/прием данных, преобразования всего из всего и выдает результат. На Рис. 2 приведена схема работы RMI (исключая registry):&lt;br /&gt;
  &lt;br /&gt;
&lt;br /&gt;
[[Изображение:LXF96-JavaEE_2.png]]&lt;br /&gt;
&lt;br /&gt;
При создании класса, который может быть вызван удаленно (схема, когда обращаются непосредственно к методу, а он скрытыми путями вызывается на удаленной машине, называется удаленным вызовом, Remote Procedure Call), генерируются специальные классы-заглушки (stubs). Они-то и делают все «взмахи волшебными палочками». На самом деле, когда клиент выполняет удаленный вызов, вызывается аналогичный серверному метод заглушки. В этом методе параметры сериализуются и через сокет передаются на сервер, где вновь десериализуются, вызывается сервер, а ответ передается таким же образом (через заглушки) клиенту.&lt;br /&gt;
Чтобы RMI заработал, нужно сделать интерфейс для сервера и его реализацию, написать клиент, потом сгенерировать заглушки, все скомпилировать и запустить.&lt;br /&gt;
Интерфейс сервера прост:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
public interface IRMIServer extends Remote { &lt;br /&gt;
  public abstract int increment(int aValue) throws RemoteException; &lt;br /&gt;
} &lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Он должен наследоваться от интерфейса Remote, и все методы, вызываемые удаленно, должны выбрасывать исключение RemoteException. Реализация сервера не менее проста:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
public class RMIServer implements IRMIServer { &lt;br /&gt;
  public RMIServer() throws RemoteException { &lt;br /&gt;
    super(); &lt;br /&gt;
  } &lt;br /&gt;
&lt;br /&gt;
  public int increment(int aValue) throws RemoteException { &lt;br /&gt;
    return aValue + 1; &lt;br /&gt;
  } &lt;br /&gt;
} &lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Реализация должна обязательно иметь конструктор по умолчанию, который выкидывает исключение RemoteException и вызывает super(). Метод реализуется как обычно.&lt;br /&gt;
Регистрация сервера может быть выполнена, например, в методе main того же сервера:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
public static void main(String[] args) throws Exception { &lt;br /&gt;
  System.setSecurityManager(new RMISecurityManager()); &lt;br /&gt;
  RMIServer Server = new RMIServer(); &lt;br /&gt;
  Naming.rebind(&amp;quot;RMIServer&amp;quot; , Server); &lt;br /&gt;
} &lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Здесь устанавливается менеджер безопасности (иначе нам скажут, что нет прав для работы с RMI), создается сервер и регистрируется в registry (rebind). Есть и метод bind, но он выдаст ошибку, если такой сервер уже зарегистрирован. В целях экономии места используем rebind, который проверку на существование такого же сервера не выполняет.&lt;br /&gt;
&lt;br /&gt;
Клиент тоже устанавливает менеджер безопасности, после чего в registry ищет сервер (имя он знает). Как только найдет — просто вызывает метод, как если бы RMI не существовало.&lt;br /&gt;
&amp;lt;source lang=&amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
public class RMIClient { &lt;br /&gt;
    public static void main(String[] args) throws MalformedURLException,NotBoundException, RemoteException { &lt;br /&gt;
      System.setSecurityManager(new RMISecurityManager()); &lt;br /&gt;
      String url = &amp;quot;//localhost/RMIServer&amp;quot;; &lt;br /&gt;
      IRMIServer remoteObject = (IRMIServer) Naming.lookup(url); &lt;br /&gt;
      System.out.println(&amp;quot;Result is &amp;quot; + remoteObject.increment(1)); &lt;br /&gt;
    } &lt;br /&gt;
} &lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Запуск сервера и клиента несколько более сложен, чем обычно. До сих пор мы компилировали и запускали классы «просто так». Теперь создадим заглушки и запустим registry. Это делается так:&lt;br /&gt;
 rmic RMIServer &lt;br /&gt;
&lt;br /&gt;
создаст заглушку сервера (сначала нужно все скомпилировать, а потом выполнить эту команду с полным именем класса сервера). А команда&lt;br /&gt;
 rmiregistry &amp;amp; &lt;br /&gt;
&lt;br /&gt;
запустит RMI Registry с параметрами по умолчанию. Чтобы разрешить менеджеру безопасности делать все что угодно, нужно запустить клиент и сервер с параметром, указывающим на файл политики безопасности. Назовем этот файл open.policy и запишем в него следующее:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
grant { &lt;br /&gt;
  permission java.security.AllPermission; &lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Теперь запустим клиент и сервер:&lt;br /&gt;
 java -Djava.security.policy=open.policy RMIServer &lt;br /&gt;
&lt;br /&gt;
и&lt;br /&gt;
 java -Djava.security.policy=open.policy RMIClient &lt;br /&gt;
&lt;br /&gt;
Вот и все. Как можно заметить, чем проще и понятнее код клиента и сервера, тем больше телодвижений нужно сделать для запуска разных вспомогательных приложений. Это общая закономерность. Пока приложения простые, зачастую удобнее использовать просто сокеты; в более тяжелых случаях (например, для работы клиент-серверных приложений уровня предприятия, тех самых Enterprise Applications) проще один раз запустить что-то вроде registry, а потом пользоваться «благами цивилизации».&lt;br /&gt;
&lt;br /&gt;
Стоит отметить, что RMI, создаваемый «по умолчанию», в нормальных приложениях использовать нельзя. Слишком много ограничений, слишком много неразрешимых проблем (например, если клиент и сервер находятся в разных подсетях, соединение становится почти невозможным; или — невозможно внести в registry-сервер, который запущен не на той же машине, где и registry). Но реализации, которые предоставляют так называемые контейнеры приложений (GlassFish, Sun WebSphere, JBoss и другие) — это именно то, что используется для связи клиентов с сервером. Там это все работает, и работает отлично.&lt;/div&gt;</summary>
		<author><name>Dionysius</name></author>	</entry>

	<entry>
		<id>http://wiki.linuxformat.ru/wiki/%D0%A4%D0%B0%D0%B9%D0%BB:JavaEE_LXF96.tar.gz</id>
		<title>Файл:JavaEE LXF96.tar.gz</title>
		<link rel="alternate" type="text/html" href="http://wiki.linuxformat.ru/wiki/%D0%A4%D0%B0%D0%B9%D0%BB:JavaEE_LXF96.tar.gz"/>
				<updated>2009-01-01T06:40:49Z</updated>
		
		<summary type="html">&lt;p&gt;Dionysius: Код урока JavaEE LXF96&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Код урока JavaEE LXF96&lt;/div&gt;</summary>
		<author><name>Dionysius</name></author>	</entry>

	<entry>
		<id>http://wiki.linuxformat.ru/wiki/LXF95:Java_EE</id>
		<title>LXF95:Java EE</title>
		<link rel="alternate" type="text/html" href="http://wiki.linuxformat.ru/wiki/LXF95:Java_EE"/>
				<updated>2009-01-01T06:37:14Z</updated>
		
		<summary type="html">&lt;p&gt;Dionysius: /* Серверная часть */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Цикл/Java EE}}&lt;br /&gt;
== А я? (кс) ==&lt;br /&gt;
''ЧАСТЬ 6 Заинтригованы ребусом в заголовке? То ли еще будет, когда прочитаете статью — '''Александр Бабаев''' большой мастер загадывать загадки, особенно если речь заходит о Web 2.0.''&lt;br /&gt;
&lt;br /&gt;
Когда я начинал писать эту статью в первый раз, я думал, что&lt;br /&gt;
все достаточно просто. Сел-написал-отдал. Но сразу после&lt;br /&gt;
этого пришлось переделывать одно приложение, которое&lt;br /&gt;
изменило понимание и сложности, и самого Аякса. Полностью.&lt;br /&gt;
В чем же сложность? В несоответствии. С одной стороны, Аякс&lt;br /&gt;
позволяет приблизить логику работы интернет-приложений к обычной,&lt;br /&gt;
«десктопной». С другой стороны — все браузеры таки разные. И это&lt;br /&gt;
последнее «таки» местами убивает наповал. Вы думаете, что браузеров три? IE/Firefox/Opera? Хех. Для правильного приложения их нужно&lt;br /&gt;
протестировать десяток. IE 5.5/6/7, Firefox 1.5/2, Opera 8/9, Safari 1.3/2/&lt;br /&gt;
(а теперь и 3). И у каждого свой нрав.&lt;br /&gt;
&lt;br /&gt;
=== Ладно, что же такое Аякс? ===&lt;br /&gt;
AJAX — технология асинхронного доступа к серверу с использованием&lt;br /&gt;
JavaScript и XML. Представим себе автомобиль. Чтобы заправить его -&lt;br /&gt;
едем на заправку, заправляем, уезжаем. Чтобы поменять фару — едем,&lt;br /&gt;
снимаем, ставим, уезжаем. Вот это — AJAX. А если бы Аякса не было,&lt;br /&gt;
то для каждой такой операции нужно было бы отдавать машину в сервис, а из сервиса возвращалась бы другая, хоть и максимально точная&lt;br /&gt;
копия вашей, с обновлениями (бак заправлен). Асинхронность — это&lt;br /&gt;
вообще что-то вроде дозаправки в воздухе. То есть обновление данных&lt;br /&gt;
без «отрыва от просмотра страницы». Давайте подробнее рассмотрим&lt;br /&gt;
компоненты этой технологии:&lt;br /&gt;
&lt;br /&gt;
==== A ====&lt;br /&gt;
Асинхронность реализуется разными способами. XMLHttpRequest,&lt;br /&gt;
фреймы, Flash. Есть и другие (те же апплеты), но они распространены&lt;br /&gt;
меньше. Второй способ — самый «простой» для человека, знакомого&lt;br /&gt;
с HTML. Создается ма-а-аленький iframe (одна точка) и помещается&lt;br /&gt;
куда-нибудь далеко, чтобы не было видно. Потом при необходимости&lt;br /&gt;
подгрузить что-либо, запрос выполняется как обычно, но с целевым&lt;br /&gt;
фреймом — тем самым, маленьким. Во фрейм загружается страничка,&lt;br /&gt;
на которой обычно есть скрипт, который выполняется и делает свое&lt;br /&gt;
дело (обновляет основной фрейм). Flash — это примерно то же самое,&lt;br /&gt;
только вместо маленького скрытого фрейма используется маленькая&lt;br /&gt;
«флэшка».&lt;br /&gt;
&lt;br /&gt;
Впрочем, обычно оба эти способа используются только в случае,&lt;br /&gt;
если не подходит «основной» — XMLHttpRequest. Это название объекта&lt;br /&gt;
в JavaScript, который может выполнять асинхронные (и синхронные)&lt;br /&gt;
HTTP-запросы. Например:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;var xmlHttpRequestValue = new XMLHttpRequest();&lt;br /&gt;
xmlHttpRequestValue.open(“POST”, “/ajax.jsp?action=update”, true);&lt;br /&gt;
xmlHttpRequestValue.onreadystatechange = processXmlHttpResponse;&lt;br /&gt;
xmlHttpRequestValue.send(“Информация для пересылки серверу”);&amp;lt;/source&amp;gt;&lt;br /&gt;
Все, запрос ушел. Сразу предупрежу, что это не рабочий код, только иллюстрация. Обратите внимание на третью строчку. Выделенное&lt;br /&gt;
жирным — это имя функции, которая будет обрабатывать события,&lt;br /&gt;
генерируемые во время передачи запроса, такие, как «начат прием данных», «закончен прием заголовка», «закончен прием данных». Если мы&lt;br /&gt;
хотим что-либо предпринять по окончании приема данных, эта функция будет выглядеть как-то так:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;java&amp;quot;&amp;gt;function processXmlHttpResponse() {&lt;br /&gt;
if (xmlHttpRequestValue.readyState == 4) {&lt;br /&gt;
…сделать «что-либо»…&lt;br /&gt;
}&lt;br /&gt;
}&amp;lt;/source&amp;gt;&lt;br /&gt;
Число «4» как раз обозначает, что обработка завершена. Функция&lt;br /&gt;
processXmlHttpResponse() может вызываться много раз (даже точно не&lt;br /&gt;
определено, сколько), но как только readyState станет равным 4, мы это&lt;br /&gt;
«поймаем» и обработаем результат.&lt;br /&gt;
&lt;br /&gt;
==== J ====&lt;br /&gt;
JavaScript — объектный язык. То есть создать класс, как в Java, в нем&lt;br /&gt;
нельзя. А вот новый объект — запросто. И сформировать структуру объекта (прописать методы, поля) «на лету» тоже можно.&lt;br /&gt;
&lt;br /&gt;
Чаще всего JavaScript используется как простой способ динамически изменять структуру документа. Например, чтобы поменять цвет&lt;br /&gt;
параграфа, можно выполнить такой код:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;java&amp;quot;&amp;gt;document.getElementById(“paragraph”).style.color = “red”;&amp;lt;/source&amp;gt;&lt;br /&gt;
Параграф после этого покраснеет. Чтобы понимать, как добраться до&lt;br /&gt;
отдельных параграфов, нужно изучить DOM (Document Object Model).&lt;br /&gt;
&lt;br /&gt;
==== X ====&lt;br /&gt;
XML, который попал в AJAX последней буквой, используется (как уже&lt;br /&gt;
упоминалось) далеко не всегда, но часто. Благо, в JavaScript есть возможности по достаточно простому преобразованию чего угодно в XML,&lt;br /&gt;
да и в других языках (не только в Java) библиотеки по работе с этим&lt;br /&gt;
форматом достаточно хорошо развиты.&lt;br /&gt;
&lt;br /&gt;
=== Помощники ===&lt;br /&gt;
Из-за того, что реализации XMLHttpRequest различаются (в паре крупных деталей и, что хуже, в паре десятков мелочей), самому писать&lt;br /&gt;
кросс-браузерную обработку достаточно тяжело и долго. Поэтому&lt;br /&gt;
давайте посмотрим на библиотеки, в состав которых входит «AJAX-подсистема» и которые сделаны либо специально для Java-северной&lt;br /&gt;
составляющей, либо поддерживают ее.&lt;br /&gt;
*; DOJO: Огромная библиотека, которая много чего умеет. Если смотреть только на коммуникативные возможности, то тут тоже все в порядке: можно использовать XMLXttpRequest, фрейм, Flash. Первый вариант предлагается по умолчанию. Чуть ниже будет пример работы с DOJO, а сейчас просто отметим, что с учетом достаточно приличной документации, логичной организации и большого количества модулей (в том числе и для использования разных методов коммуникации), библиотеку можно назвать хорошей.&lt;br /&gt;
*; DWR — Direct Web Remoting: Сделана специально для Java. Достоинство состоит в том, что DWR умеет «публиковать» классы Java в JavaScript. То есть вы пишете класс в Java, настраиваете DWR (при помощи XML-дескриптора), и после запуска сервлета (обработкой запросов от DWR занимается отдельный сервлет) в JavaScript «появляются» методы этого класса, которые можно вызывать, так же, как если бы это были обычные функции JavaScript. Способ удобен, но навязывает определенный способ коммуникации. Подходит, когда не нужно контролировать каждый байт передаваемых данных и если с JavaScript вы знакомы не очень хорошо (хотя все равно его нужно знать, так как кроме коммуникации есть много чего еще).&lt;br /&gt;
*; GWT — Google Web Toolkit: В свое время эта библиотека наделала много шуму. Все дело в принципе ее работы. В общем и целом, если на DWR мы пишем на Java коммуникацию, то на GWT — все приложение. Затем GWT компилирует его в клиентскую часть (HTML + JavaScript) и серверную часть (Java). То есть клиент получается автоматически (не целиком, но близко к тому). GWT берет на себя все заботы и по взаимодействию клиента с сервером, и по созданию интерфейса (этот процесс несколько напоминает Swing), и многое другое. В принципе, достаточно хорошее решение, естественно со своими нюансами. Мы еще посмотрим на него поближе… к концу статьи.&lt;br /&gt;
&lt;br /&gt;
=== Адресная книга с AJAX ===&lt;br /&gt;
Настало время потренироваться. Давайте возьмем нашу адресную&lt;br /&gt;
книгу и сделаем так, чтобы показывалась страничка с табличкой (списком телефонов), а по нажатии на ссылки вместо перехода на новые&lt;br /&gt;
странички менялся сам список: удалялись и добавлялись строки и так&lt;br /&gt;
далее. Использовать будем DOJO. Идеология от этого не пострадает, но&lt;br /&gt;
станет немного проще, короче и понятнее.&lt;br /&gt;
&lt;br /&gt;
==== HTML/JavaScript ====&lt;br /&gt;
Вот строка HTML-таблицы, из которой видно, как будет выводиться&lt;br /&gt;
список телефонов.&lt;br /&gt;
&amp;lt;source lang=&amp;quot;html4strict&amp;quot;&amp;gt;&amp;lt;tr id=”1232323” &amp;gt;&lt;br /&gt;
&amp;lt;td&amp;gt;1232323&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;Vasya Beanov&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&amp;lt;td&amp;gt;…&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&amp;lt;a&lt;br /&gt;
href=”#” on click=”remove(‘1232323’); return false;”&amp;gt;Remove&amp;lt;/a&amp;gt; / &amp;lt;a&lt;br /&gt;
href=”#” on click=”startEdit(‘1232323’); return false;”&amp;gt;Edit&amp;lt;/a&amp;gt;&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&amp;lt;/source&amp;gt;&lt;br /&gt;
Обратите внимание на id=&amp;quot;…&amp;quot;. Это своеобразные метки, которые&lt;br /&gt;
позволят при необходимости найти нужную строку. Теперь рассмотрим JavaScript-код, который выполняется при нажатии на «кнопку» Edit&lt;br /&gt;
(как самую сложную).&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;function startEdit(aPhone) {&lt;br /&gt;
getCell(aPhone, 0).innerHTML = ‘&amp;lt;input type=”text” value =”’ +&lt;br /&gt;
getCell(aPhone, 0).innerHTML + ‘” id =”phone_’ + aPhone + ‘”/&amp;gt;’;&lt;br /&gt;
getCell(aPhone, 1).innerHTML = ‘&amp;lt;input type=”text” value = “’ +&lt;br /&gt;
getCell(aPhone, 1).innerHTML + ‘” id = “name_’ + aPhone + ‘”/&amp;gt;’;&lt;br /&gt;
getCell(aPhone, 2).innerHTML = ‘&amp;lt;input type = “text” value =”’ +&lt;br /&gt;
getCell(aPhone, 2).innerHTML + ‘” id =”comment_’ + aPhone + ‘”/&amp;gt;’;&lt;br /&gt;
getCell(aPhone, 3).innerHTML = ‘&amp;lt;a href = “#” onclick = “submitEdit(\’’&lt;br /&gt;
+ aPhone + ‘\’); return false;”&amp;gt;Save changes&amp;lt;/a&amp;gt;’;&lt;br /&gt;
}&amp;lt;/source&amp;gt;&lt;br /&gt;
Простым русским языком этом можно выразить так: ячейки с текстом заменяются на ячейки с полями ввода.&lt;br /&gt;
&lt;br /&gt;
После редактирования и нажатия на кнопку Save changes данные&lt;br /&gt;
отсылаются на сервер, и в ячейках снова прописывается текст.&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;function submitEdit(aPhone) {&lt;br /&gt;
getCell(aPhone, 0).innerHTML = document.getElementById(‘phone_’ +&lt;br /&gt;
aPhone).value;&lt;br /&gt;
getCell(aPhone, 1).innerHTML = document.getElementById(‘name_’ +&lt;br /&gt;
aPhone).value;&lt;br /&gt;
getCell(aPhone, 2).innerHTML = document.getElementById(‘comment_’&lt;br /&gt;
+ aPhone).value;&lt;br /&gt;
getCell(aPhone, 3).innerHTML = ‘&amp;lt;a href = “#” onclick = “startEdit(\’’ +&lt;br /&gt;
aPhone + ‘\’); return false;”&amp;gt;Edit&amp;lt;/a&amp;gt; &amp;lt;a href = “#” onclick = “remove(\’’&lt;br /&gt;
+ aPhone + ‘\’); return false;”&amp;gt;Remove&amp;lt;/a&amp;gt;’;&lt;br /&gt;
sendAJAXRequest(“action=edit” +&lt;br /&gt;
“&amp;amp;old=” + aPhone +&lt;br /&gt;
“&amp;amp;phone=” + getCell(aPhone, 0).innerHTML +&lt;br /&gt;
“&amp;amp;name=” + getCell(aPhone, 1).innerHTML +&lt;br /&gt;
“&amp;amp;comment=” + getCell(aPhone, 2).innerHTML);&lt;br /&gt;
}&amp;lt;/source&amp;gt;&lt;br /&gt;
Теперь посмотрим, как отсылаются и принимаются данные. Для&lt;br /&gt;
этого у нас есть показательная функция loadTable(), которая загружает табличку с сервера. Остальные функции только отправляют&lt;br /&gt;
данные.&lt;br /&gt;
&amp;lt;source lang=&amp;quot;javascript&amp;quot;&amp;gt;function loadTable() {&lt;br /&gt;
dojo.io.queueBind({&lt;br /&gt;
url : “/ajax?action=loadTable”,&lt;br /&gt;
method : “get”,&lt;br /&gt;
load : function (aType, aData, aEvent) {&lt;br /&gt;
document.getElementById(“table”).innerHTML = aData;&lt;br /&gt;
},&lt;br /&gt;
preventCache : true&lt;br /&gt;
});&lt;br /&gt;
}&amp;lt;/source&amp;gt;&lt;br /&gt;
Обратите внимание на две вещи. Первая — использование dojo.&lt;br /&gt;
queueBind — это местная реализация AJAX. Нам не нужно беспокоиться,&lt;br /&gt;
что будет использоваться «внутри» (хотя контролировать внутренности&lt;br /&gt;
тоже можно). Вторая — параметр load. Это функция, которая вызывается после загрузки данных с сервера (сами данные передаются в параметре aData). sendAJAXRequest выглядит абсолютно так же, только без&lt;br /&gt;
параметра load.&lt;br /&gt;
&lt;br /&gt;
Кстати, в queueBind’е сами параметры обрамлены фигурными скобками. Это как раз и есть объектность JavaScript. Тут создается объект с&lt;br /&gt;
полями url, method, load и preventCache, и уже этот объект передается&lt;br /&gt;
в качестве параметра функции.&lt;br /&gt;
&lt;br /&gt;
==== Серверная часть ====&lt;br /&gt;
А теперь посмотрим, как это все обрабатывать на сервере. Логика&lt;br /&gt;
его работы осталась той же: опять действия. Только вместо выдачи&lt;br /&gt;
большого количества HTML-кода (через шаблоны или напрямую), в&lt;br /&gt;
большинстве случаев не выдается ничего. Вот, например, код действия удаления:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;java&amp;quot;&amp;gt;String phone = aRequest.getParameter(“phone”);&lt;br /&gt;
_recordsBook.removeRecord(phone);&amp;lt;/source&amp;gt;&lt;br /&gt;
Только действие создания таблицы телефонов выдает код таблицы&lt;br /&gt;
(но только ее, а не всю страничку). Остальные методы только получают данные и сохраняют их. Изменение таблицы происходит на клиенте при помощи JavaScript (как — можно посмотреть на код submitEdit&lt;br /&gt;
выше или в [[Media:JavaEE_LXF95.tar.gz‎|полных исходных текстах]] на LXFDVD).&lt;br /&gt;
&lt;br /&gt;
=== GWT ===&lt;br /&gt;
А что делать, если вы вообще ничего не понимаете в JavaScript? Не&lt;br /&gt;
расстраиваться, скачать GWT (он немаленький, но и перспектива&lt;br /&gt;
читать 1000 с лишним страниц талмуда под названием «JavaScript: The&lt;br /&gt;
Definitive Guide» тоже не вдохновляет, ведь правда? К тому же архив&lt;br /&gt;
можно найти и на нашем DVD), установить. А дальше?&lt;br /&gt;
&lt;br /&gt;
Дальше процедура очень похожа на то, что позволяет делать,&lt;br /&gt;
например, Ruby on Rails (или аналогичные каркасы — см. [[LXF95:Akelos]]):&lt;br /&gt;
* Вы создаете некий код. Клиентский, серверный, шаблон странички. При необходимости также можно применять свои CSS-стили.&lt;br /&gt;
* Затем создаете специальный XML-дескриптор. Это файл, который описывает, что нужно запускать, что будет сервером.&lt;br /&gt;
* Запускаете компилятор GWT. Он читает клиентский код, создает HTML-странички (испoльзуя ваши шаблоны) и JavaScript-код, который будет связываться с сервером и выполнять запросы, которые были описаны ранее.&lt;br /&gt;
* Запускаете сервер GWT-приложений.&lt;br /&gt;
При этом вы не пишете ни строчки на JavaScript. HTML и CSS — да,&lt;br /&gt;
приходится, но опять же достаточно немного. Посмотрим подробнее?&lt;br /&gt;
&lt;br /&gt;
==== Структура приложения GWT ====&lt;br /&gt;
Для начала стоит создать HTML-файл, который будет «заготовкой»&lt;br /&gt;
для странички. Вот его код:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;html4strict&amp;quot;&amp;gt;&amp;lt;html&amp;gt;&lt;br /&gt;
&amp;lt;head&amp;gt;&amp;lt;meta name=’gwt:module’ content=’phoneBook.PhoneBook’&amp;gt;&amp;lt;/&lt;br /&gt;
head&amp;gt;&lt;br /&gt;
&amp;lt;body&amp;gt;&lt;br /&gt;
&amp;lt;script language=”javascript” type=”text/javascript” src=”gwt.js”&amp;gt;&amp;lt;/&lt;br /&gt;
script&amp;gt;&lt;br /&gt;
&amp;lt;table&amp;gt;&lt;br /&gt;
&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;Phone: &amp;lt;/td&amp;gt;&amp;lt;td id=”phoneCell”&amp;gt;&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;td&amp;gt;Name: &amp;lt;/td&amp;gt;&amp;lt;td id=”nameCell”&amp;gt;&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;td&amp;gt;Comment: &amp;lt;/td&amp;gt;&amp;lt;td id=”commentCell”&amp;gt;&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;tr&amp;gt;&amp;lt;td colspan=”6” id=”buttonCell”&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;/table&amp;gt;&lt;br /&gt;
&amp;lt;div id=”main”&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/body&amp;gt;&lt;br /&gt;
&amp;lt;/html&amp;gt;&amp;lt;/source&amp;gt;&lt;br /&gt;
Обратите внимание, что ни одной кнопки нет. И таблицы нет. Зато&lt;br /&gt;
есть элементы, которые помечены id, в них-то и будут вставляться компоненты GWT, которые здесь называются виджетами.&lt;br /&gt;
&lt;br /&gt;
==== Виджеты ====&lt;br /&gt;
Сами виджеты будут вставляться уже из «Java-кода». Почему в&lt;br /&gt;
кавычках? Просто это код только пишется на Java, но потом компилируется в JavaScript и выполняется в браузере. Из-за этого есть множество ограничений. Например, нельзя использовать конструкции&lt;br /&gt;
Java 5, такие как новые циклы foreach, аннотации, обобщенное программирование (generics) и другие. Нельзя использовать все классы&lt;br /&gt;
подряд — только те, для которых в GWT есть «реализация» (для java.&lt;br /&gt;
lang.* и java.util.* — есть, и это сильно упрощает дело).&lt;br /&gt;
Код вставки виджетов выглядит примерно следующим образом:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;java&amp;quot;&amp;gt;Button button = new Button(“Add new record”);&lt;br /&gt;
RootPanel.get(“buttonCell”).add(button);&amp;lt;/source&amp;gt;&lt;br /&gt;
Первая строчка создает кнопку, вторая вставляет ее в элемент с id&lt;br /&gt;
«buttonCell».&lt;br /&gt;
&lt;br /&gt;
Список виджетов достаточно велик, и можно также создавать свои&lt;br /&gt;
собственные. Например, для вывода таблицы контактов можно использовать FlexTable.&lt;br /&gt;
&amp;lt;source lang=&amp;quot;java&amp;quot;&amp;gt;FlexTable table = new FlexTable();&amp;lt;/source&amp;gt;&lt;br /&gt;
Этот виджет умеет динамически изменяться, например, вот этак:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;java&amp;quot;&amp;gt;aTable.setText(aRow, aColumn, “Текст в ячейку”);&amp;lt;/source&amp;gt;&lt;br /&gt;
Этот код вставляет в ячейку таблицы текст. Можно также вставить&lt;br /&gt;
туда и другой виджет, например, так:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;java&amp;quot;&amp;gt;aTable.setWidget(aRow, aColumn, new Button(“Edit”));&amp;lt;/source&amp;gt;&lt;br /&gt;
Для работы нужны не только виджеты, но и обработчики событий. Это тоже реализовано достаточно просто: через слушателей (см.&lt;br /&gt;
[[LXF92:Java EE|LXF92]]), как в Swing. Вот как, например, добавляется обработчик&lt;br /&gt;
нажатия на кнопку:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;java&amp;quot;&amp;gt;editButton.addClickListener(new ClickListener() {&lt;br /&gt;
public void onClick(Widget aWidget) {&lt;br /&gt;
код обработчика…&lt;br /&gt;
}&lt;br /&gt;
}&amp;lt;/source&amp;gt;&lt;br /&gt;
Есть, правда, и кардинальное отличие — в асинхронности. Для этого&lt;br /&gt;
перейдем к сервисам.&lt;br /&gt;
&lt;br /&gt;
==== Сервисы ====&lt;br /&gt;
Для обработки клиентских запросов пишутся так называемые сервисы. Чтобы собрать сервис, создается два интерфейса (phoneBook.client.PhoneBookService и phoneBook.client.PhoneBookServiceAsync) и реализация (phoneBook.server.PhoneBookServiceImpl). В сервис выносятся методы, которые сервер выполняет по запросу клиента (у нас это добавление,&lt;br /&gt;
удаление, редактирование записей и выдача списка записей).&lt;br /&gt;
&lt;br /&gt;
В «асинхронном» интерфейсе прописываются те же методы, что и&lt;br /&gt;
в «обычном», но с одним дополнительным параметром AsyncCallback&lt;br /&gt;
async. Это «Callback», обратный вызов. Когда делается асинхронный&lt;br /&gt;
вызов, выполнение кода (работа с пользователем) продолжается. А&lt;br /&gt;
когда работа асинхронного запроса завершается, вызывается этот&lt;br /&gt;
обратный вызов, который сообщает клиенту: «Товарищ, задание партии выполнено, список пользователей доставлен».&lt;br /&gt;
&lt;br /&gt;
Поэтому обработчики событий (например, кнопки удаления записи) выглядят примерно следующим образом (показан только метод&lt;br /&gt;
onClick соответствующего обработчика):&lt;br /&gt;
&amp;lt;source lang=&amp;quot;java&amp;quot;&amp;gt;// вызов сервиса, второй параметр – это и есть обратный вызов,&lt;br /&gt;
// то есть то, что вызывается после выполнения запроса&lt;br /&gt;
PhoneBookService.App.getInstance().&lt;br /&gt;
removeRecord(_phone, new AsyncCallback() {&lt;br /&gt;
public void onFailure(Throwable aThrowable) {&lt;br /&gt;
// ничего тут не будем делать&lt;br /&gt;
}&lt;br /&gt;
public void onSuccess(Object o) {&lt;br /&gt;
// удаляем строку из таблицы, соответствующую телефону&lt;br /&gt;
for (int i = 0; i &amp;lt; _table.getRowCount(); i++) {&lt;br /&gt;
if (_table.getText(i, 0).equals(_phone)) {&lt;br /&gt;
_table.removeRow(i);&lt;br /&gt;
break;&lt;br /&gt;
}&lt;br /&gt;
}&lt;br /&gt;
}&lt;br /&gt;
});&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Компиляция и запуск ====&lt;br /&gt;
[[Изображение:Img 95 85 1.png|thumb|Итог терзаний — все та же адресная книга и консоль на заднем плане.]]&lt;br /&gt;
Ранее я говорил про дескриптор. Он очень прост и выглядит примерно так:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;xml&amp;quot;&amp;gt;&amp;lt;module&amp;gt;&lt;br /&gt;
&amp;lt;inherits name=’com.google.gwt.user.User’/&amp;gt;&lt;br /&gt;
&amp;lt;entry-point class=’phoneBook.client.PhoneBook’/&amp;gt;&lt;br /&gt;
&amp;lt;servlet path=’/PhoneBookService’ class=’phoneBook.server.PhoneBookServiceImpl’/&amp;gt;&lt;br /&gt;
&amp;lt;/module&amp;gt;&amp;lt;/source&amp;gt;&lt;br /&gt;
Чтобы скомпилировать GWT-код, нужно запустить класс com.&lt;br /&gt;
google.gwt.dev.GWTCompiler с параметром «полное имя класса, который нужно скомпилировать». Также можно указать параметр -out&lt;br /&gt;
«каталог, куда складывать результат».&lt;br /&gt;
&lt;br /&gt;
Запуск специальной оболочки для отладки осуществляется при&lt;br /&gt;
помощи класса com.google.gwt.dev.GWTShell, которому в качестве&lt;br /&gt;
параметра передается путь к HTML-файлу запуска. Более подробные&lt;br /&gt;
строки компиляции и запуска лучше посмотреть в примерах, которые&lt;br /&gt;
поставляются вместе с самим GWT. А как выглядит Google Web Toolkit&lt;br /&gt;
Development Shell, можно узнать из рисунка — «вон он, змей, в окне&lt;br /&gt;
маячит…»&lt;br /&gt;
&lt;br /&gt;
=== Ну, а все-таки, если делать самому? ===&lt;br /&gt;
Давайте поподробнее остановимся на некоторых подводных камнях&lt;br /&gt;
технологии, связанных и с Java и с XMLHttpRequest’ом и с остальными&lt;br /&gt;
ее компонентами.&lt;br /&gt;
&lt;br /&gt;
Во-первых, Java (точнее, серверная сторона). Тут проблема одна,&lt;br /&gt;
UTF-8. Почему-то некоторые браузеры не понимают UTF-8 в ответах.&lt;br /&gt;
Safari, например. Есть достаточно простой обходной путь — выставлять&lt;br /&gt;
не совсем правильный, но работающий тип (Content-Type) содержимого ответа (response), «text/plain; charset=utf-8».&lt;br /&gt;
&lt;br /&gt;
Далее — клиентская часть. Тут подводных камней больше. Во-первых, количество соединений: на сайт браузер разрешает обычно не&lt;br /&gt;
более двух. Поэтому «сделаем сразу 239 AJAX-запросов» не получится.&lt;br /&gt;
Один-два в параллели, и все.&lt;br /&gt;
&lt;br /&gt;
Во-вторых, выше упоминалось про callback, вызываемый, когда&lt;br /&gt;
идет процесс загрузки ответа с сервера. Как и сколько раз он вызывается — четко не определено. То есть нельзя надеяться, что он вызовется&lt;br /&gt;
с кодом события 4 ровно один раз.&lt;br /&gt;
&lt;br /&gt;
В-третьих, на XMLHttpRequest не существует утвержденного стандарта или рекомендации, а есть только черновой вариант W3C.&lt;br /&gt;
Поэтому реализации в разных браузерах различаются. Радует, правда,&lt;br /&gt;
что не катастрофично.&lt;br /&gt;
&lt;br /&gt;
Именно поэтому примеры приводятся с использованием сторонних&lt;br /&gt;
библиотек. А если уж и нужно сделать «самому», то стоит воспользоваться опытом и посмотреть их исходные тексты. В этом-то и сила&lt;br /&gt;
Open Source.&lt;br /&gt;
&lt;br /&gt;
=== Вот и весь AJAX ===&lt;br /&gt;
{{Врезка&lt;br /&gt;
|Заголовок=Ссылки и интересные статьи&lt;br /&gt;
|Содержание=&lt;br /&gt;
* DOJO: http://dojotoolkit.org,&lt;br /&gt;
* DWR: http://getahead.org/dwr,&lt;br /&gt;
* GWT: http://code.google.com/webtoolkit/,&lt;br /&gt;
* Статья про GWT от IBM developerWorks: http://www.ibm.com/developerworks/ru/library/j-AJAX4/index.html.&lt;br /&gt;
|Ширина=200px}}&lt;br /&gt;
В статье рассмотрен AJAX «по верхам». Тема безграничная, так как&lt;br /&gt;
кроме технологии, использование AJAX меняет стиль и принципы&lt;br /&gt;
работы web-приложений. Все то, как они работали до этого, должно&lt;br /&gt;
быть переосмыслено на совершенно другом уровне. Дерзайте, и все&lt;br /&gt;
получится. Клиенты будут довольны, а, следовательно, разработчики&lt;br /&gt;
будут получать больше денег.&lt;/div&gt;</summary>
		<author><name>Dionysius</name></author>	</entry>

	<entry>
		<id>http://wiki.linuxformat.ru/wiki/%D0%A4%D0%B0%D0%B9%D0%BB:JavaEE_LXF95.tar.gz</id>
		<title>Файл:JavaEE LXF95.tar.gz</title>
		<link rel="alternate" type="text/html" href="http://wiki.linuxformat.ru/wiki/%D0%A4%D0%B0%D0%B9%D0%BB:JavaEE_LXF95.tar.gz"/>
				<updated>2009-01-01T06:36:29Z</updated>
		
		<summary type="html">&lt;p&gt;Dionysius: Код урока JavaEE LXF95&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Код урока JavaEE LXF95&lt;/div&gt;</summary>
		<author><name>Dionysius</name></author>	</entry>

	<entry>
		<id>http://wiki.linuxformat.ru/wiki/LXF94:Java_EE</id>
		<title>LXF94:Java EE</title>
		<link rel="alternate" type="text/html" href="http://wiki.linuxformat.ru/wiki/LXF94:Java_EE"/>
				<updated>2009-01-01T06:15:36Z</updated>
		
		<summary type="html">&lt;p&gt;Dionysius: /* Команды */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Цикл/Java EE}}&lt;br /&gt;
== Команды и фабрики ==&lt;br /&gt;
''ЧАСТЬ 6 '''Антон Черноусов''' готов познакомить вас с очередной партией паттернов, которые помогут сделать ваши приложения еще более гибкими и расширяемыми.''&lt;br /&gt;
&lt;br /&gt;
=== Вместо предисловия ===&lt;br /&gt;
&lt;br /&gt;
В предыдущей статье мой коллега Александр Бабаев рассмотрел вопросы организации и использования БД в Java-приложениях, и в том числе вопросы подключения к БД посредством ConnectionPool.&lt;br /&gt;
&lt;br /&gt;
Сегодня мы рассмотрим применение двух паттернов, безусловно, оказавших огромное воздействие на проектирование систем — Command и Factory Method. Их применение позволит сделать ваше приложение расширяемым.&lt;br /&gt;
&lt;br /&gt;
=== Команды ===&lt;br /&gt;
&lt;br /&gt;
[[Media:JavaEE_LXF94.tar.gz‎|Скачать исходный код примера]]&lt;br /&gt;
&lt;br /&gt;
В [[LXF92:Java EE|LXF92]] мы кратко описали стратегии, предназначенные для реализации «Контроллера», и обещали более подробно рассмотреть стратегию Command and Controller. Чтобы выполнить это обещание, нам придется сначала познакомиться с паттерном Command.&lt;br /&gt;
&lt;br /&gt;
Задача, которая стоит перед контроллером (сервлетом) при получении управляющего сигнала, как правило, заключается в выполнении последовательности действий, часто атомарной (то есть обрабатываемой как единое целое). Например, в сервлете AddressBookServlet, реализованном в предыдущей статье, метод handleEdit вызывается, когда адрес на который обращается пользователь — это «/edit».&lt;br /&gt;
&lt;br /&gt;
К сожалению, на примере AddressBookServlet мы видим, что при увеличении функциональности web-приложения растёт и количество методов, реализованных в сервлете; класс «засоряется», код становится менее структурированным и читабельным. Решить проблемы с кодом можно, «обернув» методы в специальные классы, которые будут выполнять атомарные операции и предоставлять сервлету стандартный интерфейс, предназначенный для этих целей. Для выполнения поставленной задачи воспользуемся паттерном Command.&lt;br /&gt;
&lt;br /&gt;
Команды удобны прежде всего тем, что они маскируют конкретную реализацию, находящуюся за интерфейсной прослойкой. Интерфейс остается одним и тем же, независимо от того, с чем работает команда [1].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
 public interface Command {&lt;br /&gt;
    public void execute() throws Exception;&lt;br /&gt;
 }&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Выше представлен простой интерфейс Command всего с одним методом execute(), который и олицетворяет идею одноименного паттерна. Он будет нашим стандартым интерфейсом. Более сложная реализация интерфейса включает метод unexecute(). Класс, реализующий интерфейс Command, инкапсулирует в методе execute() обозначенные выше атомарные операции, а в методе unexecute() реализуется механизм отмены. Часто методы execute() и unexecute() называют do() и undo(), соответственно.&lt;br /&gt;
&lt;br /&gt;
[[Image:Java 6 1.jpg|right|thumb|300px|Рис. 1. Диаграмма классов.]]&lt;br /&gt;
&lt;br /&gt;
Выделим команды, которые нам необходимо реализовать (отметим удачное разделение на методы): Add, Auth, Edit, View, Remove. Учитывая то, что у наших команд будут некоторые идентичные методы и атрибуты, предлагаю создать абстрактный класс AbstractHTTPCommand, в котором они будут собраны. Класс будет реализовывать интерфейс Command, и его наследование автоматически позволит обеспечить необходимый уровень интеграции. Итак, в основе каждой команды будет лежать абстрактный класс AbstractHTTPCommand, в котором реализован интерфейс для выполнения операций (на Рис. 1 вы можете видеть диаграмму классов команд нашего приложения).&lt;br /&gt;
&lt;br /&gt;
Определим общие методы для абстрактного класса: initCommand() — предназначен для инициализации команды, makeDataToView() — для подготовки данных для отображения в случае их изменения, outputPage() — метод для переадресации пользователя (он будет перенесен из AddressBookServlet без изменений) и другие. Ниже представлена реализация методов initCommand() и makeDataToView():&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
 protected void initCommand(ServletContext sc, HttpServletRequest&lt;br /&gt;
 aRequest,&lt;br /&gt;
    HttpServletResponse aResponse, String viewPath,&lt;br /&gt;
    String resultPath, String errorPath) {&lt;br /&gt;
  this.setSc(sc);&lt;br /&gt;
  this.setARequest(aRequest);&lt;br /&gt;
  this.setAResponse(aResponse);&lt;br /&gt;
  this.setResultPath(resultPath);&lt;br /&gt;
  this.setErrorPath(errorPath);&lt;br /&gt;
  this.setViewPath(viewPath);&lt;br /&gt;
 }&lt;br /&gt;
 public void makeDataToView() {&lt;br /&gt;
  Map&amp;lt;String, String&amp;gt; numbers = new HashMap&amp;lt;String, String&amp;gt;();&lt;br /&gt;
  Map&amp;lt;String, String&amp;gt; comments = new HashMap&amp;lt;String, String&amp;gt;();&lt;br /&gt;
  for (Map.Entry&amp;lt;String, Contact&amp;gt; entry :&lt;br /&gt;
     _addressBook.getContacts().entrySet()) {&lt;br /&gt;
    numbers.put(entry.getKey(), entry.getValue().getNumber());&lt;br /&gt;
    comments.put(entry.getKey(), entry.getValue().getComment());&lt;br /&gt;
  }&lt;br /&gt;
  aRequest.setAttribute(&amp;quot;numbers&amp;quot;, numbers);&lt;br /&gt;
  aRequest.setAttribute(&amp;quot;comments&amp;quot;, comments);&lt;br /&gt;
  if (aRequest.getAttribute(&amp;quot;message&amp;quot;) == null) {&lt;br /&gt;
    aRequest.setAttribute(&amp;quot;message&amp;quot;, &amp;quot;&amp;quot;);&lt;br /&gt;
  }&lt;br /&gt;
 }&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Метод makeDataToView() — существенная часть метода handleView() класса AddressBookServlet. Вы можете удивиться, для чего метод initCommand() содержит так много параметров; это необходимо для того, чтобы в момент создания команды полностью передать ей всю необходимую для ее выполнения информацию. Параметры viewPath, resultPath и errorPath появились не случайно — они предназначены для адресов (видов, если использовать термины MVC), используемых в случае простого отображения данных, удачного и, соответственно, неудачного выполнения команды.&lt;br /&gt;
&lt;br /&gt;
Перейдем к реализации самих команд. Рассмотрим, например, метод execute() класса EditHTTPCommand. Он практически полностью соответствует первоначальному методу handleEdit класса AddressBookServlet, исключая переадресацию пользователя на конкретный вид.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
 public void execute() throws Exception {&lt;br /&gt;
    if (aRequest.getParameter(&amp;quot;number&amp;quot;) == null) {&lt;br /&gt;
     _addressBook.removeContactByNumber(aRequest.getParameter(&amp;quot;number&amp;quot;));&lt;br /&gt;
     aRequest.setAttribute(&amp;quot;message&amp;quot;,&amp;quot;Не определено, что редактировать&amp;quot;);&lt;br /&gt;
     outputPage(this.getErrorPath(), aRequest, aResponse);&lt;br /&gt;
    } else if (aRequest.getParameter(&amp;quot;edited&amp;quot;) != null) {&lt;br /&gt;
     _addressBook.editContactByNumber(aRequest.getParameter(&amp;quot;edited&amp;quot;),&lt;br /&gt;
        aRequest.getParameter(&amp;quot;name&amp;quot;),&lt;br /&gt;
        aRequest.getParameter(&amp;quot;number&amp;quot;),&lt;br /&gt;
        aRequest.getParameter(&amp;quot;comment&amp;quot;));&lt;br /&gt;
     aRequest.setAttribute(&amp;quot;message&amp;quot;, &amp;quot;Контакт \&amp;quot;&amp;quot; +&lt;br /&gt;
        aRequest.getParameter(&amp;quot;name&amp;quot;) + &amp;quot;\&amp;quot; отредактирован&amp;quot;);&lt;br /&gt;
     makeDataToView();&lt;br /&gt;
     outputPage(this.getResultPath(), aRequest, aResponse);&lt;br /&gt;
  } else {&lt;br /&gt;
     Contact contact = _addressBook.getContactByNumber(aRequest.getParameter(&amp;quot;number&amp;quot;));&lt;br /&gt;
     aRequest.setAttribute(&amp;quot;action&amp;quot;, &amp;quot;edit&amp;quot;);&lt;br /&gt;
     aRequest.setAttribute(&amp;quot;edit.name&amp;quot;, contact.getName());&lt;br /&gt;
     aRequest.setAttribute(&amp;quot;edit.number&amp;quot;, contact.getNumber());&lt;br /&gt;
     aRequest.setAttribute(&amp;quot;edit.comment&amp;quot;, contact.getComment());&lt;br /&gt;
     outputPage(this.getViewPath(), aRequest, aResponse);&lt;br /&gt;
   }&lt;br /&gt;
 }&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Остальные команды реализуются аналогичным образом. Исключение из общего процесса рефакторинга кода составит команда ViewHTTPCommand, для которой уже реализована большая часть функционала:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
 public void execute() throws Exception {&lt;br /&gt;
   makeDataToView();&lt;br /&gt;
   outputPage(this.getViewPath(), aRequest, aResponse);}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Выполнение любой команды унифицировано и будет выглядеть следующим образом:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
   Command cmd;&lt;br /&gt;
   cmd = new EditHTTPCommand(this.getServletContext(),&lt;br /&gt;
         aRequest, aResponse, &amp;quot;edit.jsp&amp;quot;, &amp;quot;view.jsp&amp;quot;, &amp;quot;view.jsp&amp;quot;);&lt;br /&gt;
   try {&lt;br /&gt;
       cmd.execute();&lt;br /&gt;
   } catch (Exception e) {&lt;br /&gt;
       e.printStackTrace();&lt;br /&gt;
 }&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
После создания всех команд контроллер должен преобразиться: лишние методы уйдут, а его главная функция — управление — будет восстановлена:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
 private void handle(HttpServletRequest aRequest,&lt;br /&gt;
    HttpServletResponse aResponse) throws ServletException, IOException {&lt;br /&gt;
    aRequest.setCharacterEncoding(&amp;quot;utf-8&amp;quot;);&lt;br /&gt;
    String target = aRequest.getRequestURI().substring(aRequest.getContextPath().length());&lt;br /&gt;
    Command cmd;&lt;br /&gt;
    if (target.equals(&amp;quot;/&amp;quot;)) {&lt;br /&gt;
       cmd = new ViewHTTPCommand(this.getServletContext(),aRequest, aResponse, &amp;quot;index.jsp&amp;quot;, null, null);&lt;br /&gt;
    } else if (&amp;quot;/add&amp;quot;.equals(target)) {&lt;br /&gt;
       cmd = new AddHTTPCommand(this.getServletContext(), Request, aResponse, &amp;quot;edit.jsp&amp;quot;, &amp;quot;view.jsp&amp;quot;, null);&lt;br /&gt;
    } else if (&amp;quot;/view&amp;quot;.equals(target)) {&lt;br /&gt;
       cmd = new ViewHTTPCommand(this.getServletContext(), aRequest, aResponse, &amp;quot;view.jsp&amp;quot;, null, null);&lt;br /&gt;
    } else if (&amp;quot;/edit&amp;quot;.equals(target)) {&lt;br /&gt;
       cmd = new EditHTTPCommand(this.getServletContext(), aRequest, aResponse, &amp;quot;edit.jsp&amp;quot;, &amp;quot;view.jsp&amp;quot;, &amp;quot;view.jsp&amp;quot;);&lt;br /&gt;
    } else if (&amp;quot;/remove&amp;quot;.equals(target)) {&lt;br /&gt;
       cmd = new RemoveHTTPCommand(this.getServletContext(), aRequest, aResponse, null, &amp;quot;view.jsp&amp;quot;, null);&lt;br /&gt;
    } else if (&amp;quot;/auth&amp;quot;.equals(target)) {&lt;br /&gt;
       cmd = new AuthHTTPCommand(this.getServletContext(), aRequest, aResponse, &amp;quot;auth.jsp&amp;quot;, &amp;quot;index.jsp&amp;quot;, &amp;quot;auth.jsp&amp;quot;);&lt;br /&gt;
    } else {&lt;br /&gt;
       cmd = new ViewHTTPCommand(this.getServletContext(), aRequest, aResponse, &amp;quot;view.jsp&amp;quot;, null, null);&lt;br /&gt;
    }&lt;br /&gt;
    try {&lt;br /&gt;
       cmd.execute();&lt;br /&gt;
    } catch (Exception e) {&lt;br /&gt;
        // oopst...&lt;br /&gt;
        e.printStackTrace();&lt;br /&gt;
    }&lt;br /&gt;
 }&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
С одной стороны, от внедрения паттерна Command в наше web-приложение мы получили следующие преимущества: управление сосредоточилось в одном месте (в методе handle(), представленном выше), код стал более структурированным. С другой стороны, мы пока не добились возможности полного отторжения продукта от разработчика (при котором исходный код не передается заказчику), так как для изменения функциональности или доработки приложения (как минимум, при добавлении новой команды) необходимо производить перекомпиляцию.&lt;br /&gt;
&lt;br /&gt;
В принципе, ничего непреодолимого нет, в любую программу можно внести исправления, но в одни они вносятся проще, чем в другие.&lt;br /&gt;
Поставим себе задачу сделать внесение исправлений в наше приложение простым. Для этого нам надо:&lt;br /&gt;
&lt;br /&gt;
* Вынести связи между адресами, командами и видами за пределы приложения.&lt;br /&gt;
* Заложить возможность создания экземпляра команды при наличии ее названия.&lt;br /&gt;
* Изменить процесс вызова конкретной команды, чтобы при расширении приложения (дополнении новых команд) не требовалось вносить изменения в уже существующий код.&lt;br /&gt;
&lt;br /&gt;
=== Хранение настроек команд ===&lt;br /&gt;
&lt;br /&gt;
Для решения первой из поставленных задач мы не будем «изобретать велосипед» и воспользуемся средствами конфигурирования web-приложения. В файл web.xml введем параметры для нашего контроллера:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
 &amp;lt;servlet&amp;gt;&lt;br /&gt;
  &amp;lt;display-name&amp;gt;AddressBook&amp;lt;/display-name&amp;gt;&lt;br /&gt;
  &amp;lt;servlet-name&amp;gt;ABServlet&amp;lt;/servlet-name&amp;gt;&lt;br /&gt;
  &amp;lt;servlet-class&amp;gt;AddressBookServlet&amp;lt;/servlet-class&amp;gt;&lt;br /&gt;
  &amp;lt;init-param&amp;gt;&lt;br /&gt;
       &amp;lt;param-name&amp;gt;/&amp;lt;/param-name&amp;gt;&lt;br /&gt;
       &amp;lt;param-value&amp;gt;root&amp;lt;/param-value&amp;gt;&lt;br /&gt;
  &amp;lt;/init-param&amp;gt;&lt;br /&gt;
  &amp;lt;init-param&amp;gt;&lt;br /&gt;
       &amp;lt;param-name&amp;gt;rootCommand&amp;lt;/param-name&amp;gt;&lt;br /&gt;
       &amp;lt;param-value&amp;gt;ViewHTTPCommand&amp;lt;/param-value&amp;gt;&lt;br /&gt;
  &amp;lt;/init-param&amp;gt;&lt;br /&gt;
  &amp;lt;init-param&amp;gt;&lt;br /&gt;
       &amp;lt;param-name&amp;gt;rootView&amp;lt;/param-name&amp;gt;&lt;br /&gt;
       &amp;lt;param-value&amp;gt;index.jsp&amp;lt;/param-value&amp;gt;&lt;br /&gt;
  &amp;lt;/init-param&amp;gt;&lt;br /&gt;
  &amp;lt;init-param&amp;gt;&lt;br /&gt;
       &amp;lt;param-name&amp;gt;rootResult&amp;lt;/param-name&amp;gt;&lt;br /&gt;
       &amp;lt;param-value&amp;gt;null&amp;lt;/param-value&amp;gt;&lt;br /&gt;
  &amp;lt;/init-param&amp;gt;&lt;br /&gt;
  &amp;lt;init-param&amp;gt;&lt;br /&gt;
       &amp;lt;param-name&amp;gt;rootError&amp;lt;/param-name&amp;gt;&lt;br /&gt;
       &amp;lt;param-value&amp;gt;null&amp;lt;/param-value&amp;gt;&lt;br /&gt;
       &amp;lt;/init-param&amp;gt;&lt;br /&gt;
  &amp;lt;load-on-startup&amp;gt;0&amp;lt;/load-on-startup&amp;gt;&lt;br /&gt;
 &amp;lt;/servlet&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Для каждого адреса вводится параметр, который мы будем называть ключом (для «/» это «root»). Ключ с добавлением суффикса (Command, View, Result, Error) обозначает конкретный параметр, значение которого мы будем извлекать прямо в приложении. Для получения необходимых данных можно воспользоваться следующим кодом (хотя мы в дальнейшем будем действовать по-другому):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
 ServletContext sc = this.getServletContext();&lt;br /&gt;
   String key = sc.getInitParameter(&amp;quot;/&amp;quot;);&lt;br /&gt;
   String commandName = sc.getInitParameter(key + &amp;quot;Command&amp;quot;);&lt;br /&gt;
   String commandView = sc.getInitParameter(key + &amp;quot;View&amp;quot;);&lt;br /&gt;
   String commandResult = sc.getInitParameter(key + &amp;quot;Result&amp;quot;);&lt;br /&gt;
   String commandError = sc.getInitParameter(key + &amp;quot;Error&amp;quot;);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Аналогичные настройки необходимо сделать для всех адресов, с которыми работает наше приложение («/», «/add», «/auth», «/edit», «/remove», «/view»). Включение в файл конфигурации этих значений позволит настраивать web-приложение без исправлений в коде.&lt;br /&gt;
&lt;br /&gt;
=== Создание экземпляра класса по имени ===&lt;br /&gt;
&lt;br /&gt;
Экземпляр класса можно создавать при наличии полного имени, записанного в строке, с помощью методов forName() и newInstance() класса Class (например, для класса Date: Object o = Class.forName(«java.util.Date»).newInstance()). Если при создании объекта на основе имени&lt;br /&gt;
класса конструктору необходимо передать ряд параметров, то воспользоваться данным методом нельзя, но создать объект возможно с помощью класса java.lang.reflect.Constructor, например:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
 Class c = Class.forName(key);&lt;br /&gt;
 Class[] parameterTypes = new Class[3];&lt;br /&gt;
 parameterTypes[0] = String.class;&lt;br /&gt;
 parameterTypes[1] = String.class;&lt;br /&gt;
 parameterTypes[2] = String.class;&lt;br /&gt;
 Constructor constructor = c.getConstructor(parameterTypes);&lt;br /&gt;
 Object[] args = new Object[3];&lt;br /&gt;
 args[0] = &amp;quot;par1&amp;quot;;&lt;br /&gt;
 args[1] = &amp;quot;par2&amp;quot;;&lt;br /&gt;
 args[2] = &amp;quot;par3&amp;quot;;&lt;br /&gt;
 Object o = constructor.newInstance(args);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Давайте разберем приведенный пример. Сначала мы создаем экземпляр класса Class по имени key, далее выделяем массив из типов параметров в том количестве и последовательности, как они идут в нужном нам конструкторе. Используя метод getConstructor(), в качестве параметра которому мы передаем наш массив, получаем экземпляр класса Conctructor. Если у Conctructor вызвать метод newInstance(args), то мы получим требуемый класс.&lt;br /&gt;
&lt;br /&gt;
Осталось разобраться с последней проблемой, в решении которой нам поможет паттерн Factory Method.&lt;br /&gt;
&lt;br /&gt;
=== Фабрика команд ===&lt;br /&gt;
&lt;br /&gt;
Суть паттерна Factory Method заключается в том, что его реализация позволяет создавать экземпляры конкретных объектов, причем в этом случае сохраняется зависимость от абстрактных интерфейсов. Следовательно, данный шаблон в значительной мере может пригодиться в процессе активной разработки приложений, при которой конкретные классы обладают высоким уровнем изменчивости, что и требуется для решения последней задачи [2].&lt;br /&gt;
&lt;br /&gt;
Если мы применим идею паттерна Factory Method для создания фабрики команд к нашему web-приложению, оно приобретет свойство расширяемости за счет слабосвязанности классов (см. Рис. 2), то есть класс AddressBookServlet напрямую не будет связан с конкретными командами, а будет взаимодействовать с ними посредством Command и CommandFactory.&lt;br /&gt;
&lt;br /&gt;
[[Image:Java 6 2.jpg|right|thumb|320px|Рис. 2. Диаграмма классов, применение фабрики команд.]]&lt;br /&gt;
&lt;br /&gt;
Ниже представлен ключевой метод getCommand() класса CommanFactory, в котором согласно параметру name извлекаются настройки из файла конфигурации web-приложения и создается экземпляр необходимой команды (метод реализован не оптимально):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
 public Command getCommand(String name, HttpServletRequest&lt;br /&gt;
 aRequest,&lt;br /&gt;
 HttpServletResponse aResponse) {&lt;br /&gt;
 if (name != null) {&lt;br /&gt;
   if (config.getInitParameter(name) != null) {&lt;br /&gt;
     try {&lt;br /&gt;
         String key = config.getInitParameter(name);&lt;br /&gt;
         Class cmd1 = Class.forName(config.getInitParameter(key + &amp;quot;Command&amp;quot;));&lt;br /&gt;
         try {&lt;br /&gt;
             Class[] parameterTypes = new Class[6];&lt;br /&gt;
             parameterTypes[0] = ServletContext.class;&lt;br /&gt;
             parameterTypes[1] = HttpServletRequest.class;&lt;br /&gt;
             parameterTypes[2] = HttpServletResponse.class;&lt;br /&gt;
             parameterTypes[3] = String.class;&lt;br /&gt;
             parameterTypes[4] = String.class;&lt;br /&gt;
             parameterTypes[5] = String.class;&lt;br /&gt;
             Constructor constructor = cmd1.getConstructor(parameterTypes);&lt;br /&gt;
             Object[] args = new Object[6];&lt;br /&gt;
             args[0] = sc;&lt;br /&gt;
             args[1] = aRequest;&lt;br /&gt;
             args[2] = aResponse;&lt;br /&gt;
             args[3] = config.getInitParameter(key + &amp;quot;View&amp;quot;);&lt;br /&gt;
             args[4] = config.getInitParameter(key + &amp;quot;Result&amp;quot;);&lt;br /&gt;
             args[5] = config.getInitParameter(key + &amp;quot;Error&amp;quot;);&lt;br /&gt;
             Command cmd = (Command) constructor.newInstance(args);&lt;br /&gt;
             return cmd;&lt;br /&gt;
          } catch (Exception e) {&lt;br /&gt;
             e.printStackTrace();&lt;br /&gt;
             return null;&lt;br /&gt;
          }&lt;br /&gt;
        } catch (ClassNotFoundException e) {&lt;br /&gt;
          e.printStackTrace();&lt;br /&gt;
          return null;&lt;br /&gt;
        }}}&lt;br /&gt;
 return null;}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Добавим последний штрих в web-приложение — изменим метод handle класса AddressBookServlet, чтобы он работал с созданной фабрикой:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
 Command cmd;&lt;br /&gt;
 CommandFactory cf = new CommandFactory(this.getServletConfig(),this.getServletContext());&lt;br /&gt;
 cmd = cf.getCommand(&lt;br /&gt;
 aRequest.getRequestURI().substring(aRequest.getContextPath().length()),&lt;br /&gt;
 aRequest, aResponse);&lt;br /&gt;
 if (cmd == null) {&lt;br /&gt;
     cmd = cf.getCommand(&amp;quot;/&amp;quot;, aRequest, aResponse);&lt;br /&gt;
 }&lt;br /&gt;
 try {&lt;br /&gt;
     cmd.execute();&lt;br /&gt;
 } catch (Exception e) {&lt;br /&gt;
     // oopst...&lt;br /&gt;
     e.printStackTrace();&lt;br /&gt;
 }&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Предложенная реализация закрывает для пользователя вызов не описанных в конфигурации адресов.&lt;br /&gt;
&lt;br /&gt;
=== Что дальше? ===&lt;br /&gt;
&lt;br /&gt;
Основной целью этой статьи было знакомство с паттернами Command и Factory Method, с чем мы неплохо справились, а заодно провели модернизацию web-приложения.&lt;br /&gt;
&lt;br /&gt;
Создание экземпляров команд на основе файла конфигурации web-приложения, наверняка, не самая хорошая идея: более правильно выносить их настройку в отдельный файл (который кстати, можно будет менять из самого приложения, необходимо лишь реализовать соответствующие функции). Одно из улучшений, которое можно произвести — это изменить сами команды: сделать их stateless, то есть не хранящими внутри себя сведения о состоянии. Это улучшение позволило бы создать экземпляры команд один раз и использовать их и по мере необходимости.&lt;br /&gt;
&lt;br /&gt;
Отметим, что применяя предложенные методы, можно создать приложение, модификация которого возможна без остановки его выполнения, так сказать, «на лету», но это уже тема тема отдельной статьи.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
# Тейт, Б. Горький вкус Java: Библиотека программиста. — СПб: Питер, 2003. — 333 с.&lt;br /&gt;
# Мартин, Р. С. Быстрая разработка программ: принципы, примеры, практика. — М.: Издательский дом «Вильямс», 2004. — 752 с.: ил.&lt;/div&gt;</summary>
		<author><name>Dionysius</name></author>	</entry>

	<entry>
		<id>http://wiki.linuxformat.ru/wiki/%D0%A4%D0%B0%D0%B9%D0%BB:JavaEE_LXF94.tar.gz</id>
		<title>Файл:JavaEE LXF94.tar.gz</title>
		<link rel="alternate" type="text/html" href="http://wiki.linuxformat.ru/wiki/%D0%A4%D0%B0%D0%B9%D0%BB:JavaEE_LXF94.tar.gz"/>
				<updated>2009-01-01T06:14:17Z</updated>
		
		<summary type="html">&lt;p&gt;Dionysius: Код примера урока JavaEE LXF94&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Код примера урока JavaEE LXF94&lt;/div&gt;</summary>
		<author><name>Dionysius</name></author>	</entry>

	<entry>
		<id>http://wiki.linuxformat.ru/wiki/LXF93:Java_EE</id>
		<title>LXF93:Java EE</title>
		<link rel="alternate" type="text/html" href="http://wiki.linuxformat.ru/wiki/LXF93:Java_EE"/>
				<updated>2008-12-30T18:05:33Z</updated>
		
		<summary type="html">&lt;p&gt;Dionysius: /* Все на базу! */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Цикл/Java EE}}&lt;br /&gt;
: '''Java Enterprise Edition''' Учимся писатьклиент-серверные приложения на Java&lt;br /&gt;
&lt;br /&gt;
[[Категория:Учебники]]&lt;br /&gt;
&lt;br /&gt;
==Все на базу!==&lt;br /&gt;
&lt;br /&gt;
[[Media:AddressBook4.tar.bz2|Скачать исходный код примера]]&lt;br /&gt;
&lt;br /&gt;
:'''ЧАСТЬ 5''' Ни одно серьезное приложение не обходится без большой базы данных. '''Александр Бабаев''' расскажет, что для этого требуется.&lt;br /&gt;
&lt;br /&gt;
В предыдущих статьях цикла рассказывалось, как сделать интернет-приложение: правильно разделить создание интерфейса&lt;br /&gt;
и бизнес-логику, а также реализовать некоторые технические&lt;br /&gt;
детали. Но до сих пор мы использовали для хранения записей адресной книги простейшую сериализацию. Настало время сделать все&lt;br /&gt;
«по-взрослому».&lt;br /&gt;
&lt;br /&gt;
Хранение записей в базе данных имеет и достоинства, и недостатки. Из последних можно указать на необходимость поставки с&lt;br /&gt;
приложением СУБД (системы управления базами данных), а также&lt;br /&gt;
изучения специального языка запросов, который помогает сохранять&lt;br /&gt;
данные в БД и получать их оттуда и достаточно сложен, если изучать&lt;br /&gt;
его досконально.&lt;br /&gt;
&lt;br /&gt;
Но есть и явные выгоды, иначе СУБД не использовались бы&lt;br /&gt;
настолько широко, как они используются сейчас. Во-первых, язык, о&lt;br /&gt;
котором я говорил (так называемый структурированный язык запросов, ''Structured Query Language, SQL'') – более или менее стандартен.&lt;br /&gt;
Это позволяет изучить его один раз, после чего использовать с разными СУБД. Эти системы также достаточно надежны и отлажены (и&lt;br /&gt;
имеют приличную скорость работы), что позволяет использовать их в&lt;br /&gt;
самых сложных ситуациях.&lt;br /&gt;
&lt;br /&gt;
Хочется отметить еще одно свойство реляционных СУБД. Дело в&lt;br /&gt;
том, что реляционными они называются не просто так. Есть специальная область математики, которая описывает такие объекты, как таблицы и поля, а также отношения (relations) между ними – реляционная&lt;br /&gt;
алгебра и реляционное счисление. Знание основ этого раздела науки&lt;br /&gt;
позволяет очень четко представить, что может и чего не может реляционная СУБД. А это, в свою очередь, дает понимание, где ее стоит, а где –&lt;br /&gt;
не стоит использовать. В мире ПО это очень редкое и очень полезное&lt;br /&gt;
свойство. Оно означает, что если сделать программу точно по математическим лекалам, то даже в условиях сверхдальнего космоса она&lt;br /&gt;
будет работать так, как задумано. Значит, на нее можно положиться.&lt;br /&gt;
&lt;br /&gt;
А теперь вернемся из космоса к нашим адресам…&lt;br /&gt;
&lt;br /&gt;
===JDBC===&lt;br /&gt;
&lt;br /&gt;
Рассмотрим, как можно обеспечить работу с СУБД, используя Java.&lt;br /&gt;
Есть несколько подходов (например, применение специальных СУБД,&lt;br /&gt;
которые имеют собственные API для Java), но в подавляющем боль-&lt;br /&gt;
шинстве случаев вам придется использовать ''JDBC'' (Java DataBase&lt;br /&gt;
Connectivity). Это библиотека, которая позволяет работать с любой&lt;br /&gt;
СУБД, для которой создан так называемый JDBC-драйвер. Он преобразует запросы ''JDBC'' в родные команды СУБД и обратно.&lt;br /&gt;
&lt;br /&gt;
Мы будем использовать самую обычную связку: ''MySQL'' + стандартный драйвер ''MySQL JDBC''. Если вы занимаетесь созданием интернет-приложений, то эта СУБД, скорее всего, у вас уже установлена. А&lt;br /&gt;
драйвер можно скачать с сайта ''MySQL'' по адресу: http://dev.mysql.com/downloads/connector/j/5.0.html или найти на нашем DVD. Из всего архива нам понадобится только файл '''mysql-connector-java-5.0.5-bin.jar'''. &lt;br /&gt;
&lt;br /&gt;
===Работа с JDBC===&lt;br /&gt;
&lt;br /&gt;
Для выполнения дальнейших действий СУБД (''MySQLd'') должна быть&lt;br /&gt;
запущена.&lt;br /&gt;
&lt;br /&gt;
В целом, работа с ''JDBC'' не сильно отличается от работы с любым&lt;br /&gt;
другим API (в PHP, например, все идеологически очень похоже). Вопервых, нужно подключиться к СУБД, т.е. создать соединение. После&lt;br /&gt;
работы соединение обязательно нужно закрыть. Пока соединение&lt;br /&gt;
«открыто», можно опрашивать СУБД, сохранять или получать данные,&lt;br /&gt;
используя язык ''SQL''.&lt;br /&gt;
&lt;br /&gt;
Вспомним основные и самые простые команды ''SQL''. Научимся&lt;br /&gt;
создавать записи в БД, изменять и удалять их. Но прежде нужно&lt;br /&gt;
создать саму базу данных.&lt;br /&gt;
&lt;br /&gt;
Если ''MySQL'' был только что установлен, то создать базу данных&lt;br /&gt;
можно следующей командой:&lt;br /&gt;
&lt;br /&gt;
 mysqladmin -uroot create database addressbook&lt;br /&gt;
&lt;br /&gt;
Теперь в этой БД нужно сделать таблицу, которую мы будем&lt;br /&gt;
использовать для хранения записей. Сделаем маленькую программу&lt;br /&gt;
на Java, которая выполнит эту работу за нас (см. листинг 1). Заодно&lt;br /&gt;
посмотрим на общую структуру процесса работы с СУБД.&lt;br /&gt;
&lt;br /&gt;
'''Листинг 1. Создание таблицы'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
 import java.sql.*;&lt;br /&gt;
 public class CreateTable {&lt;br /&gt;
   public static void main(String[] args) throws Exception {&lt;br /&gt;
     // эта строчка загружает драйвер MySQL, чтобы его можно было использовать&lt;br /&gt;
     Class.forName(“org.gjt.mm.mysql.Driver”).newInstance();&lt;br /&gt;
     // получим соединение&lt;br /&gt;
     Connection connection = DriverManager.&lt;br /&gt;
         getConnection(“jdbc:mysql://localhost:3306/addressbook”, “root”,“”);&lt;br /&gt;
     try {&lt;br /&gt;
       // создадим «выражение»&lt;br /&gt;
       Statement statement = connection.createStatement();&lt;br /&gt;
       statement.executeUpdate(&lt;br /&gt;
           “CREATE TABLE `addresses` (\n” +&lt;br /&gt;
           “`name` varchar(200) default NULL,\n” +&lt;br /&gt;
           “`number` varchar(200) default NULL\n” +&lt;br /&gt;
           “`comment` varchar(200) default NULL,\n” +&lt;br /&gt;
           “) DEFAULT CHARSET=utf8”);&lt;br /&gt;
       statement.close();&lt;br /&gt;
     } finally {&lt;br /&gt;
       // закроем соединение&lt;br /&gt;
       connection.close();&lt;br /&gt;
     }&lt;br /&gt;
   }&lt;br /&gt;
 }&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В этой программе присутствует одна «волшебная» строчка, которая&lt;br /&gt;
загружает драйвер. Нужно отметить, что в Java 6 она необязательна.&lt;br /&gt;
Остальное более или менее понятно. Мы открываем соединение (легко видеть, что при этом мы подключаемся к локальному компьютеру,&lt;br /&gt;
порту 3306 и базе данных '''addressbook''', с именем пользователя root и&lt;br /&gt;
пустым паролем), потом генерируем выражение ('''statement''') и выполняем ''SQL''-код, который и создает таблицу. Затем выражение «закрывается», после чего в final-блоке закрывается и само соединение. Final-&lt;br /&gt;
блок нужен для того, чтобы соединение было закрыто независимо от&lt;br /&gt;
возникновения исключения в блоке '''try ... catch'''. Как правило, число&lt;br /&gt;
одновременных соединений ограничено, а значит, это ценный ресурс,&lt;br /&gt;
которым нужно пользоваться аккуратно.&lt;br /&gt;
&lt;br /&gt;
Вместо кода, который создает таблицу, можно написать любой другой ''SQL''-запрос, например, добавляющий запись или изменяющий ее.&lt;br /&gt;
Для получения записи нужен несколько другой код, и его мы обсудим&lt;br /&gt;
чуть позже.&lt;br /&gt;
&lt;br /&gt;
Чтобы добавить запись в уже созданную таблицу, можно использовать следующий код (См. листинг 2):&lt;br /&gt;
&lt;br /&gt;
'''Листинг 2. Создание записи в таблице'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
 private void addEntry(String aName, String aNumber, String aComment)&lt;br /&gt;
 throws SQLException {&lt;br /&gt;
   Connection connection = DriverManager.&lt;br /&gt;
   getConnection(“jdbc:mysql://localhost:3306/addressbook”, “root”, “”);&lt;br /&gt;
   try {&lt;br /&gt;
     PreparedStatement statement = connection.prepareStatement(&lt;br /&gt;
         NSERT INTO `addresses` (`name`, `number`, `comment`) VALUES (?, ?, ?)”);&lt;br /&gt;
     statement.setString(1, aName);&lt;br /&gt;
     statement.setString(2, aNumber);&lt;br /&gt;
     statement.setString(3, aComment);&lt;br /&gt;
     statement.executeUpdate();&lt;br /&gt;
     statement.close();&lt;br /&gt;
   } finally {&lt;br /&gt;
     connection.close();&lt;br /&gt;
   }&lt;br /&gt;
 }&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Чтобы поменять номер, следует воспользоваться командой '''UPDATE''':&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
 PreparedStatement statement = connection.prepareStatement(&lt;br /&gt;
        “UPDATE `addresses` SET `number`=?, `comment`=? WHERE&lt;br /&gt;
`name`=?;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Внимательный читатель может заметить, что здесь используется не&lt;br /&gt;
'''Statement''', а '''PreparedStatement''' (предварительно подготовленное выражение). Оба они служат одной цели – с их помощью формируются&lt;br /&gt;
и выполняются запросы к СУБД. Но '''Statement''' формирует запросы и&lt;br /&gt;
тут же выполняет их, в то время как '''PreparedStatement''' сначала подготавливает запрос, затем заполняет его параметры значениями (места вставки параметров – это вопросительные знаки), и только потом&lt;br /&gt;
выполняет его. '''PreparedStatement''' имеет множество преимуществ&lt;br /&gt;
перед обычными запросами. Во-первых, предварительно подготовленные запросы могут кэшироваться прямо в СУБД. Это ускоряет их&lt;br /&gt;
выполнение. Кроме того, можно выполнить несколько запросов сразу, используя пакетную обработку. Например, если нужно добавить не одну, а несколько записей, можно поступить примерно так:&lt;br /&gt;
* Создать '''PreparedStatement'''.&lt;br /&gt;
* Заполнить его ('''statement.set'''…).&lt;br /&gt;
* Вызывать '''statement.addBatch()'''.&lt;br /&gt;
* При необходимости повторить предыдущие два пункта.&lt;br /&gt;
* Вызывать '''statement.executeBatch()'''.&lt;br /&gt;
&lt;br /&gt;
Теперь давайте разберемся, как можно вытащить информацию из&lt;br /&gt;
БД. Это не намного сложнее (см. листинг 3).&lt;br /&gt;
&lt;br /&gt;
'''Листинг 3. Получение записи из таблицы'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
 Contact result = null;&lt;br /&gt;
 PreparedStatement statement = connection.prepareStatement(&lt;br /&gt;
        “SELECT `name`, `number`, `comment` FROM `addresses` WHERE `name`=?”);&lt;br /&gt;
 statement.setString(1, aName);&lt;br /&gt;
 ResultSet resultSet = statement.executeQuery();&lt;br /&gt;
 if (resultSet.next()) {&lt;br /&gt;
     result.setData(resultSet.getString(1), resultSet.getString(2), resultSet.&lt;br /&gt;
 getString(3));&lt;br /&gt;
 }&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Теперь мы вызываем метод '''statement.executeQuery()''', который возвращает '''resultSet'''. Согласно документации, он никогда не равен '''null''',&lt;br /&gt;
поэтому проверку я не произвожу. Далее вызывается метод '''resultSet'''.&lt;br /&gt;
'''next()''', который возвращает true тогда и только тогда, когда в результате есть еще одна запись, и, одновременно с этим, переходит на эту&lt;br /&gt;
запись. После чего можно получить доступ к полям записи. Я получаю&lt;br /&gt;
доступ по номерам, мне так проще. Но можно написать и '''resultSet'''.&lt;br /&gt;
'''getString(“name”)''', это тоже будет работать.&lt;br /&gt;
&lt;br /&gt;
===Но это будет работать плохо===&lt;br /&gt;
&lt;br /&gt;
{{Врезка&lt;br /&gt;
|Заголовок=Сокорая помощь&lt;br /&gt;
|Содержание=Напомню, что&lt;br /&gt;
для того, чтобы&lt;br /&gt;
подключить jar-файл к проекту,&lt;br /&gt;
нужно указать в параметрах ''javac'' и ''java'' '''«-cp [имя jar-файлов через разделитель]»''',&lt;br /&gt;
где разделителем&lt;br /&gt;
является точка с&lt;br /&gt;
запятой в Windows и&lt;br /&gt;
двоеточие в Unix.&lt;br /&gt;
|Ширина=200px&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Почему? Потому что никто в «нормальных» системах не работает&lt;br /&gt;
с соединениями напрямую, а если работает, то никогда не открывает/закрывает соединение на каждый запрос. Иногда это может&lt;br /&gt;
занять секунды и минуты. А сам запрос – несколько миллисекунд.&lt;br /&gt;
Становится ясно, что создание соединения и его закрытие – узкое&lt;br /&gt;
место, с которым нужно что-то делать. И решение называется «пул&lt;br /&gt;
соединений». Пул ('''Pool''') – это общий паттерн (см. [[LXF92:Java_EE|LXF92]]), который&lt;br /&gt;
состоит в следующем:&lt;br /&gt;
* У нас есть хранилище объектов. Представим себе оружейный ящик местной шпаны. В нем лежат несколько рогаток. Это и есть хранилище или пул.&lt;br /&gt;
* В каждый момент времени любой (кому позволено) может взять рогатку, попользоваться и положить обратно.&lt;br /&gt;
* Если рогатки нет, можно подождать, пока кто-нибудь другой не вернет ее в ящик.&lt;br /&gt;
&lt;br /&gt;
Тогда, если время пользования рогаткой невелико (никто не держит&lt;br /&gt;
ее долго), один пул может обслуживать очень большое количество&lt;br /&gt;
хулиганов. Схематично пул изображен на рисунке. Видно, что при&lt;br /&gt;
помощи небольшого количества соединений с ограниченным ресурсом&lt;br /&gt;
обрабатывается большое количество «пользователей».&lt;br /&gt;
&lt;br /&gt;
То же самое можно проделать и с соединениями. Создается&lt;br /&gt;
пул, в котором держится открытыми, например, десять соединений.&lt;br /&gt;
Если нужно произвести операцию с БД, соединение берется из пула,&lt;br /&gt;
используется и возвращается на место. При этом оно остается открытым. И получается (кроме самого первого раза) – тоже уже открытым.&lt;br /&gt;
Экономия зачастую просто огромна.&lt;br /&gt;
&lt;br /&gt;
[[Изображение:LXF93_javaee.png|Рис. 1]]&lt;br /&gt;
&lt;br /&gt;
'''Рис. 1. Схема работы пула.'''&lt;br /&gt;
&lt;br /&gt;
===Полезные «Commons»===&lt;br /&gt;
&lt;br /&gt;
Если взглянуть на сайт http://jakarta.apache.org/, то можно увидеть&lt;br /&gt;
очень много проектов, которые сделаны для ''Java''. Среди них достаточно много хороших. Нам понадобится Commons (http://jakarta.apache.org/commons/). Это проект, в котором собраны маленькие «общие»&lt;br /&gt;
библиотеки, которые приходится писать каждому программисту.&lt;br /&gt;
Вместо того, чтобы терять время зря, можно просто скачать нужный&lt;br /&gt;
пакет и использовать его. Сегодня нам будут интересны ''commons-dbcp''&lt;br /&gt;
и ''commons-pool''. Их можно загрузить со страниц http://jakarta.apache.org/site/downloads/downloads_commons-dbcp.cgi и http://jakarta.apache.org/site/downloads/downloads_commons-pool.cgi соответственно или&lt;br /&gt;
взять с нашего DVD. После скачивания нужно вытащить из архивов&lt;br /&gt;
файлы '''commons-dbcp-1.2.2.jar''' и '''commons-pool-1.3.jar'''.&lt;br /&gt;
&lt;br /&gt;
Основное предназначение второй библиотеки – создание пулов,&lt;br /&gt;
первой – пулов соединений с СУБД. Давайте посмотрим, как все это&lt;br /&gt;
используется. Для начала пул нужно инициализировать. Для этого мы&lt;br /&gt;
напишем следующий метод:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
 String connectionString = “jdbc:mysql://localhost:3306/addressbook?user=root&amp;amp;passowrd=”;&lt;br /&gt;
 GenericObjectPool connectionPool = new GenericObjectPool(null);&lt;br /&gt;
 ConnectionFactory connectionFactory =&lt;br /&gt;
 new DriverManagerConnectionFactory(connectionString, null);&lt;br /&gt;
 new PoolableConnectionFactory(connectionFactory, connectionPool, null,&lt;br /&gt;
 null, false, true);&lt;br /&gt;
 PoolingDataSource _poolingDataSource = new PoolingDataSource(conne&lt;br /&gt;
 ctionPool);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Теперь вместо создания соединения «самим» можно получать его&lt;br /&gt;
из '''PoolingDataSource'''. Сама работа с соединением при этом не изменится ни на грамм. Вот, например, добавление записи (листинг 4, сравните с листингом 2):&lt;br /&gt;
&lt;br /&gt;
'''Листинг 4. Добавление записи (используем пул соединений)'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
 private void addEntry(String aName, String aNumber, String aComment)&lt;br /&gt;
 throws SQLException {&lt;br /&gt;
  Connection connection = _poolingDataSource.getConnection();&lt;br /&gt;
  try {&lt;br /&gt;
    PreparedStatement statement = connection.prepareStatement(&lt;br /&gt;
      INSERT INTO `addresses` (`name`, `number`, `comment`) VALUES (?,&lt;br /&gt;
?, ?)”);&lt;br /&gt;
    statement.setString(1, aName);&lt;br /&gt;
    statement.setString(2, aNumber);&lt;br /&gt;
    statement.setString(3, aComment);&lt;br /&gt;
    statement.executeUpdate();&lt;br /&gt;
    statement.close();&lt;br /&gt;
   } finally {&lt;br /&gt;
     connection.close();&lt;br /&gt;
   }&lt;br /&gt;
 }&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Повторяющийся текст оставлен специально. Чтобы было видно, что&lt;br /&gt;
не поменялось ровно ничего. Даже '''close''' нужен. Но в действительности&lt;br /&gt;
'''close''' перехватывается пулом соединений и вместо реального закрытия&lt;br /&gt;
соединения возвращает его в пул.&lt;br /&gt;
&lt;br /&gt;
Стоит упомянуть, что в данном случае используется шаблон '''Proxy'''.&lt;br /&gt;
То есть создается класс, который унаследован от соединения, но в&lt;br /&gt;
котором изменено поведение одного из методов (в данном случае,&lt;br /&gt;
'''close'''). Благодаря этому код приложения не изменяется, но зато кардинальным образом изменяется поведение.&lt;br /&gt;
&lt;br /&gt;
===Другие пути===&lt;br /&gt;
&lt;br /&gt;
''JDBC'' – великолепный способ работы с базами данных. Но не единственный. Главная особенность ''JDBC'' в том, что это самая низкоуровневая библиотека для работы с базами данных в Java. Все распространенные средства (''EJB'' или ''Hibernate'' например), внутри так или иначе&lt;br /&gt;
используют ''JDBC''. Поэтому для понимания того, что происходит, ''JDBC''&lt;br /&gt;
знать просто обязательно. Иначе трудности отладки и недоумения поповоду «а что это тут произошло» будут преследовать вас постоянно и на каждом шагу.&lt;br /&gt;
&lt;br /&gt;
Достоинства ''JDBC'' очевидны. Это сравнительная простота, мощность (мы не обсудили и десятой части того, что может эта библиотека:&lt;br /&gt;
здесь есть и транзакции, и курсоры, и блокировки, и много чего еще), а&lt;br /&gt;
также скорость работы. Но иногда требуется немного другое. В нашем&lt;br /&gt;
случае в БД была только одна таблица, которая к тому же имела простую структуру. В сложных приложениях часто приходится работать с&lt;br /&gt;
сотнями «бизнес-объектов», у которых бывает очень нетривиальная&lt;br /&gt;
структура и взаимосвязи. При этом структура СУБД получается очень&lt;br /&gt;
непростая. И процедура вытаскивания объектов (которые часто «раскиданы» по десяткам таблиц) – превращается в кошмар.&lt;br /&gt;
&lt;br /&gt;
На этом этапе на передний план выходят другие библиотеки. В&lt;br /&gt;
первую очередь это ''Hibernate''. Во вторую – ''EJB''. Во вторую, потому что&lt;br /&gt;
''EJB'' – это не только способ хранения объектов, но несколько более&lt;br /&gt;
сложная «структура». А ''Hibernate'' – это типичный пример ORM (ObjectRelational Mapping, объектно-реляционное отображение), то есть биб-&lt;br /&gt;
лиотеки, автоматически преобразующей объекты в строки таблиц и&lt;br /&gt;
обратно. Работа с ''Hibernate'' сильно отличается от работы с ''JDBC'', но&lt;br /&gt;
внутри – это примерно то же, что мы и рассмотрели.&lt;br /&gt;
&lt;br /&gt;
Также нельзя забывать, что несмотря на то, что ''JDBC'' – это унифицированная библиотека, которая работает с десятками различных&lt;br /&gt;
СУБД (был бы драйвер), код, написанный для одной СУБД, далеко не&lt;br /&gt;
всегда будет работать с другой. Разница обычно заключается в двух&lt;br /&gt;
вещах:&lt;br /&gt;
&lt;br /&gt;
* Возможности драйвера. Иногда что-то не реализовано. Например, вам нужны транзакции, а в драйвере их просто нет.&lt;br /&gt;
* Разница в диалекте ''SQL''. Ни одна СУБД не повторяет никакую другу в языке запросов. Всегда есть различия. Поэтому, несмотря на стандар ты, одно приложение не обязательно будет работать со всеми СУБД.&lt;br /&gt;
&lt;br /&gt;
Ну вот, собственно, и все. Адресная книга успешно «покладена» в базу данных. Это отличное начало, но пора двигаться дальше.&lt;br /&gt;
Интересно, а что же такое ''AJAX''? '''LXF'''&lt;br /&gt;
&lt;br /&gt;
===А дайте нам ваше соединение!===&lt;br /&gt;
&lt;br /&gt;
Существует еще один способ работы с соединениями, который используется в крупных приложениях. Точнее, не в крупных, а в тех, где за&lt;br /&gt;
соединениями следит контейнер приложений или контейнер сервлетов.&lt;br /&gt;
&lt;br /&gt;
Для этого в дескрипторе нужно описать способ подключения к СУБД и&lt;br /&gt;
параметры подключения. И при запуске контейнера можно будет получить&lt;br /&gt;
'''DataSource''', который будет проинициализирован самим контейнером. Как&lt;br /&gt;
это делается – лучше смотреть в документации к контейнеру/СУБД.&lt;br /&gt;
Например, для используемого нами Tomcat нужная информация находится тут: http://tomcat.apache.org/tomcat-5.5-doc/jndi-datasource-examples-howto.html.&lt;br /&gt;
&lt;br /&gt;
===Что за штука... встраиваемые СУБД?===&lt;br /&gt;
&lt;br /&gt;
В ''Java 6'' появилось новшество – некая ''Java DB''. Что это? Это —&lt;br /&gt;
встраиваемая СУБД (embeddable DB engine).&lt;br /&gt;
&lt;br /&gt;
Обычно сервер СУБД работает отдельно (часто даже на своем&lt;br /&gt;
собственном компьютере), но на этапе отладки приложения это&lt;br /&gt;
зачастую не нужно. Да и в простых приложениях хочется, с одной&lt;br /&gt;
стороны, использовать мощь баз данных, а с другой, распространять с программой сервер, учить пользователей им пользовать-&lt;br /&gt;
ся – это кошмар.&lt;br /&gt;
&lt;br /&gt;
Поэтому существует специальный класс СУБД, которые запускаются вместе с приложением (например, как обычный поток&lt;br /&gt;
внутри вашей программы), и заканчивают работу вместе с ним. Их&lt;br /&gt;
использование не отличается (за исключением общих разнящихся&lt;br /&gt;
правил SQL-запросов) от работы с обычной СУБД: здесь тоже&lt;br /&gt;
нужен драйвер (всё в поставке), те же выражения… Они сродни&lt;br /&gt;
''Jetty'', которую мы использовали в самом начале, чтобы сделать&lt;br /&gt;
наш первый сервлет.&lt;br /&gt;
&lt;br /&gt;
Из известных встраиваемых СУБД можно назвать ''Java DB'' (это&lt;br /&gt;
порт ''Apache Derby'') и ''HSQLDB'', используемую в ''OpenOffice.org'', а&lt;br /&gt;
из известных за пределами мира Java — ''SQLite'' (в принципе тоже подключаемую к Java).&lt;/div&gt;</summary>
		<author><name>Dionysius</name></author>	</entry>

	<entry>
		<id>http://wiki.linuxformat.ru/wiki/%D0%A4%D0%B0%D0%B9%D0%BB:AddressBook4.tar.bz2</id>
		<title>Файл:AddressBook4.tar.bz2</title>
		<link rel="alternate" type="text/html" href="http://wiki.linuxformat.ru/wiki/%D0%A4%D0%B0%D0%B9%D0%BB:AddressBook4.tar.bz2"/>
				<updated>2008-12-30T18:05:14Z</updated>
		
		<summary type="html">&lt;p&gt;Dionysius: Исходный код примера урока JavaEE в LXF93&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Исходный код примера урока JavaEE в LXF93&lt;/div&gt;</summary>
		<author><name>Dionysius</name></author>	</entry>

	<entry>
		<id>http://wiki.linuxformat.ru/wiki/LXF92:Java_EE</id>
		<title>LXF92:Java EE</title>
		<link rel="alternate" type="text/html" href="http://wiki.linuxformat.ru/wiki/LXF92:Java_EE"/>
				<updated>2008-12-30T17:59:20Z</updated>
		
		<summary type="html">&lt;p&gt;Dionysius: /* MVC в J2EE */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Цикл/Java EE}}&lt;br /&gt;
[[Категория:Учебники]]&lt;br /&gt;
:'''Java Enterprise Edition''' Учимся писать клиент-серверные приложения на Java&lt;br /&gt;
&lt;br /&gt;
==MVC в J2EE==&lt;br /&gt;
&lt;br /&gt;
: '''ЧАСТЬ 4''' Название статьи выглядит как китайская грамота? Не волнуйтесь – '''Антон Черноусов''', вооруживщийся книгой Гамма, проведет экспресс-курс по паттернам объектно-ориентированного проектирования.&lt;br /&gt;
&lt;br /&gt;
В предыдущей статье мы говорили о сессионных и контекстных объектах, о том, как создавать и использовать фильтры, и рассмотрели простой пример авторизации пользователя. Сегодня мы рассмотрим паттерн ''MVC'' и его вариацию для создания web-приложений – ''Model2''.&lt;br /&gt;
&lt;br /&gt;
[[Media:AddressBook3.tar.bz2|Скачать исходный код примера]]&lt;br /&gt;
&lt;br /&gt;
===Волшебное слово «паттерн»===&lt;br /&gt;
&lt;br /&gt;
Паттерн ''Model-View-Controller (MVC)'' появился очень давно и достаточно активно использовался в Smalltalk-80. Он предназначен для построения интерфейсов пользователя '''[1]'''.&lt;br /&gt;
&lt;br /&gt;
Прежде чем окунуться в дальнейшее изучение Java, давайте более&lt;br /&gt;
подробно остановимся на слове «паттерн». Паттерн – это стандартный способ эффективного решения той или иной задачи в некотором контексте, но для разработчиков программного обеспечения это слово&lt;br /&gt;
приобрело особое значение. К программному обеспечению этот термин был впервые применен Кристофером Александром '''[2]''', который разработал структуру для документирования паттернов и коллекцию&lt;br /&gt;
паттернов, которые были названы «языками паттернов».&lt;br /&gt;
&lt;br /&gt;
При описании любого паттерна обычно используется следующая структура:&lt;br /&gt;
&lt;br /&gt;
* название и классификация паттерна;&lt;br /&gt;
* назначение;&lt;br /&gt;
* мотивация для использования;&lt;br /&gt;
* рекомендации по применению паттерна;&lt;br /&gt;
* структура паттерна;&lt;br /&gt;
* участники или элементы паттерна;&lt;br /&gt;
* отношения между элементами;&lt;br /&gt;
* результаты применения;&lt;br /&gt;
* описание идеи реализации паттерна;&lt;br /&gt;
* пример кода (по возможности);&lt;br /&gt;
* родственные паттерны.&lt;br /&gt;
&lt;br /&gt;
Представленная выше структура описания паттерна применена в&lt;br /&gt;
книге Э. Гамма, ставшей классическим справочником по паттернам '''[3]'''.&lt;br /&gt;
&lt;br /&gt;
Фактически, паттерн является средством описания общих решений&lt;br /&gt;
распространенных задач, то есть это – абстрактное решение технических задач, с которыми приходится сталкиваться программистам '''[4]'''.&lt;br /&gt;
&lt;br /&gt;
Если сказать еще более простым языком, то паттерн – это стандартное решение стандартной проблемы. Моему коллеге очень нравится следующий пример. Задача: построить кирпичную стену, через&lt;br /&gt;
которую можно видеть; решение: строить эту стену с окном. В связи с&lt;br /&gt;
тем, что количество стандартных задач очень велико, паттернов тоже&lt;br /&gt;
существует достаточно много.&lt;br /&gt;
&lt;br /&gt;
===Что такое ''MVC''?===&lt;br /&gt;
&lt;br /&gt;
''MVC'' – паттерн, представляющий объектно-ориентированный метод&lt;br /&gt;
для разделения логики представления, бизнес-логики и модели данных. ''MVC'' – не просто удачное решение, а общепринятый образец для&lt;br /&gt;
построения современных приложений. Иногда паттерн ''MVC'' называют&lt;br /&gt;
«архитектурой». На рис. 1 представлена схема взаимодействия компонентов ''MVC''.&lt;br /&gt;
&lt;br /&gt;
[[Изображение:LXF92_javaee1.png|Рис. 1]]&lt;br /&gt;
&lt;br /&gt;
'''Рис. 1. Архитектура ''MVC''.'''&lt;br /&gt;
&lt;br /&gt;
Как вы можете видеть на этом рисунке, основными компонентами являются '''Модель (Model), Вид (View) и Контроллер (Controller)'''.&lt;br /&gt;
Компонент '''Модель''' представляет собой совокупную модель данных&lt;br /&gt;
предметной области, реализованной в приложении, например, объект,&lt;br /&gt;
содержащий в себе информацию о счете пользователя. '''Вид''' – это компонент, отвечающий за отображение '''Модели''' и реализацию пользовательского интерфейса. Вся логика по обработке действий пользователя сосредоточена в '''Контроллере'''.&lt;br /&gt;
&lt;br /&gt;
Иными словами, все данные предметной области, участвующие в&lt;br /&gt;
работе приложения, сгруппированы в классы, которые предназначены для хранения данных в процессе обработки – эти классы названы '''Моделью'''. Классы, реализующие пользовательский интерфейс (какой&lt;br /&gt;
бы вид они ни принимали: командная строка, GUI или JSP) именуются&lt;br /&gt;
'''Видом''', а те классы, которые на основании внутренней логики и реакции пользователя принимают решение о выполнении тех или иных&lt;br /&gt;
действий, принято называть '''Контроллером'''.&lt;br /&gt;
&lt;br /&gt;
Зачем нужно использовать паттерн ''MVC''? Основным мотивом является облегчение модификации или настройки каждой части в отдельности. Этот паттерн очень полезен в тех случаях, когда нужно создавать&lt;br /&gt;
компоненты, которые одновременно должны соответствовать требованиям гибкости и удобства сопровождения.&lt;br /&gt;
&lt;br /&gt;
Что не так с гибкостью в приложениях, построенных по другим схемам? В книге Брюса Тейта '''[5]''' уделено достаточно много внимания этой&lt;br /&gt;
проблеме. Вся загвоздка в том, что достаточно часто модель данных&lt;br /&gt;
и ее представление пользователю сильно переплетены между собой,&lt;br /&gt;
что вызывает множество проблем при отладке программ и еще больше – при их модификации. Но немаловажной проблемой является и&lt;br /&gt;
то, что логика работы оказывается рассредоточенной, что опять же не&lt;br /&gt;
сказывается на приложении положительным образом.&lt;br /&gt;
&lt;br /&gt;
===Реализация ''MVC'' в ''J2EE''-приложении.===&lt;br /&gt;
&lt;br /&gt;
Паттерн ''MVC'' был предложен для классических настольных приложений, но он практически без изменений может быть использован и для построения приложений ''J2EE'', хотя здесь имеются свои нюансы.&lt;br /&gt;
Существует две реализации паттерна ''MVC'' для ''J2EE''-приложений – это&lt;br /&gt;
'''Model1''' и '''Model2'''. В качестве '''Вида''' в каждом из них используются ''JSP'', в&lt;br /&gt;
качестве '''Модели''' – ''JavaBean''. Основное отличие подходов заключается&lt;br /&gt;
в том, каким образом реализован '''Контроллер''': в '''Model1''' – это ''JSP'', а в&lt;br /&gt;
'''Model2''' – ''сервлет''.&lt;br /&gt;
&lt;br /&gt;
Первая реализация основана на логике, которая хранится в страницах. Браузер пользователя поочередно посещает ряд страниц для&lt;br /&gt;
выполнения какого-либо бизнес-процесса. Такая реализация имеет&lt;br /&gt;
следующие недостатки.&lt;br /&gt;
&lt;br /&gt;
* '''1''' Cложно обеспечить разделение труда между дизайнерами и программистами.&lt;br /&gt;
* '''2''' Архитектуру '''Model1''' сложно поддерживать, и она не является гибкой, что особенно критично для больших проектов '''[6]'''.&lt;br /&gt;
&lt;br /&gt;
Исходя из этих предпосылок, давайте более подробно рассмотрим&lt;br /&gt;
реализацию компонентов '''Model2''', которая чаще всего лежит в основе&lt;br /&gt;
web-приложений.&lt;br /&gt;
&lt;br /&gt;
===Модель – ''JavaBeans''===&lt;br /&gt;
&lt;br /&gt;
Прежде всего, '''Модель''' в ''J2EE''-приложениях, как и обычных приложениях, удобно реализовывать в виде совокупности ''JavaBeans''. ''JavaBean''’ом&lt;br /&gt;
может называться любой класс Java, который удовлетворяет достаточно простым требованиям:&lt;br /&gt;
&lt;br /&gt;
* все атрибуты класса должны быть защищенными ('''protected''');&lt;br /&gt;
* для каждого атрибута, предназначенного для чтения, должен быть реализован метод вида: '''PropertyClass getPropertyName(){...}''';&lt;br /&gt;
* для каждого атрибута, предоставляющего возможность записи должен быть реализован метод вида: '''setPropertyName(PropertyClass propertyName){...}''';&lt;br /&gt;
* обязательно должен быть реализован конструктор без параметров.&lt;br /&gt;
&lt;br /&gt;
Для примера давайте разработаем небольшое web-приложение,&lt;br /&gt;
отображающее новости. Пусть Моделью приложения будут JavaBean-объекты, инкапсулирующие в себе сведения о новостях. Объект '''News''',&lt;br /&gt;
содержащий в себе одну-единственную новость, является простым&lt;br /&gt;
объектом ''JavaBean'' и имет только два метода (чтение и запись) для&lt;br /&gt;
всех своих атрибутов:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
 public class News {&lt;br /&gt;
    protected String caption;&lt;br /&gt;
    protected String message;&lt;br /&gt;
    protected Date date;&lt;br /&gt;
    public String getCaption() {&lt;br /&gt;
       return caption;&lt;br /&gt;
    }&lt;br /&gt;
 }&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Второй объект, логически завершающий нашу модель данных, –&lt;br /&gt;
это '''NewsTape''', который помимо обязательных методов, необходимых&lt;br /&gt;
''JavaBean'', имеет дополнительные методы, расширяющие его функциональность, что не запрещено:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
 public void addNews(News news) {&lt;br /&gt;
       if (news != null) {&lt;br /&gt;
           AllNews.add(news);&lt;br /&gt;
       }&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Связь Вида и Контроллера===&lt;br /&gt;
&lt;br /&gt;
Для реализации Вида нам потребуется создать две JSP, одну – для&lt;br /&gt;
отображения новостей ('''view.jsp'''), другую – для их добавления ('''edit.jsp''').&lt;br /&gt;
Вторая процедура содержит небольшую форму:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
 &amp;lt;form action=”&amp;lt;%=request.getContextPath()%&amp;gt;/&lt;br /&gt;
    &amp;lt;%=request.getAttribute(“action”)%&amp;gt;” method=”post”&amp;gt;&lt;br /&gt;
           &amp;lt;input type=”hidden” name=”edited”&lt;br /&gt;
              value=”&amp;lt;%=request.getAttribute(“edit.number”)%&amp;gt;”/&amp;gt;&lt;br /&gt;
         &amp;lt;table&amp;gt;&lt;br /&gt;
              &amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;Заговок: &amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&amp;lt;input type=”text” name=”caption”&lt;br /&gt;
  value=”&amp;lt;%=request.getAttribute(“edit.caption”)%&amp;gt;”/&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&lt;br /&gt;
              &amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;Сообщение: &amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&amp;lt;input type=”text”&lt;br /&gt;
 name=”message”&lt;br /&gt;
  value=”&amp;lt;%=request.getAttribute(“edit.message”)%&amp;gt;”/&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&lt;br /&gt;
              &amp;lt;tr&amp;gt;&amp;lt;td colspan=”2” align=”center”&amp;gt;&amp;lt;input type=”submit”&lt;br /&gt;
  name=”Отправить”/&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&lt;br /&gt;
         &amp;lt;/table&amp;gt;&lt;br /&gt;
 &amp;lt;/form&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Для реализации контроллера нам необходимо создать сервлет,&lt;br /&gt;
который в соответствии с запросом пользователя производит выбор&lt;br /&gt;
необходимого Вида, то есть JSP, например, при помощи следующего&lt;br /&gt;
метода:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
    private void handle(HttpServletRequest aRequest, HttpServletResponse&lt;br /&gt;
 aResponse)&lt;br /&gt;
  throws ServletException, IOException {&lt;br /&gt;
       aRequest.setCharacterEncoding(“utf-8”);&lt;br /&gt;
       String target =&lt;br /&gt;
 aRequest.getRequestURI().substring(aRequest.getContextPath().length());&lt;br /&gt;
       if (target.equals(“/”)) {&lt;br /&gt;
           outputPage(“index.jsp”, aRequest, aResponse);&lt;br /&gt;
       } else if (“/add”.equals(target)) {&lt;br /&gt;
           handleAdd(aRequest, aResponse);&lt;br /&gt;
       } else if (“/view”.equals(target)) {&lt;br /&gt;
           handleView(aRequest, aResponse);&lt;br /&gt;
       }&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Вы скажете: «Эй! Да мы подобное уже делали!» И я вам отвечу:&lt;br /&gt;
«Правильно, вы уже реализовывали контроллер на основе сервлета».&lt;br /&gt;
'''Контроллер''' определяет поведение приложения, то есть интерпретирует действия пользователя и обеспечивает изменение состояния модели&lt;br /&gt;
или выбор другого представления, или то и другое одновременно.&lt;br /&gt;
&lt;br /&gt;
Из-за особенностей работы web-приложений, выбор '''Вида''' из&lt;br /&gt;
'''Контроллера''' можно произвести двумя способами: вызвав метод&lt;br /&gt;
'''forward''' у экземпляра класса '''RequestDispatcher''' или вызвав метод&lt;br /&gt;
'''sendRedirect''' у экземпляра класса '''HttpServletResponse'''. В то же время,&lt;br /&gt;
воздействия пользователей на «'''Контроллер'''», в основном, могут быть&lt;br /&gt;
переданы посредствам запросов, то есть нажатием на ссылки, или в&lt;br /&gt;
качестве результатов работы форм. Управление работой приложения&lt;br /&gt;
происходит благодаря методу '''handle''' нашего сервлета. Такой вид организации обработки принято назвать «реализация стратегии '''Servlet Front''' паттерна '''Front Controller'''». Хотя, если быть более точным, реализован подвид стратегии '''Servlet Front – Dispatcher in Controller'''.&lt;br /&gt;
&lt;br /&gt;
Видимо, такое обилие терминов сбивает с толку, поэтому давайте&lt;br /&gt;
вкратце рассмотрим стратегии реализации паттерна '''Front Controller''',&lt;br /&gt;
предназначенные для реализации «'''Контроллера'''» в паттерне ''MVC''.&lt;br /&gt;
* '''1''' Стратегия '''Servlet Front''' фактически соответствует поведению контроллера в архитектуре '''Model2''', описанной выше. «'''Контроллер'''» редко выполняет все функции, но в случае, если это происходит, и функциональность, отвечающая за перенаправление пользователя к другим «'''Видам'''», полностью реализована в нем, то такая реализация называется '''Dispatcher in Controller'''. Еще один подвид данной стратегии – '''Base Front''', она отличается тем, что реализован не один контроллер, а несколько, причем все они расширяют некий базовый контроллер, в котором реализована базовая функциональность. Применение этой стратегии не всегда оправдано.&lt;br /&gt;
* '''2''' Стратегия '''JSP Front''' фактически соответствует поведению контроллера в архитектуре '''Model1''', описанной выше, и мы не будем на ней останавливаться, потому как она практически нигде не применяется.&lt;br /&gt;
* '''3''' '''Mapping Controller''' – это стратегия, которая тем или иным образом применяется при построении практически любого web-приложения. Технология реализована через дескриптор развертывания приложе ния и имеет следующие подвиды: '''Physical Resource Mapping''' – когда ресурс отображается по месту его физического расположения в каталогах, '''Logical Resource Mapping''' – когда ресурс отображается в соот ветствии с некоторой логикой группирования функции приложения, и '''Multiplexed Resource Mapping''' – это смешивание обоих методов разме щения ресурсов.&lt;br /&gt;
* '''4''' Стратегия '''Filter Controller''' – это реализация контроллера в виде фильтра, который перехватывает все запросы пользователя и в соответствии с внутренней логикой обеспечивает тот или иной бизнеспроцесс.&lt;br /&gt;
* '''5''' '''Command and Controller''' – это стратегия, основанная на применении двух замечательных паттернов, '''Command''' и '''Factory Method''', и, на мой взгляд, одна из самых удачных стратегий. Ее применение мы обсудим в одной из наших следующих статей.&lt;br /&gt;
&lt;br /&gt;
[[Изображение:LXF92_javaee2.png|Рис. 2]]&lt;br /&gt;
&lt;br /&gt;
''' Рис. 2. Схема взаимодействия некоторых объектов электронного магазина.'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Извещение об изменениях===&lt;br /&gt;
&lt;br /&gt;
Последний вопрос, который необходимо осветить для полного обзора&lt;br /&gt;
паттерна ''MVC'' – это извещение '''Вида'' об изменениях состояния '''Модели'''.&lt;br /&gt;
К сожалению, в большинстве случаев в ''J2EE'' нельзя выполнить оповещение «'''Вида'''» за исключением случаев, когда приложение построено&lt;br /&gt;
с применением ряда современных технологий. Примером такой кооперации может служить ''AJAX''.&lt;br /&gt;
&lt;br /&gt;
И все же, давайте рассмотрим механизм оповещения одного объекта об изменениях, произошедших в другом объекте. Реализация&lt;br /&gt;
механизма оповещения необходима в следующем случае: существует&lt;br /&gt;
как минимум один объект, рассылающий уведомления, и имеется не&lt;br /&gt;
менее одного получателя сообщений, причем количество сообщений&lt;br /&gt;
и их состав может меняться во время работы, а также различаться у&lt;br /&gt;
разных элементов приложения.&lt;br /&gt;
&lt;br /&gt;
Классическим примером такого поведения может служить электронный магазин, где необходимо обеспечить взаимодействие корзин&lt;br /&gt;
покупателей, их счетов и склада с товаром. На рис. 2 представлена схема взаимодействия рассмотренных объектов. Пользователь выбирает&lt;br /&gt;
товар, и он помещается в корзину, корзина уведомляет объект – счет,&lt;br /&gt;
где формируется стоимость товара и доставки, и склад, где происходит бронирование или подготовка к дополнительному заказу товара у поставщиков.&lt;br /&gt;
&lt;br /&gt;
В основе описанного процесса лежит реализация паттерна&lt;br /&gt;
''Observer''. Его цель – это организовать зависимость «один ко многим» между объектами так, чтобы при изменении состояния одного&lt;br /&gt;
объекта автоматически уведомлялись и обновлялись все его зависимые объекты.&lt;br /&gt;
&lt;br /&gt;
Считается, что ''Observer'' является одним из главных паттернов&lt;br /&gt;
объектно-ориентированного программирования, по крайней мере, он&lt;br /&gt;
широко применяется в настольных приложениях. Особенно хорошо его&lt;br /&gt;
влияние видно в приложениях ''Swing''. Для применения этого шаблона&lt;br /&gt;
необходимо ответить на следующие вопросы:&lt;br /&gt;
&lt;br /&gt;
* '''1''' Кто является объектом наблюдения, а кто наблюдателем?&lt;br /&gt;
* '''2''' Когда объект наблюдения должен послать уведомление своим наблюдателям?&lt;br /&gt;
* '''3''' Что должен делать наблюдатель при получении уведомления?&lt;br /&gt;
* '''4''' Как должно начинаться и как заканчиваться взаимодействие для организации наблюдения?&lt;br /&gt;
&lt;br /&gt;
Естественно, что на все эти вопросы уже найдены ответы, поэтому давайте рассмотрим рис. 3, на котором изображена диаграмма классов паттерна ''Observer''.&lt;br /&gt;
&lt;br /&gt;
[[Изображение:LXF92_javaee3.png|Рис. 3]]&lt;br /&gt;
&lt;br /&gt;
'''Рис. 3. Диаграмма классов паттерна Observer.'''&lt;br /&gt;
&lt;br /&gt;
В диаграмме определены два интерфейса: '''обозреватель (Observer)'''&lt;br /&gt;
и '''объект наблюдения (Observable)'''. Класс, реализующий интерфейс&lt;br /&gt;
'''Observable''', предоставляет методы для подключения и отключения&lt;br /&gt;
'''обозревателей''', содержит в себе текущий список наблюдателей (атрибут '''observers'''), а также имеет метод оповещения всех наблюдателей – '''notifyObserver()'''. Итак, используем интерфейсы для создания небольшого примера:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
 public interface Observer {&lt;br /&gt;
   public void handleEvent();&lt;br /&gt;
 }&lt;br /&gt;
 public interface Observable {&lt;br /&gt;
    public void addObserver(Observer o);&lt;br /&gt;
    public void removeObserver(Observer o);&lt;br /&gt;
    public void notyfyObserver();&lt;br /&gt;
 }&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Создадим объект наблюдения – пусть это будет совсем простенькая&lt;br /&gt;
модель погоды, которая содержит в себе сведения о температуре окружающей среды (атрибут '''temperature'''):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
 public class Weather implements Observable {&lt;br /&gt;
    private Set&amp;lt;Observer&amp;gt; observers = new HashSet();&lt;br /&gt;
    private int temperature;&lt;br /&gt;
    public void addObserver(Observer o) { observers.add(o); }&lt;br /&gt;
    public void removeObserver(Observer o) { observers.remove(o); }&lt;br /&gt;
    public void notyfyObserver() {&lt;br /&gt;
       for (Observer o : observers) {&lt;br /&gt;
           o.handleEvent();&lt;br /&gt;
       }&lt;br /&gt;
    }&lt;br /&gt;
    public Weather() { this.temperature = 0;}&lt;br /&gt;
    public int getTemperature() { return temperature; }&lt;br /&gt;
    public void setTemperature(int temperature) {&lt;br /&gt;
       this.temperature = temperature;&lt;br /&gt;
       notyfyObserver();&lt;br /&gt;
    }&lt;br /&gt;
 }&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Теперь создадим класс для ведения наблюдения за температурой,&lt;br /&gt;
то есть термометр:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
 public class Thermometer implements Observer {&lt;br /&gt;
    private int temperature;&lt;br /&gt;
    private Weather weather;&lt;br /&gt;
    public Thermometer(Weather weather) {&lt;br /&gt;
       this.weather = weather; this.handleEvent();&lt;br /&gt;
    }&lt;br /&gt;
    public int getTemperature() { return temperature; }&lt;br /&gt;
    public void setWeather(Weather weather) {&lt;br /&gt;
       this.weather = weather; this.handleEvent();&lt;br /&gt;
    }&lt;br /&gt;
    public void handleEvent() {&lt;br /&gt;
       if (weather != null) {&lt;br /&gt;
           temperature = weather.getTemperature();&lt;br /&gt;
           System.out.println(“температура : “ + temperature);&lt;br /&gt;
       }&lt;br /&gt;
    }&lt;br /&gt;
 }&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Чтобы проверить работоспособность оповещения, попробуйте&lt;br /&gt;
выполнить следующий код:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
       Weather currentWeather = new Weather();&lt;br /&gt;
       Thermometer theThermometer1 = new Thermometer(currentWeather);&lt;br /&gt;
       Thermometer theThermometer2 = new Thermometer(currentWeather);&lt;br /&gt;
       currentWeather.addObserver(theThermometer1);&lt;br /&gt;
       currentWeather.addObserver(theThermometer2);&lt;br /&gt;
       System.out.println(“Изменение температуры 1”);&lt;br /&gt;
       currentWeather.setTemperature(10);&lt;br /&gt;
       System.out.println(“Изменение температуры 2”);&lt;br /&gt;
       currentWeather.setTemperature(15);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Вы убедитесь в том, что у каждого наблюдателя вызывается&lt;br /&gt;
метод '''handleEvent()''', что влечет за собой изменение его внутреннего&lt;br /&gt;
состояния.&lt;br /&gt;
&lt;br /&gt;
Подведем итоги. В этой статье мы рассмотрели реализацию шаблона ''MVC'' в ''J2EE'' и ряда других паттернов, которые позволяют реализовывать масштабируемые приложения и разобрались в требованиях,&lt;br /&gt;
предъявляемых к классам так называемых ''JavaBeans''. Кроме того, мы&lt;br /&gt;
вкратце ознакомились со способами организации '''Контроллера''' и обратили свое внимание на процесс оповещения объектов на основе паттерна ''Observer''. Пожалуй, на сегодня хватит. '''LXF'''&lt;br /&gt;
===  Литература  ===&lt;br /&gt;
&lt;br /&gt;
* '''1''' Krasner G.E., Pope S.T. A Cookbook for Using the Model-View-Controller User Interface Paradigm in Smalltalk-80.// Journal of Object-Oriented Programming. – 1988. – № 3, ч. 1. – С. 26-49.&lt;br /&gt;
&lt;br /&gt;
* '''2''' Alexander C., Ishikawa S., Silverstein M., Jacobson M., Fiksdahl-King I. and Angel S. A Pattern Language. – New York: Oxford University Press, 1977. – 1216 с.&lt;br /&gt;
&lt;br /&gt;
* '''3''' Гамма Э., Хелм Р., Джонсон Р., Влиссидес Дж. Приемы объектно-ориентированного проектирования. Паттерны проектирования. – СПб.:  Питер, 2001. – 368 с.&lt;br /&gt;
&lt;br /&gt;
* '''4''' Мак-Карти Д., Мак-Карти М. Программируем командный дух. – СПб.: Символ-Плюс, 2004. – 416 с.&lt;br /&gt;
&lt;br /&gt;
* '''5''' Тейт Б. Горький вкус Java: Библиотека программиста. – СПб.:  Питер, 2003. – 333 с.&lt;br /&gt;
&lt;br /&gt;
* '''6''' Курняван Б. Создание Web-приложений на языке Java. – Москва: Издательство “ЛОРИ”, 2005. – 880 с.&lt;/div&gt;</summary>
		<author><name>Dionysius</name></author>	</entry>

	<entry>
		<id>http://wiki.linuxformat.ru/wiki/%D0%A4%D0%B0%D0%B9%D0%BB:AddressBook3.tar.bz2</id>
		<title>Файл:AddressBook3.tar.bz2</title>
		<link rel="alternate" type="text/html" href="http://wiki.linuxformat.ru/wiki/%D0%A4%D0%B0%D0%B9%D0%BB:AddressBook3.tar.bz2"/>
				<updated>2008-12-30T17:59:02Z</updated>
		
		<summary type="html">&lt;p&gt;Dionysius: Исходный код примера урока JavaEE в LXF92&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Исходный код примера урока JavaEE в LXF92&lt;/div&gt;</summary>
		<author><name>Dionysius</name></author>	</entry>

	<entry>
		<id>http://wiki.linuxformat.ru/wiki/%D0%A4%D0%B0%D0%B9%D0%BB:AddressBook.tar.bz2</id>
		<title>Файл:AddressBook.tar.bz2</title>
		<link rel="alternate" type="text/html" href="http://wiki.linuxformat.ru/wiki/%D0%A4%D0%B0%D0%B9%D0%BB:AddressBook.tar.bz2"/>
				<updated>2008-12-30T17:54:29Z</updated>
		
		<summary type="html">&lt;p&gt;Dionysius: загружена новая версия «Изображение:AddressBook.tar.bz2»: Исходный код примера урока JavaEE в LXF89&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Исходники к уроку JavaEE в LXF89&lt;/div&gt;</summary>
		<author><name>Dionysius</name></author>	</entry>

	<entry>
		<id>http://wiki.linuxformat.ru/wiki/LXF91:Java_EE</id>
		<title>LXF91:Java EE</title>
		<link rel="alternate" type="text/html" href="http://wiki.linuxformat.ru/wiki/LXF91:Java_EE"/>
				<updated>2008-12-30T17:43:44Z</updated>
		
		<summary type="html">&lt;p&gt;Dionysius: /* Процесс передачи информации */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Цикл/Java EE}}&lt;br /&gt;
[[Категория:Учебники]]&lt;br /&gt;
__TOC__&lt;br /&gt;
:В прошлый раз вы, под чутки руководством '''Александра''', разобрались с JSP и переписали &amp;quot;Телефонную книгу&amp;quot; с учетом этой технологии. Сегодня мы двинемся дальше и поговорим об авторизации, сессиях, фильтрах и использовании разделяемых объектов в web-приложениях.&lt;br /&gt;
&lt;br /&gt;
:''{{oncolor||red|ЧАСТЬ 3}} Каждый линусоид знает, что система уровня предприятия должна обеспечивать разграничение доступа. '''Антон Черноусов''' расскажет, что может предложить здесь Java EE.&lt;br /&gt;
[[Media:AddressBook2.tar.bz2|Скачать исходный код примера]]&lt;br /&gt;
&lt;br /&gt;
==Процесс передачи информации==&lt;br /&gt;
Давайте рассмотрим стандартную ситуацию: пользователь вызывает различные страницы одного web-приложения. Если бы этот &amp;quot;диалог&amp;quot; происходил по телефону, он был бы примерно таким:&lt;br /&gt;
*'''Пользователь (набирает номер): Алло, это приложение?'''&lt;br /&gt;
*Приложение: Да, это приложение.&lt;br /&gt;
*(Пользователь кладет трубку)&lt;br /&gt;
*'''Пользователь (набирает номер): Меня зовут Георг.'''&lt;br /&gt;
*Приложение: Да, Георг, мы вас внимательно слушаем.&lt;br /&gt;
*(Пользователь кладет трубку)&lt;br /&gt;
*'''Пользователь (набирает номер): Дайте мне, пожалуйста, всю информацию.'''&lt;br /&gt;
*Приложение: Возьмите...&lt;br /&gt;
Отметим, что после каждого обмена репликами пользователь кладет трубку, разрывая соединение - именно таким образом в большинстве случаев и происходит обмен данными между браузером пользователя и сервером, на котором работает web-приложение. Возникает резонный вопрос: как передавать информацию между соединениями, открываемые в рамках одной сессии? Для этого могут использоваться самые различные методы:&lt;br /&gt;
*перезапись URL;&lt;br /&gt;
*скрытые поля;&lt;br /&gt;
*cookie;&lt;br /&gt;
*сессионный объект.&lt;br /&gt;
Из всех представленных методов наиболее простым и в тоже время можным является сессионный объект (Session), реализованный в Java посредством интерфейса {{oncolor||red|javax.servlet.http.Session}}.&lt;br /&gt;
Ранее, чтобы получить данные от пользователя или обеспечить возможность их транспортировки внутри приложения, мы использовали объект типа &lt;br /&gt;
{{oncolor||red|HttpServletRequest}}. Основное отличие {{oncolor||red|HttpSession}} заключается во времени жизни, схематично изображенном на '''Рис.1'''. Объект типа {{oncolor||red|HttpServletRequest}} предназначен для передачи запроса (и необходимых данных) от браузера к web-приложению и существует только между запросом и ответом, в то время как объект {{oncolor||red|HttpSession}} обеспечивает средства для хранения и доступа к данным пользователя на протяжениии всего периода работы с приложением.&lt;br /&gt;
)(Рис.1) Время жизни объектов HttpServletRequest и HttpSession.&lt;br /&gt;
Следует, однако, иметь в виду, что отследить момент, когда пользователь перестает работать с приложением, не всегда возможно. Поэтому в настройках сервера устанавливается некоторое предельное время существования объекта {{oncolor||red|HttpSession}} после получения последнего запроса.&lt;br /&gt;
&lt;br /&gt;
[[Изображение:LXF91_javaee1.png|Рис. 1]]&lt;br /&gt;
&lt;br /&gt;
'''Время жизни объектов ''HttpServletRequest'' и ''HttpSession'''''&lt;br /&gt;
&lt;br /&gt;
==Сессия==&lt;br /&gt;
Объект session в JSP является предопределеным, то есть с ним можно сразу же начинать работать. Чтобы создать сессионный объект в сервлете, воспользуемся следующим методом:&lt;br /&gt;
&lt;br /&gt;
 HttpSession session = aRequest.getSession(true);&lt;br /&gt;
&lt;br /&gt;
Здесь '''aRequest''' – это экземпляр объекта '''HttpServletRequest''', то есть&lt;br /&gt;
запрос переданный сервлету.&lt;br /&gt;
&lt;br /&gt;
Классы, реализующие интерфейс '''HttpSession''', имеют два замечательных метода: '''setAttribute(String, Object)''' и '''getAttribute(String)'''. Метод&lt;br /&gt;
'''setAttribute(String, Object)''' применяется для добавления объекта в сессию, а '''getAttribute(String)''' – для извлечения объекта из нее. Например:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
 session.setAttribute(“sameKey”,sameObject);&lt;br /&gt;
 SameObject aObject = (SameObject) session.getAttribute(“sameKey”);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Фактически, объект '''session''' – это таблица '''Hashtable''', в которой можно хранить любое количество пар типа «ключ – объект». При использовании сессионного объекта данные приложения не отправляются&lt;br /&gt;
пользователю так, как это происходит с cookie. Сессионный объект&lt;br /&gt;
хранится на сервере персонально для каждого клиента. Сервер различает сессии с помощью маркера, который передается пользователю.&lt;br /&gt;
Маркер хранится в cookies браузера до конца сессии, что накладывает&lt;br /&gt;
некоторые ограничения на клиентское рабочее место (использование&lt;br /&gt;
cookies для вашего приложения должно быть разрешено). В этом можно легко убедиться, просмотрев сохраненные cookies в вашем любимом браузере.&lt;br /&gt;
&lt;br /&gt;
[[Изображение:LXF91_javaee2.png|Рис. 2]]&lt;br /&gt;
&lt;br /&gt;
'''Маркер сессии. Герои романа «''Лабиринт отражений''» пытались повесить друг другу маркер под видом невинного поцелуя, а наше web-приложение действует еще хитрее и незаметнее.'''&lt;br /&gt;
&lt;br /&gt;
==Авторизация==&lt;br /&gt;
&lt;br /&gt;
Логично предположить, что чаще всего сессии применяются там, где&lt;br /&gt;
необходимо авторизовать пользователя и предоставлять доступ к&lt;br /&gt;
определенным функциям приложения только при наличии соответствующих привилегий. Давайте обратимся к нашей телефонной книге:&lt;br /&gt;
ее просмотр разрешен всем желающим, а для добавления нового телефона, редактирования и удаления записи необходима авторизация.&lt;br /&gt;
&lt;br /&gt;
Давайте создадим JSP-страницу '''auth.jsp''', которая будет запрашивать у пользователя имя и пароль. Сохраните файл в каталоге '''jsps''' и введите в него следующий текст:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
&amp;lt;span style=”color: green;”&amp;gt;&amp;lt;%=request.getAttribute(“message”)%&amp;gt;&lt;br /&gt;
&amp;lt;/span&amp;gt;&lt;br /&gt;
&amp;lt;form action=”&amp;lt;%=request.getContextPath()%&amp;gt;/auth” method=”post”&amp;gt;&lt;br /&gt;
        &amp;lt;table&amp;gt;&lt;br /&gt;
               &amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;Логин: &amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&amp;lt;input type=”text” name=”login”/&amp;gt;&lt;br /&gt;
&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&lt;br /&gt;
               &amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;Пароль: &amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&lt;br /&gt;
                         &amp;lt;input type=”password” name=”password”/&amp;gt;&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
               &amp;lt;tr&amp;gt;&amp;lt;td colspan=”2” align=”center”&amp;gt;&lt;br /&gt;
                        &amp;lt;input type=”submit” value=”Авторизоваться”/&amp;gt;&amp;lt;/td&amp;gt;&lt;br /&gt;
&amp;lt;/tr&amp;gt;&lt;br /&gt;
           &amp;lt;/table&amp;gt;&lt;br /&gt;
&amp;lt;/form&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Легко видеть, что в форме имеется два поля ввода и одна кнопка.&lt;br /&gt;
Заметим также, что поле для ввода пароля имеет тип '''password''', так что&lt;br /&gt;
вместо нажатых клавиш в окне браузера будут отображаться до боли&lt;br /&gt;
знакомые «звездочки» ('''‘*’''').&lt;br /&gt;
&lt;br /&gt;
Теперь внесем изменения в '''AddressBookServlet''': расширим ветвление в методе '''handle''' следующим образом:&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
 }else if (“/auth”.equals(target)) {&lt;br /&gt;
           handleAuth(aRequest, aResponse);&lt;br /&gt;
       }&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Дело за малым – осталось написать метод '''handleAuth(aRequest,aResponse)''', который и будет обрабатывать данные, введенные в форму, то есть производить авторизацию пользователя.&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
    private void handleAuth(HttpServletRequest aRequest,&lt;br /&gt;
               HttpServletResponse aResponse) throws IOException,&lt;br /&gt;
 ServletException {&lt;br /&gt;
       String login = aRequest.getParameter(“login”);&lt;br /&gt;
       String password = aRequest.getParameter(“password”);&lt;br /&gt;
       if ((login != null) &amp;amp;&amp;amp; (password != null)) {&lt;br /&gt;
           // here is auth process&lt;br /&gt;
           if ((login.equals(“user”)) &amp;amp;&amp;amp; (password.equals(“userPass”))) {&lt;br /&gt;
               //here is success auth&lt;br /&gt;
               HttpSession session = aRequest.getSession(true);&lt;br /&gt;
               session.setAttribute(“auth”,aRequest.getParameter(“login”));&lt;br /&gt;
               aRequest.setAttribute(“message”, null);&lt;br /&gt;
               outputPage(“index.jsp”, aRequest, aResponse);&lt;br /&gt;
           } else {&lt;br /&gt;
               //here is failed auth&lt;br /&gt;
               aRequest.setAttribute(“message”,&lt;br /&gt;
                            “Неверный логин или пароль, повторите ввод&lt;br /&gt;
 данных”);&lt;br /&gt;
               outputPage(“auth.jsp”, aRequest, aResponse);&lt;br /&gt;
           }&lt;br /&gt;
       } else {&lt;br /&gt;
           //here is no data to auth&lt;br /&gt;
           aRequest.setAttribute(“message”,&lt;br /&gt;
                        “Логин и пароль не могут быть пустыми, повторите&lt;br /&gt;
 ввод данных”);&lt;br /&gt;
           outputPage(“auth.jsp”, aRequest, aResponse);&lt;br /&gt;
      }&lt;br /&gt;
   }&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Чтобы не усложнять приложение, мы используем простейший&lt;br /&gt;
способ авторизации – имя пользователя и пароль жестко зашиты в&lt;br /&gt;
теле метода. С точки зрения безопасности и масштабируемости лучше&lt;br /&gt;
использовать для хранения этих данных БД.&lt;br /&gt;
&lt;br /&gt;
В случае удачной авторизации мы помещаем имя пользователя в&lt;br /&gt;
объект '''session''' и перенаправляем его на главную страницу. Если же&lt;br /&gt;
авторизация не удалась (введено неверное имя пользователя и/или&lt;br /&gt;
пароль), пользователю будет предложено повторить попытку.&lt;br /&gt;
&lt;br /&gt;
Таким образом, если авторизация прошла успешно, в сессии пользователя будет сохранен объект, содержащий его имя. Используя этот&lt;br /&gt;
факт, можно изменить '''index.jsp''' и спрятать ссылку на добавление нового телефона от посторонних глаз:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
        &amp;lt;%String name = (String)session.getAttribute(“auth”);&lt;br /&gt;
         if (name != null){ %&amp;gt;&lt;br /&gt;
              &amp;lt;p&amp;gt;Вы авторизованны как: &amp;lt;%=name%&amp;gt; &amp;lt;/p&amp;gt;&lt;br /&gt;
              &amp;lt;a href=”&amp;lt;%=request.getContextPath()%&amp;gt;/add”&amp;gt;Добавить&lt;br /&gt;
 запись&amp;lt;/a&amp;gt;&amp;lt;br/&amp;gt;&lt;br /&gt;
         &amp;lt;%} else {%&amp;gt;&lt;br /&gt;
             &amp;lt;a href=”&amp;lt;%=request.getContextPath()%&amp;gt;/auth”&amp;gt;&lt;br /&gt;
 Авторизоваться&amp;lt;/a&amp;gt;&amp;lt;br/&amp;gt;&lt;br /&gt;
         &amp;lt;%} %&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Мы приветствуем пользователя, используя значение атрибута '''auth'''&lt;br /&gt;
и предоставляем ему доступ к функции добавления нового контакта.&lt;br /&gt;
Чтобы спрятать удаление и редактирование записей от неавторизованного пользователя, необходимо внести аналогичные изменения в&lt;br /&gt;
файл '''view.jsp'''.&lt;br /&gt;
&lt;br /&gt;
==Фильтры==&lt;br /&gt;
&lt;br /&gt;
Наше приложение, к сожалению, страдает проблемами безопасности:&lt;br /&gt;
злонамеренный пользователь может обойти процедуру авторизации,&lt;br /&gt;
обратившись к функциям редактирования/удаления напрямую, по&lt;br /&gt;
адресу '''request.getContextPath() + действие (/add; /edit; /remove)'''; можно также непосредственно вызывать JSP-страницы, расположенные в каталоге '''/jsps''' – нужно только узнать их имена.&lt;br /&gt;
&lt;br /&gt;
Что же делать? Проверять, авторизован ли пользователь перед&lt;br /&gt;
выполнением привилегированного действия? Это неудобно – если&lt;br /&gt;
количество JSP будет расти, это заставит вас написать много строк&lt;br /&gt;
однотипного кода, поддержание которых будет отнимать ваше драгоценное время. Мы пойдем другим путем и воспользуемся так называемыми фильтрами.&lt;br /&gt;
&lt;br /&gt;
Схема работы фильтров представлена на Рис. 3. Если для адреса,&lt;br /&gt;
на который отображен сервлет, осуществляется фильтрация, запрос&lt;br /&gt;
будет передан сервлету только после того, как пройдет через каждый&lt;br /&gt;
установленный фильтр.&lt;br /&gt;
&lt;br /&gt;
[[Изображение:LXF91_javaee3.png|Рис. 3]]&lt;br /&gt;
&lt;br /&gt;
'''Схема прохождения запроса через фильтры.'''&lt;br /&gt;
&lt;br /&gt;
На самом деле, фильтр представляет из себя обыкновенный Java-&lt;br /&gt;
класс, реализующий интерфейс '''javax.servlet.Filter'''. Например:&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
 public class FirstFilter implements Filter {&lt;br /&gt;
   private FilterConfig filterConfig;&lt;br /&gt;
   public void init(FilterConfig filterConfig) throws ServletException {&lt;br /&gt;
       System.out.println(“Filter init”);&lt;br /&gt;
       this.filterConfig = filterConfig;&lt;br /&gt;
   }&lt;br /&gt;
   public void doFilter(ServletRequest aRequest, ServletResponse&lt;br /&gt;
 aResponse, FilterChain filterChain)&lt;br /&gt;
   throws IOException, ServletException {&lt;br /&gt;
       System.out.println(“Filter used”);&lt;br /&gt;
       filterChain.doFilter(aRequest, aResponse);&lt;br /&gt;
   }&lt;br /&gt;
   public void destroy() {&lt;br /&gt;
       System.out.println(“Filter dead”);&lt;br /&gt;
       this.filterConfig = null;&lt;br /&gt;
   }&lt;br /&gt;
 }&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Реализовав всего три метода: '''init''' (инициализация фильтра в&lt;br /&gt;
момент старта приложения), '''destroy''' (освобождение ресурсов перед&lt;br /&gt;
завершением работы приложения) и '''doFilter''' (собственно метод, выполняющий фильтрацию), вы получаете класс, способный существенным образом повлиять на работу вашего приложения.&lt;br /&gt;
&lt;br /&gt;
Обратите внимание, что в приведенном выше примере метод&lt;br /&gt;
'''doFilter''' заканчивается вызовом метода '''doFilter(aRequest, aResponse)'''&lt;br /&gt;
объекта '''filterChain''' – это обеспечивает вызов следующего фильтра в&lt;br /&gt;
цепочке (естественно, если фильтров несколько). Если фильтров больше нет, то управление будет передано следующему ресурсу, например, сервлету.&lt;br /&gt;
&lt;br /&gt;
Остановимся подробнее на методе '''init''', а точнее на объекте, реализующем интерфейс '''FilterConfig'''. Этот объект имеет четыре замечательных метода:&lt;br /&gt;
* '''getFilterName()''' – возвращает имя фильтра;&lt;br /&gt;
* '''getInitParameterNames()''' – возвращает объект '''Enumeration''', который содержит в своем теле имена параметров текущего фильтра;&lt;br /&gt;
* '''getInitParameter(String)''' – возвращает значение параметра, имя которого было передано в качестве аргумента;&lt;br /&gt;
* '''getServletContext()''' – возвращает объект '''ServletContext''', о котором мы поговорим ниже.&lt;br /&gt;
&lt;br /&gt;
Как вы уже, наверное, поняли, объект '''FilterConfig''' позволяет получить доступ к конфигурации фильтра, которая была задана при его объявлении в дескрипторе развертывания ('''web.xml''').&lt;br /&gt;
&lt;br /&gt;
==Ограничение доступа==&lt;br /&gt;
&lt;br /&gt;
Итак, воспользуемся фильтрами для ограничения доступа к ресурсам&lt;br /&gt;
нашего приложения. Прежде всего, запретим пользователю обращаться напрямую к JSP, и, при попытке запросить ресурс из каталога '''/jsps''',&lt;br /&gt;
заставим его перейти на индексную (первую) страницу. Для этого&lt;br /&gt;
напишем простой фильтр '''AccessFilter'''. Метод '''doFilter''' будет выглядеть&lt;br /&gt;
следующим образом:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
   public void doFilter(ServletRequest aRequest,&lt;br /&gt;
           ServletResponse aResponse, FilterChain filterChain)&lt;br /&gt;
           throws IOException, ServletException {&lt;br /&gt;
              ((HttpServletResponse)aResponse).&lt;br /&gt;
            sendRedirect(((HttpServletRequest)aRequest).getContextPath());&lt;br /&gt;
   }&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Благодаря методу '''sendRedirect''', вместо ожидаемого ресурса неавторизованный пользователь увидит '''index.jsp'''. Чтобы фильтр заработал, его необходимо подключить в дескрипторе развертывания. Это делается при помощи следующих строк:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
   &amp;lt;filter&amp;gt;&lt;br /&gt;
       &amp;lt;filter-name&amp;gt;AccessFilterName&amp;lt;/filter-name&amp;gt;&lt;br /&gt;
       &amp;lt;filter-class&amp;gt;AccessFilter&amp;lt;/filter-class&amp;gt;&lt;br /&gt;
   &amp;lt;/filter&amp;gt;&lt;br /&gt;
   &amp;lt;filter-mapping&amp;gt;&lt;br /&gt;
      &amp;lt;filter-name&amp;gt;AccessFilterName&amp;lt;/filter-name&amp;gt;&lt;br /&gt;
      &amp;lt;url-pattern&amp;gt;/jsps/*&amp;lt;/url-pattern&amp;gt;&lt;br /&gt;
   &amp;lt;/filter-mapping&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В секции '''filter''' мы объявляем, что имени фильтра '''AccessFilterName'''&lt;br /&gt;
соответствует класс '''AccessFilter''', а в секции '''filter-mapping''' указываем,&lt;br /&gt;
на какие объекты распространяется зона действия фильтра. В данном случае фильтр работает по шаблону, то есть используется для&lt;br /&gt;
всех адресов типа '''/jsps/*''', где вместо звездочки может быть все, что&lt;br /&gt;
угодно.&lt;br /&gt;
&lt;br /&gt;
Теперь давайте создадим класс '''AuthFilter''', который будет ограничивать доступ к некоторым действиям для неавторизованных пользователей. Метод '''doFilter''' будет выглядеть следующим образом:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
   public void doFilter(ServletRequest aRequest,&lt;br /&gt;
          ServletResponse aResponse, FilterChain filterChain)&lt;br /&gt;
          throws IOException, ServletException {&lt;br /&gt;
      HttpSession session = ((HttpServletRequest) aRequest).&lt;br /&gt;
 getSession();&lt;br /&gt;
      String name = (String) session.getAttribute(“auth”);&lt;br /&gt;
      if (name != null) {&lt;br /&gt;
          filterChain.doFilter(aRequest, aResponse);&lt;br /&gt;
      } else {&lt;br /&gt;
          aRequest.setAttribute(“action”, “auth”);&lt;br /&gt;
          RequestDispatcher dispatcher = aRequest.getRequestDispatcher(“/&lt;br /&gt;
 auth”);&lt;br /&gt;
          dispatcher.forward(aRequest, aResponse);&lt;br /&gt;
      }&lt;br /&gt;
   }&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Если в текущей сессии не задан атрибут '''auth''', пользователь будет&lt;br /&gt;
отправлен на страничку авторизации. Добавим в дескриптор развертывания следующие строки:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
   &amp;lt;filter&amp;gt;&lt;br /&gt;
      &amp;lt;filter-name&amp;gt;AuthEditFilter&amp;lt;/filter-name&amp;gt;&lt;br /&gt;
      &amp;lt;filter-class&amp;gt;AuthFilter&amp;lt;/filter-class&amp;gt;&lt;br /&gt;
   &amp;lt;/filter&amp;gt;&lt;br /&gt;
   &amp;lt;filter-mapping&amp;gt;&lt;br /&gt;
      &amp;lt;filter-name&amp;gt;AuthEditFilter&amp;lt;/filter-name&amp;gt;&lt;br /&gt;
      &amp;lt;url-pattern&amp;gt;/edit&amp;lt;/url-pattern&amp;gt;&lt;br /&gt;
   &amp;lt;/filter-mapping&amp;gt;&lt;br /&gt;
   &amp;lt;filter&amp;gt;&lt;br /&gt;
      &amp;lt;filter-name&amp;gt;AuthAddFilter&amp;lt;/filter-name&amp;gt;&lt;br /&gt;
      &amp;lt;filter-class&amp;gt;AuthFilter&amp;lt;/filter-class&amp;gt;&lt;br /&gt;
   &amp;lt;/filter&amp;gt;&lt;br /&gt;
   &amp;lt;filter-mapping&amp;gt;&lt;br /&gt;
      &amp;lt;filter-name&amp;gt;AuthAddFilter&amp;lt;/filter-name&amp;gt;&lt;br /&gt;
      &amp;lt;url-pattern&amp;gt;/add&amp;lt;/url-pattern&amp;gt;&lt;br /&gt;
   &amp;lt;/filter-mapping&amp;gt;&lt;br /&gt;
   &amp;lt;filter&amp;gt;&lt;br /&gt;
      &amp;lt;filter-name&amp;gt;AuthRemFilter&amp;lt;/filter-name&amp;gt;&lt;br /&gt;
      &amp;lt;filter-class&amp;gt;AuthFilter&amp;lt;/filter-class&amp;gt;&lt;br /&gt;
   &amp;lt;/filter&amp;gt;&lt;br /&gt;
   &amp;lt;filter-mapping&amp;gt;&lt;br /&gt;
      &amp;lt;filter-name&amp;gt;AuthRemFilter&amp;lt;/filter-name&amp;gt;&lt;br /&gt;
      &amp;lt;url-pattern&amp;gt;/remove&amp;lt;/url-pattern&amp;gt;&lt;br /&gt;
   &amp;lt;/filter-mapping&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Обратите внимание, что хотя наш фильтр реализован одним-единственным классом, он может быть доступен по нескольким именам (в&lt;br /&gt;
нашем случае: '''AuthEditFilter, AuthAddFilter, AuthRemFilter''') и работать&lt;br /&gt;
для разных URL.&lt;br /&gt;
&lt;br /&gt;
Чтобы закрыть тему развертывания фильтров, рассмотрим пример&lt;br /&gt;
настройки параметров фильтра в дескрипторе:&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
      &amp;lt;init-param&amp;gt;&lt;br /&gt;
          &amp;lt;description&amp;gt;&lt;br /&gt;
              That is description&lt;br /&gt;
          &amp;lt;/description&amp;gt;&lt;br /&gt;
          &amp;lt;param-name&amp;gt;&lt;br /&gt;
              sameParamName&lt;br /&gt;
          &amp;lt;/param-name&amp;gt;&lt;br /&gt;
          &amp;lt;param-value&amp;gt;&lt;br /&gt;
              sameParamValue&lt;br /&gt;
          &amp;lt;/param-value&amp;gt;&lt;br /&gt;
       &amp;lt;/init-param&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Секцию '''init-param''' необходимо создавать отдельно для каждого&lt;br /&gt;
параметра, при этом она должна быть размещена внутри секции '''filter'''.&lt;br /&gt;
Обязательными являются подсекции '''param-name''' и '''param-value''', которые задают название параметра и его значение, соответственно.&lt;br /&gt;
&lt;br /&gt;
==Доступ к общим объектам==&lt;br /&gt;
&lt;br /&gt;
Наше приложение уже вполне работоспособно, однако рано или поздно нам захочется расширить его функциональность, и, возможно, для&lt;br /&gt;
этого потребуется уже не один сервлет, а несколько, причем все они&lt;br /&gt;
будут обращаться к некому общему ресурсу (информации или объектам). Например, вы можете захотеть узнать, сколько пользователей&lt;br /&gt;
в данный момент авторизовано в приложении или получить из двух&lt;br /&gt;
несвязанных сервлетов доступ к информации, хранящейся в одном&lt;br /&gt;
файле.&lt;br /&gt;
&lt;br /&gt;
Подобно сессионным объектам, существует и контекстный объект, который обеспечивает доступ к общим ресурсам из разных мест&lt;br /&gt;
приложения. Использование контекстного объекта очень похоже на&lt;br /&gt;
использование сессионного, и получить его экземпляр в сервлете можно следующим образом:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=java&amp;gt;&lt;br /&gt;
 ServletContext sc = this.getServletContext();&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Этот объект обладает следующими методами:&lt;br /&gt;
&lt;br /&gt;
* '''getAttribute(String)''' – получение общего объекта по ключу;&lt;br /&gt;
* '''getAttributeNames()''' – получение списка ключей общих объектов;&lt;br /&gt;
* '''setAttribute(String, Object)''' – добавление нового объекта и соответствующего ему ключа;&lt;br /&gt;
* '''removeAttribute(String)''' – удаление объекта, соответствующего ключу, из списка общедоступных объектов;&lt;br /&gt;
&lt;br /&gt;
Несмотря на то, что в этой статье контекстному объекту уделено&lt;br /&gt;
немного внимания, он является одним из мощнейших инструментов&lt;br /&gt;
для обеспечения работы web-приложения.&lt;br /&gt;
&lt;br /&gt;
==Вместо заключения==&lt;br /&gt;
&lt;br /&gt;
Сегодня мы поговорили о сессионных и контекстных объектах, узнали&lt;br /&gt;
как создавать и использовать фильтры, рассмотрели простейший пример авторизации пользователя (конечно, надо сделать оговорку, что&lt;br /&gt;
было представлено весьма небезопасное и не промышленное решение,&lt;br /&gt;
хотя для простого офисного приложения его может быть и достаточно).&lt;br /&gt;
Наконец, мы модифицировали наше web-приложение «Телефонная&lt;br /&gt;
книга», применяя практически все изученные возможности.&lt;br /&gt;
&lt;br /&gt;
В следующий раз мы рассмотрим паттерн ''MVC'' и его вариацию для&lt;br /&gt;
создания web-приложений: ''Model2''. '''LXF'''&lt;/div&gt;</summary>
		<author><name>Dionysius</name></author>	</entry>

	<entry>
		<id>http://wiki.linuxformat.ru/wiki/%D0%A4%D0%B0%D0%B9%D0%BB:AddressBook2.tar.bz2</id>
		<title>Файл:AddressBook2.tar.bz2</title>
		<link rel="alternate" type="text/html" href="http://wiki.linuxformat.ru/wiki/%D0%A4%D0%B0%D0%B9%D0%BB:AddressBook2.tar.bz2"/>
				<updated>2008-12-30T17:40:11Z</updated>
		
		<summary type="html">&lt;p&gt;Dionysius: Исходный код примера урока JavaEE в LXF91&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Исходный код примера урока JavaEE в LXF91&lt;/div&gt;</summary>
		<author><name>Dionysius</name></author>	</entry>

	<entry>
		<id>http://wiki.linuxformat.ru/wiki/%D0%A4%D0%B0%D0%B9%D0%BB:AddressBook.tar.bz2</id>
		<title>Файл:AddressBook.tar.bz2</title>
		<link rel="alternate" type="text/html" href="http://wiki.linuxformat.ru/wiki/%D0%A4%D0%B0%D0%B9%D0%BB:AddressBook.tar.bz2"/>
				<updated>2008-12-30T17:37:48Z</updated>
		
		<summary type="html">&lt;p&gt;Dionysius: загружена новая версия «Изображение:AddressBook.tar.bz2»: Исходный код примера урока JavaEE в LXF91&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Исходники к уроку JavaEE в LXF89&lt;/div&gt;</summary>
		<author><name>Dionysius</name></author>	</entry>

	<entry>
		<id>http://wiki.linuxformat.ru/wiki/LXF90:JavaEE</id>
		<title>LXF90:JavaEE</title>
		<link rel="alternate" type="text/html" href="http://wiki.linuxformat.ru/wiki/LXF90:JavaEE"/>
				<updated>2008-12-30T17:33:52Z</updated>
		
		<summary type="html">&lt;p&gt;Dionysius: /* А как это обрабатывается-то? */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Цикл/Java EE}}&lt;br /&gt;
== {{oncolor||red|Телефонная книга}}: переход на JSP ==&lt;br /&gt;
''{{oncolor||red|ЧАСТЬ 2}} Встречают по одежке – и Большой Босс не был сильно впечатлен созданной нами в прошлый раз адресной книгой. '''Александр Бабаев''' исправляет замеченные недочеты.''&lt;br /&gt;
&lt;br /&gt;
__TOC__&lt;br /&gt;
В прошлый раз мы создали простейшую электронную записную книжку. Она работает в браузере и показывает несколько простых страничек, на которых можно просмотреть список контактов, добавить новый контакт, удалить его или отредактировать. А сейчас давайте попробуем сделать все это более правильно.&lt;br /&gt;
&lt;br /&gt;
=== Почему было плохо? ===&lt;br /&gt;
&lt;br /&gt;
Действительно, почему? Работает, и хорошо. Достаточно быстро и не слишком сложно. Но вдруг захочется поменять дизайн страничек? А захочется через десять минут работы. Или после того, как страничку посмотрит начальник.&lt;br /&gt;
&lt;br /&gt;
Чтобы сделать это, можно изменить код проекта, потом перекомпилировать его, остановить сервер (А? Кто-то работал? Извините...), установить новый код и повторно запустить сервер. Метод, мягко говоря, неудобный. А можно изменить сам проект так, чтобы выполнение таких пожеланий не требовало столь сложных действий. Второй путь зовется рефакторингом и гораздо более корректен. Если разделить дизайн и логику работы приложения (бизнес-логику), то в дальнейшем можно будет, например, разделить и работу по их поддержанию. Хороший программист не всегда создает хорошие пользовательские интерфейсы, поэтому данный аспект тоже важен.&lt;br /&gt;
&lt;br /&gt;
=== Как сделать хорошо? ===&lt;br /&gt;
&lt;br /&gt;
Ну, вкратце уже понятно. Нужно вынести в отдельные файлы ту часть, которая меняется часто (в нашем случае, это интерфейс) и как-то подключить эти файлы из нашей программы. Плюс, желательно сделать это так, чтобы формат файлов «дизайна» был стандартным, чтобы каждый раз не переучиваться.&lt;br /&gt;
&lt;br /&gt;
Решений для данной проблемы существует множество. Рассмотрим самые распространенные:&lt;br /&gt;
&lt;br /&gt;
* '''Шаблоны.''' Одна из самых распространенных библиотек работы с шаблонами – ''Velocity''. При использовании шаблонных движков можно добавлять в текст специальные вставки, которые говорят: «Тут вставить значение переменной {{oncolor||red|Name}}». Иногда можно делать более сложные операции (вставка подшаблонов, вычисления, условные вставки).&lt;br /&gt;
&lt;br /&gt;
* '''JSP (Java Server Pages).''' По времени появления, пожалуй, первая технология для отделения дизайна от бизнес-логики. Но я ее поставил второй, так как она сложнее, чем просто шаблонная библиотека. JSP позволяет внедрить код на (по задумке) любом языке программирования внутрь специальным образом созданной странички. Впрочем, обычно используется Java. Теоретически, можно написать серверное приложение, используя исключительно JSP. Этот подход похож на PHP, с тем отличием, что JSP-страницы – это полноценные сервлеты, они компилируются при обновлении исходного текста и обрабатываются как таковые.&lt;br /&gt;
&lt;br /&gt;
* '''JSF (Java Server Faces).''' В некотором роде эта технология объединяет подходы, которые используются при создании «обычных» и «сетевых» программ. Интерфейс (как дизайн интерфейса, так и его логика) программы описывается специальным образом, а после этого пишутся JSP-странички, в которых указывается «тут вставить таблицу с именем таким-то». JSF обрабатывает эти спецвставки и «рисует» функциональные элементы интерфейса (обрабатывая события от них и так далее), позволяя дизайнеру сосредоточиться на остальном.&lt;br /&gt;
&lt;br /&gt;
* '''Google Web Toolkit.''' Не могу не остановиться на этом средстве. При его использовании на выходе получается полноценное AJAX-приложение (что это такое – тема отдельной статьи, пример – Google Mail), а на входе – все тот же Java-код. Решение интересное, не лишенное своих достоинств и недостатков.&lt;br /&gt;
&lt;br /&gt;
Мы же в рамках данной статьи рассмотрим «средненькое» решение – Java Server Pages. В основном – из-за его стандартности, хотя для данного конкретного случая можно выбрать какой-нибудь шаблонный движок, например, тот же Velocity (http://velocity.apache.org).&lt;br /&gt;
&lt;br /&gt;
=== Общая схема работы приложения ===&lt;br /&gt;
&lt;br /&gt;
Поняв, что нужно отделить логику от дизайна, давайте подумаем, каким образом это можно сделать. Предлагаю остановиться на следующей схеме - '''(Рис. 1)'''.&lt;br /&gt;
&lt;br /&gt;
Сервлет выдает данные, абсолютно не заботясь о том, как они будут отображаться. Но выдает он их не в «сыром» виде, а в полностью обработанном, готовом для отображения на экране (например, если нужно полное имя человека, а в данных – его ФИО по отдельности, то сервлет должен преобразовать второе в первое перед передачей в JSP).&lt;br /&gt;
&lt;br /&gt;
Возникает вопрос: как же передаются данные от сервлета в JSP? Через уже известный нам объект {{oncolor||red|request}}. К нему «прикручен» специальный ассоциативный массив «{{oncolor||red|String – Object}}», который называется атрибутами и который живет, пока жив запрос. К нему имеет доступ и сервлет, и JSP-страница, поэтому его можно (и это правильно) использовать для передачи данных.&lt;br /&gt;
                                                                          &lt;br /&gt;
=== Переходим на Tomcat ===&lt;br /&gt;
&lt;br /&gt;
Но сначала нужно переписать наш сервлет «по-взрослому». Встроенный сервер – это замечательно для кустарных проектов, но обычно контейнер сервлетов уже стоит, и подключаться следует к нему.&lt;br /&gt;
&lt;br /&gt;
Мы будем использовать Tomcat 5.5. Это классический, можно даже сказать, стандартный открытый сервлет-контейнер. Для установки Tomcat достаточно просто скачать его с http://tomcat.apache.org (или взять с нашего DVD), распаковать и запустить '''bin/startup.sh''' (или соответсвующий '''.bat'''). ''Tomcat'' работает с файлами специального типа Web Archive (WAR). Обнаружив такой файл в определенном каталоге, Tomcat разворачивает его и запускает содержащееся в нем приложение. Чтобы перезапустить или обновить программу, достаточно просто заменить один WAR-файл другим.&lt;br /&gt;
&lt;br /&gt;
Предыдущий код не готов для работы с Tomcat, поэтому его нужно немного переписать. Вот что будет сделано:&lt;br /&gt;
&lt;br /&gt;
* '''{{oncolor||red|AddressBook}}''' потеряет методы {{oncolor||red|start}} и {{oncolor||red|main}} и превратится в простое хранилище записей.&lt;br /&gt;
* '''{{oncolor||red|AddressBookHandler}}''' превратится в {{oncolor||red|AddressBookServlet}}, и в него будет добавлено примерно следующее '''(Листинг 1)''':&lt;br /&gt;
&lt;br /&gt;
'''{{oncolor||red|Листинг 1. Новый AddressBook}}'''&lt;br /&gt;
&lt;br /&gt;
 private AddressBook _addressBook = null;&lt;br /&gt;
 &lt;br /&gt;
 public void init(ServletConfig aServletConfig) throws ServletException {&lt;br /&gt;
  super.init(aServletConfig);&lt;br /&gt;
  _addressBook = new AddressBook();&lt;br /&gt;
 }&lt;br /&gt;
 &lt;br /&gt;
 protected void doGet(HttpServletRequest aRequest, HttpServletResponse aResponse)&lt;br /&gt;
      throws ServletException, IOException&lt;br /&gt;
  handle(aRequest, aResponse);&lt;br /&gt;
 }&lt;br /&gt;
 &lt;br /&gt;
 protected void doPost(HttpServletRequest aRequest, HttpServletResponse aResponse)&lt;br /&gt;
      throws ServletException, IOException                                &lt;br /&gt;
  handle(aRequest, aResponse);                                            &lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
Сам метод {{oncolor||red|handle}} тоже слегка преобразуется '''(Листинг 2)''':&lt;br /&gt;
&lt;br /&gt;
'''{{oncolor||red|Листинг 2. Новый метод handle}}'''&lt;br /&gt;
&lt;br /&gt;
  private void handle(HttpServletRequest aRequest, HttpServletResponse aResponse)&lt;br /&gt;
        throws ServletException, IOException {&lt;br /&gt;
   aRequest.setCharacterEncoding(&amp;quot;utf-8&amp;quot;);&lt;br /&gt;
 &lt;br /&gt;
   String target = aRequest.getRequestURI().substring(&lt;br /&gt;
     aRequest.getContextPath().length());&lt;br /&gt;
 &lt;br /&gt;
   if (target.equals(&amp;quot;/&amp;quot;)) {&lt;br /&gt;
   _drawer.outputPage(&amp;quot;index.jsp&amp;quot;, aRequest, aResponse);&lt;br /&gt;
   } else if (&amp;quot;/add&amp;quot;.equals(target)) {&lt;br /&gt;
   handleAdd(aRequest, aResponse);&lt;br /&gt;
   } else if (&amp;quot;/view&amp;quot;.equals(target)) {&lt;br /&gt;
   handleView(aRequest, aResponse);&lt;br /&gt;
   } else if (&amp;quot;/edit&amp;quot;.equals(target)) {&lt;br /&gt;
   handleEdit(aRequest, aResponse);&lt;br /&gt;
   } else if (&amp;quot;/remove&amp;quot;.equals(target)) {&lt;br /&gt;
   handleRemove(aRequest, aResponse);&lt;br /&gt;
   }&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
* Для того, чтобы Tomcat «понял», что ему положили сервлет, и знал, как его обрабатывать, нужно написать специальный файл, который называется «дескриптор». Несмотря на то, что слово страшное, это просто XML-документ с описанием сервлета. Если перевести с языка написания дескрипторов на русский, то получится примерно следующая информация:&lt;br /&gt;
&lt;br /&gt;
** Наш сервлет называется {{oncolor||red|«ABServlet»}} и запускается классом {{oncolor||red|AddressBookServlet}}. Теоретически можно назвать сервлет так же, как и класс, но мы не будем так делать, чтобы было меньше путаницы.&lt;br /&gt;
&lt;br /&gt;
** Для всех URL, которые начинаются с «/», нужно вызывать сервлет, который называется ABServlet.&lt;br /&gt;
&lt;br /&gt;
А вот как он выглядит '''(Листинг 3)''':&lt;br /&gt;
&lt;br /&gt;
'''{{oncolor||red|Листинг 3. Дескриптор для сервлета}}'''&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
  &amp;lt;web-app version=&amp;quot;2.4&amp;quot;&lt;br /&gt;
     xmlns=&amp;quot;http://java.sun.com/xml/ns/j2ee&amp;quot;&lt;br /&gt;
     xmlns:xsi=&amp;quot;http://www.w3.org/2001/XMLSchema-instance&amp;quot;&lt;br /&gt;
     xsi:schemaLocation=&amp;quot;http://java.sun.com/xml/ns/j2ee&lt;br /&gt;
     http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd&amp;quot; &amp;gt;&lt;br /&gt;
&lt;br /&gt;
   &amp;lt;servlet&amp;gt;&lt;br /&gt;
   &amp;lt;display-name&amp;gt;AddressBook&amp;lt;/display-name&amp;gt;  &lt;br /&gt;
   &amp;lt;servlet-name&amp;gt;Servlet&amp;lt;/servlet-name&amp;gt;&lt;br /&gt;
   &amp;lt;servlet-class&amp;gt;AddressBookServlet&amp;lt;/servlet-class&amp;gt;&lt;br /&gt;
   &amp;lt;load-on-startup&amp;gt;0&amp;lt;/load-on-startup&amp;gt;&lt;br /&gt;
   &amp;lt;/servlet&amp;gt;&lt;br /&gt;
&lt;br /&gt;
   &amp;lt;servlet-mapping&amp;gt;&lt;br /&gt;
   &amp;lt;servlet-name&amp;gt;Servlet&amp;lt;/servlet-name&amp;gt;&lt;br /&gt;
   &amp;lt;url-pattern&amp;gt;/&amp;lt;/url-pattern&amp;gt;&lt;br /&gt;
   &amp;lt;/servlet-mapping&amp;gt;&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;/web-app&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Дескриптор будет называться '''web.xml''' и храниться в специальном каталоге. Где именно – обсудим, когда будем собирать сервлет в {{oncolor||red|WAR}}&lt;br /&gt;
.&lt;br /&gt;
Сделайте указанные изменения самостоятельно или возьмите гото-вый код с DVD. Все в порядке? Тогда движемся дальше.&lt;br /&gt;
&lt;br /&gt;
=== Новый метод ===&lt;br /&gt;
&lt;br /&gt;
Если присмотреться более внимательно к коду нового {{oncolor||red|handle}}, можно заметить, что там появился вызов метода {{oncolor||red|outputPage}}. Раньше его, в отличие от разных {{oncolor||red|handle}}... не было. Это метод, который выбирает JSP-файл и передает ему управление для вывода страничек. Выглядит метод следующим образом '''(Листинг 4)''':&lt;br /&gt;
&lt;br /&gt;
'''{{oncolor||red|Листинг 4. Метод outputPage}}'''&lt;br /&gt;
&lt;br /&gt;
 public void outputPage(String aJSPName, HttpServletRequest aRequest, HttpServletResponse aResponse) throws IOException, ServletException&lt;br /&gt;
 {&lt;br /&gt;
  RequestDispatcher dispatcher = aRequest.getRequestDispatcher(&amp;quot;/jsps/&amp;quot; + aJSPName);&lt;br /&gt;
  dispatcher.forward(aRequest, aResponse);&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
В этом методе мы берем нужный JSP-файл и говорим сервлет-контейнеру: «Обработай, пожалуйста». Остальное берет на себя контейнер. Он ищет JSP-файл, загружает его, компилирует (если это нужно), выполняет получившийся сервлет, а результат записывает в {{oncolor||red|aResponse}}.&lt;br /&gt;
&lt;br /&gt;
=== JSP-страницы ===&lt;br /&gt;
&lt;br /&gt;
Для начала создадим каталог, в котором будем собирать наше интернет-приложение. Назвать можно как угодно, например, {{oncolor||red|WebApp}} ({{oncolor||red|Web Application}}). В нем создадим специальный каталог '''WEB-INF''', где должен находиться дескриптор '''web.xml''', и каталог '''jsps''', в котором будут храниться JSP-странички.&lt;br /&gt;
&lt;br /&gt;
Создадим три JSP-файла: для индексной странички, для редактирования (или добавления) записей и для просмотра, и назовем их, соответственно, '''index.jsp''', '''edit.jsp''', '''view.jsp'''. Не забудьте – их нужно сохранить в в {{oncolor||red|WebApp/jsps}}.&lt;br /&gt;
&lt;br /&gt;
Сам JSP достаточно прост. Рассмотрим '''index.jsp''' '''(Листинг 5)''':&lt;br /&gt;
&lt;br /&gt;
'''{{oncolor||red|Листинг 5. index.jsp}}'''&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
 &amp;lt;%@ page contentType=&amp;quot;text/html; charset=UTF-8&amp;quot; %&amp;gt;&lt;br /&gt;
 &amp;lt;html&amp;gt;&lt;br /&gt;
  &amp;lt;head&amp;gt;&lt;br /&gt;
   &amp;lt;meta http-equiv=&amp;quot;Content-Type&amp;quot; content=&amp;quot;text/html; charset=utf-8&amp;quot;/&amp;gt;&lt;br /&gt;
   &amp;lt;title&amp;gt;Адресная книга&amp;lt;/title&amp;gt;&lt;br /&gt;
  &amp;lt;/head&amp;gt;&lt;br /&gt;
  &amp;lt;body&amp;gt;&amp;lt;h1&amp;gt;Адресная книга&amp;lt;/h1&amp;gt;&lt;br /&gt;
   &amp;lt;a href=&amp;quot;&amp;lt;%=request.getContextPath()%&amp;gt;/add&amp;quot;&amp;gt;Добавить запись&amp;lt;/a&amp;gt;&amp;lt;br/&amp;gt;&lt;br /&gt;
   &amp;lt;a href=&amp;quot;&amp;lt;%=request.getContextPath()%&amp;gt;/view&amp;quot;&amp;gt;Просмотреть записи&amp;lt;/a&amp;gt;&amp;lt;br/&amp;gt;&lt;br /&gt;
  &amp;lt;/body&amp;gt;&lt;br /&gt;
 &amp;lt;/html&amp;gt;&lt;br /&gt;
 &amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Первая строчка добавляет поле «Content-type» к HTTP-заголовку ответа. Это прямой аналог строки&lt;br /&gt;
&lt;br /&gt;
 aRequest.setContentType(&amp;quot;text/html; charset=utf-8&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
из «старого» метода {{oncolor||red|handle}}. А дальше, кроме странных вставок {{oncolor||red|&amp;lt;%...%&amp;gt;}}, идет обычный HTML-код. И это хорошо! Это понятно! Теперь разберемся с непонятным.&lt;br /&gt;
&lt;br /&gt;
В JSP можно вставлять «инородный» для HTML код, который специальным образом интерпретируется сервером и может быть использован для вставки различных данных. Есть несколько типов таких вставок.&lt;br /&gt;
&lt;br /&gt;
* {{oncolor||red|&amp;lt;%@...%&amp;gt;}} – обозначает специальную вставку, которая определяет параметры страницы, в нашем случае – {{oncolor||red|ContentType}}. Можно задавать, например, язык, на котором написана страница. Он же используется для секций {{oncolor||red|import}} (см. '''view.jsp''' ниже).&lt;br /&gt;
* {{oncolor||red|&amp;lt;%&amp;amp;#61;...%&amp;gt;}} – это простой вывод переменной. Действие вставки {{oncolor||red|&amp;lt;%&amp;amp;#61;что-нибудь%&amp;gt;}} аналогично вызову {{oncolor||red|request.getWriter().write(что-нибудь)}}.&lt;br /&gt;
* {{oncolor||red|&amp;lt;%...%&amp;gt;}} – самый общий вариант вставки, внутри может быть любой код. В нашем случае, на Java.&lt;br /&gt;
&lt;br /&gt;
'''index.jsp''' – простой файл, посмотрим на нечто более сложное. Например, '''view.jsp''' '''(Листинг 6)'''.&lt;br /&gt;
&lt;br /&gt;
'''{{oncolor||red|Листинг 6. view.jsp}}'''&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
 &amp;lt;%@ page contentType=&amp;quot;text/html; charset=UTF-8&amp;quot; %&amp;gt;&lt;br /&gt;
 &amp;lt;%@ page import=&amp;quot;java.util.*&amp;quot; %&amp;gt;&lt;br /&gt;
 &amp;lt;html&amp;gt;&lt;br /&gt;
 &amp;lt;head&amp;gt;&amp;lt;title&amp;gt;Адресная книга&amp;lt;/title&amp;gt;&amp;lt;/head&amp;gt;&lt;br /&gt;
 &amp;lt;body&amp;gt;&amp;lt;h1&amp;gt;Адресная книга, список контактов&amp;lt;/h1&amp;gt;&lt;br /&gt;
  &amp;lt;a href=&amp;quot;&amp;lt;%=request.getContextPath()%&amp;gt;&amp;quot;&amp;gt;На главную&amp;lt;/a&amp;gt;&amp;lt;br/&amp;gt;&lt;br /&gt;
  &amp;lt;span style=&amp;quot;color: green;&amp;quot;&amp;gt;&amp;lt;%=request.getAttribute(&amp;quot;message&amp;quot;)%&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
  &amp;lt;table border=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
  &amp;lt;tr&amp;gt;&amp;lt;td width=&amp;quot;100&amp;quot;&amp;gt;Имя&amp;lt;/td&amp;gt;&amp;lt;td width=&amp;quot;100&amp;quot;&amp;gt;Номер&amp;lt;/td&amp;gt;&amp;lt;td width=&amp;quot;100&amp;quot;&amp;gt;Комментарий&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt; - &amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&lt;br /&gt;
  &amp;lt;% Map numbers = (Map) request.getAttribute(&amp;quot;numbers&amp;quot;);&lt;br /&gt;
   Map comments = (Map) request.getAttribute(&amp;quot;comments&amp;quot;);&lt;br /&gt;
   for (Object entry : numbers.entrySet()) {&lt;br /&gt;
   String name = (String) ((Map.Entry) entry).getKey();&lt;br /&gt;
   String number = (String) numbers.get(name);&lt;br /&gt;
   String comment = (String) comments.get(name); %&amp;gt;&lt;br /&gt;
  &amp;lt;tr&amp;gt;&lt;br /&gt;
   &amp;lt;td class=&amp;quot;name&amp;quot;&amp;gt;&amp;lt;%=name%&amp;gt;&amp;lt;/td&amp;gt;&lt;br /&gt;
   &amp;lt;td class=&amp;quot;number&amp;quot;&amp;gt;&amp;lt;%=number%&amp;gt;&amp;lt;/td&amp;gt;&lt;br /&gt;
   &amp;lt;td class=&amp;quot;comment&amp;quot;&amp;gt;&amp;lt;%=comment%&amp;gt;&amp;lt;/td&amp;gt;&lt;br /&gt;
   &amp;lt;td class=&amp;quot;name&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;a href=&amp;quot;&amp;lt;%=request.getContextPath()%&amp;gt;/remove?number=&amp;lt;%=number%&amp;gt;&amp;quot;&amp;gt;Удалить&amp;lt;/a&amp;gt;&lt;br /&gt;
 &amp;lt;a href=&amp;quot;&amp;lt;%=request.getContextPath()%&amp;gt;/edit?number=&amp;lt;%=number%&amp;gt;&amp;quot;&amp;gt;Редактировать&amp;lt;/a&amp;gt;&lt;br /&gt;
   &amp;lt;/td&amp;gt;&lt;br /&gt;
  &amp;lt;/tr&amp;gt;&lt;br /&gt;
  &amp;lt;% } %&amp;gt;&lt;br /&gt;
  &amp;lt;/table&amp;gt;&lt;br /&gt;
 &amp;lt;/body&amp;gt;&lt;br /&gt;
 &amp;lt;/html&amp;gt;&lt;br /&gt;
 &amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Как можно заметить, здесь есть и импорт (о чем я говорил чуть выше), и вставка Java-кода. Данный файл отлично показывает, как, например (не самый лучший способ, конечно), сделать вывод в цикле.&lt;br /&gt;
&lt;br /&gt;
=== А как это обрабатывается-то? ===&lt;br /&gt;
&lt;br /&gt;
Естественно, и методы {{oncolor||red|handle}}... после такого изменения стали другими. Весь вывод HTML-кода исчез, осталась подготовка данных, и вызов метода {{oncolor||red|outputPage}}. Вот, например, метод {{oncolor||red|handleEdit(...)}} '''(Листинг 7)''':&lt;br /&gt;
&lt;br /&gt;
'''{{oncolor||red|Листинг 7. Метод handleEdit, обработка редактирования записи}}'''&lt;br /&gt;
&lt;br /&gt;
 if (aRequest.getParameter(&amp;quot;number&amp;quot;) == null) {&lt;br /&gt;
  _addressBook.removeContactByNumber(aRequest.getParameter(&amp;quot;number&amp;quot;));&lt;br /&gt;
  aRequest.setAttribute(&amp;quot;message&amp;quot;, &amp;quot;Не определено, что редактировать&amp;quot;);&lt;br /&gt;
  handleView(aRequest, aResponse);&lt;br /&gt;
 } else if (aRequest.getParameter(&amp;quot;edited&amp;quot;) != null) {&lt;br /&gt;
  _addressBook.editContact(aRequest.getParameter(&amp;quot;edited&amp;quot;),           &lt;br /&gt;
   aRequest.getParameter(&amp;quot;name&amp;quot;),                                     &lt;br /&gt;
   aRequest.getParameter(&amp;quot;number&amp;quot;),&lt;br /&gt;
   aRequest.getParameter(&amp;quot;comment&amp;quot;));&lt;br /&gt;
   aRequest.setAttribute(&amp;quot;message&amp;quot;, &amp;quot;Контакт \&amp;quot;&amp;quot; +&lt;br /&gt;
   aRequest.getParameter(&amp;quot;name&amp;quot;) + &amp;quot;\&amp;quot; отредактирован&amp;quot;);&lt;br /&gt;
  handleView(aRequest, aResponse);                                    &lt;br /&gt;
 } else {&lt;br /&gt;
  Contact contact = _addressBook.getContactByNumber(aRequest.getParameter(&amp;quot;number&amp;quot;));&lt;br /&gt;
  aRequest.setAttribute(&amp;quot;action&amp;quot;, &amp;quot;edit&amp;quot;);&lt;br /&gt;
  aRequest.setAttribute(&amp;quot;edit.name&amp;quot;, contact.getName());&lt;br /&gt;
  aRequest.setAttribute(&amp;quot;edit.number&amp;quot;, contact.getNumber());&lt;br /&gt;
  aRequest.setAttribute(&amp;quot;edit.comment&amp;quot;, contact.getComment());&lt;br /&gt;
  outputPage(&amp;quot;edit.jsp&amp;quot;, aRequest, aResponse);&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
Остальные методы меняются аналогично – их [[Media:Archive.tar.bz2‎|полный код]] можно найти на диске.&lt;br /&gt;
&lt;br /&gt;
=== И как все это вставить в Tomcat? ===&lt;br /&gt;
&lt;br /&gt;
Теперь у нас есть:&lt;br /&gt;
&lt;br /&gt;
* Классы {{oncolor||red|Contact}}, {{oncolor||red|AddressBook}}, {{oncolor||red|AddressBookServlet}}.&lt;br /&gt;
* Файл '''web.xml'''.&lt;br /&gt;
* Каталог '''jsps''' с файлами '''edit.jsp''', '''index.jsp''', '''view.jsp'''.&lt;br /&gt;
&lt;br /&gt;
Для того, чтобы Tomcat понял, что ему дали полноценное приложение, нужно выполнить всего три шага:&lt;br /&gt;
&lt;br /&gt;
* Скомпилировать все, что компилируется, и создать правильную иерархию файлов и каталогов, которая представлена '''на рис. 2'''.&lt;br /&gt;
* Создать специальный файл-описание архива («манифест»).&lt;br /&gt;
* Заархивировать созданную структуру при помоци утилиты ''jar'', входящей в комплект JDK.&lt;br /&gt;
&lt;br /&gt;
Скомпилируем файлы. Тут ничего нового не появилось, разве что изменилась сама команда (обратите внимание на ключ {{oncolor||red|-cp}}, задающий библиотеки {{oncolor||red|classpath}}):&lt;br /&gt;
&lt;br /&gt;
 cd ~/Programming/AddressBook/src&lt;br /&gt;
 javac -encoding utf-8 -cp ~/bin/tomcat/common/lib/servlet-api.jar -d ../build/WEB-INF/classes/ *.java&lt;br /&gt;
&lt;br /&gt;
Переходим к созданию манифеста. Он должен называться '''MANIFEST.MF''' и располагаться в каталоге '''META-INF'''. К счастью, за этим следит сам '''jar''', поэтому нам достаточно просто сохранить где-то файл и указать его '''jar''''у как манифест. В нашем случае он предельно прост и не содержит интересной информации, но в принципе здесь могут располагаться всякие настройки для запуска вашего приложения. Вот его текст '''(Листинг 8)''':&lt;br /&gt;
&lt;br /&gt;
'''{{oncolor||red|Листинг 8. Манифест для war-файла}}'''&lt;br /&gt;
&lt;br /&gt;
 Manifest-Version: 1.0&lt;br /&gt;
 Created-By: Hands of programmer&lt;br /&gt;
&lt;br /&gt;
Теперь соберем все в {{oncolor||red|war}} (Web Archive). Манифест для приведенной ниже команды должен быть назван '''MANIFEST.MF''' и располагаться рядом с каталогом '''build'''. Результирующий архив называется '''address.war''' и располагается там же, рядом с манифестом.&lt;br /&gt;
&lt;br /&gt;
 jar -cfm ../address.war ../MANIFEST.MF *&lt;br /&gt;
&lt;br /&gt;
А сейчас наступает самый волшебный момент! Возьмите '''address.war''' и положите его в каталог webapps Tomcat'а. Подождите несколько секунд. Увидев новое приложение, Tomcat развернет его (появляется каталог с именем вашего war'а) и подключит к системе. После этого можно просто зайти в браузер и набрать:&lt;br /&gt;
&lt;br /&gt;
 http://localhost:8080/address/&lt;br /&gt;
&lt;br /&gt;
Вуаля, получите ваше приложение.&lt;br /&gt;
&lt;br /&gt;
=== И что теперь? ===&lt;br /&gt;
&lt;br /&gt;
А теперь можно менять JSP-файлы «на лету» в распакованном каталоге '''webapps/address/jsps'''. При этом будет автоматически происходить несколько действий, в результате которых файлы подхватятся приложением. Так меняется дизайн без перекомпиляции, без рестарта серверного приложения, как это у нас было до сих пор.&lt;br /&gt;
&lt;br /&gt;
Я считаю, что на данном этапе приложение «Адресная книга» работает хорошо. Оно выполняет свои несложные функции и умеет изменяться «на лету» по запросу пользователя. Оно простое – и это чуть ли не самое главное. Но есть еще достаточно аспектов, о которых стоит знать при разработке более сложных интернет-приложений. Мы рассмотрим их в следующих статьях данной серии. [http://www.linuxformat.ru LXF]&lt;/div&gt;</summary>
		<author><name>Dionysius</name></author>	</entry>

	<entry>
		<id>http://wiki.linuxformat.ru/wiki/%D0%A4%D0%B0%D0%B9%D0%BB:Archive.tar.bz2</id>
		<title>Файл:Archive.tar.bz2</title>
		<link rel="alternate" type="text/html" href="http://wiki.linuxformat.ru/wiki/%D0%A4%D0%B0%D0%B9%D0%BB:Archive.tar.bz2"/>
				<updated>2008-12-30T17:32:02Z</updated>
		
		<summary type="html">&lt;p&gt;Dionysius: Исходный код примера урока JavaEE в LXF90&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Исходный код примера урока JavaEE в LXF90&lt;/div&gt;</summary>
		<author><name>Dionysius</name></author>	</entry>

	<entry>
		<id>http://wiki.linuxformat.ru/wiki/LXF89:Java_EE</id>
		<title>LXF89:Java EE</title>
		<link rel="alternate" type="text/html" href="http://wiki.linuxformat.ru/wiki/LXF89:Java_EE"/>
				<updated>2008-12-30T17:29:53Z</updated>
		
		<summary type="html">&lt;p&gt;Dionysius: /* Что нам потребуется? */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Цикл/Java EE}}&lt;br /&gt;
'''Новая серия!''' Учимся писать серверные приложения на Java&lt;br /&gt;
&lt;br /&gt;
== Адресная книга ==&lt;br /&gt;
''ЧАСТЬ 1: Театр, как известно, начинается с вешалки, а приложения уровня предприятий – с адресной книги: надо же где-то хранить сведения о клиентах. '''Александр Бабаев''' готов познакомить вас с азбукой Java EE.''&lt;br /&gt;
&lt;br /&gt;
{{Врезка&lt;br /&gt;
|Заголовок=Наш эксперт&lt;br /&gt;
|Содержание=&lt;br /&gt;
'''Александр Бабаев'''&amp;lt;br&amp;gt;Разработчик открытой мультиблог-системы jDnevnik – победитель конкурсов IBM WAS CE Contest 2006 и конкурса проектов для разработчиков на Java - Java конкурс 2005.&lt;br /&gt;
|Ширина=150px}}&lt;br /&gt;
&lt;br /&gt;
Один из самых распространенных языков программирования для Интернета на настоящий момент – это PHP. Этот язык изначально задумывался для создания домашних страничек, небольших сайтов – и там работает замечательно.&lt;br /&gt;
&lt;br /&gt;
В отличие от PHP, Java (та ее часть, которая помогает разрабатывать серверные приложения) сразу создавалась для крупных приложений уровня предприятия (так называемых Enterprise Applications). Поэтому в Java есть множество механизмов, которые помогают быстро строить очень крупные приложения. Очень крупные – это десятки тысяч транзакций в минуту. Сложных транзакций.&lt;br /&gt;
&lt;br /&gt;
Обо всех возможностях этой технологии не рассказать ни в рамках одной статьи, ни даже в десятке. Но основные блоки JEE (Java Enterprise Edition), которые полезны не только в крупных приложениях, но и в средних и небольших, мы изучим обязательно. А на основании этого опыта можно будет двигаться дальше, читать книги, разрабатывать сложные системы.&lt;br /&gt;
&lt;br /&gt;
=== Сервлеты ===&lt;br /&gt;
[[Изображение:Img_89_82_1.jpg|thumb|right|450px|'''Рис. 1.''' Процесс обработки запроса]]&lt;br /&gt;
Для начала решим, что же такое сервлет (servlet)? Этим термином принято называть серверное приложение, но чем отличается серверное приложение от клиентского? Клиентское ПО формирует запросы и отсылает их серверному, а задача серверной части – обработать запрос и вернуть на него ответ ('''рис. 1''').&lt;br /&gt;
&lt;br /&gt;
На стороне клиента присутствуют только действия, которые выполняются браузером (показ формы на HTML-странице, формирование стандартизованного запроса).&lt;br /&gt;
&lt;br /&gt;
Серверная сторона в Java обычно состоит из контейнера, который содержит один или несколько сервлетов. Контейнер получает запрос, решает, какому сервлету он предназначен, и запускает на выполнение этот сервлет. Формально происходит примерно следующее:&lt;br /&gt;
&lt;br /&gt;
* Пришедший от клиента запрос анализируется сервлет-контейнером, который создает на его основе специальный объект '''javax.servlet.ServletRequest'''.&lt;br /&gt;
* Контейнер решает (как – обычно описывается в специальном конфигурационном файле), какому сервлету предназначен запрос, и передает ему созданный на предыдущем шаге объект '''javax.servlet.ServletRequest''', а также объект '''javax.servlet.ServletResponse''' – шаблон ответа, в который сервлет пишет все, что нужно возвратить клиенту.&lt;br /&gt;
* Сервлет обрабатывает запрос, заполняет объект '''javax.servlet.ServletResponse''' и после этого завершает работу.&lt;br /&gt;
* Контейнер получает заполненный объект '''javax.servlet.ServletResponse''', после чего преобразует его в стандартизованное сообщение и передает клиенту.&lt;br /&gt;
&lt;br /&gt;
=== Что нам потребуется? ===&lt;br /&gt;
{{Врезка&lt;br /&gt;
|Заголовок=Коротко о HTTP&lt;br /&gt;
|Содержание=&lt;br /&gt;
Протокол HTTP был предложен Тимом Бернерсом-Ли, работавшим тогда в CERN, в марте 1990 года как механизм для доступа к документам в Интернете и облегчения навигации посредством использования гипертекста. В HTTP разделяются понятия «запрос» и «ответ», то есть то, как браузер запрашивает страницу и то, чем сервер на это отвечает. И запрос, и ответ состоят из заголовка и тела. В запросе присутствует еще и строка запроса. Заголовок описывает передаваемые данные, внутри тела передается обычно результат (страница HTML или файл). Существует несколько типов запросов, но наиболее распространены два:&lt;br /&gt;
* '''GET''' Запрашивает содержимое указанного ресурса.&lt;br /&gt;
* '''POST''' Передает пользовательские данные (например, из формы HTML) заданному ресурсу. Данные включаются в тело запроса.&lt;br /&gt;
&lt;br /&gt;
Вот пример (не точный) простого HTTP-диалога:&amp;lt;br&amp;gt;&lt;br /&gt;
'''Запрос'''&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
GET /site/index.html HTTP/1.1&lt;br /&gt;
Host: ru.wikipedia.org&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
'''Ответ'''&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
HTTP/1.0 200 OK&lt;br /&gt;
Server: Apache&lt;br /&gt;
Content-Type: text/html; charset=utf-8&lt;br /&gt;
Content-Length: 2121&lt;br /&gt;
(Дальше идет 2121 байт текста странички index.html)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|Ширина=45%}}&lt;br /&gt;
Мы предполагаем, что читатель знаком с тем, что такое HTML, и пробовал делать «странички на PHP» или чем-то аналогичном. Если словосочетания «GET-запрос», «POST-запрос» являются для вас китайской грамотой – прочтите врезку «Коротко об HTTP» (и запаситесь терпением). Подробнее про протокол HTTP можно прочитать в RFC2616, например тут: http://www.w3.org/Protocols/rfc2616/rfc2616.html.&lt;br /&gt;
&lt;br /&gt;
Для работы нам понадобятся следующие инструменты:&lt;br /&gt;
* JDK 5 (или JDK 6) – если вы выполняли все задания предыдущей серии уроков Java, нужный дистрибутив, скорее всего, уже имеется в вашей системе (вы ведь выполняли задания, не так ли?). JDK можно бесплатно загрузить с сайта http://java.sun.com или установить из репозиториев вашего дистрибутива Linux. После установки JDK необходимо убедиться, что пути к каталогу '''$JDK/bin''' прописаны в переменной окружения PATH, а каталоги, в которых располагаются библиотеки классов, перечислены в CLASSPATH.&lt;br /&gt;
* Ваш любимый текстовый редактор.&lt;br /&gt;
* Библиотека Jetty (http://www.mortbay.org/). В принципе, это полноценный web-сервер, который может очень и очень многое. Нам будет полезна его особенность, позволяющая встроить web-сервер в наше приложение.&lt;br /&gt;
&lt;br /&gt;
Все необходимые библиотеки [[Media:AddressBook.tar.bz2|и исходный код примера]] есть на прилагаемом компакт-диске.&lt;br /&gt;
&lt;br /&gt;
=== Что мы будем делать? ===&lt;br /&gt;
Для примера мы возьмем и напишем корпоративную адресную книгу, в которой будем сохранять телефоны сотрудников, причем каждый сможет добавить телефоны нужных людей, и все смогут просмотреть сохраненные телефоны. Итак, начнем.&lt;br /&gt;
&lt;br /&gt;
Создадим иерархию каталогов для проекта, например, такую:&lt;br /&gt;
* '''~/Programming/AddressBook/.'''&lt;br /&gt;
* '''build''' – каталог для скомпилированных классов.&lt;br /&gt;
* '''src''' – исходные тексты.&lt;br /&gt;
* '''libs''' – каталог для библиотек.&lt;br /&gt;
&lt;br /&gt;
Заполним каталоги. Для начала возьмите Jetty, распакуйте архив, найдите в нем файлы '''jetty-6.1.0rc3.jar'''; '''jetty-util-6.1.0rc3.jar'''; '''servlet-api-2.5.jar''' и поместите их в каталог '''libs''' (можно взять и другую версию,&lt;br /&gt;
правда, с известной долей осторожности – иногда меняются API, тогда наша программа просто не скомпилируется).&lt;br /&gt;
&lt;br /&gt;
Теперь настало время придумать, как будет работать наша телефонная книга. Разумно будет создать четыре основных страницы:&lt;br /&gt;
* Главная – на ней будут ссылки помещены на остальные страницы приложения.&lt;br /&gt;
* Страница добавления нового контакта.&lt;br /&gt;
* Страница редактирования контакта.&lt;br /&gt;
* Страница поиска/просмотра контактов.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Классов в нашем приложении будет всего три:&lt;br /&gt;
* '''AddressBook.''' Главный класс, который запускается и «висит» в памяти, обрабатывая запросы пользователей. Обычно эту роль выполняет уже упомянутый сервлет-контейнер (роль которого может выполнять, например, Apache Tomcat), но мы избавились от него, использовав Jetty.&lt;br /&gt;
* '''AddressBookHandler.''' Класс-обработчик запросов пользователей. Именно этот класс и называется сервлетом. Он обычно подключается к сервлет-контейнеру через специфический конфигурационный файл, но мы избавились и от этого – спасибо, Jetty!&lt;br /&gt;
* '''Contact.''' Класс, который содержит поля контакта. Эти поля будут выводиться на странице и редактироваться. Его код очень простой:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Листинг 1.''' Контакт&lt;br /&gt;
&amp;lt;source lang=&amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
import java.io.*;&lt;br /&gt;
public class Contact implements Serializable {&lt;br /&gt;
    private String _name = &amp;quot;&amp;quot;;&lt;br /&gt;
    private String _number = &amp;quot;&amp;quot;;&lt;br /&gt;
    private String _comment = &amp;quot;&amp;quot;;&lt;br /&gt;
    public String getName() { return _name; }&lt;br /&gt;
    public String getNumber() { return _number; }&lt;br /&gt;
    public String getComment() { return _comment; }&lt;br /&gt;
    public void setData(String aName, String aNumber, String aComment) {&lt;br /&gt;
        _name = aName;&lt;br /&gt;
        _number = aNumber;&lt;br /&gt;
        _comment = aComment;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Основной класс/класс запуска приложения (AddressBook) ===&lt;br /&gt;
Этот класс содержит метод '''main''', инициализирует Jetty и подключает сервлет ('''AddressBookHandler''', '''листинг 2''').&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Листинг 2.''' Класс запуска приложения&lt;br /&gt;
&amp;lt;source lang=&amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
import org.mortbay.jetty.*;&lt;br /&gt;
import org.mortbay.jetty.nio.*;&lt;br /&gt;
&lt;br /&gt;
import java.util.*;&lt;br /&gt;
import java.io.*;&lt;br /&gt;
&lt;br /&gt;
public class AddressBook {&lt;br /&gt;
    private void start() throws Exception {&lt;br /&gt;
        try {&lt;br /&gt;
            _contactsByName = (SortedMap&amp;lt;String, Contact&amp;gt;) new XStream().&lt;br /&gt;
                fromXML(new FileReader(&amp;quot;contacts.xml&amp;quot;));&lt;br /&gt;
        } catch (Exception e) { }&lt;br /&gt;
&lt;br /&gt;
        Server _jettyServer = new Server();&lt;br /&gt;
        Connector connector = new SelectChannelConnector();&lt;br /&gt;
        connector.setPort(8081);&lt;br /&gt;
        _jettyServer.setConnectors(new Connector[]{connector});&lt;br /&gt;
        _jettyServer.setStopAtShutdown(true);&lt;br /&gt;
        Handler handler = new AddressBookHandler(this);&lt;br /&gt;
        _jettyServer.setHandler(handler);&lt;br /&gt;
        _jettyServer.start();&lt;br /&gt;
    }&lt;br /&gt;
    public static void main(String[] args) throws Exception {&lt;br /&gt;
        new AddressBook().start();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Также этот класс содержит саму адресную книгу:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Листинг 3.''' Адресная книга&lt;br /&gt;
&amp;lt;source lang=&amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
private SortedMap&amp;lt;String, Contact&amp;gt; _contactsByName = new TreeMap&amp;lt;String,Contact&amp;gt;();&lt;br /&gt;
&lt;br /&gt;
public SortedMap&amp;lt;String, Contact&amp;gt; getContactsByName() {&lt;br /&gt;
    return _contactsByName;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
public Contact getContactByNumber(String aNumber) {&lt;br /&gt;
    for (Map.Entry&amp;lt;String, Contact&amp;gt; entry : _contactsByName.entrySet()) {&lt;br /&gt;
        if (entry.getValue().getNumber().equals(aNumber)) {&lt;br /&gt;
            return entry.getValue();&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    return null;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
и методы, которые позволяют добавлять/удалять/редактировать контакты:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Листинг 4.''' Работа с контактами&lt;br /&gt;
&amp;lt;source lang=&amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
public void addContact(String aName, String aNumber, String aComment)&lt;br /&gt;
throws IOException {&lt;br /&gt;
    Contact contact = new Contact();&lt;br /&gt;
    contact.setData(aName, aNumber, aComment);&lt;br /&gt;
    _contactsByName.put(aName, contact);&lt;br /&gt;
    saveContactsInformation();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
public void removeContactByNumber(String aNumber) throws IOException {&lt;br /&gt;
    Contact contact = getContactByNumber(aNumber);&lt;br /&gt;
    if (contact != null) {&lt;br /&gt;
        _contactsByName.remove(contact.getName());&lt;br /&gt;
        saveContactsInformation();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
public void editContact(String aEditNumber, String aName, String aNumber, String aComment)&lt;br /&gt;
throws IOException {&lt;br /&gt;
    Contact contact = getContactByNumber(aEditNumber);&lt;br /&gt;
    if (contact != null) {&lt;br /&gt;
        contact.setData(aName, aNumber, aComment);&lt;br /&gt;
        saveContactsInformation();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
private void saveContactsInformation() throws IOException {&lt;br /&gt;
    new XStream().toXML(_contactsByName, new FileWriter(&amp;quot;contacts.xml&amp;quot;));&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Класс-обработчик (AddressBookHandler) ===&lt;br /&gt;
Обработка событий пользователя концентрируется в одном методе ('''public void handle(…)''') класса '''AddressBookHandler'''. Самые главные параметры этого метода – второй '''aHttpServletRequest''' и третий '''aHttpServletResponse'''. '''Request''' – это запрос. В нем хранятся все данные, которые передал нам браузер пользователя. А '''Response''' – это объект, в который нужно записать все, что требуется передать пользователю. Например, на любой запрос пользователя можно отвечать страницей с гордым заголовком «адресная книга» ('''листинг 5'''):&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Листинг 5.''' Код обработчика&lt;br /&gt;
&amp;lt;source lang=&amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
import org.mortbay.jetty.handler.*;&lt;br /&gt;
import org.mortbay.jetty.*;&lt;br /&gt;
&lt;br /&gt;
import javax.servlet.*;&lt;br /&gt;
import javax.servlet.http.*;&lt;br /&gt;
import java.io.*;&lt;br /&gt;
import java.util.*;&lt;br /&gt;
&lt;br /&gt;
public class AddressBookHandler extends AbstractHandler {&lt;br /&gt;
    private AddressBook _addressBook = null;&lt;br /&gt;
&lt;br /&gt;
    public AddressBookHandler(AddressBook aAddressBook) {&lt;br /&gt;
        _addressBook = aAddressBook;&lt;br /&gt;
    }&lt;br /&gt;
    public void handle(String aTarget, HttpServletRequest aRequest,&lt;br /&gt;
            HttpServletResponse aResponse, int aDispatchMode)&lt;br /&gt;
            throws IOException, ServletException {&lt;br /&gt;
&lt;br /&gt;
        ((Request) aRequest).setHandled(true);&lt;br /&gt;
        aRequest.setCharacterEncoding(&amp;quot;utf-8&amp;quot;);&lt;br /&gt;
        aResponse.setCharacterEncoding(&amp;quot;utf-8&amp;quot;);&lt;br /&gt;
        aResponse.addHeader(&amp;quot;Content-type&amp;quot;, &amp;quot;text/html; charset=utf-8&amp;quot;);&lt;br /&gt;
        PrintWriter writer = aResponse.getWriter();&lt;br /&gt;
        writer.write(&amp;quot;&amp;lt;html&amp;gt;&amp;lt;head&amp;gt;&amp;quot; +&lt;br /&gt;
            &amp;quot;&amp;lt;title&amp;gt;Адресная книга&amp;lt;/title&amp;gt;&amp;quot; +&lt;br /&gt;
            &amp;quot;&amp;lt;/head&amp;gt;&amp;lt;body&amp;gt;&amp;lt;h1&amp;gt;Адресная книга&amp;lt;/h1&amp;gt;&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Откомпилируйте полученный код:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
cd ~/Programming/AddressBook/src&lt;br /&gt;
javac –encoding utf-8 -cp ../libs/jetty-6.1.0rc3.jar:../libs/jetty-util-6.1.0rc3.&lt;br /&gt;
jar:../libs/servlet-api-2.5.jar -d ../build *.java&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
И запустите (не забыв подключить все требуемые библиотеки):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
cd ~/Programming/AddressBook/build&lt;br /&gt;
java -cp ../libs/jetty-6.1.0rc3.jar:../libs/jetty-util-6.1.0rc3.jar:../libs/servletapi-&lt;br /&gt;
2.5.jar:. AddressBook&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Терминал выводит несколько строчек и «зависает». Все правильно, сервер запустился. Запускать наше приложение будем пока именно так, чтобы его можно было легко выключить ('''Ctrl+C'''). Теперь откройте браузер и наберите в адресной строке: '''&amp;lt;nowiki&amp;gt;http://localhost:8081&amp;lt;/nowiki&amp;gt;'''. Вот он, наш заголовок!&lt;br /&gt;
&lt;br /&gt;
Вернемся к обработчику и попробуем уяснить, как различать запросы пользователей. Для этого в обработчике есть две возможности. Самая простая – посмотреть на первый параметр ('''aTarget'''), который содержит «файл» запроса (например в URL’е «http://www.linuxformat.ru/index.html» '''aTarget''' будет «'''/index.html'''»); посложнее – анализировать сам объект запроса. Воспользуемся первым вариантом, дописав в конец метода '''handle''' (прямо перед закрывающей скобкой) следующее:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Листинг 6.''' Более сложный обработчик с разбором адреса&lt;br /&gt;
&amp;lt;source lang=&amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
if (&amp;quot;/&amp;quot;.equals(aTarget)) {&lt;br /&gt;
    writer.write(&amp;quot;&amp;lt;a href=\&amp;quot;/add\&amp;quot;&amp;gt;Добавить запись&amp;lt;/a&amp;gt;&amp;lt;br/&amp;gt;&amp;quot;);&lt;br /&gt;
    writer.write(&amp;quot;&amp;lt;a href=\&amp;quot;/view\&amp;quot;&amp;gt;Просмотреть записи&amp;lt;/a&amp;gt;&amp;lt;br/&amp;gt;&amp;quot;);&lt;br /&gt;
} else {&lt;br /&gt;
    writer.write(&amp;quot;&amp;lt;a href=\&amp;quot;/\&amp;quot;&amp;gt;На главную&amp;lt;/a&amp;gt;&amp;lt;br/&amp;gt;&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    if (&amp;quot;/add&amp;quot;.equals(aTarget)) {&lt;br /&gt;
        writer.write(&amp;quot;&amp;lt;h2&amp;gt;Добавление записи&amp;lt;/h2&amp;gt;&amp;quot;);&lt;br /&gt;
    } else if (&amp;quot;/view&amp;quot;.equals(aTarget)) {&lt;br /&gt;
        writer.write(&amp;quot;&amp;lt;h2&amp;gt;Просмотр записей&amp;lt;/h2&amp;gt;&amp;quot;);&lt;br /&gt;
    } else if (&amp;quot;/edit&amp;quot;.equals(aTarget)) {&lt;br /&gt;
        writer.write(&amp;quot;&amp;lt;h2&amp;gt;Редактирование записи&amp;lt;/h2&amp;gt;&amp;quot;);&lt;br /&gt;
    } else if (&amp;quot;/remove&amp;quot;.equals(aTarget)) {&lt;br /&gt;
        writer.write(&amp;quot;&amp;lt;h2&amp;gt;Удаление записи&amp;lt;/h2&amp;gt;&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
writer.write(&amp;quot;&amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;&amp;quot;);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Попробуйте снова откомпилировать и запустить приложение, предварительно остановив предыдущую версию сервера. Обновите страницу в браузере – и побегайте по только что созданным страницам нашего сайта (корневая, она же «'''/'''», «'''/add'''», «'''/edit'''», «'''/remove'''»).&lt;br /&gt;
&lt;br /&gt;
Теперь в соответствующих ветках '''if''' можно написать обработчики страничек. Для упрощения вынесем эти обработчики в отдельные методы ('''листинг 7'''):&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Листинг 7.''' Добавляем обработчики отдельных действий&lt;br /&gt;
&amp;lt;source lang=&amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
    if (&amp;quot;/add&amp;quot;.equals(aTarget)) {&lt;br /&gt;
        writer.write(&amp;quot;&amp;lt;h2&amp;gt;Добавление записи&amp;lt;/h2&amp;gt;&amp;quot;);&lt;br /&gt;
        handleAdd(aRequest, writer);&lt;br /&gt;
    } else if (&amp;quot;/view&amp;quot;.equals(aTarget)) {&lt;br /&gt;
        writer.write(&amp;quot;&amp;lt;h2&amp;gt;Просмотр записей&amp;lt;/h2&amp;gt;&amp;quot;);&lt;br /&gt;
        handleView(writer);&lt;br /&gt;
    } else if (&amp;quot;/edit&amp;quot;.equals(aTarget)) {&lt;br /&gt;
        writer.write(&amp;quot;&amp;lt;h2&amp;gt;Редактирование записи&amp;lt;/h2&amp;gt;&amp;quot;);&lt;br /&gt;
        handleEdit(aRequest, writer);&lt;br /&gt;
    } else if (&amp;quot;/remove&amp;quot;.equals(aTarget)) {&lt;br /&gt;
        writer.write(&amp;quot;&amp;lt;h2&amp;gt;Удаление записи&amp;lt;/h2&amp;gt;&amp;quot;);&lt;br /&gt;
        handleRemove(aRequest, writer);&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Осталось понять, как именно обрабатывать соответствующие страницы. Рассмотрим, для примера, метод '''handleAdd()''' – остальные можно написать по аналогии:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Листинг 8.''' Обработчик добавления записи&lt;br /&gt;
&amp;lt;source lang=&amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
    private void handleAdd(HttpServletRequest aRequest, PrintWriter aWriter)&lt;br /&gt;
    throws IOException {&lt;br /&gt;
        if (aRequest.getParameter(&amp;quot;name&amp;quot;) != null) {&lt;br /&gt;
            _addressBook.addContact(aRequest.getParameter(&amp;quot;name&amp;quot;),&lt;br /&gt;
                aRequest.getParameter(&amp;quot;number&amp;quot;), aRequest.getParameter(&amp;quot;comment&amp;quot;));&lt;br /&gt;
            outputMessage(aWriter, &amp;quot;Контакт добавлен&amp;quot;);&lt;br /&gt;
        }&lt;br /&gt;
        outputForm(aWriter, aRequest, &amp;quot;&amp;quot;, &amp;quot;&amp;quot;, &amp;quot;&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В приведенном обработчике есть вызов метода '''aRequest.getParameter(&amp;quot;name&amp;quot;)'''. Этот метод выдает значение параметра, который передает браузер из формы ('''&amp;lt;input type=&amp;quot;text&amp;quot; name=&amp;quot;name&amp;quot;/&amp;gt;'''). Причем сервлету все равно, каким методом (GET/POST) был передан параметр. Конкретно для обработки добавления контакта в коде проверяется, равен ли результат вызова '''null'''. Если так – значит, форма «не выполнялась», и добавлять нечего. В противном случае мы считываем три параметра (имя, телефон и комментарий), сохраняем контакт и выводим форму (вдруг пользователь захочет добавить еще один контакт?).&lt;br /&gt;
&lt;br /&gt;
Методы '''outputForm''' и '''outputMessage''' ('''листинг 9''') выводят форму (либо пустую, либо заполненную данными, которые нужно редактировать) и сообщение (чтобы пользователь понимал, что действие произошло, и как оно завершилось):&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Листинг 9.''' Обработчик добавления записи&lt;br /&gt;
&amp;lt;source lang=&amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
private void outputForm(PrintWriter aWriter, HttpServletRequest aRequest,&lt;br /&gt;
        String aName, String aNumber, String aComment) {&lt;br /&gt;
    aWriter.write(&lt;br /&gt;
        &amp;quot;&amp;lt;form action=\&amp;quot;/edit\&amp;quot; method=\&amp;quot;post\&amp;quot;&amp;gt;&amp;quot; +&lt;br /&gt;
        &amp;quot;&amp;lt;input type=\&amp;quot;hidden\&amp;quot; name=\&amp;quot;edited\&amp;quot; value=\&amp;quot;&amp;quot; +&lt;br /&gt;
            aRequest.getParameter(&amp;quot;number&amp;quot;) + &amp;quot;\&amp;quot;/&amp;gt;&amp;quot; + &amp;quot;&amp;lt;table&amp;gt;&amp;quot; +&lt;br /&gt;
        &amp;quot;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;Имя: &amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&amp;lt;input type=\&amp;quot;text\&amp;quot; name=\&amp;quot;name\&amp;quot; value=\&amp;quot;&amp;quot; +&lt;br /&gt;
            aName + &amp;quot;\&amp;quot;/&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&amp;quot; +&lt;br /&gt;
        &amp;quot;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;Телефон: &amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&amp;lt;input type=\&amp;quot;text\&amp;quot; name=\&amp;quot;number\&amp;quot; value=\&amp;quot;&amp;quot; +&lt;br /&gt;
            aNumber + &amp;quot;\&amp;quot;/&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&amp;quot; +&lt;br /&gt;
        &amp;quot;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;Примечания: &amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&amp;lt;input type=\&amp;quot;text\&amp;quot; name=\&amp;quot;comment\&amp;quot; value=\&amp;quot;&amp;quot; +&lt;br /&gt;
            aComment + &amp;quot;\&amp;quot;/&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&amp;quot; +&lt;br /&gt;
        &amp;quot;&amp;lt;tr&amp;gt;&amp;lt;td colspan=\&amp;quot;2\&amp;quot; align=\&amp;quot;center\&amp;quot;&amp;gt;&amp;quot; +&lt;br /&gt;
            &amp;quot;&amp;lt;input type=\&amp;quot;submit\&amp;quot; name=\&amp;quot;Отправить\&amp;quot;/&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&amp;quot; +&lt;br /&gt;
        &amp;quot;&amp;lt;/table&amp;gt;&amp;lt;/form&amp;gt;&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    private void outputMessage(PrintWriter aWriter, String aMessage) {&lt;br /&gt;
        aWriter.write(&amp;quot;&amp;lt;span style=\&amp;quot;color: green;\&amp;quot;&amp;gt;&amp;quot; + aMessage + &amp;quot;&amp;lt;/span&amp;gt;&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Для того, чтобы проверить работу нового обработчика, закомментируйте вызовы методов, которые еще не написаны (или напишите «заглушки» в виде пустых методов). Теперь можно снова остановить сервер, скомпилировать программу и запустить ее на выполнение. Если все набрано правильно, ошибок не последует, и можно будет просматривать табличку с контактами, добавлять их, редактировать и удалять. Наша адресная книга готова к работе!&lt;br /&gt;
&lt;br /&gt;
На этом уроке вы создали свое первое JEE-приложение. Конечно, за кадром осталось множество возможностей – начиная с использования сессий, контроля корректности параметров, и заканчивая отделением дизайна от логики работы сервлета – но наша программа уже вполне работоспособна. И, конечно, история не заканчивается. В следующих статьях будет продолжение, посвященное развитию приложения в соответствии с усложнением запросов компании к адресной книге.&lt;/div&gt;</summary>
		<author><name>Dionysius</name></author>	</entry>

	<entry>
		<id>http://wiki.linuxformat.ru/wiki/%D0%A4%D0%B0%D0%B9%D0%BB:AddressBook.tar.bz2</id>
		<title>Файл:AddressBook.tar.bz2</title>
		<link rel="alternate" type="text/html" href="http://wiki.linuxformat.ru/wiki/%D0%A4%D0%B0%D0%B9%D0%BB:AddressBook.tar.bz2"/>
				<updated>2008-12-30T17:20:16Z</updated>
		
		<summary type="html">&lt;p&gt;Dionysius: Исходники к уроку JavaEE в LXF89&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Исходники к уроку JavaEE в LXF89&lt;/div&gt;</summary>
		<author><name>Dionysius</name></author>	</entry>

	</feed>