Основная область программирования для меня, это разработка программного обеспечения для автоматизации учёта в торговли. С возможностью использовать для этого сервлеты Java я столкнулся в 2009 году, когда вместе с последней, вышедшей для десктопа, версией Openbravo POS шёл модуль ресторана для PDA. Основной идей тогда для разработчиков Openbravo POS было, чтобы не усложнять десктопную версию приложения, вынести узкую бизнес-логику в отдельное небольшое приложение, главной изюминкой которого был компактный веб-интерфейс для доступа с любого устройства, а не только того, где может быть запущена Java c Swing. При этом тогда предполагалось, что сервлет не только будет работать с одной и той-же базой, что и десктоп версия, но и контейнер для него будет интегрирован в приложение на десктопе, после запуска которого пользователь автоматический получал доступ к POS в радиусе действия Wi-Fi сети. В рамках комьюнити данная идея дальнейшего развития не получила, но я с 2012 для своих клиентов заложенными тогда принципы продолжаю пользоваться, и в данной статье расскажу читателям, как используя Stripes Framework в связке с jQuery Mobile и ORMLite, получить инструмент для быстрой разработки небольших сервлетов ориентированных на мобильный веб.
Инструментарий
Обычно, когда ищешь инструмент разработки, то стараешься найти что-то универсальное, что можно изучив однажды использовать повторно на протяжении достаточно долгого времени. В 2012 года именно с этим я столкнулся, когда попытался переделать исходный код ресторанного модуля Openbravo POS. В его основе лежал фреймворк Struts 1, изучать написание XML-схем для которого меня совсем не радовало, но на удачу, как раз в тот момент, мне на глаза попался обзор технологий экосистемы Java от RebelLabs. Один из разделов этого обзора был посвящён популярности веб-фреймворков у разработчиков, в эту 10-ку с почётными 2% входил Stripes. С его освоения я и начал изучение основ разработки Java сервлетов. Также мне помогло то, что к тому моменту на Хабре в «Песочнице» уже была обзорная статья про этот фреймворк.
В Stripes всё подчинено логике реакции на контролеры(ActionBean), которые отвечают за получение доступа к модели данных для дальнейшего отображения пользователю через шаблоны страниц JSP. Так как мне нужен был мобильный веб-интерфейс, то для визуальной части я выбрал jQuery Mobile, главный плюс которого, это возможность практически не касаясь JavaScript использовать только HTML5 разметку для построения динамической реакции на действия контролера.
Третьим элементом, отвечающим за работу с моделью данных, мной сначала был выбран совсем простой Persist ORM/DAO, но когда его функционала стало недостаточно, я сменил его на более мощный ORMLite. Главным при выборе стала лёгкость использования уже существующей модели представления и отсутствия необходимости написания SQL-запросов обходясь, как и в случае Stripes, только аннотациями.
Модель-Представление-Контролер
Ключевым в Stripes является 100% ориентированность на концепцию MVC(Model-View-Controller). А главной целью создания данного фреймворка в 2005 году было создание более легковесной реализация MVC концепции для Java EE, чем была реализована в популярном на тот момент фреймворке Struts. К версии 1.5 в 2008 году эта цель авторами Stripes была полностью достигнута и данный фреймворк начал набирать популярность среди разработчиков.
При проектировании структуры исходного кода сервлета следует стараться не выходить за рамки парадигмы Модель-Представление-Контролер, что не очень сложно сделать для большинства задач при создании CRUD-приложений. Для примера, в исходном коде для справочника товаров, это будет выглядеть так:
Источник данных, исходная SQL-таблица
CREATE TABLE PRODUCTS (
ID VARCHAR(255) NOT NULL,
NAME VARCHAR(255) NOT NULL,
CODE VARCHAR(255) NOT NULL,
PRICESELL DOUBLE NOT NULL,
CATEGORY VARCHAR(255) NOT NULL,
PRIMARY KEY (ID),
CONSTRAINT PRODUCTS_FK_1 FOREIGN KEY (CATEGORY) REFERENCES CATEGORIES(ID)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE UNIQUE INDEX PRODUCTS_CODE_INX ON PRODUCTS(CODE);
CREATE UNIQUE INDEX PRODUCTS_NAME_INX ON PRODUCTS(NAME);
Модель данных с аннотациями для ORMLite
@DatabaseTable(tableName = "PRODUCTS")
public class Product {
public static final String ID = "ID";
public static final String NAME = "NAME";
public static final String CODE = "CODE";
public static final String PRICESELL = "PRICESELL";
public static final String CATEGORY = "CATEGORY";
@DatabaseField(generatedId = true, columnName = ID)
private UUID id;
@DatabaseField(columnName = NAME, unique = true, canBeNull = false)
private String name;
@DatabaseField(columnName = CODE, unique = true, canBeNull = false)
private String code;
@DatabaseField(columnName = PRICESELL, canBeNull = false)
private BigDecimal pricesell;
@DatabaseField(foreign = true,
columnName = CATEGORY,
foreignColumnName = ProductCategory.ID,
foreignAutoRefresh = true,
canBeNull = false)
private ProductCategory productCategory;
// ...
// Перечисления методов get и set.
// ...
}
В начале, с помощью ORMLite, задаётся структура модели данных и её связь с реляционной базой данных(значение полей
tableName
и columnName
). Затем через геттеры и сеттеры модель предоставляет доступ для остальных классов и методов сервлета.Контроллер Stripes для ввода/вывода данных
public class ProductCreateActionBean extends ProductBaseActionBean {
private static final String PRODUCT_CREATE = "/WEB-INF/jsp/product_create.jsp";
@DefaultHandler
public Resolution form() {
// Открытие формы для заполнения.
return new ForwardResolution(PRODUCT_CREATE);
}
public Resolution add() {
// ...
// После проверки полученных значений, создание записи в таблице.
// ...
}
@ValidateNestedProperties({
@Validate(on = {"add"},
field = "name",
required = true,
trim = true,
maxlength = 255),
@Validate(on = {"add"},
field = "code",
required = true,
trim = true,
minlength = 8,
maxlength = 13),
@Validate(on = {"add"},
field = "priceSell",
required = true,
converter = BigDecimalTypeConverter.class),
@Validate(field = "productCategory.id",
required = true,
converter = UUIDTypeConverter.class)
})
@Override
public void setProduct(Product product) {
super.setProduct(product);
}
}
Контроллер перехватывает данные передаваемые пользователем и обрабатывает их согласно заданных аннотаций. Например, выполняя
Resolution form()
, получив значение ключевого поля productCategory.id
, он пропускает его через UUIDTypeConverter
, проверяя соответствует-ли оно допустимым для UUID критериям.Шаблон JSP с разметкой jQueryMobile и полями Stripes
<!-- Заглавие страницы -->
<stripes:layout-component name="content">
<stripes:errors />
<stripes:messages />
<stripes:form action="/ProductCreate.action?add">
<div>
<stripes:hidden name="product.productCategory.id" value="${actionBean.product.productCategory.id}"/>
</div>
<ul data-role="listview" data-inset="true">
<li class="ui-field-contain">
<stripes:label name="label.Product.name" for="productName" />
<input name="product.name" id="productName" type="text"
data-clear-btn="true">
</li>
<li class="ui-field-contain">
<stripes:label name="label.Product.code" for="productCode" />
<input name="product.code" id="productCode" type="text"
data-clear-btn="true">
</li>
<li class="ui-field-contain">
<stripes:label name="label.Product.price" for="productPrice"/>
<input name="product.priceSell" id="productPrice" type="number"
step="0.01"
value="0.00"
data-clear-btn="true">
</li>
<li class="ui-body ui-body-b">
<fieldset class="ui-grid-a">
<div class="ui-block-a">
<sdynattr:submit name="add" data-theme="a"/>
</div>
<div class="ui-block-b">
<sdynattr:reset name="clear" data-theme="b"/>
</div>
</fieldset>
</li>
</ul>
</stripes:form>
</stripes:layout-component>
<!-- Окончание страницы -->
В шаблоне страницы разметка jQuery Mobile также может служить для предварительной проверки вводимых данных, например в визуальной форме предлагая пользователю на мобильном устройстве разные клавиатуры для текста и чисел(в
input
параметр type
равен "text"
или "number"
).Для чего подходит
В конце прошлого 2013 года RebelLabs был опубликован новый обзор перспектив развития фреймворков в будущем 2014 году. Stripes в данный обзор не вошёл, так как в основном внимание было уделено более полярным чем он фреймворкам, таким как Spring, Grails или Vaadin. Но мне кажется, что если совместно с ним использовать jQuery Mobile и ORMLite, то и он сейчас найдёт свою нишу в инструментарии разработчика на Java. По крайней мере в 4 из 7 предложенных RebelLabs сфер применения с ним можно чувствовать себя вполне уверенно:
- Приложения CRUD — об этом было выше, если всё делать последовательно и по одной схеме, то очень сложно сделать плохо. Главное при проектировании стараться каждое действие контроллера делать максимально полным, но не переполненным.
- Мобильные приложения — тут основная нагрузка лежит на jQuery Mobile, для Java программиста несомненный плюс, что кроме HTML5 можно не трогать JavaScript и CSS.
- Прототипы приложений — это основное для чего я использую эту связку, набрав с 10-ок сервлетов в портфолио, можно отдельные компоненты каждого из них легко между собой тасовать под разные бизнес-процессы.
- Портирование десктоп приложений — а с этого у меня всё и началось, и именно для такого применения я могу порекомендовать в первую очередь сплав этих трёх фрейморков.
Это итог, новый интерфейса с jQuery Mobile версии 1.4 в 2014 году, в сравнении с тем интерфейсом, что был в 2009 году у PDA модуля Openbravo POS.
В качестве примера практической реализации изложенного в статье, небольшой проект CRUD-сервлета для каталог товара: github.com/nordpos-mobi/product-catalog