如何实现Servlet与JSP协同输出HTML 而非仅靠转发渲染
你遇到的两个输出异常,和业务逻辑编写没有直接关系,完全是Servlet规范对请求转发、响应状态的强制规则导致的:
为什么不手动flush时,Servlet输出的"hello"会消失?
RequestDispatcher.forward()的设计目标就是让转发的目标资源完全接管整个响应流程。容器在执行forward操作的瞬间,会自动清空源组件(也就是你编写的Servlet)之前写入响应缓冲区的所有未提交内容,再将目标资源的输出作为唯一的响应内容返回给客户端。
你写入的"hello "只有5个字符,远小于Tomcat等容器默认的8KB响应缓冲区阈值,根本不会提前把内容推送给客户端,所以这些内容会被forward操作直接清空,最终客户端只能看到JSP输出的用户名。
为什么手动添加pw.flush()后,只剩"hello"没有JSP的内容?
手动调用flush()会强制把响应缓冲区的内容推送给客户端,同时将response标记为*已提交(committed)*状态。按照Servlet规范,已经提交的response不允许再执行任何forward操作,这时候转发逻辑会直接失效,多数容器会静默吞掉流程抛出的IllegalStateException,直接结束整个请求处理流程,所以JSP的内容根本没有机会执行。
forward()从设计初衷上就不支持多组件分别输出部分内容、拼接成完整响应的场景,你需要把转发逻辑替换成**包含(include)**模式。RequestDispatcher专门提供了include()方法,就是为多组件协同拼接输出设计的:
- 执行
include()时,容器不会清空源组件已经写入响应缓冲区的内容 - 目标JSP的输出会直接拼接到源组件已经写入的内容后面
- 整个流程中两个组件写的内容都会被完整保留,不会被随意清空
修正后的实现代码
只需要把Servlet中原来的forward调用替换为include即可:
@WebServlet("/MyServlet") public class MyServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { PrintWriter pw = resp.getWriter(); // Servlet负责输出固定文本 pw.println("hello "); // 将原forward调用替换为include RequestDispatcher dispatch = req.getRequestDispatcher("test.jsp"); dispatch.include(req, resp); } }
调整完成后,提交表单就能得到预期的hello elephant输出,两个组件的内容会按顺序完整拼接,既不会丢失Servlet输出的固定文本,也会正常带出JSP读取到的用户名。
注意:使用
include时不要手动强制调用flush()把内容提前推出缓冲区,只要写入的内容还在响应缓冲区的阈值范围内,include逻辑就可以正常工作,不会出现内容丢失的问题。
内容的提问来源于stack exchange,提问作者Stefan

