如何在使用 JSP 2 版本时避免在 JSP 文件中编写 Java 代码?
如何在使用 JSP 2 版本时避免在 JSP 文件中编写 Java 代码?
我知道像以下三行这样的
<%= x+1 %> <%= request.getParameter("name") %> <%! counter++; %>
是旧方法编写代码的方式,在 JSP 版本 2 中有一种方法可以避免在 JSP 文件中使用 Java 代码。那么 JSP 2 的替代代码是什么,这种技术叫什么?
作为保障措施:永久禁用Scriptlets
正如另一个问题正在讨论的,您可以并且应该在web.xml
web应用程序描述符中禁用scriptlets。
我总是这样做,以防止任何开发人员添加Scriptlets,特别是在较大的公司中,您迟早会失去概述。 web.xml
的设置如下:
*.jsp true
自从2001年标签库(如JSTL)和EL(表达式语言,${})的诞生以来,确实强烈反对在JSP中使用脚本(那些<% %>)。
脚本的主要缺点是:
- 重用性:无法重用脚本。
- 可替代性:无法使脚本抽象。
- 面向对象:无法使用继承/组合。
- 调试能力:如果脚本在中途抛出异常,则得到的是空白页面。
- 可测试性:无法在单元测试中测试脚本。
- 可维护性:相对而言需要更多的时间来维护混杂/杂乱/重复的代码逻辑。
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 { Listproducts = 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框架,如JSF、Spring MVC、Wicket等,最终只需一个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漏洞。