为了支持请求代理,Servlet API 2.1 引入
javax.servlet.RequestDispatcher接口。servlet调用请求对象的getRequestDispatcher()的方法获得RequestDispatcher实例,此实例可以dispatch到指定的路径上的组件(如servlet, JSP,静态文件等):
public RequestDispatcher ServletRequest.getRequestDispatcher(String path) ,可以是相对路径但不能超过当前的servlet context,不过可以使用getContext方法转发到当前context之外的地方,只是没有任何方法转发到另一个服务器上的context。如果路径以“/”开始,它被解释为相对于当前context根,如果路径包含查询字符,参数会被包含到接受组件的参数集的开始处。如果servlet容器由于任何原因不能返回
RequestDispatcher 该方法返回null。在
ServletContext类中有同名方法
public RequestDispatcher ServletContext.getRequestDispatcher(String path),不同之处在于ServletContext 中的方法(sevlet2.1引入)只接受绝对URLs(以/开头),而ServletRequest 中的方法接受绝对和相对。因此没有理由使用ServletContext中的该方法,可以看作只为历史版本兼容(不过servlet2.5仍然有此方法)。除了用路径还可以用名字指定资源来获得RequestDispatcher,ServletContext中的方法:
public RequestDispatcher ServletContext.getNamedDispatcher(String name),此方法允许转发到没必要公开发布的资源,servlet或jsp可能通过web应用描述符获得名字。不能获取RequestDispatcher仍然返回null。
RequestDispatcher有两个方法:forward()and include()。forward把整个请求交给代理;include把代理的输出加入调用servlet(个人理解为初始的那个servlet)的response中并且调用servlet仍然处于控制状态,即请求转发后,原先的Servlet还可以继续输出响应信息,转发到的Servlet对请求做出的响应将并入原先Servlet的响应对象中
forward把请求从一个servlet转发到服务器上的另一个资源,它允许一个servlet对请求做初始处理,然后另一个资源来产生response,不像sendRedirect,forward的整个操作都在服务器内部,对客户是透明的。附加查询字符串或使用请求的setAttribute的方法可以传递信息给代理 。
The rules a forwarding servlet must follow are relatively strict:
-
It may set headers and the status code but may not send any response body to the client (that's the job for an include). Consequently, the forward( ) must be called before the response has been committed.
-
If the response already has been committed, the forward( ) call throws an IllegalStateException.
-
If the response has not been committed but there's content within the response buffer, the buffer is automatically cleared as part of the forward.
-
In addition, you can't get creative by substituting new request and response objects. The forward( ) method must be called with the same request and response objects as were passed to the calling servlet's service method, and the forward( ) must be called from within the same handler thread.
如果servlet A转发到servlet B,B得到的路径信息应该是和直接调用B一样的,从这方面来说B完全控制请求。这也是为什么确保response的缓存会在调用之前冲掉,response没有被提交是很重要的事情。
用路径转发的问题是:目标组件不但对服务器组件可见,也必须对客户端可见。出于安全考虑,可能会让一些组件不是公共可见的,所以用名字代替路径做转发:getNamedDispatcher 不过没有URI路径也就不能加上查询字符串,路径也不能做调整。
forward 和sendRedirect谁更好:request dispatch 在服务器端发生,redirect在客户端发生。forward最好用在一个组件必须处理业务逻辑而且和另一个组件分享结果的情况。sendRedirict最好用于客户需要从一页重定向于另一页。看起来用forward比较有诱惑,因为forward在服务器内部完成,sendRedirect还需要从客户端倒一遍,所以forward更快。不幸的是处理相对URL的时候会引起问题。例:
public class HomePageForward extends HttpServlet {
public void doGet(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
RequestDispatcher dispatcher = req.getRequestDispatcher("/index.html");
dispatcher.forward(req, res);
}
}
如果index.html中有<img src="../../../images/trans.gif"/>则图片不能读取。sendRedirect会告诉客户端文件来源于文件根目录,forward不会。因为不需要调用getContext查询,sendRedirect更容易转发到其他context的资源中。所以建议是尽量用sendRedirect,必须的情况下才用forward。
RequestDispatcher的include方法把资源的内容放入当前的respongse中。它实现了一种我们可以称为编程式服务器端 include。不同于forward,在调用include后的servlet A仍然控制response而且可能包含 被包含进来内容的前后内容。
例1:
doGet(..){
res.setContentType("text/html");
PrintWriter out = res.getWriter();
out.println("<HTML><HEAD><TITLE>Welcome to Nile</TITLE></HEAD>");
out.println("<BODY>");
// Show an item in an online catalog
out.println("Feast your eyes on this beauty:");
RequestDispatcher dispatcher =
req.getRequestDispatcher("/servlet/NileItem?item=0596000405");
dispatcher.include(req, res);
out.println("And, since I like you, it's 20% off!");
out.println("</BODY></HTML>");
}
使用
forward,通过附加查询字符或
setAttribute方法,信息可以传到被调用资源。Using attributes instead of parameters gives you the ability to pass objects instead of simple strings,例
doGet(..){
res.setContentType("text/html");
PrintWriter out = res.getWriter();
out.println("<HTML><HEAD><TITLE>Welcome to Nile</TITLE></HEAD>");
out.println("<BODY>");
// Show items in an online catalog
RequestDispatcher dispatcher =
req.getRequestDispatcher("/servlet/NileItem");
out.println("Feast your eyes on this beauty:");
req.setAttribute("item", Book.getBook("0596000405"));
dispatcher.include(req, res);
// Remove the "item" attribute after use
req.removeAttribute("item");
out.println("Or how about this one:");
req.setAttribute("item", Book.getBook("0395282659"));
dispatcher.include(req, res);
out.println("And, since I like you, they're all 20% off!");
out.println("</BODY></HTML>");
}
接受servlet
public class NileItem extends HttpServlet {
public void doGet(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
// We do not set the content type
PrintWriter out = res.getWriter();
Book book = (Book) req.getAttribute("item");
out.println("<BR>");
if (book != null) {
out.println("<I>" + book.getTitle() + "</I>");
out.println(" by " + book.getAuthor());
}
else {
out.println("<I>No book record found</I>");
}
out.println("<BR>");
}
}
通过实例可以很好理解include,就是把下一个资源的内容直接放入本页面的任何地方,被包含的资源不能改变status code or HTTP headers ,所以NileItem 做任何改变这些的操作都会被忽略(NileItem 所以没有做这些操作)。不像forward,路径元素和请求参数仍然和调用端一样。如果被包含资源要求获得自己的路径元素和请求参数, it may retrieve them using the following server-assigned request attributes:
javax.servlet.include.request_uri
javax.servlet.include.context_path
javax.servlet.include.servlet_path
javax.servlet.include.path_info
The request and response parameters must be the same objects as were passed to the calling servlet's service method, and the include( ) must be called from within the same handler thread.
The included resource must use an output mechanism that matches the caller's. If the caller uses a PrintWriter, then the included resource must use a PrintWriter. If the caller uses an OutputStream, then the included resource must use an OutputStream. If the mechanisms don't match, the included servlet throws an IllegalStateException. If you can, make sure to use the PrintWriter for all text output, and this won't be a problem.