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

Компоненты приложения и технологии, используемые для связи между ними. Обзор программных средств и технологий, используемых в ходе работы. Трансляция кода JSP страницы в код сервлета. Создание структуры базы данных c применением фреймворка Hibernate.

Рубрика Программирование, компьютеры и кибернетика
Вид курсовая работа
Язык русский
Дата добавления 16.07.2016
Размер файла 1,1 M

Отправить свою хорошую работу в базу знаний просто. Используйте форму, расположенную ниже

Студенты, аспиранты, молодые ученые, использующие базу знаний в своей учебе и работе, будут вам очень благодарны.

Размещено на http://www.allbest.ru//

Размещено на http://www.allbest.ru//

Проектирование архитектуры приложения

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

Формулирование функциональных требований к приложению

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

Моделирование предметной области.

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

Поведенческие требования.

На данном этапе определяется, как будут взаимодействовать пользователь и система, создается набросок прототипов пользовательских интерфейсов (экранов), и связей между ними.

Обзор программных средств и технологий, используемых в ходе работы

Проектирование сайта

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

Проектирование базы данных

Обзор и использование средств, позволяющих реализовать функционал Web аудио плеера

Проектирование архитектуры приложения

Формулирование функциональных требований к приложению

Требование - это сформулированное утверждение об одной из функций

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

Характеристики хороших требований:

* требование описывает одну функцию

* требование написано на понятном языке

* требование не должно содержать больше трех предложений, т.е. быть коротким по форме.

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

Результат работы над требованиями представлен ниже. Все они приведены в формате: название: […], описание: […], и организованны по группам.

Функциональность плеера

Title: Плейлист

Description: Пользователь может сохранять аудиофайлы в свой плейлист.

Title: Прослушивание музыки.

Description: Пользователь может прослушивать аудиофайлы, хранящиеся на сервере.

Title: Загрузка музыки

Description: Пользователь может загружать аудиофайлы на сервер.

Title: Поиск музыки

Description: Пользователь может искать аудиофайлы, которые находятся:

1. На сервере.

2. В плейлисте любого пользователя.

Поиск производится по:

1. Названию песни.

2. Названию группы.

Социальная функциональность

Title: Аутентификация

Description: Система должна поддерживать авторизацию пользователей.

Title: Поиск пользователей

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

Title: Добавление в закладки

Description: Пользователь может сохранить ссылку на плейлист другого пользователя.

Title: Список подписчиков

Description: Пользователь может просматривать список видимых (для него) подписчиков, и их общее количество.

Функциональность интерфейса

Title: Списки воспроизведения

Description: Пользователь может создавать списки воспроизведения.

Title: Организация папок

Description: Пользователь может создавать папки и размещать файлы по ним.

Модель предметной области

Модель предметной области формирует однозначный терминологический словарь

для предметной области. Чтобы создать ее, используются набор функциональных требований. На практике, МПО является упрощенной диаграммой классов.

[Элемент1][Элемент2] - Элемент1 обязательно имеет один, и только один Элемент2.

[Элемент1][Элемент2] - Элемент1 может иметь несколько экземпляров Элемента2, но не меньше одного.

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

Рис. 1

Поведенческие требования и пользовательские экраны

Страница аутентификации позволяет зайти на сайт с уже существующего аккаунта, либо зарегистрироваться.

Рис. 2

Главная страница отображает плейлист, либо выбранный список воспроизведения. Активные кнопки - пауза/плей на каждой песни. Присутствует кнопка для добавления треков в плейлист.

Верхняя часть страницы одинакова для всех страниц сайта. Она содержит ссылки на социальную страницу, страницу настроек, и на главную страницу.

Рис. 3

Социальная страница выводит список и общее количество подписчиков, а также содержит ссылку на страницу поиска пользователей.

Рис. 4

Обзор программных средств и технологий, используемых в ходе работы

Java EE - набор спецификаций и соответствующей документации для языка Java, описывающей архитектуру серверной платформы. Реализациями JavaEE являются различные сервера приложений. Они включают следующие компоненты:

EJB-контейнер

JMS

Реализация драйвера для баз данных

Веб-сервер

Контейнер сервлетов

Поддержка веб-сервисов

JSP

JSF

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

Проектирование сайта

Система приложения состоит из следующих частей:

Клиентская часть

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

Веб-сервер

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

Контейнер сервлетов

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

Реляционная база данных

В базе данных хранится информация о пользователях и их настройках. На рисунке выше, в используемых технологии для связи с БД отмечен JDBC. JDBC - драйвер для взаимодействия сервера с базой данных. В моем случае это Connector/J для Tomcat.

JSP

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

Проектирование базы данных

Сущности ER-диаграммы базы данных во многом повторяют элементы диаграммы модели предметной области.

Далее приведен вид основных таблиц:

Таблица аккаунта пользователя:

Поле

Примечание

Логин

Не NULL, ключ

Пароль

Не NULL

Таблица 1

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

Таблица базы песен:

Поле

Примечание

ID песни

Не NULL, ключ

Название песни

Не NULL

Название группы

Не NULL

Таблица 2

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

Таблица списков воспроизведения:

Поле

Примечание

ID с.в.

Ключ

ID пользователя

Не NULL

Таблица 3

Поскольку в работе используется технология ORM (объектно-реляционного отображения), част реализации связей целиком ложится на фреймворк Hibernate. В число таких связей входит:

Связь между таблицами пользователей, и подписок

Связь между таблицей пользователей, и таблицей песен (многие ко многим)

Связь между таблицей списков воспроизведения и таблицей песен (многие ко многим)

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

Логирование.

В приложение использовался логгер ApacheLog4j2. Удобство применения логгера состоит в гибкой настройке отображения логов, и возможности в режиме реального времени менять уровень выводимых сообщений, а так же устанавливать запись логов в файл. Log4j2 поддерживает 5 уровней сообщений:TRACE, DEBUG, INFO, WARN, ERROR, FATAL.

Соединение сервера с базой данных MySQL.

Для соединения Javaприложений с реляционными базами данных, используется JDBC. JDBC - интерфейс программирования приложений (API) входящий в состав JavaSE. Эта технология вводит абстрактный уровень между приложением и БД, таким образом код приложения не нужно изменять при переходе к другой СУБД. Вместо того чтобы подсоединяться к БД напрямую, приложение посылает запрос через JDBCAPI, которое в свою очередь обращается к БД с помощью драйвера, который конвертирует вызовы методов JDBC в диалект используемой БД. Таким образом, если разработчику требуется обращаться к нескольким БД в рамках одного приложения, ему не потребуется адаптировать свой код: он просто будет использовать два разных JDBCдрайвера.

Соединение с БД -- это потенциальное «узкое место» приложения (часть архитектуры приложения, которая интенсивно используется, и может создать простой системы; например, если в систему приходят больше количество запросов, обращающихся к какому-нибудь ресурсу, и они встают в очередь). В высоко нагруженной среде, приложение может быть перегружено большим количеством запросов к БД, если настройка соединения не оптимизирована. Чтобы избежать этого, Tomcat использует технику под название пул соединений. Вместо того чтобы создавать новое соединение каждый раз, когда приходит новый запрос, запросы ставятся в очередь. Им выделяются соединения, которые после использования не закрываются, а возвращаются в пул, где они затем становятся доступны для последующих запросов. Таким образом, можно настроить максимальное количество хранимых соединений, максимальную длину жизни хранимого соединения при отсутствии обращений к БД и т.д. Tomca tпозволяет использовать эту технологию, определяя пул соединений как JNDIресурс.

Итак, для соединения Tomcatс MySQL требуются следующие осуществить следующие шаги (помимо установки самого контейнера сервлетов и СУБД):

MySQLJDBCдрайвер - тот самый драйвер, реализующий конвертацию запросов в соответствующий базе диалект - должен быть помещен в директорию $CATALINA_HOME/lib, где $CATALINA_HOME-директория с установленным Tomcat. Сервер делает библиотеки лежащие в этой директории доступными всем приложениям.

Необходимо зарегистрировать MySQLБД как JNDIресурс в контексте приложения (META-INF/context.xml) и указать его в дескрипторе развертывания (WEB-INF/web.xml). При регистрации ресурса будет указана информация о базе данных, в том числе: имя ресурса, класс ресурса, настройки для пула соединений, класс, который выполняет роль драйвера, а также URL хоста с MySQL сервером и порт.

Ниже приведен код из context.xml:

<Resource name="jdbc/playerDB" auth="Container"

type="javax.sql.DataSource" maxActive="100"

maxIdle="30" maxWait="10000"

username="diplom_user"

password="***************"

driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/playerdb?useSSL=false"

/>

Листинг 1

Код из web.xml:

В web.xml указана информация, не изменяющаяся при переходе на другой сервер:

<resource-ref>

<description>BD connection</description>

<res-ref-name>jdbc/playerDB</res-ref-name>

<res-type>javax.sql.DataSource</res-type>

<res-auth>Container</res-auth>

</resource-ref>

Технология объектно-ориентированного отображения

Практически любое современное приложение требует возможности сохранять данные. Реляционные СУБД применяются повсеместно, благодаря своей гибкости, и надежному подходу к управлению БД.

При работе с реляционными базами данных SQL в Java приложениях, Javaкод посылает запросы через JavaDataBaseConnectivity (JDBC) API. SQL выражения могут быть как фиксированными (тогда есть возможность выполнять валидацию запросов до запуска приложения), так и генерироваться кодом Javaна лету.JDBCAPI используется чтобы связывать аргументы с параметрами запроса, инициировать выполнение запроса, перебирать результаты, и т.д. Однако обычно разработчиков больше интересует логика приложения, нежели низкоуровневое программирования связанное с БД.

Технология ORM (Object-RelationalMapping) предоставляет возможность обращаться к реляционным базам данных сохраняя, и выгружая сложные объекты - экземпляры Java классов, избавляя от необходимости писать код-прослойку между бизнес логикой и базой данных для каждой сущности.

Приложение использующее эту технологию, не работает напрямую с табличным представлением данных: в то время как в базе данных находятся таблицы ITEMи BID, в приложении ведется работа с Java классами Itemи Bid. Бизнес логика такого приложения не выполняется в СУБД (в виде хранимой процедуры), она реализована на Java.

Если сравнить объектную и реляционную модели данных, то обнаруживается существенное несоответствие между этими двумя способами представления, из-за которых осуществление отображения данных в приложении могут быть сильно затруднены.

Выделяют следующие проблемы ORM:

Проблема разбиения

Пусть в модели данных есть сущность USER, которая среди прочих полей имеет поле ADDRESS. В большинстве систем необходимо хранить информацию об улице, городе, штате, стране и ZIPкоде отдельно. Таким образом, можно будет использовать эти части информации в бизнес логике, чтобы, например, составить статистику посещений пользователей по регионам, и т.д. Конечно, всю это информацию можно добавить в качестве полей кJavaклассу User. Однако вероятней всего, другие объекты системы тоже будут иметь поле адреса. В таком случае, с точки зрения объектно-ориентированного подхода, правильнее создать отдельный классAddress, и во всех остальных классах использовать его экземпляр как поле. Однако с точки зрения реляционной модели данных, мы не хотим создавать отдельную таблицу ADDRESS, т.к. по своей сути, адрес должен быть полем USER.Таким образом у нас есть выбор - либо добавить к таблице USER несколько новых полей, либо объявить пользовательский тип ADDRESSи добавить его как одно дополнительное поле. SQL стандарты поддерживают пользовательские типы данных, но их использования не предполагает высокой переносимости.

Таким образом, со стороны Java приложения мы имеем сущности раздробленные на разные классы, которые в свою очередь тоже могут состоять из классов. Но со стороны базы данных у нас есть лишь таблица USER, и поля наподобие ADRESS_COUNTRY, ADDRESS_STATE, и т.д.

Проблема наследования и полиморфизма

В SQLодна таблица не может наследовать другую. Таким образом, с точки зрения объектно-ориентированного подхода, User может иметь связь с BillingDetails, также как и с его подклассами. Таким образом, может возникнуть нужда написать запрос, обращающийся к BillingDetailклассу, и возвращающим экземпляры подклассов. Такой запрос называется полиморфным (например мы хотим рассмотреть все счета с суммой больше Nрублей, и выбрать только те, которые оплачивались кредитной картой). Организовывать это с помощью средств SQL, не поддерживающих механизм наследования, может быть весьма проблематично.

Проблема идентичности

Есть также ряд сложностей, связанных с сравнением сущностей. В Java есть два типа сравнения - «==» и «equals()», однако ни один из них не эквивалентен равенству с точки зрения базы данных, т.е. равенству первичных ключей.

Проблема отношений

В объектно-ориентированной модели данный, отношения реализовываются с помощью ссылок на объекты, и их коллекций. В реляционной модели, связь представляется с помощью внешнего ключа, который копирует ключевые значения таблиц. Различие заключается в том, что чтобы объекты были связаны «в обе стороны» (т.е. по объекту Aможно найти объект B, и наоборот), связь нужно определить дважды, в обоих связанных классах. Например, учитывая, что у одного пользователя может быть несколько счетов, то связь на объектном уровне будет выглядит следующим образом:

public class User

{

private Set <billingDetails>;

...

}

public class BillingDetails

{

privateUseruser;

...

}

В реляционном представлении данных, при наличии внешних ключей, СУБД берет на себя обязанности по связыванию данных.

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

Решение подобных проблем несоответствия может занять очень большое количество времени, при этом от результирующих DAO (DataAccessObject)классов требуется достаточно большая гибкость, в случае каких-либо изменений в проекте.

Hibernate предоставляет собой библиотеки для автоматического объектно-реляционного отображения, использующего метаданные, которые, в свою очередь описывают связи между полями Java классов и полями таблиц, а также отношения между таблицами. Существует две возможности представлять эти данные:

В виде Java аннотаций

В виде xml файлов

В моем приложении используется 2й способ.

Предоставляемый Hibernate APIсостоит из следующих частей:

Возможность выполнения CRUD (базовые операции - Create, Remove, Update, Delete) над объектами персистентных классов.

Язык для выполнения запросов, в которых участвуют классы, и их поля (помимо возможности писать SQLзапросы, Hibernate предоставляетязык HQL)

Аннотации и плагины для указания метаданных

Возможность настройки lazyassociationfetching (дословно - «ленивая выборка связанных данных» -означает что коллекция объектов, связанных с данным будет выгружаться из базы данных только при явном обращении) и другие настройки, позволяющие оптимизировать приложение.

HibernateEntities

Ниже приведены используемые в работе классы сущности, а также метаданные, которые Hibernate использует для их обработки.

Между таблицами USERS и SUBSCRIBERS установлена связь многие-ко-многим. Один пользователь может быть подписан на множество других пользователей, и множество других пользователей могут быть подписаны на него.

КлассUser(полный код этого и других классов приведен в приложении)

public class User implements Serializable

{

private String userID;

private String userPassword;

private Set<Subscription> subscriptions; //многиекомногим

private Set<Subscription> subscribers;//многиекомногим

private Set<Song>songs; //многиекомногим

private Set<Playlist>playlists;//Одинкомногим

@Override

public boolean equals(Object obj) {

@Override

public boolean hashCode(Object obj) {

}

Листинг 4

Также, Hibernate требует чтобы классы, которые будут хранится в качестве связи между сущностями (в полях Set), перегружали методы equals() и hashCode(). Это происходит из-за того, что Hibernate гарантирует эквивалентность между строками БД и Java классами только в пределах одной сессии. Как только появляется возможность получить экземпляры классов сущностей в пределах разных сессий, необходимо дать Hibernate возможность их сравнивать.

Класс-сущность, связанная с таблицей подписчиков:

public class Subscription implements Serializable

{

//В данном классе полями будут userIDкласса User

//В остальном этот класс устроен абсолютно так же

private User subscriberID;

private User userID;

@Override

public boolean equals(Object obj) {…}

@Override

public int hashCode() {…}

}

Листинг 5

Ниже приведен конфигурационный файлHibernate - hibernate.cfg.xml:

<hibernate-configuration>

<session-factory>

<property name="hibernate.connection.datasource">java:comp/env/jdbc/playerDB</property>

<property name="show_sql">true</property>

<property name="dialect">org.hibernate.dialect.MySQLDialect</property>

<property name="hibernate.hbm2ddl.auto">create</property>

<mapping resource="player/database/hibernate/entities/user.hbm.xml"/>

<mapping resource="player/database/hibernate/entities/subscription.hbm.xml"/>

</session-factory>

</hibernate-configuration>

Листинг 6

Здесь определяется:

Уже описанный вышеTomcat Database Connection Pool ввидеJNDI ресурса

Как поступать с SQL запросами, которые внутренние классы Hibernate посылают к базе данных - выводить в консоль.

Свойство hibernate.hbm2ddl.autoсо значением create указывает на то, что нужно пересоздавать таблички каждый раз при первой выгрузки классов в память. Этот режим работы Hibernate удобен для процесса разработки, впоследствии его необходимо отключать.

Дальше следуют указания на файлы предназначенные для маппинга конкретных сущностей - Userи Subscription.

Файлuser.hbm.xml:

<class name="User" table="USERS">

<id name="userID" column="USER_ID" length="20">

<generator class="assigned"></generator>

</id>

<property name="userPassword" column="USER_PASS" not-null="true" length="20" />

<set name="subscriptions" table="SUBSCRIBERS" inverse="true" lazy="true" fetch="select" cascade="delete">

<key>

<column name="SUBSCRIBER_ID" />

</key>

<one-to-many class="Subscription" />

</set>

<set name="subscribers" table="SUBSCRIBERS" inverse="true" lazy="true" fetch="select" cascade="delete">

<key>

<column name="USER_ID" />

</key>

<one-to-many class="Subscription"/>

</set>

</class>

</hibernate-mapping>

Листинг7

В этом файле настроек сперва задается первичный ключ для таблицы пользователей, и одно из полей, на которое налагается ограничение целостности (not-null=”true”). Одновременно с этим указываются названия столбцов и названия создаваемой таблицы.

В теге <set>указывается отношения один-ко-многим; атрибуты тега по порядку:

table- имя таблицы, с которой устанавливается связь

inverse - описываемая таблица является «главной»: это значит что при удалении записи из описываемой таблицы, также будут удаляться все записи связанные с ней из таблицы, связанной с ней отношениями.

lazy - при получении объекта из базы данных, связанные объекты не будут выгружаться (в переменную Set<Subscriptions>пока метод не будет явно вызван)

fetch- ассоциированные объекты (из связанной таблицы) будут выбираться при помощи отдельного вызова SELECT (можно потребовать вызывать их в том же запросе, в котором вызывается объект из описываемой таблицы)

cascade - при вызове delete(…) будут удалятся все связанные записи другой таблицы. Если у другой таблицы тоже указан атрибут cascade=”delete”, то аналогично будут удаляться связанные с ней записи. Следует заметить, что этот способ каскадного удаления записей не использует средство декларативного ограничения целостности СУБД ONDELETECASCADE. В Hibernateимеется возможность настроить каскадное удаление с помощью него, однако это не выгодно с точки зрения производительности приложения.

Таким образом, в поле Set<Subscription>subscriptions будет хранится связь один ко многим таблицы USER Sи таблицы SUBSCRIBERS: указания на записи из SUBSCRIBERS, в которых данный пользователь является подписчиком.

В поле Set<Subscription>subscribers будет хранится другая связь: ссылки на записи из SUBSCRIBERS, в которых на данного пользователя подписан другой пользователь.

Для использования данных классов необходимо сначала получить доступ к SessionFactory-классу Hibernate, который выделяет объекты - сессии через которые, в свою очередь, можно слать запросы к БД. Session Factory является потокобезопасным классом, реализованным с помощью паттерна Singleton: в системе есть один экземпляр класса, к которому может получить доступ любой поток. При этом класс Session, экземпляры которого потоки получают с помощью SessionFactory, потокобезопасным не является. Каждый поток должен использовать свою сессию для работы с БД. Для получения SessionFactoryбыл создан следующий вспомогательный класс:

publicfinalclassSessionFactoryRegisterer

{

private static final SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();

private static final Logger logger = LogManager.getLogger(SessionFactoryRegisterer.class);

public static Session Factory get JndiSession Factory()

{

return sessionFactory;

}

/**

* Using SessionWrapper for reassigning session from servlet.

* @param session

* @return

*/

public static boolean openSessionCheckNotNull(SessionWrapper session)

{

session.setSession(getJndiSessionFactory().openSession());

if(session.getSession()==null)

{

logger.error("CANNOT OPEN SESSION");

returnfalse;

}

returntrue;

}}

Листинг 8

В нем также определен метод для открытия сессии. Аргументом для метода является класс обертка для сессии SessionWrapper, для того чтобы можно было сохранять ссылку на открытую сессию (это вызвано тем, что в Java передача объектов в методе происходит по ссылке, и, если ей присвоить другое значение, исходный объект никак не изменяется.Поэтому для передачи экземпляров класса, у которого нет методов для изменения, приходится создавать обертку).

Пример использования этих классов приведен в приложении.

if(!ServletUtilities.openSessionIfNullRedirect(request, response, sessionWrapper))

{

usr = sessionWrapper.getSession().get(User.class, userID);

if(usr!=null&&usr.getUserPassword.equals(password))

{

// Сохранить значение пользовательского логина в

//куки браузера для дальнейшего автозаполнения формы

Cookie formLoginAutoFilling = new Cookie("user_login",usr.getUserID());

formLoginAutoFilling.setMaxAge(60 * 60 * 24);

//Длительности жизни сохраненного значения - один день

resp.addCookie(formLoginAutoFilling);

HttpSession session = req.getSession();

session.setAttribute("user", usr.getUserID());

//В сессию кладется атрибут user, чтобы запомнить что пользователь прошел авторизацию

req.setAttribute("action", "displayUserPage");

logger.debug("Logged In");

ServletUtilities.redirectToPath(req,resp,/app/controller");

//Код ServletUtilitiesописан в приложении

}

}

}

}

Листинг 9

Паттерн MVC

MVC (Model-view-controller) - это архитектурный паттерн программирования, предназначенный для разделения функциональности компонентов приложения на три части: модель, вид, контроллер.

Модель представляет из себя ту часть приложения, которая отвечает за хранение данных и их изменение (т.е. модель непосредственно реализует внутреннюю бизнес-логику приложения). Модель должна уметь передавать данные контроллеру.В качестве модели может выступать сочетание Java классов с данными реляционной БД, текстовыми файлами xml документами. В моем приложении модель представляет из себя совокупность сущностей Hibernate, вспомогательный класс Session Factory Registerer и файлы JSON.

Представление отвечает за графический интерфейс приложения. Оно должно предоставлять данные из модели пользователю, а также реагировать на его действия. Сюда могут относится кнопки, формы, и остальные предметы пользовательского интерфейса. В приложении эту роль играли JSP страницы.

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

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

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

Рис. 6

Код Controller Servlet:

public class Controller Servlet extends Http Servlet {

private static final Logger logger = LogManager.get Logger(ControllerServlet.class);

ActionFactory actionFactory = ActionFactory.getInstance();

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

processRequest(request, response);

}

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

/*ServletUtilities.redirectToPath(request, response, "second.jsp");*/

processRequest(request, response);

}

protected void process Request (HttpServletRequest request, HttpServletResponse response)

{

logger.debug("ControllerServlet ENTRY");

String path = null;

try

{

//Объявляем переменную, реализующую интерфейс, записываем в нее конкретный //классс помощью ActionFactory

Actionaction = actionFactory.getAction(request);

//метод выполнения действия возвращает путь, на который контроллер должен //перенаправить запрос после выполнения действия; это значит что каждое //действие должно выбирать представление

path = action.execute(request, response);

//использование вспомогательного класса для перенаправления запроса

if(path!=null)

{

ServletUtilities.redirectToPath(request, response, path);

}

else {logger.debug(“Controller doesn't redircet, action takes care of redirecting, or printing error page”);}

}

catch (ServletException|IOException e)

{

logger.error(e);

throw new RuntimeException(“ControllerServlet error”);}}}

Листинг 10

Паттерн Factory

В сервлете, исполняющем роль контроллера, указан универсальный порядок действий, которых необходимо выполнить при обработке запроса. В коде сервлета не используются конкретные реализации интерфейса Action (интерфейс Actionсодержит один единственный метод execute (HttpServlet Requestrequest, Http Servlet Responseresponse)). Вместо этого используется конкретная реализация фабрики действий, задача которой - вставить нужный класс действия в контроллер в зависимости от запроса. Смысл фабрики состоит в повышении возможности повторного использования кода. Для того чтобы реализовать паттерн MVCв другом приложении достаточно будет вставить тот же класс ServletController, и реализовать новую фабрику действий, содержащую классы, которые будут отвечать другим требованиям. Паттерн фабрика является одним из способов реализовать отделение интерфейса от реализации.

Ниже приведен код Action Factory:

public class ActionFactory

{

private static ActionFactory instance = null;

HashMap<String, Action> actions = new HashMap<String, Action>();

privateActionFactory()

{

//Заполнение фабрики различными реализациями интерфейса Action

actions.put("displayUserPage",newActionDisplayPage());

actions.put("displayAllFriends",newActionDisplayAllFriends());

actions.put("removeUserFromFriends",newActionRemoveUserFromFriends());

actions.put("logOut",newLogOut());

actions.put("searchUsers",new ActionSearchUsers());

}

public Action getAction(HttpServletRequest request)

{

//Получение названия действия из запроса

Stringaction_req = request.getParameter("action");

//Получение действия, соответствующего названию

Actionaction = actions.get(action_req);

if(action == null) {

//Если параметр не был передан, то отобразить страницу пользователя

action = new ActionDisplayPage();

}

return action;

}

//создание единственного объекта по шаблону Singleton

public static ActionFactory getInstance()

{

if (instance == null) {

instance = new ActionFactory();

}

return instance;

}

}

Листинг 11

Интерфейс Actionи пример его реализации приведен в приложении.

Асинхронные запросы

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

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

XMLHttpRequest

Для реализации асинхронной передачи запросов и ответов между браузером и сервером используется объект XMLHttpRequest.

Рис. 7

Обычная схема для реализации подобных запросов следующая:

Пользовательские события (наведение мыши, нажатие кнопки, новый символ строке поиска) связаны с функциями JavaScript, создающими объект XMLHttpRequest.

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

Запрос приходит на сервер и обрабатывается сервлетом.

Объект XMLHttp Request получает ответ от сервера в формате XML, вызывается функция-обработчик события Java Script, которая динамически обновляет модель DOM документа HTML (перезагрузка страницы при этом не происходит.

Ниже приведен пример применения XMLHttpRequest:

<buttononclick="removeFriend('${friend.user.userID}')">

Удалить из друзей

</button>

Листинг 12

varreq;

varfriend_ID;

varfriend_table;

functionremoveFriend(friendID,friendTable)

{

varurl=

/*в качестве аргумента функции передается URLсервлета контроллера, действие которое нужно активировать на стороне пользователя, и IDпользователя, которого нужно удалить из подписок*/ "http://localhost:8080/albert_diplom_war/app/controller?action=removeUserFromFriends&sub="+friendID;

if(confirm("Уверен что хочешь удалить пользователя "+friendID+"?"))

{

req=newXMLHttpRequest();

req.open("GET",url,true);

req.onreadystatechange= callback;

friend_ID=friendID;

friend_table=friendTable;

req.send(null);

}

}

function callback(){

if(req.readyState==4){

if(req.status==200){

isSuccessfull(req.responseXML, req.getPrameter(otherUserID));

}

}

}

Functionis Successful (responseXML, otherUser)

{

if(responseXML==null)

{

returnfalse;

}

else

{

alert("successfully deleted!");

/*Строкавтаблицысidравнымимениподпискиудаляется */

vari=document.getElementById(otherUser);

document.getElementById("usersTable").deleteRow(i);

returntrue;

}

}

Листинг 13

В данном примере, после прихода ответа от сервера, в случае если ready State поле запроса имеет значение 4 (что значит успешное выполнение HTTP взаимодействия) и значение status равно 200 (т.е. запрос успешно выполнен) - тогда из таблицы в HTML странице удаляется элемент-строка таблицы с id равным имени пользователя, ссылку на которого мы удаляем.

Использование языка выражений и тэгов JSTL

Помимо использования чистого кода Java в страницах JSP имеется также возможность использовать язык выражений.Язык выражений имеет следующий синтаксис: ${выражение, возвращающее некоторое значение}.

Когда компилятор JSP видит${} обозначение, он генерирует код, вычисляющий значение, и подставляет его в HTMLкод. Например, если сервлет передает в качестве параметра запроса IDпользователя, страницу которого отображает JSP, он может отображаться следующим образом:

<divclass=”headline”>Страница пользователя {user.userID}</div>

Где user может быть объектом Java Bean сохраненным в сессии, или аргументе запроса. Таким же образом внутри JSP можно обращаться к кукам браузера.

Жизненный цикл JSP

Первым этапом жизненного цикла является трансляция кода JSP страницы в код сервлета.

Компиляция полученного кода сервлета.

Следующие этапы жизни JSPстраницы соответствуют этапам жизни сервлета. При отсутствии экземпляра сервлета, контейнер :

Загружает класс сервлета в память JVM.

Создает экземпляр этого класса, с помощью метода jspInit (аналог init() у обычного сервлета).

Вызывает метод jspService (аналог service() у сервлетов), который принимает объекты запроса и отклика.

При необходимости удалить сервлет из памяти, вызывается метод jspDestroy (как не сложно догадаться, аналог destroyу сервлета).

Таким образом, после при выполнении запросов, вся динамика происходит только на стороне сервера, в то время как клиент получает обычную статическую веб-страницу HTML.

Пример JSP, отображающего подписки пользователя:

<c:forEachvar="friend" items="${userData}">

<tr>

<td>

<!--При нажатии на ID пользователя, вызовется контроллер, с параметрами: действие - отобразить страницу, иID указывающего поле базы данных<a/controller?action=displayUserPage&other_user=${friend.user.userID}">

${friend.user.userID}

</a>

<buttononclick="removeFriend('${friend.user.userID}', this)">

Удалить из друзей

</button>

</td>

</tr>

</c:forEach>

Листинг 14

Теги - второй способ добавить динамический Java код к статическому HTML содержимому документа. Он не использует Java синтаксис напрямую, как скриплеты, и вся его функциональность реализуется в момент трансляции страницы в код сервлета. Использования тегов упрощает вид HTML страницы, позволяя более явно отделить логику от представления.

Тег<c:forEachvar="friend" items="${userData}">позволяет вывести данные на страницу в цикле, итератором которого будет переменная var=”имя переменной”, пробегающая через коллекцию указанную в items.

В данном примере в качестве параметра этой странице JSP передавалась коллекция Set<Subscription>подписок пользователя, таким образом переменная friend содержала в себе экземпляр Subscription, класса, который соответствует формату Java Bean.Это позволяет обращаться к его полям с помощью все тех же регулярных выражений: ${friend.user.userID}, где .user ссылается на поле Userв Subscription. User так же является Java Bean, таким образом .userID обращается к полю userIDUser'а.

Библиотека audio.js

audio.js - это библиотека Java script, которая позволяет использовать HTML5 тэг <audio> во всех браузерах. Это осуществляется следующим образом: сначала проверяется поддержка браузером HTML5. Если браузер поддерживает HTML5 используется тег, в противном случае для подключения плеера будет использоваться flesh. Вместе с библиотекой поставляется UI отображение для плеера, которое может быть изменено с помощью CSS.

Для подключения модуля необходимо подключить audio.jsк проекту:

<scriptsrc="/audiojs/audio.min.js"></script>

<script>

audiojs.events.ready(function(){

var as =audiojs.createAll();

});

</script>

<audio src=”../mp3/119234.mp3” />

Листинг 15

Плагин jsTree

jsTree - это jQuery плагин, который позволяет отображать древовидную структуру в браузере. За основу может браться html разметка, JSON, XMLфайлы. Этот плагин взят для отображения структуры пользовательских папок. Т.е. сервлет, отображающий пользовательскую страницу читает файл JSON, переправляет его клиенту, где Java Script с помощью jsTree отображает структуру данных на странице.Ниже приведен кусок кода:

User user = sessionWrapper.getSession().get(User.class, userID);

JSONObject json =new JSONObject();

json = User.getPlaylist();

response.setAttribute("playlist_json", json.toString());

Листинг 16

В классе Userметодget Playlist выполняет чтение JSON данных из файла /playlists/%userID%.

JSON Parser parser =new JSONParser();

Object obj = parser.parse(newFileReader(“/playlists/”+this.getUserID+”.json)

JSONObject jsonObject =(JSONObject) obj;

return jsonObject;

Листинг 17

В функции JavaScript:

varplayList=%{playList}.toJSON());//Т.к. скрипт встроен в JSP, можно //получать значение атрибутов с помощью регулярный выражений JSP

Листинг 18

Таким образом на стороне клиента, Java Script имеет доступ к JSON файлу, в котором определена структура каталогов пользователя. Для того, чтобы jsTree мог отобразить дерево, JSON должен иметь следующую структуру:

{

id : "string"// idузладерева

parent : "string"// id родительского узла

text : "string" // название узла

}

Для того чтобы указать корневой узел, требуется указать в свойстве parent значение “#”.

Заключение

В процессе работы были рассмотрены и изучены такие компоненты Java EE, как сервлеты и JSP страницы. Построена модель предметной области приложения. Структура базы данных создана c применением фреймворка Hibernate. С помощью сервлетов, и JSPбыл реализован паттерн MVC. Указан дальнейшие шаги для реализации приложения: использование JSON в качестве основы для построения дерева папок плагином jsTree, и использования audio.js библиотеки для кроссбраузерного проигрывания музыки.

музыка приложение мультимедийный

Список литературы

ChristianBauter.HibernateinAction. / ChristianBauter,GavinKing; [ManningPublications Co.], -14p.

Building Web Apps in Java: Beginning & Intermediate Servlet & JSP Tutorials [Электронныйресурс]. 2015. URL: http://courses.coreservlets.com/Course-Materials/csajsp2.html

И.Н. Блинов. Java: промышленное программирование. / И.Н. Блинов, В.С. Романчик; [УниверсалПресс], 2007. - 711 p.

Advanced Servlet and JSP Tutorials [Электронныйресурс]. 2015. URL: http://courses.coreservlets.com/Course-Materials/msajsp.html

Database Programming [Электронныйресурс]. 2016. URL: https://www3.ntu.edu.sg/home/ehchua/programming/index.html

Apache Tomcat Documentation [Электронныйресурс]. 2016. URL:http://tomcat.apache.org/tomcat-8.0-doc/index.html

ShingWai Chan. Java Servlet Specification, version 3.1/ ShingWai Chan, Rajiv Mordani ; [Oracle Corporation], April 2013. - 240 p.

Приложение

Код класса User, следующего правилам определения Java Bean (сериализуем, поля и методы имеют определенный формат).

publicclass User implementsSerializable

{

private String userID;

private String userPassword;

private Set<Subscription> subscriptions;

private Set<Subscription> subscribers;

privatePlaylistplaylist;

public User()

{

}

public User(String login, String password)

{

this.setUserID(login);

this.setUserPassword(password);

}

Publicvoidset UserID(String id)

{

if((id!=null)&&(!id.equals("")))

this.userID= id;

elsethrownew Illegal Argument Exception("User ID setter exception: empty or null");

}

Publicvoidset User Password(String password)

{

this.userPassword = password;

}

public String getUserID()

{

returnthis.userID;

}

public String get User Password()

{

returnthis.user Password;

}

@Override

publicboolean equals(Object obj){

if(objinstanceof User){

User user=(User)obj;

if(!user.getUserID().equals(userID)){

returnfalse;

}

returntrue;

}

returnfalse;

}

@Override

publicinthashCode(){

returnuserID.hashCode()+userPassword.hashCode();

}

Publicvoidset Subscriptions(Set<Subscription> subscriptions){

this.subscriptions= subscriptions;

}

public Set<Subscription>get Subscriptions(){

return subscriptions;

}

public Set<Subscription>get Subscribers(){

return subscribers;

}

Publicvoidset Subscribers(Set<Subscription> subscribers){

this.subscribers= subscribers;

}

}

Листинг 19

КодSubscription:

public class Subscription implements Serializable

{

//В данном классе полями будут user ID класса User

//В остальном этот класс устроен абсолютно так же

private User subscriber ID;

private User user ID;

public Subscription() {

}

public Subscription(User subscriberID, User userID) {

this.setSubscriberID(subscriberID);

this.setUserID(userID);

}

public void setSubscriberID(User subscriberID) {

this.subscriberID = subscriberID;

}

public void setUserID(User userID) {

this.userID = userID;

}

public User getSubscriberID() {return subscriberID;}

public User getUserID() {

return userID;

}

@Override

public boolean equals(Object obj) {

if (obj instance of Subscription) {

Subscription subscription = (Subscription) obj;

if (!subscription.get SubscriberID().equals(subscriberID)) {

return false;

}

if (!subscription.get UserID().equals(userID)) {

return false;

}

return true;

}

return false;

}

@Override

public int hashCode() {

return subscriberID.hashCode() + userID.hashCode();

}

}

Листинг 20

Остальные классы сущностей Hibernate выглядят аналогично.

Код класса помощника, использующегося в сервлетах для переадресации запросов и получения Hibernate сессии:

publicclassServletUtilities

{

private static final Logger logger = LogManager.getLogger(ServletUtilities.class);

/**

* Перенаправляет запрос на другой сервлет или jsp

* Если происходит ошибка в перенаправлении, пытается вывести

* сообщение об ошибке в виде htmlкода

* @param req

* @param resp

* @param path

*/

public static boolean redirectToPath(HttpServletRequest req, HttpServletResponse resp, String path)

{

RequestDispatcher rd = req.getRequestDispatcher(path);

try {

rd.forward(req, resp);

return true;

}

catch (ServletException|IOException e)

{

printErrorPage(req, resp, path);

logger.error(e);

returnfalse;

}

}

/**

* метод для получение сессии внутри сервлетов

* расширяет метод определенный Session Factory Registerer и добавляет

* перенаправление на страницу ошибки в случае неудачи

* возвращает false если сессию удалось успешно открыть

* @param req

* @param resp

* @param session

* @return

*/

public static boolean openSessionIfNullRedirect(HttpServletRequest req, HttpServletResponse resp, SessionWrapper session)

{

if(!SessionFactoryRegisterer.openSessionCheckNotNull(session)) {

redirectToPath(req, resp, "app/errors/database_error.jsp");

logger.error("CANNOT OPEN SESSION");

return true;

}

return false;

}

private static void printErrorPage(HttpServletRequest req, HttpServletResponse resp, String path)

{

resp.setContentType("text/html");

try {

PrintWriter out = resp.getWriter();

out.println("<html>");

out.println("<head>");

out.println("<title>Sample Application Servlet Page</title>");

out.println("<title>App Error</title>");

out.println("</head>");

out.println("<body bgcolor=white>");

out.println("<h1>Internal application error occurred: cannot redirect you to:"+path+"</h1>");

out.println("<h3>Your request was:"+req.getRequestURL()+"</h3>");

out.println("</body>");

out.println("</html>");

out.close();

} catch (IOException e1) {

logger.error(e1);

e1.printStackTrace();

throw new RuntimeException("ServletUtilities cannot redirect, nor print error page");

}

}

}

Листинг 21

Интерфейс Action, используемый в классе Controller Servlet и Action Factory:

publicinterface Action

{

public String execute(Http Servlet Request request, Http Servlet Response response)throwsServletException,IOException;

}

Листинг 22

Пример его реализации:

publicclassActionDisplayAllFriendsimplementsplayer.servlets.mvc.Action

{

privatestaticfinal Logger logger=LogManager.getLogger(ActionDisplayAllFriends.class);

@Override

public String execute(Http Servlet Request request,Http Servlet Response response)throwsServletException,IOException

{

logger.trace("ActionDisplayAllFriends ENTRY");

String userID=ServletUtilities.getAuthorizedUserID(request);

User usr;

SessionWrappersessionWrapper=newSessionWrapper();

if(!ServletUtilities.openSessionIfNullRedirect(request, response,sessionWrapper))

{

usr=sessionWrapper.getSession().get(User.class,userID);

logger.debug("I found User"+usr.getUserID());

request.set Attribute("userData",usr.getSubscriptions());

session Wrapper. Close Session();

return"/app/communication/user_friends.jsp";

}

else { return null;}}}Листинг 23

Размещено на Allbest.ru


Подобные документы

Работы в архивах красиво оформлены согласно требованиям ВУЗов и содержат рисунки, диаграммы, формулы и т.д.
PPT, PPTX и PDF-файлы представлены только в архивах.
Рекомендуем скачать работу.