Разработка приложения Spring Portlet MVC Framework для использования в IBM WebSphere Portal: Обработка форм

Источник: developerworks
Сунил Патил, разработчик программного обеспечения, BMC

В первой статье данной серии "Введение в Spring Portlet MVC Framework" мы рассмотрели, как разработать простой портлет HelloWorld, используя Spring Portlet MVC Framework. В этой статье мы рассмотрим, как можно обработать передаваемые из формы данные и как сгенерировать динамическое содержимое, что является типичными требованиями в большинстве реальных портлет-приложений.

В качестве примера мы разработаем приложение для управления контактами, которое позволяет создать, извлечь, обновить и удалить (create, retrieve, update и delete - CRUD) контакты в используемом источнике данных. Мы также рассмотрим последовательность событий, происходящих при получении Spring Portlet MVC Framework запроса на обработку переданных из формы данных.

Обработка данных формы

Портлет HelloWorld, разработанный нами в первой части данной серии статей, имеет одно существенное ограничение: он не поддерживает обработку действий, что является основным требованием для большинства реальных портлет-приложений. Здесь мы разработаем пример приложения для управления контактами, позволяющего отображать список контактов, добавлять, изменять и удалять контакты. Чтобы упростить разработку, мы сохраняем информацию о контактах в Hashmap, а не в таблице CONTACT базы данных. Выполните приведенные ниже действия для создания приложения управления контактами.

Первым действием является создание ContactManagPortlet в виде динамического Web-приложения в IBM Rational Application Developer for WebSphere Software. Используйте /src/main/java в качестве каталога исходного Java-кода и /src/main/webapp в качестве каталога Web-содержимого.

Затем скопируйте сценарий компоновки pom.xml из примера кода для данной статьи в корневой каталог вашего нового проекта. После этого выполните команду mvn integration-test, которая загрузит все необходимые зависимости для ContactManagPortlet.

После загрузки всех зависимостей выполните команду mvn eclipse:eclipse для добавления всех этих JAR-файлов в файл classpath вашего проекта. При нажатии правой кнопкой мыши на проекте ContactManagPortlet и его обновлении вы увидите, что все зависимые JAR-файлы были добавлены в путь компоновки вашего проекта.

Разработка ContactDAO.java

С точки зрения разработки первым действием является определение всех операций доступа к данным для приложения управления контактами. Создайте интерфейс ContactDAO.java, как показано в листинге 1.

Листинг 1. Исходный код для ContactDAO.java

                

public interface ContactDAO {
	public ArrayList getContactList();
	public Contact getContact(String contactId);
	public int insertContact(Contact contact);
	public int updateContact(Contact contact);
	public int deleteContact(String contactId);
}

Как можно заметить, интерфейс ContactDAO определяет операции создания, извлечения, удаления и изменения (CRUD) для приложения управления контактами.

Разработка Contact.java

Затем создайте класс Contact, который является DTO-объектом, используемым для переноса данных между Web-частью и уровнем доступа к данным, как показано в листинге 2.

Листинг 2. Исходный код для Contact.java

                

public class Contact {
	private String contactId;
	private String firstName;
	private String lastName;
	private String email;
	private String phoneNumber;
	
public String getContactId() {
		return contactId;
	}
	public void setContactId(String contactId) {
		this.contactId = contactId;
	}
}

В примере приложения каждый контакт имеет пять полей: contactId, firstName, lastName, phoneNumber и email. Добавьте методы getter и setter для всех остальных полей либо скопируйте Contact.java непосредственно из примера исходного кода для данной статьи.

Затем скопируйте com.ibm.developerworks.contact.service.HashmapContact.java и com.ibm.developerworks.contact.service.ContactDAOImpl.java из примера кода, включенного в раздел "Загрузка" данной статьи. Класс ContactDAOImpl реализует интерфейс ContactDAO, и он делегирует управление объекту HashmapContact для реального сохранения записей Contact. Объект HashmapContact сохраняет информацию о контактах в Hashmap, поэтому эта информация не сохраняется после перезапуска приложения. Можно создать класс ContactDAORDBMS.java для сохранения информации о контактах в реальной базе данных.

Разработка applicationContext.xml

Следующим действием является создание файла applicationContext.xml в папке /WEB-INF/, как показано в листинге 3.

Листинг 3. Исходный код для изменений в applicationContext.xml

                

<bean id="contactDAO"
	class="com.ibm.developerworks.contact.service.ContactDAOImpl">
	<property name="hashMapContact" ref="hashMapContact"/>
</bean>
<bean id="hashMapContact"
	class="com.ibm.developerworks.contact.service.HashmapContact" />

ПРИМЕЧАНИЕ. Это фрагмент листинга applicationContext.xml. Просмотрите полный исходный код в примере. Мы добавили в applicationContext.xml определения следующих двух bean-компонентов:

  • contactDAO. contactDAO - это объект класса ContactDAOImpl; он имеет свойство hashMapContact.
  • hashMapContact. Компонент hashMapContact - это объект класса HashmapContact. Это основанная на Hashmap реализация таблицы контактов.

Spring Framework загружает файл applicationContext.xml в объект контекста приложения. Обратите внимание на то, что Spring Framework создает иерархию контекстов приложения, в которой дочерний объект может видеть родителя, но родитель не видит дочернего объекта. Bean-компоненты, определенные в файле applicationContext.xml, создаются на уровне портлет-приложения, и bean-компонент в контексте портлета может обратиться к bean-компоненту, определенному на уровне контекста портлет-приложения. Но bean-компонент, определенный на уровне контекста портлет-приложения, не может обратиться к компоненту в контексте портлета. В примере исходного кода bean-компоненты contactDAO и hashMapContact доступны на уровне портлет-приложения.

Разработка InsertController.java

В среде Spring Portlet MVC Framework для обработки данных, переданных из формы, мы наследуем класс Controller либо из класса AbstractFormController, либо из его подкласса. SimpleFormController - это одна из реальных реализаций AbstractFormController, которая обеспечивает простой поток работ для обработки данных, переданных из формы. При получении запроса SimpleFormController проверяет, обращаетесь вы к странице с формой или передаете данные из нее.

  • Если вы обращаетесь к странице с формой, SimpleFormController возвращает страницу formView.
  • Если вы передаете данные из формы, SimpleFormController читает значение commandClass для данного Controller, создает объект commandClass и заполняет его свойства значениями, введенными пользователем в форме. Затем он позволяет выполнить определенную бизнес-логику и отображает страницу successView пользователю.

Ниже мы подробнее рассмотрим эту информацию.

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

Листинг 4. Исходный код для InsertController.java

                


public class InsertController extends SimpleFormController{
	private ContactDAO contactDAO;
	public ContactDAO getContactDAO() {
		return contactDAO;
	}
	public void setContactDAO(ContactDAO contactDAO) {
		this.contactDAO = contactDAO;
	}
	
protected void onSubmitAction(ActionRequest request, ActionResponse response, 
Object command, BindException errors) throws Exception {
		contactDAO.insertContact((Contact)command);
		response.setRenderParameter("action", "list");
	}
}

Как можно увидеть, реализация InsertController является очень простой. Во-первых, мы определили contactDAO как свойство для этого bean-компонента с соответствующими методами getter и setter. Мы решили переопределить метод onSubmitAction() контроллера SimpleFormController. Внутри метода onSubmitAction() мы сначала вызываем метод contactDAO.insertContact(contact), который вставляет новую запись о контакте в используемый источник данных. Затем мы устанавливаем действие, как параметр визуализации, в значение "insert".

ПРИМЕЧАНИЕ. SimpleFormController также определяет метод doSubmitAction(command), который достаточно хорош для реализации бизнес-логики. Но в нашем случае мы хотим перенаправить пользователя в сводную таблицу контактов после добавления нового контакта. Для этого мы передаем управление контроллеру SelectController во время фазы визуализации. Это указывается установкой параметра визуализации действия.

Определение bean-компонента для insertController

Откуда среда Spring Portlet MVC Framework знает, какой JSP-файл использовать для отображения страницы Insert new contact? Откуда она знает, что мы хотим использовать com.ibm.developerworks.contact.domain.Contact в качестве класса command для InsertController? Вот где важно определение bean-компонента insertController. Создайте SpringContactManag-portlet.xml в папке /WEB-INF/ вашего приложения и добавьте определение bean-компонента insertController, как показано в листинге 5.

Помните, что InsertController - это имя Java-класса, а insertController - это имя bean-компонента, определенного в файле контекста Spring. Обычно первая буква имени Java-класса пишется с большой буквы. В имени bean-компонента, определенного в файле контекста Spring, имя начинается с маленькой буквы.

Листинг 5. Исходный код для определения компонента insertController в ContactManagPortlet-portlet.xml

                

<bean id="insertController" 
class="com.ibm.developerworks.contact.controller.InsertController">
	<property name="contactDAO" ref="contactDAO"></property>
	<property name="commandName" value="contact"/>
<property name="commandClass" value="com.ibm.developerworks.contact.domain.Contact" />
	<property name="formView" value="insert"></property>
	<property name="successView" value="list"></property>
</bean>l

В примере исходного кода мы устанавливаем значения для следующих свойств bean-компонента insertController:

  • contactDAO. Мы передаем ссылку на bean-компонент contactDAO, который является вспомогательным классом, ответственным за реальную вставку информации о контакте. Вспомните, что мы объявили компонент contactDAO в файле applicationContext.xml.
  • commandClass. В примере кода мы используем класс Contact в качестве класса command, который заполняется значениями, введенными пользователем в форму.
  • commandName. Значение commandName используется при установке объекта commandClass в модели для данной формы.
  • formView. Значение параметра formView используется для определения того, какое представление должно отображаться пользователю для сбора входных данных. В нашем примере кода, когда пользователь выбирает ссылку Add Contact, мы хотим отображать страницу insert.jsp, поэтому значением свойства formView является insert (мы используем InternalResourceViewResolver для ViewResolution, и он отображает значение insert в файл /WEB-INF/jsp/insert.jsp).
  • successView. Значение параметра successView используется для определения представления, отображаемого пользователю, если добавление информации о новом контакте было успешным. В данном случае мы хотим отобразить файл list.jsp, поэтому значением этого свойства является list.

Разработка insert.jsp

Во время определения bean-компонента insertController мы настроили insert.jsp в качестве значения свойства formView. Среда Spring Portlet MVC Framework пытается найти страницу /WEB-INF/jsp/insert.jsp при выборе ссылки Add Contact в сводной таблице контактов. Следующим шагом является создание страницы insert.jsp в /WEB-INF/jsp/insert.jsp, как показано в листинге 6.

Листинг 6. Исходный код для insert.jsp

                

<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>
<%@ taglib prefix="portlet" uri="http://java.sun.com/portlet" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>

<portlet:actionURL var="formAction">
	<portlet:param name="action" value="insert"/>
</portlet:actionURL>
<h3>Add New Contact</h3>
<form:form commandName="contact" method="post" action="${formAction}">
	<table cellpadding="4">
		<tr>
			<td>Contact Id</td>
<td><form:input path="contactId" size="30" maxlength="80"/></td>
		</tr>
		<tr>
<td><input type="submit" name="_finish" value="Save"/></td>
<td><input type="submit" name="_cancel" value="Cancel"/></td>
		</tr>
	</table>
</form:form>

Страница insert.jsp имеет три объявления библиотеки тегов. Вы уже знакомы с двумя из них - библиотеками тегов jstl и portlet. Кроме них мы определяем также библиотеку тегов form. Начиная с версии 2.0, Spring Framework предоставляет библиотеку тегов form, которая может использоваться JSP-файлами, включенными из кода приложения Web MVC Framework или Portlet MVC Framework.

Библиотека тегов form интегрируется с Spring Portlet MVC Framework и предоставляет доступ к объекту command. В файле insert.jsp <form:form> - это первый тег, который мы используем для генерирования HTML-тега <form> в окончательной разметке. Этот тег читает также объект command и устанавливает его в pageContext нашего JSP-файла. В примере кода commandName - это contact, поэтому значение атрибута commandName для тега <form> равно contact.

ПРИМЕЧАНИЕ. Все остальные теги в библиотеке тегов form являются вложенными тегами тега <form>. Следующим используемым нами тегом из этой библиотеки является тег <input>, который нужен для генерирования элемента <input type="text"> в окончательной разметке. В примере кода мы хотим связать поле ввода contactId со свойством contactId в объекте Contact, и для этого добавили атрибут path со значением, равным contactId. Обратите внимание на то, что это фрагмент исходного кода для insert.jsp; ссылка на полный листинг приведена в разделе "Загрузка" данной статьи.

Разработка SpringContactManagPortlet-portlet.xml

Следующим шагом является добавление объявлений пары bean-компонентов в SpringContactManagPortlet-portlet.xml, как показано в листинге 7.

Листинг 7. Исходный код для изменений в SpringContactManagPortlet-portlet.xml

                

<bean id="parameterMappingInterceptor" 
class= "org.springframework.web.portlet.handler.ParameterMappingInterceptor" />
<bean id="portletModeParameterHandlerMapping" 
class= "org.springframework.web.portlet.handler.PortletModeParameterHandlerMapping">
	<property name="interceptors">
		<list>
			<ref bean="parameterMappingInterceptor" />
		</list>
	</property>
	<property name="portletModeParameterMap">
		<map>
			<entry key="view">
				<map>
					<entry key="select">
						<ref bean="selectController" />
					</entry>
					<entry key="insert">
						<ref bean="insertController" />
					</entry>
				</map>
			</entry>
		</map>
	</property>
</bean>

Как можно увидеть, мы определили еще два bean-компонента в файле SpringContactManagPortlet-portlet.xml.

  • parameterMappingInterceptor. parameterMappingInterceptor используется для перенаправления значения параметра запроса действия из ActionRequest в RenderRequest, для того чтобы среда Spring Portlet MVC Framework использовала один и тот же контроллер для обработки запроса действия и запроса визуализации. Более подробно мы рассмотрим это ниже.
  • portletModeParameterHandlerMapping. portletModeParameterHandlerMapping - это расширенная реализация HandlerMapping, которая использует режим портлета, а также значение параметра запроса действия для выбора обработчика запроса. В примере кода у нас есть более одного обработчика в режиме View, поэтому мы используем portletModeParameterHandlerMapping. Bean-компонент portletModeParameterHandlerMapping имеет свойство portletModeParameterMap, которое является картой всех режимов, поддерживаемых нашим портлетом. Эта карта отображений принимает пары ключ-значение с названием режима портлета в качестве ключа. Значение - это еще одна карта, которая принимает значение параметра запроса действия в качестве ключа и ссылку на контроллер, обрабатывающий этот запрос, в качестве его значения.

В примере кода мы хотим поддерживать только режим View, поэтому карта самого верхнего уровня имеет только одну запись. Карта второго уровня имеет ключ insert со значением, ссылающимся на bean-компонент insertController. В результате, когда портлет принимает запрос с параметром действия, равным insert в режиме View, он передает управление в контролер InsertController для его обработки.

Последним шагом в создании этого примера кода является копирование дополнительных файлов SelectController.java, UpdateController.java, DeleteController.java и соответствующих JSP-файлов (list.jsp и update.jsp) из примера кода для данной статьи. После копирования всех необходимых файлов выполните команду mvn package для компоновки портлет-приложения. Когда WAR-файл будет готов, можно протестировать его в IBM WebSphere Portal.

Как работает обработка действия

Интегрированная среда Spring Portlet MVC Framework выполняет много действий в фоновом режиме; со временем, когда вы будете чувствовать себя комфортно при работе с данным программным обеспечением, это может быть большим преимуществом. Но до этого момента такой подход, возможно, будет труден для понимания. В этом разделе мы рассмотрим, что происходит за кулисами при получении портлетом DispatcherPortlet запроса действия (см. рисунок 1).

Рисунок 1. Схема последовательности событий, возникающих при получении DispatcherPortlet запроса действия
Рисунок 1. Схема последовательности событий, возникающих при получении DispatcherPortlet запроса действия

При получении портлетом DispatcherPortlet запроса действия insert имеет место следующая последовательность событий:

  1. DispatcherPortlet проверяет, разрешена ли в вашем портлете обработка запросов из нескольких частей. Если да, DispatcherPortlet проверяет, состоит ли запрос из нескольких частей; то есть, установлен ли атрибут enctype для вашего HTML-элемента <form> в значение multipart/form-data. Если это так, запрос заключается в объект MultipartActionRequest.
  2. DispatcherPortlet просматривает список всех отображений HandlerMappings, настроенных в портлет-приложении, вызывая их метод getHandler() для получения соответствующего обработчика для текущего запроса. В примере кода мы настроили только один класс реализации HandlerMapping - PortletModeParameterHandlerMapping. Когда он получает управление, то проверяет режим портлета текущего запроса, который в данном случае равен режиму View, и значение параметра запроса действия, которое равно insert. Теперь, при определении bean-компонента portletModeParameterHandlerMapping в SpringContactManagPortlet-portlet.xml, ключ insert отображается на InsertController, поэтому он возвращает объект InsertController.
  3. После того как DispatcherPortlet узнает, что за обработку этого запроса отвечает InsertController, он начинает выполнять итерацию по всем перехватчикам (interceptor) для этого обработчика, вызывая их метод preHandleAction(). В примере кода мы настроили только один класс перехватчика, ParameterMappingInterceptor, поэтому вызывается его метод preHandleAction().
  4. ParameterMappingInterceptor используется для перенаправления значения параметра запроса действия из ActionRequest в RenderRequest, поэтому он устанавливает параметр визуализации в имя, равное action, и значение, равное insert.

    ПРИМЕЧАНИЕ. Среда Spring Portlet MVC Framework пытается выбрать Handler для текущего запроса на обеих фазах, как действия, так и визуализации. Если вы хотите, чтобы Spring Portlet MVC Framework использовала один и тот же Handler на обеих фазах, используйте ParameterMappingInterceptor.
  5. DispatcherPortlet вызывает метод handleActionRequest() контроллера InsertController; это место, где происходит реальная обработка действия. В примере кода InsertController расширяет SimpleFormController, для того чтобы он следовал в цикле жизни запроса действия, определенном SimpleFormController.
  6. SimpleFormController сначала читает имя commandClass (которое настроено для InsertController и равно com.ibm.developerworks.contact.domain.Contact) и создает его объект.
  7. После этого SimpleFormController создает объект PortletRequestDataBinder, который отвечает за заполнение объекта Contact значениями, введенными пользователем в форму. Например, он читает значение параметра запроса firstName и устанавливает его в качестве значения свойства firstName в объекте Contact.
  8. Вы можете переопределить метод initBinder() в вашем классе, если хотите, чтобы Spring Portlet MVC Framework использовала какую-либо специализированную логику при преобразовании значения параметра запроса в значение свойства класса command. Например, можно указать, как преобразовать String в Date или MultiPartFile в массив byte[]. Позже мы покажем примеры этого.
  9. После заполнения bean-компонента значениями, введенными пользователем в форму, SimpleFormController выполняет итерацию по всем Validators приложения, вызывая их метод validate(). Здесь можно написать логику проверки корректности на стороне сервера. Например, можно проверить, что идентификатор контакта является обязательным полем. Мы поговорим об этом подробнее ниже.
  10. После выполнения проверки корректности SimpleFormController проверяет, возникли ли какие-либо ошибки. Если нет, он вызывает onSubmitAction()/doSubmitAction() контролера InsertController. Обычно мы рекомендуем переопределять doSubmitAction() в классе Controller. Но в примере кода мы передаем управление в SelectController для фазы визуализации, поэтому мы переопределяем onSubmitAction().

После завершения фазы действия DispatcherPortlet проверяет, возникли ли какие-либо ошибки проверки корректности. Если да, он читает значение formView и направляет пользователя на эту страницу. Если ошибок нет, он направляет пользователя на страницу successView.

Заключение

В данной статье мы рассмотрели, как можно разработать пример приложения для управления контактами, используя интегрированную среду Spring Portlet MVC Framework. Мы обсудили, как можно создать класс, переопределяющий SimpleFormController, который передает управление в зависимости от значения, определенного свойством formView, при получении GET-запроса, а также передает управление в зависимости от значения, определенного свойством successView, при получении POST-запроса и успешной обработке действия. Также мы рассмотрели все конфигурационные файлы, необходимые для создания, а также то, как использовать специализированную библиотеку тегов, определенную средой Spring Portlet MVC Framework.

В последней части данной серии статей мы рассмотрим некоторые темы повышенной сложности, например, обработку проверки корректности формы, элегантную обработку исключительных ситуаций и вопросы интернационализации. Мы также продемонстрируем, как интегрировать Apache Tiles Framework со средой Spring Portlet MVC Framework.


Страница сайта http://test.interface.ru
Оригинал находится по адресу http://test.interface.ru/home.asp?artId=19576