Проект Java Servlet + jsp. Часть 5. Повторяющиеся элементы и директива include

В моём пет-проекте у меня есть несколько элементов, которые в одном и том же виде мне хотелось бы использовать на разных страницах. Например, это поиск песен в каталоге исполнителей и странице поиска песен: пара ArtistSongsServlet с artistsongs.jsp и SongServlet c songs.jsp. Две разные страницы, но их части идентичные. Так какой смысл дублировать код? И потом при редактировании кода на одной странице вручную менять его ещё и во всех остальных местах?

Эти вопросы актуальны и для более стандартных объектов, которые есть в большинстве проектов: меню, шапка сайта, футер…

В этой части цикла я разберусь с такими повторяющимися фрагментами кода. Подробнее про сам проект и все этапы его создания можно прочитать в прошлых частях цикла.

Две разных jsp страницы с одинаковым списком песен
(дизайн так и задуман, он у меня изображает windows 95)

Директива @include

Первым делом я конечно же создал новый .jsp файл в новой папке components, куда буду размещать подобные повторяющиеся фрагменты. Назвал его song-list-search.jsp и переместил туда часть кода, которая отвечает за обработку каждой песни из выборки.

В итоге в основных файлах songs.jsp и artistsongs.jsp осталось только это:

<%@include file="components/song-list-search.jsp"%>

в file указываю адрес jsp-файла. В самом файле song-list-search.jsp теперь весь код:

<div id="song-list">
    <div class="song-row">
        <div class="flex1">
            <p><b>Peak</b></p>
        </div>
        <div class="flex9">
        </div>
        <div class="flex-end">
            <p><b>WOC</b></p>
        </div>
        <div class="flex1">
        </div>
    </div>
    <c:forEach items="${songs}" var="song">
        <%@include file="song-entry.jsp"%>
    </c:forEach>
</div>

И я так увлёкся, что насоздавал ещё кучу компонентов для лучшей читаемости кода. Отнёсся к этому коду, как к java-коду, где надо обязательно длинные фрагменты разбивать на методы по логике. Например, вынес ещё и каждую песню.

=<%@include file="song-entry.jsp"%>

Но вроде не особо переусердствовал.

Тег <jsp:include>

Похожая схема работы с тегом <jsp:include>: тоже создаётся отдельный файл и подключается тегом:

<jsp:include page="components/song-list-search.jsp" />

Альтернативой можно считать тег include из библиотеки jstl:core.

<c:import url="components/song-list-search.jsp"/>

Разница директивы include и тега jsp:include

По сути, @include это статический «инклюд», а jsp:include — динамический. Первый вариант копирует содержимое подключаемого файла в обозначенное место в момент компиляции. Второй вариант во время выполнения делает запрос, ответ на который выводится вместо тега.

Самое главное, что при использовании директивы код вставляется на основную страницу и имеет доступ ко всем переменным и ко всем данным на этой странице. Если я из сервлета передал на song.jsp параметр songs, то я его спокойно могу использовать в подключаемом файле (что я и делаю). С тегом этот фокус уже не пройдёт.

Минус тега jsp:include. При подключении страницы через тег она выполняется со своим собственным контекстом и все данные нужно передать туда заново.

В тело тега помещаем теги jsp:param с именем каждого параметра и его значением. Тогда на странице song-entry.jsp сможем работать с этими данными, обращаясь к ним, например, ${param.songName}. Обязательно нужно добавлять этот param.

        <jsp:include page="/song-entry.jsp">
            <jsp:param name="songName" value="${song.name}"/>
        </jsp:include>

И в этом примере я передаю лишь одно поле — строковое значение, потому что у меня этот способ не работает с объектами — ${param.song.name} или ${param.song.id} не получается — и со списками тоже никакого успеха.

Лучше дела у меня пошли с альтернативой от jstl core библиотеки. Перед каждым подключением страницы объявляем все необходимые переменные с c:set и областью видимости request. Достаются данные на вызываемой странице как положено, без всяких парамов, по имени: ${song.id}, ${song.name} и т.д.

        <c:set var="song" value="${song}" scope="request"/>
        <jsp:include page="/components/song-entry.jsp" />

Но со списками и такой вариант у меня не сработал.

Минус директивы @include в том, что не получится подключать несколько раз файл, если в нём объявляются новые переменные. Это приведёт к ошибке при попытке открыть такую страницу: org.apache.jasper.JasperException: Unable to compile class for JSP.

Чтобы это проверить, я объявил на подключаемой странице новую переменную и попытался подключить эту страницу дважды:

    <%
        int i = 0;
    %>

Понятное дело, что раз код просто вставляется в нужное место, то мы в итоге получаем на одной странице два раза объявление переменной с одним и тем же именем. Нельзя так.


Ещё есть более замороченный вариант с кастомными тэгами или фрагментами (<jsp:invoke fragment> и <jsp:fragment>), но с ними я не разбирался, потому что меня очень устраивает директива.

На моих страницах никакие новые переменные вообще не объявляю, поэтому опасности получить ошибку нет. С другой стороны, я работаю с данными с бэка, поэтому не хочется их дополнительно по несколько раз передавать между страницами — экономлю строчки кода и устраняю лишние возможности ошибиться и напутать что-нибудь с именами параметров. Директива — мой выбор в моём пет-проекте.

1 Comment

Оставить комментарий