О сайте Начало Java Студенческий отдел кадров Статьи Курсы по Java Вопросы/Ответы

Много страниц или даже очень много страниц

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&param2=value2

Теперь наша задача состоит в том, чтобы из этого запроса получить данные и поместить их в удобную структуру. Здорово бы сразу в класс с таким набором полей, который напоминает набор пришедших параметров. Вторым совершенно логичным шагом будет проверить наши данные - может они содержат какие-то ошибки или просто опасны для обработки. Т.к. наше приложение смотрит в большой мир, желающих прислать не то будет гораздо больше. Учтите это обстоятельство. Далее следует вызов обработчика данных. Определение класса, который будет обрабатывать наш запрос обычно происходит путем анализа URL - в нем муогут быть например специальные слова. Если вспомните Spring, то там использовался UrlHandlerMapping. Наш класс все обработал (при этом он может обратиться к нашему Business Layer - у нас же там есть все необходимые функции), получил какие-то данные и теперь нам предстоит заключительный шаг - надо решить куда мы направляемся или другими словами - на какую JSP страницу нам идти и как удобно данные поместит на нее.
Таким образом можно выделить следующие задачи Web-layer:

  1. Преобразование данных из HTTP в класс
  2. Проверка (валидация) данных
  3. Определение обработчика запроса
  4. Интеграция с объектами на Business Layer
  5. Система навигации
  6. Удобные возможности отображения данных
Псле этих рассуждений мы можем приступить к рассмотрению 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 основные частей:

  1. Описание контроллера - ActionServlet в web.xml
  2. Конфигурационный файл(файлы)
  3. Классы, наследуемые от Action и ActionForm
  4. JSP-страницы для отображения и ввода информации
  5. Тэги Struts
Начнем по порядку. Прописывание ActionServlet в web.xml не представляет каких-либо проблем. Это просто сервлет (специальный класс Struts), который будет получать все URL, которые надо обрабатывать. Выглядит это так:

<?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

<?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.

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. Именно в нем происходит вся логика.

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

<%@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 еще проще.

<%@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 будем считать состоявшимся. Впереди нас ждут еще некоторые возможности этого заслуженного пакета.

Архив с исходными кодами: Исходный код