Как стать автором
Обновить

Stripes Framework

Я хочу рассказать о малоизвестном фреймворке для разработки веб-приложений на Java — Stripes. Поиск по сайту слова «stripes» дал понять, что статей про него еще не было.


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, просто для примера использования форматтера.

Теги:
Хабы:
Данная статья не подлежит комментированию, поскольку её автор ещё не является полноправным участником сообщества. Вы сможете связаться с автором только после того, как он получит приглашение от кого-либо из участников сообщества. До этого момента его username будет скрыт псевдонимом.