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


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

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

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


1526
2017-07-05 07:24


起源


我不认为这在jsp文件中有效:<%!计数器++; %> - Koray Tugay
@Koray Tugay,只要计数器变量在使用之前在某处声明,那么它肯定是有效的...... - Sheldon R.
@SheldonR。这是有效的:<%= counter ++%>或者:<%! int counter = 0; int x = counter ++; %>但不是:<%! int counter = 0;计数器++; %> - Koray Tugay
@KorayTugay,我的意思是如果变量计数器在早期的脚本块中声明,它应该在稍后的块中有效。但最终,J2EE程序员最近应该使用EL变量而不是scriptlet,无论如何...... - Sheldon R.


答案:


指某东西的用途 小脚本 (那些 <% %> 事情) JSP 自从诞生以来,确实非常沮丧 标签库 (喜欢 JSTL)和 EL (表达语言那些 ${} 早在2001年。

的主要缺点 小脚本 是:

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

太阳 Oracle本身也建议在 JSP编码约定 避免使用 小脚本 每当(标签)类可以实现相同的功能时。以下是几个相关的引用:

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

...

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

...

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


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

  • 如果你想调用 相同 Java代码 一切 请求,少于或多或少于所请求的页面,例如检查用户是否已登录,然后执行 过滤 并相应地编写代码 doFilter() 方法。例如。:

    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.
        }
    }
    

    映射到适当的时候 <url-pattern>覆盖感兴趣的JSP页面,然后您不需要在所有JSP页面上复制相同的代码段。


  • 如果要调用某些Java代码 预处理 请求,例如从数据库预加载一些列表以显示在某些表中,如果有必要根据一些查询参数,然后实现一个 的servlet 并相应地编写代码 doGet() 方法。例如。:

    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);
        }
    }
    

    这种处理异常的方式更容易。在JSP渲染过程中不会访问DB,但是在JSP显示之前。每当数据库访问引发异常时,您仍然可以更改响应。在上面的示例中,将显示默认错误500页面,您可以通过a自定义 <error-page> 在 web.xml


  • 如果要调用某些Java代码 后期处理 请求,例如处理表单提交,然后实现一个 的servlet 并相应地编写代码 doPost() 方法。例如。:

    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.
        }
    }
    

    这样处理不同的结果页面目标更容易:在出现错误时重新显示带有验证错误的表单(在此特定示例中,您可以使用重新显示它 ${message} 在 EL),或者只是在成功的情况下进入所需的目标页面。


  • 如果要调用某些Java代码 控制 执行计划和/或请求的目的地和响应,然后实现一个 的servlet 根据 MVC的前端控制器模式。例如。:

    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);
        }
    }
    

    或者只是采用像MVC这样的框架 JSFSpring MVC便门等等,这样您最终只需要一个JSP / Facelets页面和一个Javabean类,而无需自定义servlet。


  • 如果要调用某些Java代码 控制流量 在JSP页面内,然后你需要获取(现有的)流控制taglib之类的 JSTL核心。例如。显示 List<Product> 在表中:

    <%@ 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>
    

    使用XML样式的标签非常适合所有HTML,代码比具有各种打开和关闭括号的一堆scriptlet更易读(因此更易于维护)(“这个闭合支架属于什么?”)。一个简单的帮助是配置您的Web应用程序,以便随时抛出异常 小脚本 仍然被添加以下作品使用 web.xml

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

    Facelets的,JSP的后继者,它是Java EE提供的MVC框架的一部分 JSF,它已经是  可以使用 小脚本。这样你就会被迫以“正确的方式”做事。


  • 如果要调用某些Java代码 访问和显示 JSP页面中的“后端”数据,那么你需要使用EL(表达式语言),那些 ${} 的东西。例如。重新显示提交的输入值:

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

    ${param.foo} 显示结果 request.getParameter("foo")


  • 如果你想调用一些 效用 Java代码直接在JSP页面中(通常是 public static 方法),然后你需要将它们定义为EL函数。有一个标准 函数taglib 在JSTL,但是 您也可以自己轻松创建功能。这是JSTL的一个例子 fn:escapeXml 有用的预防 XSS  攻击

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

    请注意,XSS灵敏度绝不与Java / JSP / JSTL / EL /无关,这个问题需要考虑在内 一切你开发的webapplication。的问题 小脚本 是它没有提供内置预防的方法,至少不使用标准的Java API。 JSP的后继者Facelets已经隐式HTML转义,因此您不必担心Facelets中的XSS漏洞。

也可以看看:


1853
2017-07-05 14:19



+1很棒的答案。但是不要去教条,有时候使用scriptlet是可以的,但这应该是证明规则的例外。 - svachon
@svachon:Scriptlet对于快速原型设计/测试非常有用。据我所知,只有一个“合法”的生产用途,即 <% response.getWriter().flush(); %> 在。。之间 </head> 和 <body> 改进webbrowser中的网页解析性能。但是,当服务器端的输出缓冲区大小较低(1~2KB)时,这种使用又完全可以忽略不计。 另见本文。 - BalusC
@BalusC有几次我一直坚持使用不遵循getter / setter模式的java类。恕我直言,这是一个scripltet完成工作的情况。 - svachon
@svachon:我用自己的javabean类包装这些类,然后使用它们。 - BalusC
这是一个非常好的答案,但doGet和doPost部分是误导性的。这些方法用于处理特定请求(HEAD,GET和POST)方法,不用于“预处理”或“后处理”请求! - MetroidFan2002


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

另一个问题 正在讨论,你可以并且总是应该禁用你的scriptlet web.xml Web应用程序描述符

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

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

207
2017-08-14 21:59



这是否也会禁用scriptlet注释?: <%-- comment that i don't want in the final HTML --%> 。我发现使用这些而不是HTML注释很有用。 - Mr Spoon
@MrSpoon找到了你的答案。按照 这个回答+评论,这会关闭scriptlet <% %>,scriptlet表达式 <%! %>和scriptlet声明 <%= %>。这意味着指令 <%@ %> 和评论 <%-- --%> 保持启用和可用,所以你仍然可以做评论和包括。 - Martin Carney
禁用scriptlet指令会很糟糕 - 修剪空白对于与遗留额外空格的遗留系统进行交互是非常宝贵的。 - corsiKa


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

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

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


102
2017-07-05 07:28



为什么说JSTL与请求属性一起使用?他们可以在任何范围内使用属性,不是吗? - Koray Tugay


我不确定我是否正确。

你应该阅读一些关于MVC的内容。 Spring MVC & Struts 2 是两种最常见的解决方案。


56
2017-07-05 07:29



使用上述许多技术,没有Spring或Struts,可以使用servlet / jsp实现MVC。 - stepanian
它如何回答这个问题? - xyz


您可以将JSTL标记与EL表达式一起使用,以避免混合使用Java和HTML代码:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<html>
    <head>
    </head>
    <body>

        <c:out value="${x + 1}" />
        <c:out value="${param.name}" />
    // and so on

    </body>
</html>

48
2017-07-05 08:45





还有基于组件的框架,如 便门 这会为你生成很多HTML。最终在HTML中的标签非常基本,几乎没有混合的逻辑。结果几乎是空的HTML页面,具有典型的HTML元素。缺点是,有很多组件 便门 要学习的API和在这些约束下难以实现的一些事情。


32
2018-03-22 18:24





在MVC架构模式中,JSP表示View层。在JSP中嵌入Java代码被认为是一种不好的做法。 您可以使用 JSTLFreemarker的速度 用JSP作为“模板引擎”。 这些标签的数据提供者 取决于框架 你正在处理。 Struts 2 和 webwork 作为MVC Pattern使用的实现 OGNL “将Beans Properties暴露给JSP的非常有趣的技术”。


28
2018-02-04 10:41





经验表明,JSP有一些缺点,其中一个难以避免将标记与实际代码混合。

如果可以,那么请考虑使用专门的技术来完成您的工作。在Java EE 6中有JSF 2.0,它提供了许多不错的功能,包括通过Java将Java bean与JSF页面粘合在一起 #{bean.method(argument)} 做法。


25
2017-07-05 08:30



老答案,但我无法抗拒地说JSF是Java领域最可怕的发明之一。尝试制作一个(HTTP GET之类)链接,你就会明白为什么。 - Alex
@Alex但仍然更好。随意推荐一些更好的东西。 - Thorbjørn Ravn Andersen


Wicket也是一种完全将java与html分开的替代方案,因此设计人员和程序员可以在不同的代码集上协同工作,彼此之间几乎没有相互理解。

看看Wicket。


23
2018-05-20 20:42





如果您只是想避免JSP中Java编码的缺点,即使使用scriplet也可以这样做。只需遵循一些规则,在JSP中使用最少的Java,在JSP页面中几乎不需要计算和逻辑。

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%//instantiate a JSP controller
MyController clr = new MyController(request, response);

//process action if any
clr.process(request);

//process page forwaring if necessary

//do all variable assignment here
String showMe = clr.getShowMe();%>

<html>
    <head>
    </head>
    <body>
        <form name="frm1">
            <p><%= showMe %>
            <p><% for(String str : clr.listOfStrings()) { %>
            <p><%= str %><% } %>

            // and so on   
        </form>
    </body>
</html>

23
2018-05-11 16:45