Скрытые особенности JSP/Servlet

Меня интересуют ваши трюки и т.д., используемые при написании JSP/Servlet. Я начну:

Недавно я узнал, как вы можете включить вывод одного тега JSP в атрибут другого тега:

<c:forEach items="${items}">
  <jsp:attribute name="var">
    <mytag:doesSomething/>
  </jsp:attribute>
  <jsp:body>
    <%-- when using jsp:attribute the body must be in this tag --%>
  </jsp:body>
</c:forEach>

Ответ 1

Примечание. Мне сложно думать о каких-либо "скрытых функциях" для JSP/Servlet. По-моему, "лучшие практики" - лучшая формулировка, и я могу думать о любом из них. Это также зависит от вашего опыта работы с JSP/Servlet. После многих лет развития вы больше не видите эти "скрытые возможности". В любом случае, я перечислил некоторые из тех небольших "лучших практик", о которых я в течение многих лет обнаружил, что многие стартеры не знают об этом полностью. Они будут классифицированы как "скрытые возможности" в глазах многих стартеров. Во всяком случае, вот список:)


Скрыть страницы JSP из прямого доступа

Поместив JSP файлы в папку /WEB-INF, вы можете скрыть их от прямого доступа, например, http://example.com/contextname/WEB-INF/page.jsp. Это приведет к 404. Затем вы можете получить к ним доступ только с помощью RequestDispatcher в Servlet или используя jsp:include.


Запрос на предварительный запрос для JSP

Большинство из нас знают о Servlet doPost() в пост -процесс запроса (форма отправки), но большинство из них не знают, что вы можете использовать метод Servlet doGet() для pre -процесса запроса на JSP. Например:

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    List<Item> items = itemDAO.list();
    request.setAttribute("items", items);
    request.getRequestDispatcher("/WEB-INF/page.jsp").forward(request, response);
}

который используется для предварительной загрузки некоторых табличных данных, которые должны отображаться с помощью JSTL c:forEach:

<table>
    <c:forEach items="${items}" var="item">
        <tr><td>${item.id}</td><td>${item.name}</td></tr>
    </c:forEach>
</table>

Настройте такой сервлет на url-pattern из /page (или /page/*) и просто вызовите http://example.com/contextname/page по адресной строке браузера или простой ванильной ссылке для ее запуска. См. Также, например. doGet и doPost в сервлетах.


Динамический включает

Вы можете использовать EL в jsp:include:

<jsp:include page="/WEB-INF/${bean.page}.jsp" />

bean.getPage() может просто вернуть действительное имя файла.


EL может получить доступ к любому получателю

EL не требует, чтобы объект для доступа был полноправным джавабеем. Наличие метода no-arg с префиксом get или is более чем достаточно для доступа к нему в EL. Например:.

${bean['class'].name}

Это возвращает значение bean.getClass().getName(), где метод getClass() фактически унаследован от Object#getClass(). Обратите внимание, что class указывается с использованием "символа привязки" [] по указанным здесь причинам instanceof для проверки на языке экспрессии EL.

${pageContext.session.id}

Это возвращает значение pageContext.getSession().getId(), которое полезно в a.o. Может ли апплет обмениваться данными с экземпляром сервлета.

${pageContext.request.contextPath}

Это возвращает значение pageContext.getRequest().getContextPath(), которое полезно в a.o. Как использовать относительные пути без включения имени корневого контекста?


EL также может получить доступ к Картам

Следующее обозначение EL

${bean.map.foo}

разрешается bean.getMap().get("foo"). Если ключ Map содержит точку, вы можете использовать "обозначение скобок" [] с помощью ключевого слова:

${bean.map['foo.bar']}

который разрешается до bean.getMap().get("foo.bar"). Если вы хотите использовать динамический ключ, используйте также условные обозначения, но затем не кавычки:

${bean.map[otherbean.key]}

который разрешает bean.getMap().get(otherbean.getKey()).


Итерация по карте с помощью JSTL

Вы можете использовать c:forEach, чтобы выполнить итерацию по Map. Каждая итерация дает Map.Entry, который, в свою очередь, имеет методы getKey() и getValue() (так что вы можете просто получить к нему доступ в EL ${entry.key} и ${entry.value}). Пример:

<c:forEach items="${bean.map}" var="entry">
    Key: ${entry.key}, Value: ${entry.value} <br>
</c:forEach>

См. также, например. Отладка с помощью jstl - как именно?


Получить текущую дату в JSP

Вы можете получить текущую дату с jsp:useBean и отформатировать ее с помощью JSTL fmt:formatDate

<jsp:useBean id="date" class="java.util.Date" />
...
<p>Copyright &copy; <fmt:formatDate value="${date}" pattern="yyyy" /></p>

Это печатает (на данный момент), как показано ниже: "Copyright & copy; 2010".


Удобный URL-адрес

Простым способом иметь дружественный URL-адрес является использование HttpServletRequest#getPathInfo() и JSP, скрытый в /WEB-INF:

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    request.getRequestDispatcher("/WEB-INF" + request.getPathInfo() + ".jsp").forward(request, response);
}

Если вы сопоставляете этот сервлет, например, /pages/*, тогда запрос на http://example.com/contextname/pages/foo/bar будет эффективно отображать /WEB-INF/foo/bar.jsp. Вы можете сделать еще один шаг, разделив pathinfo на / и перенесите первую часть как URL-адрес страницы JSP, а остаток - как "бизнес-действия" (пусть сервлет действует как контролер страницы). См. Также, например. Веб-приложения с шаблонами проектирования.


Повторный ввод пользовательского ввода с помощью ${param}

Неявный EL-объект ${param}, который ссылается на HttpServletRequest#getParameterMap(), может использоваться для повторного отображения пользовательского ввода после отправки формы в JSP

<input type="text" name="foo" value="${param.foo}">

В основном это означает, что request.getParameterMap().get("foo"). См. Также, например. Как сохранить значения полей формы HTML в JSP после отправки формы в Servlet?
Не забудьте предотвратить XSS! См. Следующую главу.


JSTL для предотвращения XSS

Чтобы ваш сайт не был XSS, все, что вам нужно сделать, - это (re) показать пользовательский > данные с использованием JSTL fn:escapeXml или c:out.

<p><input type="text" name="foo" value="${fn:escapeXml(param.foo)}">
<p><c:out value="${bean.userdata}" />

Переменные <table> строки с LoopTagStatus

Атрибут varStatus JSTL c:forEach дает вам LoopTagStatus назад, который, в свою очередь, имеет несколько методов getter (которые можно использовать в EL!). Итак, чтобы проверить четные строки, просто проверьте, если loop.getIndex() % 2 == 0:

<table>
    <c:forEach items="${items}" var="item" varStatus="loop">
        <tr class="${loop.index % 2 == 0 ? 'even' : 'odd'}">...</tr>
    <c:forEach>
</table>

который фактически окажется в

<table>
    <tr class="even">...</tr>
    <tr class="odd">...</tr>
    <tr class="even">...</tr>
    <tr class="odd">...</tr>
    ...
</table>

Используйте CSS, чтобы дать им другой цвет фона.

tr.even { background: #eee; }
tr.odd { background: #ddd; }

Заполнять commasepared строку из List/Array с помощью LoopTagStatus:

Другим полезным LoopTagStatus является isLast():

<c:forEach items="${items}" var="item" varStatus="loop">
    ${item}${!loop.last ? ', ' : ''}
<c:forEach>

Это приводит к чему-то вроде item1, item2, item3.


EL-функции

Вы можете объявить методы утилиты public static как функции EL (например, JSTL функции), чтобы вы могли использовать их в EL. Например.

package com.example;

public final class Functions {
     private Functions() {}

     public static boolean matches(String string, String pattern) {
         return string.matches(pattern);
     }
}

с /WEB-INF/functions.tld, которые выглядят следующим образом:

<?xml version="1.0" encoding="UTF-8" ?>
<taglib 
    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-jsptaglibrary_2_1.xsd"
    version="2.1">

    <tlib-version>1.0</tlib-version>
    <short-name>Custom_Functions</short-name>
    <uri>http://example.com/functions</uri>

    <function>
        <name>matches</name>
        <function-class>com.example.Functions</function-class>
        <function-signature>boolean matches(java.lang.String, java.lang.String)</function-signature>
    </function>
</taglib>

который можно использовать как

<%@taglib uri="http://example.com/functions" prefix="f" %>

<c:if test="${f:matches(bean.value, '^foo.*')}">
    ...
</c:if>

Получить исходный URL-адрес запроса и строку запроса

Если JSP был отправлен, вы можете получить исходный URL-адрес запроса,

${requestScope['javax.servlet.forward.request_uri']} 

и исходную строку запроса запроса,

${requestScope['javax.servlet.forward.query_string']}

Это было так далеко. Может быть, я добавлю еще более рано или поздно.