Много страниц или даже очень много страниц
Struts наверно был самым первым фреймворком, с которым я стал знакомиться при погружении в мир Web для Java. Если честно, то мне это доставило немало неприятных минут (даже часов/дней). Причина в том, что к тому моменту я сам слабо представлял себе все, что связано с Web-программированием на Java (подозреваю, что те, кто добрался до этой статьи понимают уже гораздо больше). Да и документация Struts до сих пор не блещет. Сейчас уже появилось немало книг, но тогда, в 2002 году это была проблема. Ну да ладно — в итоге я все-таки разобрался и теперь могу свои знания оформить в виде статьи.
Начиная разговор о Struts версии 1.х — в данной статье я буду рассматривать версию 1.3 — надо четко понять, для чего он был создан и до сих пор используется.
На тот момент КАЖДЫЙ запрос к серверу сопровождался перегрузкой страницы, т.к. технология AJAX не появилась. В случае большого приложения разработчик получал большое количество страниц и еще большее количество параметров. И эти параметры надо было проверить, собрать в некоторый объект, вызвать обработчик этого запроса, получить данные и показать пользователю какую-то новую страницу. А может и ту же самую. Через две недели придет предложение все-таки на новую. Или другую.
Думаю те, кто полагает, что с появление AJAX актуальность подобных задач ушла в прошлое, заблуждаются. Да, многие современные мобильники поддерживают JavaScript. И их будет все больше и больше. Но, во-первых, есть еще много мобильных устройств, которые не поддерживают JavaScript. Во-вторых — не будем думать, что не появятся новые устройства, которые будут иметь гораздо меньшие возможности, чем нынешние мобильные телефоны. Но им может вполне потребовать услуги удаленного сервера. В-третьих — кто вам сказал. что пользовательский интерфейс будет выглядеть лучше и проще на JavaScript ? Ну и наконец в-четвертых — это то, что вам могут рассказать разработчики, которые вынуждены тестировать свое творение на нескольких версиях тогое же IE. А если мы добавим к нему Firefox, Opera, Google Chrome, Safari и еще много чего, то этим ребятам не позавидуешь. Теперь представьте, что появится еще несколько десятков, а то и сотен версий иных клиентов ? То-то. И мы сразу задумаемся — а может рисовать на клиенте что-то очень простое ? И пусть сервер решает, какая страничка подходит данному клиенту. В конце концов кто сказал, что HTML у нас единственный и неповторимый ? Есть еще WML, PDF, HDML да и просто текст. И заметим, что реализовать переключение на сервере на разные варианты отображения гораздо удобнее. Сформировать предварительный документ в XML, а потом пиши XSL, который подходит для очередного броузера. Проблем в разы меньше.
Думаю, что этого достаточно для размышлений. Надеюсь, что мое несколько затянувшееся отступление Вам не помешает читать дальше.
Прежде чем углубиться непосредственно в сам Struts я бы хотел, чтобы вы попробовали «увидеть» архитектуру Web-приложения.
Представьте обычное GUI-приложение. Оно состоит из большого (или не очень) набора форм, ан которых вы можете отображать и вводить какие-то данные. После всяких действий вы нажимаете какую-то заветную кнопку, выбираете пункт меню или совершаете еще какое-то действие и … переноситесь в другую форму. Данные в этой форме могут быть как совсем новые, так и из предыдущей формы. На вашей форме есть списки, поля для ввода, таблицы и много чего еще. События от элементов обрабатываются какими-то функциями. Представили ?
А теперь замените формы на HTML-страницы. И вы увидите, что основная сложность — это иметь удобный механизм «улавливания» HTTP-запросов со страницы, удобный разбор пришедших данных и система перехода на другую форму, э-э-э-э пардон, страницу. Увидели ? Постарайтесь. Это существенно облегчит вам понимание многих аспектов web-програмирования. Во всяком случае мне это сильно помогает.
Давайте еще раз посмотрим на наше приложение. Мы оказываемся на странице, набираем какие-то данные, нажимаем кнопку. Наш HTTP-запрос (чудес тут не будет — все основывается на HTTP) приходит на Web-Server (тот же Tomcat) с каким-то набором параметров. Причем еще раз обращаю ваше внимание — это HTTP. Т.е. параметры приходят в самом привычном для HTTP виде — после URL стоит знак «?» и дальше через разделитель «&» идут пары «имя=значение». Т.е. что-то вроде такого
/MyApp/MyCommand?param1=value1¶m2=value2
Теперь наша задача состоит в том, чтобы из этого запроса получить данные и поместить их в удобную структуру. Здорово бы сразу в класс с таким набором полей, который напоминает набор пришедших параметров. Вторым совершенно логичным шагом будет проверить наши данные — может они содержат какие-то ошибки или просто опасны для обработки. Т.к. наше приложение смотрит в большой мир, желающих прислать не то будет гораздо больше. Учтите это обстоятельство. Далее следует вызов обработчика данных. Определение класса, который будет обрабатывать наш запрос обычно происходит путем анализа URL — в нем муогут быть например специальные слова. Если вспомните Spring, то там использовался UrlHandlerMapping. Наш класс все обработал (при этом он может обратиться к нашему Business Layer — у нас же там есть все необходимые функции), получил какие-то данные и теперь нам предстоит заключительный шаг — надо решить куда мы направляемся или другими словами — на какую JSP страницу нам идти и как удобно данные поместит на нее.
Таким образом можно выделить следующие задачи Web-layer:
- Преобразование данных из HTTP в класс
- Проверка (валидация) данных
- Определение обработчика запроса
- Интеграция с объектами на Business Layer
- Система навигации
- Удобные возможности отображения данных
Псле этих рассуждений мы можем приступить к рассмотрению Struts держа в голове те идеи, которые я только что изложил.
Где скачать и библиотеки
Скачать Struts можно на сайте Struts. Распакуйте ZIP-файл в какой-нибудь каталог. Что касается библитек, то можно просто скопировать все, что находится в подкаталоге lib. Разве что jstl-1.0.2.jar можно не брать. Мой каталог с библиотеками включает больше, чем надо для нашего примера. Здесь вы видите и Hibernate, и Spring, но так уж сложилось и оптимизировать список я не вижу смысла. antlr-2.7.6.jar
asm.jar
aspectjweaver.jar
bsf-2.3.0.jar
cglib-2.1.jar
commons-beanutils-1.8.0.jar
commons-chain-1.2.jar
commons-collections-3.1.jar
commons-digester-1.8.jar
commons-fileupload-1.1.1.jar
commons-io-1.1.jar
commons-logging.jar
commons-validator-1.3.1.jar
dom4j-1.6.1.jar
ejb3-persistence.jar
hibernate-annotations.jar
hibernate-commons-annotations.jar
hibernate-entitymanager.jar
hibernate3.jar
javaee.jar
javassist-3.4.GA.jar
jstl.jar
jta-1.1.jar
junit-4.4.jar
log4j-1.2.15.jar
oro-2.0.8.jar
slf4j-api-1.5.3.jar
slf4j-log4j12-1.5.3.jar
spring-test.jar
spring-webmvc-struts.jar
spring-webmvc.jar
spring.jar
standard.jar
struts-core-1.3.10.jar
struts-el-1.3.10.jar
struts-extras-1.3.10.jar
struts-faces-1.3.10.jar
struts-mailreader-dao-1.3.10.jar
struts-scripting-1.3.10.jar
struts-taglib-1.3.10.jar
struts-tiles-1.3.10.jar
Алгоритм работы и составные части Struts
Как я уже отмечал, Struts решает проблему организации запросов от пользователя, получения данных и отображения. По сути — это уже известный нам шаблон MVC (Model, View, Controller). В качестве контроллера выступает ActionServlet, который по заданной конфигурации (чуть позже мы ее разберем) обрабатывает данные от клиента, помещает их в объект определенного класса (а точнее в наследника от Action Form) и вызывает подходящий обработчик этого события — наследника от класса Action. Обработчик должен выполнить какие-то действия, заполнить данные и вернуть некоторый статус, характеризующий насколько удачно все выполнилось. По данному статусу происходит поиск той JSP-страницы, которая будет показана пользователю. И данные, которые возможно будут собраны обработчиком, будут отображены на этой странице. Дальше цикл повторяется — ActionServlet получит и предварительно соберет данные в контейнер-наследник от ActionForm, вызовет обработчик (наследник Action), получит от него статус, по статусу вызовет нужную JSP. Вуаля.
В этой части мы напишем очень простое приложение, которое запросит у пользователя ID специальности и по этому ID найдет название. В базу данных мы ходить не будем, поэтому поиск будет достаточно формальным — мы просто пропишем его в нашем классе. В случае неудачного поиска попросим пользователя снова ввести данные.
Итак, давайте еще раз подробнее рассмотрим составные части Struts. Я выделил 5 основные частей:
- Описание контроллера — ActionServlet в web.xml
- Конфигурационный файл(файлы)
- Классы, наследуемые от Action и ActionForm
- JSP-страницы для отображения и ввода информации
- Тэги Struts
Начнем по порядку. Прописывание ActionServlet в web.xml не представляет каких-либо проблем. Это просто сервлет (специальный класс Struts), который будет получать все URL, которые надо обрабатывать. Выглядит это так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" 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"> <servlet> <servlet-name>action</servlet-name> <servlet-class>org.apache.struts.action.ActionServlet</servlet-class> <init-param> <param-name>config</param-name> <param-value>/WEB-INF/struts-config.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <session-config> <session-timeout>30</session-timeout> </session-config> <servlet-mapping> <servlet-name>action</servlet-name> <url-pattern>*.do</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> <taglib> <taglib-uri>/WEB-INF/tld/struts-html.tld</taglib-uri> <taglib-location>/WEB-INF/tld/struts-html.tld</taglib-location> </taglib> </web-app> |
Думаю, что что-то сверхудивительное вы не увидели. Отметим только параметр config в описании ActionServlet. Он указывает на файл конфигурации, который нам надо рассмотреть более подробно. Также отметим, что сервлет обрабатывает запросы, которые оканчиваются на .do
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
<?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE struts-config PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 1.3//EN" "http://struts.apache.org/dtds/struts-config_1_3.dtd"> <struts-config> <form-beans> <form-bean name="lookupForm" type="students.web.LookupForm"/> </form-beans> <action-mappings> <action path="/Lookup" type="students.web.LookupAction" name="lookupForm" validate="true" input="/index.jsp"> <forward name="success" path="/result.jsp"/> <forward name="failure" path="/index.jsp"/> </action> </action-mappings> <message-resources parameter="students.ApplicationResources"/> </struts-config> |
Наш конфигурационный файл состоит из нескольких частей. Первая описана тэгом form-beans. Она включает в себя описание всех классов, которые наследуются от ActionForm. Как я уже говорил, задача этих классов служить контейнерами для хранения данных пришедших со страниц. В описании мы указываем имя lookupForm под которым к этом классу можно будет обратиться и название класса. Кроме этого можно указать несколько атрибутов, но для понимания сути это пока несущественно.
Следующая часть — action-mappings. Она описывает во-первых класс, который будет являться обработчиком и во-вторых, связь с классом отActionForm. Здесь вы можете видеть следующие атрибуты для тэга action:
- path — описывает URL. По сути это реализация идеи, которую я описывал. Для понимания кто что обрабатывает используются разные URL
- type — класс для наследника ActionForm
- name — имя ActionForm. В нашем случае это lookupForm, его мы видим в тэге form-bean
- validate — атрибут, указывающий на необходимость вызова проверки данных
- input — указывает на страницу, на которую возвращается приложение в случае ошибки
Также надо обратить внимание на часть с тэгом forward. Это раздел навигации. Формируя какое-либо ключевое слово мы будем переходить на нужную нам страницу.
Ну и наконец тэг message-resources. Он служит для описания файла, в котором находятся строковые константы. Это удобный механизм интернационализации и упрощения использавония строк. Вопросы связанные с этим файлов мы рассмотрим в свое время.
Рассмотрев вкратце вопросы конфигурации обратим наше внимание на «сладкую парочку» — Action/ActionForms. Сначала рассмотримLookupForm. Как уже говорилось этот класс служит контейнером для параметров. Но кроме этого он может иметь функцию проверки (для этого он реализует метод validate. И функцию инициализации данных — reset.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
package students.web; import javax.servlet.http.HttpServletRequest; import org.apache.struts.action.ActionErrors; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionMapping; import org.apache.struts.action.ActionMessage; public class LookupForm extends ActionForm { private String professionId = null; private String professionName = null; public String getProfessionId() { return professionId; } public void setProfessionId(String professionId) { this.professionId = professionId; } public String getProfessionName() { return professionName; } public void setProfessionName(String professionName) { this.professionName = professionName; } @Override public void reset(ActionMapping mapping, HttpServletRequest request) { this.professionId = null; } @Override public ActionErrors validate(ActionMapping mapping, HttpServletRequest request) { ActionErrors ae = new ActionErrors(); if (professionId == null || professionId.trim().isEmpty()) { ae.add("professionId", new ActionMessage("errors.lookup.symbol.required")); } return ae; } } |
В классе указано всего лишь два свойства — professionId и professionName. Надо обратить внимание, что для каждого свойства есть пара методов set/get.
Также обратите внимание на метод validate. Он возвращает объект класса ActionErrors. Это список ошибок обнаруженных при проверке. Если список не пустой — значит ошибки есть. Если пустой — значит все в порядке.
Посмотрите на то, как мы создаем запись об ошибке. При добавлении мы указываем два параметра:
— имя свойства, на котором произошла ошибка
— строка-ключ. Она указывает на значение из файла ApplicationResource
Больше каких-либо замечаний по этому классу на данный момент нет.
Теперь давайте рассмотрим файл LookupAction.java. В нем сосредоточена логика обработки нашего выдающегося запроса. Ключевым методом является execute. Именно в нем происходит вся логика.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
package students.web; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.struts.action.Action; import org.apache.struts.action.ActionErrors; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionForward; import org.apache.struts.action.ActionMapping; import org.apache.struts.action.ActionMessage; public class LookupAction extends Action { protected String getProfession(Long professionId) { if (professionId.equals(1L)) { return "Chemist"; } return null; } @Override public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { String profession = null; // Получаем нашу форму-контейнер с данными LookupForm lookupForm = (LookupForm) form; // Устанавливаем ответ по умолчанию String target = "success"; Long professionId = null; if (form != null) { try { professionId = Long.parseLong(lookupForm.getProfessionId()); profession = getProfession(professionId); } catch (Exception e) { } } // И формируем ответ в случае неудачи if (profession == null) { target = "failure"; ActionErrors errors = new ActionErrors(); errors.add(ActionErrors.GLOBAL_MESSAGE, new ActionMessage("errors.lookup.unknown", professionId)); saveErrors(request, errors); } else { lookupForm.setProfessionName(profession); } // Переходим на нужную страницу в зависимости от установленного target return (mapping.findForward(target)); } } |
Код в методе execute достаточно прозрачный — мы приводим переданный объект типа ActionForm к нашему типу, потом ищем профессию по указанному ID (у нас их немного) и потом формируем ответ — куда идти. Надеюсь, что основная концепция Struts уже более менее понятна.
Нам осталось немного — посмотреть на тэги Struts, которые облегчают нам жизнь. У нас всего две страницы — index.jsp и result.jsp. Давайте посмотрим на них. Итак, index.jsp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
<%@page contentType="text/html" pageEncoding="UTF-8" language="java" %> <%@taglib uri="/WEB-INF/tld/struts-html.tld" prefix="html" %> <html> <head> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Struts Example Page</title> </head> </head> <body> <html:errors/> <html:form action="Lookup"> <table> <tr> <td>Symbol:</td> <td><html:text property="professionId" /></td> </tr> <tr> <td colspan="2" align="center"><html:submit /></td> </tr> </table> </html:form> </body> </html> |
Как видите, здесь мы используем тэги Struts — они помечены префиксом html. На полном и подробном описании мы пока не будем останавливаться, но очень советую вам почитать о них — описание есть на официальном сайте. И достаточно неплохое.
Сосредоточим наше внимание на тэгах, которые указаны в файле.
- html:errors — этот тэг позволяет выводить те ошибки, которые мы с вами создавали. Также обратите внимание на свойстваerrors.header, errors.footer в файле ApplicationResource. Именно эти свойства «обрамляют» сообщение об ошибках сверху и снизу.
- html:form — это аналого тэга FORM для HTML. Но тэг от Struts позволяет не думать об окончании «.do»
- html:text — текстовое поле для ввода у которого я советую обратить внимание на имя — professionId. Именно по имени будет найдено свойство в нашем классе LookupForm.
- html:submit — кнопка для отправки нашей формы на сервер по указаному атрибуту action в тэге html:form
Файл result.jsp еще проще.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<%@page contentType="text/html" pageEncoding="UTF-8" language="java" %> <%@taglib uri="/WEB-INF/tld/struts-bean.tld" prefix="bean" %> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Struts Example Page</title> </head> <body> <table> <tr> <td>Profession : </td> <td><bean:write name="lookupForm" property="professionName"/></td> </tr> </table> </body> </html> |
Здесь мы используем тэг bean:write, который позволяет нам вывести свойство определенного класса. На класс указывает атрибут name а атрибутproperty указывает на свойство класса.
На этом наше первое и очень поверхностное знакомство с пакетом Struts 1.x будем считать состоявшимся. Впереди нас ждут еще некоторые возможности этого заслуженного пакета.
Архив с исходными кодами: Исходный код