协慌网

登录 贡献 社区

如何避免 JSP 文件中的 Java 代码?

我是 Java EE 的新手,我知道类似以下三行

<%= x+1 %>
<%= request.getParameter("name") %>
<%! counter++; %>

是一种旧式的编码方式,在 JSP 版本 2 中,存在一种避免 JSP 文件中的 Java 代码的方法。有人可以告诉我替代的 JSP 2 行,以及这种技术的名称是什么?

答案

自从 2001 年诞生taglibs (如JSTL )和EL表达语言 ,那些${}事物)以来,在JSP 中使用scriptlet (那些<% %>事物)确实非常气馁。

scriptlet的主要缺点是:

  1. 可重用性:您无法重用 scriptlet。
  2. 可替换性:您不能使 scriptlet 抽象化。
  3. OO-ability:你不能利用继承 / 组合。
  4. 可调试性:如果 scriptlet 中途抛出一个异常,你得到的只是一个空白页面。
  5. 可测试性: scriptlet 不是可单元测试的。
  6. 可维护性:每个 saldo 需要更多时间来维护混杂 / 混乱 / 重复的代码逻辑。

Sun Oracle 本身也建议在JSP 编码约定中避免在(标记)类可能的情况下使用scriptlet 。以下是几个相关的引用:

从 JSP 1.2 规范中,强烈建议在 Web 应用程序中使用 JSP 标准标记库(JSTL),以帮助减少页面中JSP scriptlet 的需求 。通常,使用 JSTL 的页面更易于阅读和维护。

...

尽可能在标记库提供等效功能时避免使用 JSP scriptlet 。这使页面更易于阅读和维护,有助于将业务逻辑与表示逻辑分离,并使您的页面更容易演变为 JSP 2.0 样式的页面(JSP 2.0 规范支持但不强调使用 scriptlet)。

...

本着采用模型 - 视图 - 控制器(MVC)设计模式以减少表示层与业务逻辑之间的耦合的精神, JSP scriptlet 不应用于编写业务逻辑。相反,如果需要,可以使用 JSP scriptlet 将从处理客户端请求返回的数据(也称为 “值对象”)转换为适当的客户端就绪格式。即使这样,使用前端控制器 servlet 或自定义标签也可以做得更好。


如何完全替换scriptlet取决于代码 / 逻辑的唯一目的。此代码通常被置于一个完整的 Java 类中:

  • If you want to invoke the same Java code on every request, less-or-more regardless of the requested page, e.g. checking if an user is logged in, then implement a filter and write code accordingly in doFilter() method. E.g.:

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
        if (((HttpServletRequest) request).getSession().getAttribute("user") == null) {
            ((HttpServletResponse) response).sendRedirect("login"); // Not logged in, redirect to login page.
        } else {
            chain.doFilter(request, response); // Logged in, just continue request.
        }
    }

    When mapped on an appropriate <url-pattern> covering the JSP pages of interest, then you don't need to copypaste the same piece of code over all JSP pages.


  • If you want to invoke some Java code to preprocess a request, e.g. preloading some list from a database to display in some table, if necessary based on some query parameters, then implement a servlet and write code accordingly in doGet() method. E.g.:

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        try {
            List<Product> products = productService.list(); // Obtain all products.
            request.setAttribute("products", products); // Store products in request scope.
            request.getRequestDispatcher("/WEB-INF/products.jsp").forward(request, response); // Forward to JSP page to display them in a HTML table.
        } catch (SQLException e) {
            throw new ServletException("Retrieving products failed!", e);
        }
    }

    This way dealing with exceptions is easier. The DB is not accessed in the midst of JSP rendering, but far before the JSP is been displayed. You still have the possibility to change the response whenever the DB access throws an exception. In the above example, the default error 500 page will be displayed which you can anyway customize by an <error-page> in web.xml.


  • If you want to invoke some Java code to postprocess a request, e.g. processing a form submit, then implement a servlet and write code accordingly in doPost() method. E.g.:

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        User user = userService.find(username, password);
    
        if (user != null) {
            request.getSession().setAttribute("user", user); // Login user.
            response.sendRedirect("home"); // Redirect to home page.
        } else {
            request.setAttribute("message", "Unknown username/password. Please retry."); // Store error message in request scope.
            request.getRequestDispatcher("/WEB-INF/login.jsp").forward(request, response); // Forward to JSP page to redisplay login form with error.
        }
    }

    This way dealing with different result page destinations is easier: redisplaying the form with validation errors in case of an error (in this particular example you can redisplay it using ${message} in EL), or just taking to the desired target page in case of success.


  • If you want to invoke some Java code to control the execution plan and/or the destination of the request and the response, then implement a servlet according the MVC's Front Controller Pattern. E.g.:

    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        try {
            Action action = ActionFactory.getAction(request);
            String view = action.execute(request, response);
    
            if (view.equals(request.getPathInfo().substring(1)) {
                request.getRequestDispatcher("/WEB-INF/" + view + ".jsp").forward(request, response);
            } else {
                response.sendRedirect(view);
            }
        } catch (Exception e) {
            throw new ServletException("Executing action failed.", e);
        }
    }

    Or just adopt a MVC framework like JSF, Spring MVC, Wicket, etc so that you end up with just a JSP/Facelets page and a Javabean class without the need for a custom servlet.


  • If you want to invoke some Java code to control the flow inside a JSP page, then you need to grab an (existing) flow control taglib like JSTL core. E.g. displaying List<Product> in a table:

    <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
    ...
    <table>
        <c:forEach items="${products}" var="product">
            <tr>
                <td>${product.name}</td>
                <td>${product.description}</td>
                <td>${product.price}</td>
            </tr>
        </c:forEach>
    </table>

    With XML-style tags which fits nicely among all that HTML, the code is better readable (and thus better maintainable) than a bunch of scriptlets with various opening and closing braces ("Where the heck does this closing brace belong to?"). An easy aid is to configure your web application to throw an exception whenever scriptlets are still been used by adding the following piece to web.xml:

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

    In Facelets, the successor of JSP, which is part of the Java EE provided MVC framework JSF, it is already not possible to use scriptlets. This way you're automatically forced to do things"the right way".


  • If you want to invoke some Java code to access and display "backend" data inside a JSP page, then you need to use EL (Expression Language), those ${} things. E.g. redisplaying submitted input values:

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

    The ${param.foo} displays the outcome of request.getParameter("foo").


  • If you want to invoke some utility Java code directly in the JSP page (typically public static methods), then you need to define them as EL functions. There's a standard functions taglib in JSTL, but you can also easily create functions yourself. Here's an example how JSTL fn:escapeXml is useful to prevent XSS attacks.

    <%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
    ...
    <input type="text" name="foo" value="${fn:escapeXml(param.foo)}" />

    Note that the XSS sensitivity is in no way specifically related to Java/JSP/JSTL/EL/whatever, this problem needs to be taken into account in every webapplication you develop. The problem of scriptlets is that it provides no way of builtin preventions, at least not using the standard Java API. JSP's successor Facelets has already implicit HTML escaping, so you don't need to worry about XSS holes in Facelets.

See also:

作为安全措施:禁用 Scriptlets for Good

正如另一个问题所讨论的那样,您可以并且始终应该在web.xml Web 应用程序描述符中禁用 scriptlet。

我总是这样做是为了防止任何开发人员添加 scriptlet,特别是在大型公司中,您迟早会丢失概述。 web.xml设置如下所示:

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

JSTL为条件,循环,集合,获取等提供标签。例如:

<c:if test="${someAttribute == 'something'}">
   ...
</c:if>

JSTL 使用请求属性 - 它们通常由 Servlet 设置在请求中,Servlet 转发到 JSP。