使用JSP 2避免JSP文件中的Java代码
技术背景
在早期的JSP开发中,使用脚本片段(<% %>
、<%! %>
、<%= %>
)嵌入Java代码是常见做法,但这种方式存在诸多弊端。随着JSP 2的出现,标签库(如JSTL)和表达式语言(EL)等技术逐渐成熟,使得避免在JSP文件中直接使用Java代码成为可能。
脚本片段的缺点
- 可复用性差:脚本片段无法复用。
- 可替换性差:无法将脚本片段抽象化。
- 面向对象能力弱:难以利用继承和组合。
- 调试困难:若脚本片段中途抛出异常,页面将显示空白。
- 可测试性差:脚本片段无法进行单元测试。
- 维护成本高:混合、杂乱和重复的代码逻辑需要更多时间维护。
实现步骤
1. 定义过滤器处理通用逻辑
若要在每个请求中调用相同的Java代码(如检查用户是否登录),可实现一个过滤器,并在doFilter()
方法中编写相应代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException;
public class LoginFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException { if (((HttpServletRequest) request).getSession().getAttribute("user") == null) { ((HttpServletResponse) response).sendRedirect("login"); } else { chain.doFilter(request, response); } } }
|
在web.xml
中配置过滤器:
1 2 3 4 5 6 7 8
| <filter> <filter-name>LoginFilter</filter-name> <filter-class>com.example.LoginFilter</filter-class> </filter> <filter-mapping> <filter-name>LoginFilter</filter-name> <url-pattern>*.jsp</url-pattern> </filter-mapping>
|
2. 使用Servlet处理GET请求
若要调用Java代码处理GET请求(如从数据库预加载列表以在表格中显示),可实现一个Servlet,并在doGet()
方法中编写相应代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.sql.SQLException; import java.util.List;
@WebServlet("/products") public class ProductServlet extends HttpServlet { private ProductService productService = new ProductService();
@Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { try { List<Product> products = productService.list(); request.setAttribute("products", products); request.getRequestDispatcher("/WEB-INF/products.jsp").forward(request, response); } catch (SQLException e) { throw new ServletException("Retrieving products failed!", e); } } }
|
3. 使用Servlet处理POST请求
若要调用Java代码处理POST请求(如收集表单数据并进行业务处理),可实现一个Servlet,并在doPost()
方法中编写相应代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException;
@WebServlet("/login") public class LoginServlet extends HttpServlet { private UserService userService = new UserService();
@Override 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); response.sendRedirect("home"); } else { request.setAttribute("message", "Unknown username/password. Please retry."); request.getRequestDispatcher("/WEB-INF/login.jsp").forward(request, response); } } }
|
4. 使用MVC的前端控制器模式
若要调用Java代码控制请求和响应的执行计划和目标,可根据MVC的前端控制器模式实现一个Servlet。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException;
public class FrontControllerServlet extends HttpServlet { @Override 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); } } }
|
5. 使用JSTL控制JSP页面流程
若要调用Java代码控制JSP页面内的流程,可使用JSTL核心标签库。
1 2 3 4 5 6 7 8 9 10
| <%@ 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>
|
6. 使用EL表达式访问和显示数据
若要调用Java代码访问和显示JSP页面内的“后端”数据,可使用EL表达式。
1
| <input type="text" name="foo" value="${param.foo}" />
|
7. 使用EL函数调用实用Java代码
若要在JSP页面中直接调用实用Java代码(通常是公共静态方法),可将其定义为EL函数。
1 2
| <%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> <input type="text" name="foo" value="${fn:escapeXml(param.foo)}" />
|
最佳实践
- 遵循MVC模式:将业务逻辑与表示逻辑分离,JSP作为视图层,专注于页面展示。
- 使用标签库和EL表达式:尽量使用JSTL标签库和EL表达式替代脚本片段。
- 禁用脚本片段:在
web.xml
中添加以下配置,防止开发者使用脚本片段。
1 2 3 4 5 6
| <jsp-config> <jsp-property-group> <url-pattern>*.jsp</url-pattern> <scripting-invalid>true</scripting-invalid> </jsp-property-group> </jsp-config>
|
常见问题
1. 如何处理不遵循getter/setter模式的Java类?
可将这些类包装在自己的JavaBean类中并使用。
2. 使用JSP替代技术有哪些缺点?
- 学习成本:如JSF、Wicket等框架需要学习新的API和概念。
- 性能问题:某些框架可能会带来额外的性能开销。
- 复杂度:框架可能会增加项目的复杂度。
3. 能否完全避免在JSP中使用Java代码?
理论上可以,但实际开发中可能需要根据具体情况进行权衡。一些特殊情况(如临时计算、特定页面的修复)可能仍需要少量Java代码。