如何避免在JSP页面中使用scriptlet?

35

有人告诉我,在我的JSP页面中使用脚本片段(<%= ... %>)并不是一个好主意。

能否请具有更多java/jsp经验的人指点一下,如何修改这段代码使其更符合“最佳实践”,无论那是什么?

实际上,这个JSP是我的sitemesh主要装饰器页面。基本上,我的Web设计有一个选项卡条和一个子菜单,我希望通过查看当前请求URI来突出显示当前标签并显示正确的子菜单。

<%@ taglib uri="http://www.opensymphony.com/sitemesh/decorator" prefix="decorator" %>

<html>
<head>
  <title>My Events - <decorator:title /></title>
  <link href="<%= request.getContextPath() %>/assets/styles.css" rel="stylesheet" type="text/css" />
</head>
<body>

<div class="tabs">
  <a 
    <%= request.getRequestURI().contains("/events/") ? "class='selected'" : "" %>
    href='<%= request.getContextPath() %>/events/Listing.action'>Events</a>
  <a 
    <%= request.getRequestURI().contains("/people/") ? "class='selected'" : "" %>
    href='<%= request.getContextPath() %>/people/Listing.action'>People</a>
</div>

<div class="submenu">
  <% if(request.getRequestURI().contains("/events/")) { %>
    <a href="Listing.action">List of Events</a>
    |<a href="New.action">New Event</a>
  <% } %>
  <% if(request.getRequestURI().contains("/people/")) { %>
    <a href="Listing.action">List of People</a>
    |<a href="New.action">New Person</a>
  <% } %>  
  &nbsp;
</div>

<div class="body">
  <decorator:body />
</div>

</body>
</html>

谢谢大家


顺便问一下,'<%= request.getContextPath() %>' 这种使用脚本片段的方式是否被广泛接受,不会引起太多反感呢? - Chris
你应该使用Facelets来进行模板化。它可以强制你编写正确的代码。 - Andrew Dyster
你是说要使用Facelets而不是Sitemesh吗? - Chris
3
可能是重复的问题,参考链接:https://dev59.com/13A75IYBdhLWcg3wqK10 - Bozho
7个回答

42

我认为,如果你亲眼看到它可以完全没有脚本片段就能实现,这会更有帮助。

以下是通过使用JSTL等工具(只需将jstl-1.2.jar放入/WEB-INF/lib)进行的一对一重写。使用了corefunctions taglib:

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>

<html>
<head>
  <title>My Events - <decorator:title /></title>
  <link href="${pageContext.request.contextPath}/assets/styles.css" rel="stylesheet" type="text/css" />
</head>
<body>

<div class="tabs">
  <a 
    ${fn:contains(pageContext.request.requestURI, '/events/') ? 'class="selected"' : ''}
    href="${pageContext.request.contextPath}/events/Listing.action">Events</a>
  <a 
    ${fn:contains(pageContext.request.requestURI, '/people/') ? 'class="selected"' : ''}
    href="${pageContext.request.contextPath}/people/Listing.action">People</a>
</div>

<div class="submenu">
  <c:if test="${fn:contains(pageContext.request.requestURI, '/events/')}">
    <a href="Listing.action">List of Events</a>
    |<a href="New.action">New Event</a>
  </c:if>
  <c:if test="${fn:contains(pageContext.request.requestURI, '/people/')}">
    <a href="Listing.action">List of People</a>
    |<a href="New.action">New Person</a>
  </c:if>
  &nbsp;
</div>

这里是一个更加优化的重写版本,注意我使用了c:set来“缓存”表达式结果以便重复使用,并且我使用HTML <base>标签来避免在每个链接中都加入上下文路径(只需将网页中所有相对URL与其相关联即可-不含前导斜杠!):

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>

<c:set var="isEvents" value="${fn:contains(pageContext.request.requestURI, '/events/')}" />
<c:set var="isPeople" value="${fn:contains(pageContext.request.requestURI, '/people/')}" />

<html>
<head>
  <title>My Events - <decorator:title /></title>
  <base href="${pageContext.request.contextPath}">
  <link href="assets/styles.css" rel="stylesheet" type="text/css" />
</head>
<body>

<div class="tabs">
  <a ${isEvents ? 'class="selected"' : ''} href="events/Listing.action">Events</a>
  <a ${isPeople ? 'class="selected"' : ''} href="people/Listing.action">People</a>
</div>

<div class="submenu">
  <c:if test="${isEvents}">
    <a href="Listing.action">List of Events</a>|<a href="New.action">New Event</a>
  </c:if>
  <c:if test="${isPeople}">
    <a href="Listing.action">List of People</a>|<a href="New.action">New Person</a>
  </c:if>
  &nbsp;
</div>

如果你将所有那些像 eventspeople 这样的“硬编码”值,以及链接文本都收集到应用程序范围内的一个Map中,并在每个值下使用 JSTL 的 <c:forEach>来显示选项卡,它实际上可以进行更多优化。

至于你的实际问题,你可以通过在 web 应用程序的 web.xml 中添加以下条目来禁用脚本(并得到有关使用它的运行时错误)。这可能有助于发现被忽略的脚本。

<jsp-config>
    <jsp-property-group>
        <url-pattern>*.jsp</url-pattern>
        <scripting-invalid>true</scripting-invalid>
    </jsp-property-group>
</jsp-config>

想要了解更多关于EL的内容,请查看Java EE教程第二部分第5章。隐式EL对象,例如${pageContext}这里描述。如果您想要了解更多关于JSTL的内容,请查看Java EE教程第二部分第7章。请注意,JSTL和EL是两个不同的东西。JSTL是一个标准的标签库,而EL仅仅是使得程序能够访问后端数据。虽然它通常用于像JSTL这样的标签库中,但它也可以单独用于模板文本。


5
如果你比较“之前”和“之后”的模板,你会发现它们基本上是一样的。除了看起来更整洁外,我不认为后者自动比前者更好。 - Ree
3
@Ree - 说实话,我认为JSTL最大的优点是它不让你在JSP中写任何想到的代码。JSTL提供了大部分你需要编写模板所需的工具,而不是控制器、业务逻辑管理器或数据访问对象。 - RustyTheBoyRobot

10

顺便提一下,<%= request.getContextPath() %> 是使用脚本片段的可接受用法吗,是否会被严格排斥?

这可能是不受欢迎的观点,但如果你只是进行简单的条件判断和文本插入,我认为使用脚本片段并没有太大问题。(注意:如果)

我可能会使用 JSTL 和表达式语言,主要是因为它可以减少输入,而且 IDE 支持可能更好(但一个好的 JSP IDE 也可以找到缺少的闭合括号等问题)。

但从根本上说(即“将逻辑保持在模板之外”),我没有看到任何区别。

<% if(request.getRequestURI().contains("/events/")) { %>

以及

${fn:contains(pageContext.request.requestURI, '/events/') 

3
好的,太棒了!我原以为只有我持有这个观点! - Chris
我认为这里的背景是最佳实践,当你不是唯一开发或维护模板时,它更加重要。尽管在您的示例中,在功能上没有区别,但您已经设置了使用脚本的先例,而不知道更好的人可能会插入一些改变模型状态的任意Java代码。这只是需要考虑的事情。我喜欢禁用它们,就像BalusC上面提到的那样。 - Cole Stanfield

7

脚本片段并非世界上最糟糕的东西。重要的考虑因素是要考虑谁将维护代码。如果是没有太多Java经验的Web设计师,最好使用标签库。然而,如果是Java开发人员进行维护,使用脚本片段可能对他们更容易。

如果您最终使用标签库和JSTL,则需要维护者也学习标签库并了解JSTL。有些开发人员会接受这一点,因为这是他们想要或已经拥有的技能,但对于一些只需每几个月处理一次JSP的开发人员来说,使用清晰编写的脚本片段可能更加轻松,这些脚本片段使用熟悉的Java语言编写。


6

这并不是对你问题的直接回答(已经有几个好的回答了,所以我不会试图添加),但是你提到了:

有更多java/jsp经验的人可以给我一些指导如何更改此代码,使其更符合最佳实践,无论那是什么?

在我看来,关于JSP的最佳实践是严格将其用作模板引擎,而不再使用它(即,在其中没有业务逻辑)。正如许多人指出的那样,使用JSTL肯定有助于您实现这一点,但即使使用JSTL,也很容易在JSP中做太多事情。

我个人喜欢在开发JSP时遵循Terence Parr在Enforcing Strict Model-View Separation in Templating Engines中提出的规则。该论文提到了模板引擎的目的(分离模型和视图)以及良好的模板引擎的特征。它仔细研究了JSP,并指出了它不是一个好的模板引擎的方法。毫不奇怪,JSP基本上太强大了,允许开发人员做太多事情。我强烈推荐阅读这篇论文,它将帮助您将自己限制在JSP的“好”部分。

如果您只阅读了该论文中的一个部分,请阅读第7章,其中包括以下规则:

  1. 视图不能通过直接更改模型数据对象或调用会导致副作用的模型方法来修改模型。 也就是说,模板可以访问模型中的数据并调用方法,但这些引用必须是无副作用的。这个规则部分地产生是因为数据引用必须是无序的。请参见第7.1节。
  2. 视图不能对依赖数据值进行计算,因为这些计算可能会在未来发生变化,并且它们应该被清晰地封装在模型中。例如,视图不能将书籍销售价格计算为“$price*.90”。为了独立于模型,视图不能对数据的含义进行假设。
  3. 视图不能比较依赖数据值,但可以测试数据的属性,例如存在/不存在或多值数据值的长度。像 $bloodPressure<120 这样的测试必须移动到模型中,因为医生喜欢不断降低我们的最大收缩压。视图中的表达式必须替换为测试值的存在,模拟布尔值,例如 $bloodPressureOk!=null 模板输出可以根据模型数据和计算条件,条件只需在模型中计算。即使是简单的测试,如使负数变红色,也应在模型中计算;正确的抽象级别通常是更高层次的,例如“部门 X 正在亏损”。
  4. 视图不能进行数据类型假设。 当视图假定数据值为日期时,某些类型假设是显而易见的,但更微妙的类型假设也会出现:如果模板假定 $userID 是一个整数,那么程序员无法在模型中将此值更改为非数字而不破坏模板。这个规则禁止数组索引,如 colorCode[$topic] 和 $name[$ID]。视图还不能调用带有参数的方法,因为(静态或动态地)存在假定的参数类型,除非可以保证模型方法仅将它们视为对象处理。此外,平面设计师不是程序员;期望他们调用方法并知道要传递什么是不现实的。
  5. 来自模型的数据不得包含显示或布局信息。 模型不能将任何显示信息伪装成数据值传递给视图。这包括不传递应用于其他数据值的模板名称。

顺带提一下,Terence创建了自己的模板引擎,名字叫做String Template,据说能很好地执行这些规则。虽然我没亲身体验过,但是很想在下一个项目中试试看。


3
你可以开始使用标签库。可以使用标准标签库JSTL 来完成大部分需要使用脚本语言的常见任务。还有许多其他更丰富的标签库,如在 struts2 框架或来自 Apache 中使用的标签库。
例如:
  <c:if test="${your condition}">
       Your Content
  </c:if>

将替换您的if语句。


3
这里有一个很好的概述。您需要像这样添加标签库:
<%@ taglib uri='http://java.sun.com/jsp/jstl/core' prefix='c' %>

作为一个例子,JSTL提供了一堆隐式对象,为你提供所需的东西;你想要的是pageContext.request
所以你可以用${pageContext.request.requestURI}替换<%request.getRequestURI%>
你可以使用<c:if>标签进行条件判断。

JSTL不等于表达式语言。JSTL是一个标准的标签库,网址为http://java.sun.com/products/jsp/jstl/1.1/docs/tlddocs/。而表达式语言则是那些`${}`符号,网址为http://java.sun.com/javaee/5/docs/tutorial/doc/bnahq.html。 - BalusC
1
供日后参考:在我的标签库URI中,我使用'http://java.sun.com/jsp/jstl/core'比使用'http://java.sun.com/jstl/core'更加成功。 - Chris
“/jsp/-less” URI确实是十年前JSTL 1.0的一部分。不要使用它。 - BalusC

2

您需要使用一些web框架。或者至少使用一些方便的标签库。或者像FreeMarker这样的模板引擎。

广告框架:

如果您喜欢JSP方式编码,那么我建议使用Struts 2

<s:if test="%{false}">
    <div>Will Not Be Executed</div>
</s:if>
<s:elseif test="%{true}">
    <div>Will Be Executed</div>
</s:elseif>
<s:else>
    <div>Will Not Be Executed</div>
</s:else>

接下来是面向组件的JSF

如果您喜欢面向对象编程并且想使用Java编写所有内容,请尝试Apache Wicket(我最喜欢的)或Google Web Toolkit


我正在使用Struts2,如果这有帮助的话。 - Chris
现在将其与Vincent建议的JSTL标签库结合使用,您就可以摆脱脚本了。 请参见此处:http://struts.apache.org/2.1.8.1/docs/tag-reference.html,在那里您会找到,除其他外,http://struts.apache.org/2.1.8.1/docs/text.html。 - Ondra Žižka

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