JSP/Servlet的隐藏特性

78

我对你在编写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个回答

155

注意:我认为很难想到JSP / Servlet的任何“隐藏功能”。在我看来,“最佳实践”是更好的措辞,我可以想到其中任何一个。这确实取决于您对JSP / Servlet的经验。经过多年的开发,您不再看到那些“隐藏功能”。无论如何,我将列出一些小的“最佳实践”,其中我在多年中发现许多初学者并不完全了解。在许多初学者眼中,它们将被归类为“隐藏功能”。无论如何,以下是列表 :)


隐藏 JSP 页面,防止直接访问

将 JSP 文件放置在 /WEB-INF 文件夹中,可以有效地防止它们被直接访问,例如通过 http://example.com/contextname/WEB-INF/page.jsp。这将导致返回 404 错误。您只能通过 Servlet 中的 RequestDispatcher 或使用 jsp:include 来访问它们。


预处理 JSP 请求

大多数人知道 Servlet 的 doPost() 方法用于 处理请求(例如表单提交),但是很少有人知道你可以使用 Servlet 的 doGet() 方法来 处理 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>

将这样的servlet映射到/page(或/page/*)的url-pattern上,只需通过浏览器地址栏或普通链接调用http://example.com/contextname/page即可运行它。另请参见例如Servlet中的doGet和doPost

动态包含

您可以在jsp:include中使用EL:

<jsp:include page="/WEB-INF/${bean.page}.jsp" />
< p> bean.getPage() 可以返回一个有效的页面名称。


EL可以访问任何getter

EL本身并不要求被访问的对象是一个完整的JavaBean。只要存在一个以getis为前缀的无参方法,就足以在EL中访问它。例如:

${bean['class'].name}

这将返回bean.getClass().getName()的值,其中getClass()方法实际上是从Object#getClass()继承的。请注意,使用"括号表示法"[]指定class是出于instanceof check in EL expression language中提到的原因。
${pageContext.session.id}

这会返回pageContext.getSession().getId()的值,该值在一个小程序能否与servlet实例进行通信等方面非常有用。

${pageContext.request.contextPath}

这将返回pageContext.getRequest().getContextPath()的值,该值在不包括上下文根名称的情况下使用相对路径是非常有用的,例如如何在不包括上下文根名称的情况下使用相对路径?


EL同样可以访问Maps

以下是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迭代Map

您也可以使用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>

这将打印如下内容:"版权 © 2010"。


易于理解的友好URL

实现友好的URL的简单方法是利用HttpServletRequest#getPathInfo()和位于/WEB-INF中的JSP文件:

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

如果您将此servlet映射到/pages/*,那么对于http://example.com/contextname/pages/foo/bar的请求将有效地显示/WEB-INF/foo/bar.jsp。您可以通过在/上拆分pathinfo并仅获取第一部分作为JSP页面URL和剩余部分作为“业务操作”(让Servlet充当页面控制器)来更进一步。请参见例如基于Web的应用程序设计模式

使用${param}重新显示用户输入

在JSP中,可以使用隐式EL对象${param}来引用HttpServletRequest#getParameterMap(),以便在表单提交后重新显示用户输入:

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

这基本上与request.getParameterMap().get("foo")相同。例如,还可以参见如何在将表单提交到Servlet后在JSP中保留HTML表单字段值?
不要忘记防止XSS!请参见下一章节。


JSTL防止XSS

为了防止您的网站受到跨站脚本攻击(XSS),您只需要使用JSTL fn:escapeXmlc:out重新显示用户可控数据即可。

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

使用LoopTagStatus交替显示<table>

JSTL中的c:forEach标签的varStatus属性会返回一个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; }

使用LoopTagStatus从List/Array中填充逗号分隔的字符串:

另一个有用的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']}

到此为止。也许我以后会再添加一些。


20
这是Stackoverflow上最详尽的帖子之一。没有什么诡计,只有值得一试的好知识和常见做法。对于交替表格行,更好的做法是使用现代CSS语法,并使用tr:nth-child(even)进行着色,这会让你的HTML输出更加干净。 - Photodeus
6
永远完美,BalusC先生 :) - Muhammad Hewedy
1
哇,非常信息量大的答案和伟大的通用实践,非常感谢 BlausC。 - palAlaa
1
在 BalusC 的作品中,这是我继 JDK 中的设计模式之后的第二个最喜欢的。 - zawhtut
LoopTagStatus +1 - 美妙! - Wojciech Owczarczyk
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接