Stripes - это это фреймворк для разработки веб-приложений с открытым исходным кодом, построенный по модели MVC. Если вникнуть в его суть, то можно быстро и красиво писать web-приложения. Он предоставляет объектно-ориентированный доступ к представлениям. Рассмотрим устройство этого фреймворка на примере простого приложения для заполнения регистрационной формы. Для разработки будем использовать Intellij IDEA. Перед работой необходимо поставить плагин IntelliStripes. Создадим новый проект и поставим галочку напротив пункта Web application.
Приложение Stripes состоит из нескольких основных частей:
- контроллеры (ActionBean);
- представления (jsp);
- модели (model);
- интерсепторы (Interceptor);
- конвертеры типов (TypeConverter);
- форматтеры (Formatter).
Исходный код Stripes доступен и неясные моменты всегда можно прояснить с его помощью, потому что он, с моей точки зрения, очень качественный и легко расширяемый средствами ООП.
Скачаем последнюю версию фреймворка
официального сайта.
В каталог lib кладем файлы stripes.jar и commons-logging.jar из скачанного архива и добавляем их к проекту через меню File->Project Structure->Libraries.
Структура проекта в простейшем общем случае должна выглядеть так:
Кроме этой библиотеки нужно добавить:
- jstl.jar (для общей поддержки jsp);
- standard.jar (для тега c:forEach, который мы будем использовать);
- mail.jar (для валидации введенного email средствами stripes).
Добавляем конфигурацию stripes в web.xml. Конфигурация представляет собой два фильтра и один маппинг фильтра:
xml version="1.0" encoding="UTF-8"?>
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
StripesFilterdisplay-name>
StripesFilterfilter-name>
net.sourceforge.stripes.controller.StripesFilterfilter-class>
ActionResolver.Packagesparam-name>
com.example.actionparam-value>
init-param>
Extension.Packagesparam-name>
com.example.extparam-value>
init-param>
filter>
DynamicMappingFilterfilter-name>
net.sourceforge.stripes.controller.DynamicMappingFilterfilter-class>
filter>
DynamicMappingFilterfilter-name>
*.actionurl-pattern>
REQUESTdispatcher>
filter-mapping>
web-app>
У фильтра StripesFilter основной параметр ActionResolver.Packages, в котором задается имя пакета, содержащего контроллеры. В параметре Extension.Packages указывается путь к дополнительным классам конвертеров типов или форматтеров, которые Stripes автоматически подключает при старте приложения.
Создадим для контроллеров пакет com.example.action. В этом пакете создадим класс FormActionBean, который реализует интерфейс ActionBean. Контроллер с таким именем будет доступен по URL: http://localhost:8080/stripes/Form.action.
Контроллер по мнению stripes должен реализовывать по крайней мере два метода: getContext и setContext. Создаем приватное поле context класса ActionBeanContext и делаем к нему геттер и сеттер. Очень удобно для каждого приложения создавать свой класс контекста, в котором хранить текущие параметры запроса/пользователя, например объекты DAO, параметры сессии (чтобы приведение их к типу было локализовано в одном месте). Объект контроллера и объект контекста создается новый на каждый запрос, так что они изолированы между различными запросами и пользователями.
Создадим модель пользователя User в классе com.example.model.User. Пользователь будет иметь имя, дату рождения, электронный адрес и страну проживания. Это будет обычный бин POJO. У него обязательно должен быть публичный конструктор без параметров, если определены другие, чтобы stripes смог создать его во время стадии биндинга переменных.
package com.example.model;
import java.util.Date;
public class User {
private String name;
private Date birthDate;
private String email;
private String country;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getBirthDate() {
return birthDate;
}
public void setBirthDate(Date birthDate) {
this.birthDate = birthDate;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
@Override
public String toString() {
return name + " " + birthDate + " " + email + " " + country;
}
}
Для того, чтобы данные пользователя из jsp записались в объект, нужно создать в контроллере приватное поле типа User и геттер и сеттер к нему. Код контроллера:
package com.example.action;
import com.example.Data;
import com.example.model.User;
import net.sourceforge.stripes.action.*;
import net.sourceforge.stripes.validation.DateTypeConverter;
import net.sourceforge.stripes.validation.EmailTypeConverter;
import net.sourceforge.stripes.validation.Validate;
import net.sourceforge.stripes.validation.ValidateNestedProperties;
import java.util.Collection;
public class FormActionBean implements ActionBean {
private static String[] countries = new String[]{"Russia", "America"};
@ValidateNestedProperties({
@Validate(field = "name", required = true, minlength = 10, on = "add"),
@Validate(field = "birthDate", converter = DateTypeConverter.class, on = "add"),
@Validate(field = "email", required = true, converter = EmailTypeConverter.class, on = "add"),
@Validate(field = "country", required = true, on = "add")
})
private User user;
private ActionBeanContext context;
public void setContext(ActionBeanContext actionBeanContext) {
this.context = actionBeanContext;
}
public ActionBeanContext getContext() {
return context;
}
@DefaultHandler
public Resolution view() {
return new ForwardResolution("WEB-INF/page.jsp");
}
public Resolution add() {
Data.users.put(user.getName(), user);
return new RedirectResolution(FormActionBean.class);
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public String[] getCountries() {
return countries;
}
public Collection getUsers() {
return Data.users.values();
}
}
Разберем подробнее код контроллера. Когда пользователь делает GET-запрос по URL Form.action, Stripes вызывает обработчик по умолчанию, помеченный аннотацией @DefaultHandler. В терминологии Stripes имя обработчика называется event. Если нужно вызвать другой обработчик, то его имя должно быть одним из параметров запроса, например так: "Form.action?add=". В этом случае вызовется метод add класса FormActionBean.
Метод getCountries нужен для вывода списка стран на странице, а метод getUsers - для вывода добавленных пользователей. С помощью класса Data мы эмулируем постоянное хранилище (типа базы данных) и для простоты используем потокобезопасный мап пользователей по имени. Код класса Data:
package com.example;
import com.example.model.User;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public class Data {
public static Map users = Collections.synchronizedMap(new HashMap());
}
Открывая страницу по адресу http://localhost:8080/stripes/Form.action мы попадаем в метод view(), который ни делает в данном случае ничего, кроме того, что передает управление коду рендеринга jsp-страницы. Код страницы page.jsp:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="s" uri="http://stripes.sourceforge.net/stripes.tld" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
Stripes Frameworktitle>
head>
Имя:
Дата рождения:
Электронный адрес:
Страна:
s:select>
s:form>
Добавленные пользователиh1>
c:forEach>
body>
html>
Stripes заменит свои теги на html, подставит текущие значения полей контроллера и их вложенных полей. Так как при вызове из метода view поле user равно null, то в форму вставятся значения по умолчанию из атрибутов value. Параметр beanclass у формы определяет метод какого класса вызывать при нажатии кнопки, помеченной как submit. В данном случае кнопка submit имеет атрибут name, равный add, что означает, что при нажатии на нее вызывется метод add и все параметры формы отправятся POST-запросом на сервер.
POST-запрос в данном случае будет выглядеть примерно так:
user.name=Foo+Bar+Buzzovich&user.birthDate=01.01.1985&user.email=user%40example.com&
user.country=Russia&add=%D0%94%D0%BE%D0%B1%D0%B0%D0%B2%D0%B8%D1%82%D1%8C&
_sourcePage=WD-cxfwrDfHq57V_5vLKPcjZ6ep322Ej&__fp=HX9golAsBE8d0xswK8OSb0z4QsxFsxhx
Перед вызовом метода add все параметры будут преобразованы в строки и объекты Java и создан объект класса User. Например, дата рождения станет из строки объектом класса Date благодаря указанию в аннтоации к этому полю параметра converter. Метод add добавит созданного Stripes пользователя в мап и сделает редирект снова на метод view: RedirectResolution вернет в браузер 302 и заголовок "Location: http://localhost:8080/stripes/Form.action".
Если сделать запрос вида "Location: http://localhost:8080/stripes/Form.action?user=Foo%20Bar%20Buzzovich", то сработает конвертер типов для класса User:
package com.example.ext;
import com.example.Data;
import com.example.model.User;
import net.sourceforge.stripes.format.Formatter;
import net.sourceforge.stripes.validation.TypeConverter;
import net.sourceforge.stripes.validation.ValidationError;
import java.util.Collection;
import java.util.Locale;
public class UserTCF implements TypeConverter, Formatter {
public void setFormatType(String s) {
}
public void setFormatPattern(String s) {
}
public void init() {
}
public String format(User user) {
return user.toString();
}
public void setLocale(Locale locale) {
}
public User convert(String name, Classextends User> aClass, Collection validationErrors) {
return Data.users.get(name);
}
}
В этом классе объединены одновременно и конвертер из строки и форматтер в строку (они не обязательно должны быть противоположны по логике преобразования). Stripes увидит, что в контроллере есть поле user с именем таким же, что и в параметре запроса и вызовет конвертер типов класса этого поля, в данном случае User. Метод convert по строке найдет в мапе нужного пользователя и вернет его, и он присвоится в поле user.
После срабатывания конвертера типов поле user уже будет заполнено нужным объектом и при отображении страницы выведутся значения из этого объекта.
Ниже формы на jsp-странице выводится список всех уже добавленных пользователей. При этом используется тег stripes format. В данном случае, он фиктивен и вызывает метод toString объекта User, просто для примера использования форматтера.