Pull to refresh

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

Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.