В моём пет-проекте у меня есть несколько элементов, которые в одном и том же виде мне хотелось бы использовать на разных страницах. Например, это поиск песен в каталоге исполнителей и странице поиска песен: пара ArtistSongsServlet с artistsongs.jsp и SongServlet c songs.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