如何在使用 JSP 2 版本时避免在 JSP 文件中编写 Java 代码?

23 浏览
0 Comments

如何在使用 JSP 2 版本时避免在 JSP 文件中编写 Java 代码?

我知道像以下三行这样的

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

是旧方法编写代码的方式,在 JSP 版本 2 中有一种方法可以避免在 JSP 文件中使用 Java 代码。那么 JSP 2 的替代代码是什么,这种技术叫什么?

admin 更改状态以发布 2023年5月23日
0
0 Comments

作为保障措施:永久禁用Scriptlets

正如另一个问题正在讨论的,您可以并且应该在web.xml web应用程序描述符中禁用scriptlets。

我总是这样做,以防止任何开发人员添加Scriptlets,特别是在较大的公司中,您迟早会失去概述。 web.xml的设置如下:


  
    *.jsp
     true
  

0
0 Comments

自从2001年标签库(如JSTL)和EL(表达式语言,${})的诞生以来,确实强烈反对在JSP中使用脚本(那些<% %>)。

脚本的主要缺点是:

  1. 重用性:无法重用脚本。
  2. 可替代性:无法使脚本抽象。
  3. 面向对象:无法使用继承/组合。
  4. 调试能力:如果脚本在中途抛出异常,则得到的是空白页面。
  5. 可测试性:无法在单元测试中测试脚本。
  6. 可维护性:相对而言需要更多的时间来维护混杂/杂乱/重复的代码逻辑。

Sun Oracle 还建议在JSP编码规范中避免使用脚本,除非(tag)类提供了相同的功能。下面是几个相关引用:

从JSP 1.2规范开始,强烈建议在您的Web应用程序中使用JSP Standard Tag Library(JSTL)来帮助减少您的页面中的JSP脚本需求。使用JSTL的页面通常更易于阅读和维护。

...

尽可能地,在标记库提供同等功能的情况下,避免使用JSP脚本。这使得页面更易于阅读和维护,有助于将业务逻辑与表示逻辑分离,将使您的页面更容易演变成JSP 2.0样式的页面(JSP 2.0规范支持但弱化了脚本的使用)。

...

为了采用模型-视图-控制器(MVC)设计模式来减少表示层和业务逻辑之间的耦合,不应该使用JSP脚本编写业务逻辑。相反,如果必要转换从处理客户端请求返回的数据(也称为“值对象”),则可以使用JSP脚本。即使在这种情况下,最好还是使用前端控制器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.
      }
  }

当适当的被映射到感兴趣的JSP页面时,您就不需要在所有的JSP页面中复制粘贴相同的代码片段。


  • 如果您想要调用一些Java代码来处理GET请求,例如预加载一些从数据库中获取的列表以在某些表格中显示,如果必要的话,可以基于一些查询参数,那么就要实现一个servlet并相应地在doGet()方法中编写代码。例如:

      protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
          try {
              List 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之前的很远的地方进行。当DB访问抛出异常时,您仍然可以更改响应。在上面的示例中,将显示默认的错误500页面,您可以使用web.xml中的自定义它。


  • 如果您想要调用一些Java代码来处理POST请求,例如从提交的HTML表单中收集数据并对其进行一些业务操作(转换、验证、保存在DB中等),那么就要实现一个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.
          }
      }
    

    这样处理不同的结果页面目标会更容易:在发生错误时将带有验证错误的表单重新显示(在这个特定的示例中,您可以使用EL中的${message}重新显示它),或者在成功的情况下直接转到所需的目标页面。


  • 如果您想要调用一些Java代码来控制请求和响应的执行计划和/或目标,那么就要按照MVC的前端控制器模式实现一个servlet。例如:

      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 MVCWicket等,最终只需一个JSP/Facelets页面和一个JavaBean类,而不需要自定义servlet。

  • 如果您想在JSP页面内调用一些Java代码以控制流程,则需要获取现有的控制流程标签库,例如JSTL core。例如,在表格中显示 List

      <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
      ...
      
              
    ${product.name} ${product.description} ${product.price}

    使用适合HTML的XML样式标签,代码更易于阅读(因此更易于维护),而不是一堆带有各种开放和关闭大括号的脚本(“这个闭合括号属于哪里?”)。一种简单的办法是将您的Web应用程序配置为在仍在使用脚本时抛出异常,方法是将以下内容添加到web.xml中:

      
          
              *.jsp
              true
          
      
    

    Facelets,也就是JSP的继承者,它是Java EE提供的MVC框架JSF的一部分,已经无法使用脚本。这样,您就会自动被强制执行“正确的方法”。


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

      
    

    $ {param.foo} 显示request.getParameter(“foo”)的结果。


  • 如果您想在JSP页面中直接调用一些实用Java代码(通常是 public static 方法),则需要将它们定义为EL函数。在JSTL中有一个标准的函数标签库,但是您也可以轻松地创建自己的函数。下面是一个示例,说明JSTL fn:escapeXml 如何有用以防止XSS攻击

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

    请注意,XSS敏感性与Java / JSP / JSTL / EL /任何其他方式无关,这个问题需要在每个您开发的Web应用程序中考虑到。脚本的问题在于它没有内置的预防方法,至少不使用标准Java API。 JSP的继承者Facelets已经具有隐式的HTML转义,因此您不需要担心Facelets中的XSS漏洞。

  • 另请参见:

    0