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"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
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">
<filter>
<display-name>StripesFilter</display-name>
<filter-name>StripesFilter</filter-name>
<filter-class>net.sourceforge.stripes.controller.StripesFilter</filter-class>
<init-param>
<param-name>ActionResolver.Packages</param-name>
<param-value>com.example.action</param-value>
</init-param>
<init-param>
<param-name>Extension.Packages</param-name>
<param-value>com.example.ext</param-value>
</init-param>
</filter>
<filter>
<filter-name>DynamicMappingFilter</filter-name>
<filter-class>net.sourceforge.stripes.controller.DynamicMappingFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>DynamicMappingFilter</filter-name>
<url-pattern>*.action</url-pattern>
<dispatcher>REQUEST</dispatcher>
</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<User> 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<String, User> users = Collections.synchronizedMap(new HashMap<String, User>());
}
Открывая страницу по адресу 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" %>
<html>
<head>
<title>Stripes Framework</title>
</head>
<body>
<s:messages/>
<s:errors/>
<s:form beanclass="com.example.action.FormActionBean">
Имя: <s:text name="user.name" value="Foo Bar Buzzovich"/><br/>
Дата рождения: <s:text name="user.birthDate" value="01.01.1985"/><br/>
Электронный адрес: <s:text name="user.email" value="user@example.com"/><br/>
Страна:
<s:select name="user.country">
<s:options-collection collection="${actionBean.countries}"/>
</s:select><br/>
<s:submit name="add" value="Добавить"/>
</s:form>
<h1>Добавленные пользователи</h1>
<c:forEach items="${actionBean.users}" var="user">
<s:format value="${user}"/><br/>
</c:forEach>
</body>
</html>
Stripes заменит свои теги на html, подставит текущие значения полей контроллера и их вложенных полей. Так как при вызове из метода view поле user равно null, то в форму вставятся значения по умолчанию из атрибутов value. Параметр beanclass у формы определяет метод какого класса вызывать при нажатии кнопки, помеченной как submit. В данном случае кнопка submit имеет атрибут name, равный add, что означает, что при нажатии на нее вызывется метод add и все параметры формы отправятся POST-запросом на сервер.
POST-запрос в данном случае будет выглядеть примерно так:
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<User>, Formatter<User> {
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, Class<? extends User> aClass, Collection<ValidationError> validationErrors) {
return Data.users.get(name);
}
}
В этом классе объединены одновременно и конвертер из строки и форматтер в строку (они не обязательно должны быть противоположны по логике преобразования). Stripes увидит, что в контроллере есть поле user с именем таким же, что и в параметре запроса и вызовет конвертер типов класса этого поля, в данном случае User. Метод convert по строке найдет в мапе нужного пользователя и вернет его, и он присвоится в поле user.
После срабатывания конвертера типов поле user уже будет заполнено нужным объектом и при отображении страницы выведутся значения из этого объекта.
Ниже формы на jsp-странице выводится список всех уже добавленных пользователей. При этом используется тег stripes format. В данном случае, он фиктивен и вызывает метод toString объекта User, просто для примера использования форматтера.