6.4.1 out对象
out对象用来向客户端输出信息。如下面的代码所示:
<!-- jspout.jsp -->
<%@ page language="java" pageEncoding="UTF-8" %>
<%
out.println("使用out对象输出<br>");
java.io.PrintWriter myOut = response.getWriter();
myOut.println("使用PrintWriter对象输出<br>");
%>
在上面的代码中,首先使用了out对象向客户端输出信息,然后调用了response对象的getWriter方法获得一个PrintWriter对象,并通过该对象的println方法向客户端输出信息。
在浏览器地址栏中输入如下的URL:
http://localhost:8080/demo/chapter6/jspout.jsp
浏览器显示的信息如图6.7所示。
图6.7 使用out和PrintWriter对象输出的信息
从图6.7显示的信息可以看出,使用PrintWriter对象输出的信息显示在了使用out对象输出的信息的前面。这是因为out对象实际上通过pageContext对象的getOut方法获得的JspWriter对象,通过JspWriter对象输出的信息首先会被写入out对象的缓冲区,在满足如下两个条件中的一个时,系统会将out对象缓冲区中的内容写入Servlet引擎提供的缓冲区:
l 整个JSP页面结束时。
2 当前out对象缓冲区已满时。
将out对象缓冲区中的内容写到Servlet引擎提供的缓冲区后,再通过PrintWriter对象将这些内容输出到客户端。也就是说,不管是JSP,还是Servlet,最终都是依靠PrintWriter对象向客户端输出信息的。
从上面的程序可以看出,虽然一开始就使用了out对象输出信息,但这些信息都被写入out对象的缓冲区,而使用PrintWriter对象输出的内容则直接被写入了Servlet引擎提供的缓冲区,当整个页面结束时,系统会将out对象缓冲区中的内容写入Servlet引擎提供的缓冲区。因此,从写入Servlet引擎提供的缓冲区的顺序看,使用PrintWriter对象输出的信息要比使用out对象输出的内容更早地被写入Servlet引擎提供的缓冲区,这也就是为什么输出信息的顺序会和out及PrintWriter对象在JSP页面中的调用顺序正好相反的原因。
如果想让输出顺序和JSP页面中的调用顺序保持一致,可以通过禁止out对象缓冲区的方法来解决,如下面的代码所示:
<!-- jspout.jsp -->
<%@ page language="java" pageEncoding="UTF-8" buffer="none" %>
<%
out.println("使用out对象输出<br>");
java.io.PrintWriter myOut = response.getWriter();
myOut.println("使用PrintWriter对象输出<br>");
%>
上面的代码在page指令中加了一个buffer属性,并将该属性的值设为“none”,也就是禁止out对象的缓冲区。这时再次访问jspout.jsp页面,就会看到信息的输出顺序改变了。
由于buffer属性的默认值是8k,因此,当使用out对象输出的信息总量超过8k时,就算JSP页面未结束,也会将信息(out对象缓冲区中的8k的内容)写入Servlet引擎提供的缓冲区,并清空out对象的缓冲区。下面的JSP页面将buffer属性值设为1k(该值是buffer属性可设置的最小值),来模拟out缓冲区溢出的过程。
<!-- jspbuffer.jsp -->
<%@ page language="java" pageEncoding="UTF-8" buffer="1kb" %>
<%
for(int i = 0; i < 1024; i++)
out.println("x");
java.io.PrintWriter myOut = response.getWriter();
myOut.println("使用PrintWriter对象输出信息");
%>
下面的代码循环产生了1024个“x”字符,并通过out对象输出的客户端,在最后使用PrintWriter对象输出了一条信息。
在浏览器地址栏中输入如下的URL:
http://localhost:8080/demo/chapter6/jspbuffer.jsp
浏览器显示的信息如图6.8所示。
图6.8 out对象缓冲区溢出
从图6.8所示的输出信息可以看出,使用PrintWriter对象输出的信息被夹在了1024个“x”字符中间。在该信息前面的“x”字符是out对象缓冲区未满时写入的。虽然用程序产生了1024个“x”,但由于JSP的静态部分(如页面开头的注释部分)也占用了一定的out对象缓冲区空间,因此,out对象缓冲区空间容纳的“x”字符数要小于1024,因此,会出现1024个“x”未被完全写入out对象的缓冲区,该缓冲区就溢出了的现象。
当out对象缓冲区被第一次写满时,就会将该缓冲区的内容一次性地写入Servlet引擎的缓冲区,然后清空out对象缓冲区,并会再次写入剩余的“x”。因此,在使用PrintWriter对象输出信息之前,已经有1024个字节的信息被写入到了Servlet引擎的缓冲区。所以会出现图6.8所示的输出结果。
由于JSP向客户端输出信息时使用了JspWriter对象(out对象),并且在out对象缓冲区被写入Servlet引擎的缓冲区后,Servlet引擎会使用PrintWriter输出缓冲区中的内容,因此,如果JSP页面中包含有静态内容,则无法使用ServletOutputStream对象来输出信息,否则会造成冲突,如下面的代码所示:
<!-- jspstream.jsp -->
<%@ page language="java" pageEncoding="UTF-8" %>
<%
ServletOutputStream sos = response.getOutputStream();
sos.println(new String("使用ServletOutputStream输出信息".getBytes("UTF-8"), "ISO-8859-1"));
%>
在上面的代码中使用了response.getOutputStream方法获得了一个ServletOutputStream对象,并通过该对象的println方法向客户端输出信息。
在浏览器地址栏中输入如下的URL:
http://localhost:8080/demo/chapter6/jspstream.jsp
浏览器将显示如图6.9所示的异常信息。
图6.9 使用ServletOutputStream对象输出信息时抛出的异常
产生图6.9所示的异常的原因是由于jspstream.jsp页面包含了静态部分(注释、\r\n等),而这些注释部分最终要通过PrintWriter输出到客户端,但在jspstream.jsp页面中又使用了ServletOutputStream对象,在前面讲过,不能同时使用ServletOutputStream和PrintWriter对象向客户端输出信息。因此,才会抛出上面的异常。
如果将jspstream.jsp页面中所有的静态部分都删除,那么JSP引擎不会向out对象缓冲区写入任何内容,也不会使用PrintWriter对象向客户端输出信息。因此,这时Servlet引擎实际上只使用了ServletOutputStream对象,所以可以正常向客户端输出信息。
除了直接在JSP页面中使用ServletOutputStream对象可能会抛出异常外,使用forward和include方法转发和包含页面时也可能会抛出和图6.9相同的异常信息,如下面的代码所示:
<!-- jspforward.jsp -->
<%@ page language="java" pageEncoding="UTF-8" %>
<%
RequestDispatcher rd = request.getRequestDispatcher("/test.html");
rd.forward(request, response);
// 使用include方法和使用forward都会带来同样的问题
// rd.include(request, response);
%>
在浏览器地址栏中输入如下的URL:
http://localhost:8080/demo/chapter6/jspforward.jsp
浏览器将会显示如图6.9所示的异常信息。
抛出异常的原因是由于Servlet引擎通过默认的Servlet来处理html、jpg等Web资源。默认Servlet会首先检查是否调用了getWriter方法获得PrintWriter对象,如果系统还未获得PrintWriter对象,则默认的Servlet会使用ServletOutputStream对象来处理这些Web资源。而在jspforward.jsp页面中并未显式地调用getWriter方法来获得PrintWriter对象,而且out对象缓冲区也未满,因此,也不可能通过将out对象缓冲区的内容写入Servlet引擎缓冲区的方式来调用getWriter方法获得PrintWriter对象。所以这时使用forward方法来转发test.html页面,实际上是使用ServletOutputStream对象来处理的。
当jspforward.jsp页面结束时,会因为将out对象缓冲区的内容写入Servlet引擎的缓冲区而调用getWriter方法。因此,实际上jspforward.jsp页面相当于先调用了getOutputStream方法,再调用了getWriter方法,因此,就会造成冲突,从而抛出异常。
读者可以通过如下三种方法来解决这个问题,从而避免抛出异常:
l 清空jspforward.jsp页面中的所有静态部分,包括\r\n。这样系统就不会向out对象的缓冲区写入任何内容了。
2 如果在<%...%>前面有静态内容的话(在一般情况下<%...%>前都会有一些静态内容),可以使用page指令的buffer属性将out对象缓冲区关闭,也就是将buffer属性设为“none”。这样只要在<%...%>前面有静态内容,就可以直接写到Servlet引擎的缓冲区中,也就相当于调用了getWriter方法。
3 在<%...%>中的开始部分加上response.getWriter方法的调用。这样再调用forward或include方法,默认Servlet就会检测到已经调用了getWriter方法,因此,就会使用PrintWriter来处理完成forward或include方法的工作。