Средства разработки web приложений(Web frameworks)
Малышкин Фёдор
2 ноября 2007
Содержание
Введение Основа архитектуры «тонких» клиентов Средства разработки веб-приложений:
JSPStrutsSpringTapestryJSF
Сравнение
Введение
Данная презентация познакомит Вас с существующими библиотеками разработки веб-приложений на языке Java.
Будут описаны основополагающие моменты, лежащие в их основе, приведены примеры их использования, описаны их преимущества и недостатки.
Так же будут описаны классические модели реализации «тонких» клиентов, коим веб-приложение и является.
Основа архитектуры «тонких» клиентов
В качестве основы для всех клиентов, связанных с пользовательским вводом (не обязательно «тонких»), используется MVC (Model-View-Controller).
Эта архитектура разделяет приложение на: Модель данных (Model), занимающуюся хранением данных,
обработкой данных (бизнес - логикой), а так же всем остальными
«не визуальными» вещами. Представление (View), занимающуюся отображением и
представлением данных Контроллер (Controller), занимающийся коммуникацией между
данными и представлением. В веб-приложениях данная модель называется «Model-2»
(что бы отделить от настольной реализации MVC) и указать, на то что она является приемником «Model-1».
Основа архитектуры «тонких» клиентов
DB
1) Запрос 2) Создание
3) Пересылка запроса
5) Ответ4) Извлечение
данных
Модель: Java Bean
Представление: JSP
Контроллер (Сервлет)
JSP (Краткая характеристика)
Положительные стороны: ?
Отрицательные стороны: ?
JSP (Жизненный цикл)
Запрос
Ответ
Компиляция JSP в Java класс
Выполнение Java класса
JSP
Struts (Краткая характеристика)
Положительные стороны: Много проектов реализованных с помощью данной библиотеки,
подтверждает её стабильность и надёжность Огромное количество примеров и документации HTML библиотека тэгов одна из лучших
Отрицательные стороны: Программирование «контроллера» - ActionForms – задача не из
лёгких Невозможно автономное тестирование Ходят слухи, что проект «мёртв»
Spring (Краткая характеристика)
Положительные стороны: Переопределение правил связки данных на форме и в
приложении, правил навигации и проверки введённых значений Прозрачная интеграция с многочисленными средствами
представления данных: JSP/JSTL, Tiles, Velocity, FreeMaker, Excel,
XSL, PDF. Удобная среда для автономного тестирования
Отрицательные стороны: Много XML (в области конфигурирования) Требует большого количества кода в JSP «Слишком» гибок
Spring (Жизненный цикл GET)
Запрос
Ответ
fromBackingObject()
initBinder()
showForm()
referenceData()
View (JSP, Velocity, Tiles...)
Spring (Жизненный цикл POST)
Запрос
Ответ
fromBackingObject()
onBind()
onBindAndValidate()
showForm()/processFormSubmission()
Вызов Validator’а
ServletReequestDataBinder
«Контроллер» Spring
public class UserController implements Controller {
private final Log log = LogFactory.getLog(UserController.class);
private UserManager mgr = null;
public void setUserManager(UserManager userManager) {
this.mgr = userManager;
}
public ModelAndView handleRequest(HttpServletRequest request,
HttpServletResponse response) throws Exception {
if (log.isDebugEnabled()) {
log.debug("entering 'handleRequest' method...");
}
return new ModelAndView("userList", "users", mgr.getUsers());
}
}
«Контроллер» Spring
public class UserController implements Controller {
private final Log log = LogFactory.getLog(UserController.class);
private UserManager mgr = null;
public void setUserManager(UserManager userManager) {
this.mgr = userManager;
}
public ModelAndView handleRequest(HttpServletRequest request,
HttpServletResponse response) throws Exception {
if (log.isDebugEnabled()) {
log.debug("entering 'handleRequest' method...");
}
return new ModelAndView("userList", "users", mgr.getUsers());
}
}
Конфигурирование Spring
<bean id="userController" class="org.appfuse.web.UserController"><property name="userManager" ref="userManager"/></bean>
Конфигурирование Spring
<bean id="userController" class="org.appfuse.web.UserController"><property name="userManager" ref="userManager"/></bean><bean id="viewResolver"class="org.springframework.web.servlet.view.InternalResourceViewResolver"><property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/><property name="prefix" value="/"/><property name="suffix" value=".jsp"/></bean>
Конфигурирование Spring
<bean id="userController" class="org.appfuse.web.UserController"><property name="userManager" ref="userManager"/></bean><bean id="viewResolver"class="org.springframework.web.servlet.view.InternalResourceViewResolver"><property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/><property name="prefix" value="/"/><property name="suffix" value=".jsp"/></bean><bean id="urlMapping"class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"><property name="mappings"><value>/users.html=userController</value></property></bean>
JSP представление Spring
<form:form commandName="user" method="post"><form:errors path="*" cssClass="error"/><form:hidden path="id" /><table class="detail"><tr>
<th><label for="firstName"><fmt:message key="user.firstName"/>:</label></th>
<td><form:input path="firstName" id="firstName"/><form:errors path="firstName" cssClass="fieldError"/>
</td></tr><tr>
<th><label for="lastName" class="required">* <fmt:message key="user.lastName"/>:</label></th>
<td><form:input path="lastName" id="firstName"/><form:errors path="lastName" cssClass="fieldError"/>
</td></tr>
JSP представление Spring
<form:form commandName="user" method="post"><form:errors path="*" cssClass="error"/><form:hidden path="id" /><table class="detail"><tr>
<th><label for="firstName"><fmt:message key="user.firstName"/>:</label></th>
<td><form:input path="firstName" id="firstName"/><form:errors path="firstName" cssClass="fieldError"/>
</td></tr><tr>
<th><label for="lastName" class="required">* <fmt:message key="user.lastName"/>:</label></th>
<td><form:input path="lastName" id="firstName"/><form:errors path="lastName" cssClass="fieldError"/>
</td></tr>
Velocity представление Spring
<form method="post" action="#springUrl('/editUser.html')">#springFormHiddenInput("user.id" '')<table><tr>
<th><label for="firstName">#springMessage("user.firstName"):</label></th><td>
#springFormInput("user.firstName" 'id="firstName"')#springShowErrors("<br/>" "fieldError")
</td></tr><tr>
<th><label for="lastName">#springMessage("user.lastName"):</label></th><td>
#springFormInput("user.lastName" 'id="lastName"')#springShowErrors("<br/>" "fieldError")
</td></tr>
Spring Web Flow
Инфраструктура позволяющая определять последовательность переходов между страницами
Определяется программно или через XML Правила навигации активируются на основании
строковых значений, возвращённых вызванными методами (подобно JSF)
Spring Web Flow
<webflow id="userFlow" start-state="setupForm"><action-state id="setupForm">
<action bean="userFormAction"/><transition on="success" to="display.nameForm"/>
</action-state><view-state id="display.nameForm" view="flow/name">
<transition on="submit" to="display.addressForm"><action bean="userFormAction" method="bindAndValidate"/>
</transition><transition on="cancel" to="finish"/>
</view-state><view-state id="display.addressForm" view="flow/address">
<transition on="previous" to="display.nameForm"><action bean="userFormAction" method="bindAndValidate"/>
</transition><transition on="submit" to="display.otherForm">
<action bean="userFormAction" method="bindAndValidate"/></transition><transition on="cancel" to="finish"/>
</view-state>
Tapestry (Краткая характеристика)
Положительные стороны: Очень эффективна после изучения Шаблоны являются HTML, что очень хорошо для дизайнеров Хорошее сообщество пользователей
Отрицательные стороны: Документация достаточно сложна для восприятия Крутая кривая обучения Мало примеров Долгие циклы релизов – ведущие релизы 1-2 раза в год
Tapestry (Жизненный цикл)
Запрос
Ответ
Инициализация страницы pageBeginRender()Публикация
свойств страницы
Вызов методов-«слушателей»
Активация следующей страницы
pageBeginRender()
Отображение исключения
Класс Tapestry
public abstract class UserForm extends BasePage {public abstract UserManager getUserManager();public abstract void setUser(User user);public abstract User getUser();public void save(IRequestCycle cycle) {
if (log.isDebugEnabled()) {log.debug("entered 'save' method");
}getUserManager().saveUser(getUser());UserList nextPage = (UserList) cycle.getPage("users");nextPage.setMessage(getMessages().format("user.saved", getUser().getFullName()));
throw new PageRedirectException(nextPage);}
Конфигурирование Tapestry
<application name="tapestry"><page name="Home" specification-path="/pages/home.page"/><page name="users" specification-path="/pages/users.page"/><page name="userForm" specification-path="/pages/userForm.page"/>
<library id=“contrib” specificationpath="/org/apache/tapestry/contrib/Contrib.library"/>
</application>
Конфигурирование Tapestry
<page-specification class="org.appfuse.web.UserForm"><bean name="delegate“ class="org.apache.tapestry.valid.ValidationDelegate"/><component id="form" type="Form">
<binding name="delegate" value="ognl:beans.delegate"/><binding name="clientValidationEnabled" value="true"/>
</component><property name="user"/><inject property="userManager" object="spring:userManager"/><component id="lastNameField" type="TextField">
<binding name="value" value="user.lastName"/><binding name="validators" value="validators:required"/><binding name="displayName"
value="message:lastName"/></component>
</page-specification>
HTML представление Tapestry
<form jwcid="@Form" delegate="ognl:beans.delegate" name="userForm"><input type="hidden" jwcid="@Hidden" value="ognl:user.id"/><table class="detail"><tr>
<th><label for="firstName"><span key="firstName">First Name</span></label>:
</th><td><input jwcid="@TextField" type="text" value="ognl:user.firstName" id="firstName"/></td>
</tr><tr>
<th><label jwcid="@FieldLabel" field="ognl:components.lastNameField">Last Name</label>:</th><td><input jwcid="lastNameField" type="text" id="lastName"/></td>
</tr><tr>
<th><label for="birthday"><span key="birthday">Birthday</span></label>:
</th><td>
<input jwcid="@DatePicker" format="message:date.format" type="text”size="11" value="ognl:user.birthday" id="birthday"/>
</td></tr>
HTML представление Tapestry
<form jwcid="@Form" delegate="ognl:beans.delegate" name="userForm"><input type="hidden" jwcid="@Hidden" value="ognl:user.id"/><table class="detail"><tr>
<th><label for="firstName"><span key="firstName">First Name</span></label>:
</th><td><input jwcid="@TextField" type="text" value="ognl:user.firstName" id="firstName"/></td>
</tr><tr>
<th><label jwcid="@FieldLabel" field="ognl:components.lastNameField">Last Name</label>:</th><td><input jwcid="lastNameField" type="text" id="lastName"/></td>
</tr><tr>
<th><label for="birthday"><span key="birthday">Birthday</span></label>:
</th><td>
<input jwcid="@DatePicker" format="message:date.format" type="text”size="11" value="ognl:user.birthday" id="birthday"/>
</td></tr>
HTML представление Tapestry
<form jwcid="@Form" delegate="ognl:beans.delegate" name="userForm"><input type="hidden" jwcid="@Hidden" value="ognl:user.id"/><table class="detail"><tr>
<th><label for="firstName"><span key="firstName">First Name</span></label>:
</th><td><input jwcid="@TextField" type="text" value="ognl:user.firstName" id="firstName"/></td>
</tr><tr>
<th><label jwcid="@FieldLabel" field="ognl:components.lastNameField">Last Name</label>:</th><td><input jwcid="lastNameField" type="text" id="lastName"/></td>
</tr><tr>
<th><label for="birthday"><span key="birthday">Birthday</span></label>:
</th><td>
<input jwcid="@DatePicker" format="message:date.format" type="text”size="11" value="ognl:user.birthday" id="birthday"/>
</td></tr>
Улучшения в следующей версии Tapestry
Богатая поддержка аннотаций Высокий уровень конфигурирования – базирование на IoC
контейнере Hivemind Требует меньше кода – более простая реализация
классов реализующих логику страниц Поддержка URL дружественных к пользователю Компоненты Tacos AJAX
JSF (Краткая характеристика)
Положительные стороны: J2EE Стандарт Быстрая и простая разработка Богата библиотека навигации (аналог Spring Web Flow)
Отрицательные стороны: Мешанина из JSP тэгов Плохая поддержка «легковесных» вызовов (REST) Нет единого источника реализации
JSF (Жизненный цикл)
Бин страницы JSF
public class UserForm {private String id;public User user = new User();public UserManager mgr;public void setId(String id) {
this.id = id;}public void setUser(User user) {
this.user = user;}public void setUserManager(UserManager userManager) {
this.mgr = userManager;}public String edit() {
if (id != null) {// assuming editsetUser(mgr.getUser(id));
}return "success";
}
Конфигурация JSF
<application><variable-resolver>
org.springframework.web.jsf.DelegatingVariableResolver</variable-resolver><locale-config>
<default-locale>en</default-locale><supported-locale>en</supported-locale><supported-locale>es</supported-locale>
</locale-config><message-bundle>messages</message-bundle>
</application><navigation-rule>
<from-view-id>/userForm.jsp</from-view-id><navigation-case>
<from-outcome>cancel</from-outcome><to-view-id>/userList.jsp</to-view-id>
</navigation-case><navigation-case>
<from-outcome>success</from-outcome><to-view-id>/userList.jsp</to-view-id><redirect/>
</navigation-case></navigation-rule>
Конфигурация JSF
<application><variable-resolver>
org.springframework.web.jsf.DelegatingVariableResolver</variable-resolver><locale-config>
<default-locale>en</default-locale><supported-locale>en</supported-locale><supported-locale>es</supported-locale>
</locale-config><message-bundle>messages</message-bundle>
</application><navigation-rule>
<from-view-id>/userForm.jsp</from-view-id><navigation-case>
<from-outcome>cancel</from-outcome><to-view-id>/userList.jsp</to-view-id>
</navigation-case><navigation-case>
<from-outcome>success</from-outcome><to-view-id>/userList.jsp</to-view-id><redirect/>
</navigation-case></navigation-rule>
Конфигурация JSF
<managed-bean><managed-bean-name>userForm</managed-bean-name><managed-bean-class>org.appfuse.web.UserForm</managed-bean-class><managed-bean-scope>request</managed-bean-scope><managed-property>
<property-name>id</property-name><value>#{param.id}</value>
</managed-property><managed-property>
<property-name>userManager</property-name><value>#{userManager}</value>
</managed-property></managed-bean>
JSP представление JSF
<f:view><f:loadBundle var="messages" basename="messages"/><h:form id="userForm"><h:inputHidden value="#{userForm.user.id}">
<f:convertNumber/></h:inputHidden><h:panelGrid columns="3" styleClass="detail" columnClasses="label">
<h:outputLabel for="firstName" value="#{messages['user.firstName']}"/><h:inputText value="#{userForm.user.firstName}" id="firstName"/><h:message for="firstName" styleClass="errorMessage"/><h:outputLabel for="lastName" value="#{messages['user.lastName']}"/><h:inputText value="#{userForm.user.lastName}" id="lastName" required="true"/><h:message for="lastName" styleClass="errorMessage"/><h:outputLabel for="birthday" value="#{messages['user.birthday']}"/><t:inputCalendar monthYearRowClass="yearMonthHeader"
weekRowClass="weekHeader" id="birthday"currentDayCellClass="currentDayCell" value="#{userForm.user.birthday}"renderAsPopup="true" addResources="false"/>
<h:message for="birthday" styleClass="errorMessage"/>
JSP представление JSF
<f:view><f:loadBundle var="messages" basename="messages"/><h:form id="userForm"><h:inputHidden value="#{userForm.user.id}">
<f:convertNumber/></h:inputHidden><h:panelGrid columns="3" styleClass="detail" columnClasses="label">
<h:outputLabel for="firstName" value="#{messages['user.firstName']}"/><h:inputText value="#{userForm.user.firstName}" id="firstName"/><h:message for="firstName" styleClass="errorMessage"/><h:outputLabel for="lastName" value="#{messages['user.lastName']}"/><h:inputText value="#{userForm.user.lastName}" id="lastName" required="true"/><h:message for="lastName" styleClass="errorMessage"/><h:outputLabel for="birthday" value="#{messages['user.birthday']}"/><t:inputCalendar monthYearRowClass="yearMonthHeader"
weekRowClass="weekHeader" id="birthday"currentDayCellClass="currentDayCell"
value="#{userForm.user.birthday}"renderAsPopup="true" addResources="false"/>
<h:message for="birthday" styleClass="errorMessage"/>
JSP представление JSF
<f:view><f:loadBundle var="messages" basename="messages"/><h:form id="userForm"><h:inputHidden value="#{userForm.user.id}">
<f:convertNumber/></h:inputHidden><h:panelGrid columns="3" styleClass="detail" columnClasses="label">
<h:outputLabel for="firstName" value="#{messages['user.firstName']}"/><h:inputText value="#{userForm.user.firstName}" id="firstName"/><h:message for="firstName" styleClass="errorMessage"/><h:outputLabel for="lastName" value="#{messages['user.lastName']}"/><h:inputText value="#{userForm.user.lastName}" id="lastName" required="true"/><h:message for="lastName" styleClass="errorMessage"/><h:outputLabel for="birthday" value="#{messages['user.birthday']}"/><t:inputCalendar monthYearRowClass="yearMonthHeader"
weekRowClass="weekHeader" id="birthday"currentDayCellClass="currentDayCell"
value="#{userForm.user.birthday}"renderAsPopup="true" addResources="false"/>
<h:message for="birthday" styleClass="errorMessage"/>
Улучшения в следующей версии JSF (1.2)
Унифицированный EL – лучшая поддержка JSTL Фокусировка на лёгком использовании Расширенная поддержка AJAX Дополнительные реализации: ADF Faces, Facelets
Сравнение. Критерии
Сортируемые/Листаемые списки – насколько просто создать список данных с листаемыми страницами и возможностями сортировки.
Возможность создания закладок – может ли пользователь создавать закладки на страницы для последующего обращения к ним?
Валидация - проверка введённых значений. Тестируемость – возможности для автономного
тестирования классов, составляющих клиента, вне контейнера.
Сравнение. Критерии
Интернационализация Модификация «на лету» - принятие исправлений без
необходимости перекомпиляции или перезапуска контейнера
Поддержка разработчиками Производительность/Масштабируемость «Компонетность» - возможность создания повторно-
используемых, параметризуемых модулей Возможности языка выражений
Сортируемые/Листаемые списки
JSP – никакой поддержки Spring & Struts могут использовать библиотеки тэгов, типа
“DisplayTag” Tapestry имеет contrib:Table компонент JSF имеет h:dataTable компонент без возможностей
сортировки – необходимо писать свою собственную логику для реализации данного функционала
Возможность создания закладок
JSP, String & Strut имеют полный контроль над строкой запроса
Tapestry имеет слегка корявую поддержку создания закладок, но всё же все возможности реализованы
JSF делает POST для всего – закладки даже не рассматриваются (но при желании и это можно обойти)
Валидация
JSP – «собственные» решения, либо перенос проверки в модель данных
String & Struts используют проект Apache – Commons Validator – надёжное и зрелое решение
Tapestry – хорошая архитектура валидации – хорошие сообщения (даже без необходимости корректировки под свои нужды)
JSF – «некрасивые» сообщения об ошибках по-умолчанию (но легко исправляется)
Тестируемость
Struts – необходимо использование StrutsTestCase JSP, Spring – легко тестируется с использованием средств
генерации «заглушек» (mock) (EasyMock, jMock, Spring Mock…)
Tapestry – неочевидное тестирование, т.к. классы абстрактные – класс Creator помогает
JSF – самое простая архитектура для тестирования – классы – просто бины
Интернационализация
JSTL <fmt:message> позволяет делать это легко почти в любой реализации
JSP, Struts, Spring, JSF – используют один ResourceBundle на язык
Tapestry – предпочитает отдельные файлы для страниц/компонентов
JSF требует, что данные с локализацией были объявлены на каждой странице
Модификация «на лету»
JSP – самый гибкий в данном случае (конечно при корректном использовании). JSP файлы перекомпиливаются при каждом изменении, а вот классы нет.
Tapestry, Spring & Struts – всё (страницы, компоненты, библиотеки и конфигурационные файлы) (кроме классов) перечитывается при изменении.
JSF – не перечитываются конфигурационные файлы.
Поддержка разработчиками
JSP & JSF – стандарт, большое сообщество разработчиков и множество документации
Spring – большое количество примеров и документации, хорошая поддержка разработчиками библиотеки
Tapestry – мало хорошей, понятной документации. Мало примеров.
Struts – отсутствие поддержки со стороны разработчиков, но при этом много документации и примеров использования в больших проектах
Производительность / Масштабируемость
JSP – хорошие показатели, в связи с отсутствием каких-либо промежуточных уровней
Tapestry – после версии 4.0 (где было удалено широкое использование интроспекции) скорость стала сравнима с JSP
JSF – не самые лучшие показатели… Spring & Struts – адекватные показатели.
«Компонетность»
JSP – тэг-файлы и тэги. Tapestry – очень ориентированная на создание и
использование компонетов, хорошая интеграция с JavaScript.
JSF – хорошие возможности по созданию компонентов, но создавать не так легко как в Tapestry.
Spring & Struts – лишь то же, что даёт JSP.
Возможности языка выражений
JSP, JSF, Spring, Struts – богатые возможности EL говорят сами за себя.
Tapestry – вместо EL используется OGNL, который предоставляет ещё большие возможности.
Финал
Выбирайте с умом…