本篇供个人学习使用,有问题欢迎讨论
多个Servlet来处理同一次请求方案
一、前提
1、一个 servlet 只负责实现一个功能
2、浏览器在一次请求时,只能请求一个资源文件
3、如果浏览器的请求需要由多个 Servlet 来协同完成,需要用户多次通过【手动提交请求】来完成任务,这样会降低用户的服务质量
4、方案分类
只需要用户手动通过浏览器发送一次请求,就可以将与本次请求相关的 Servlet 依次调用
(1)重定向
(2)请求转发
二、重定向
1、原理
在第一个 servlet 工作完毕后,将【第二个servelt 地址】推送给用户浏览器,由用户浏览器根据这个地址来【自动的】向第二个 servelt 发送请求
2、重定向涉及命令
response.sendRedirect(第二个servelt地址);
将一个地址写入到【响应头】中 location 浏览器接受到响应包之后,自动根据 location 地址发送第二次请求,这样好在避免用户多次手动发送请求
注意:重定向时需要从网站根目录名称开始写!
3、重定向特征
(1)发生位置:在客户端的浏览器上
(2)浏览器发送的请求次数:多次
(3)地址栏内容是否会发生变化:会发生变化,内容是浏览器第二次需要访问的资源地址
(4)重定向时,浏览器采用的请求方式:由于此时通过地址栏命令浏览器发送请求,所以浏览器采用请求方式一定是 GET
(5)重定向时,访问的资源文件范围:可以访问的是资源文件,可以是同一个网站的资源,也可以是其他网站的资源
4、重定向时的数据传递
login.jsp:
<form action="one.do" method="get">
用户名:<input type="text" name="username" /><br>
年龄:<input type="text" name="age" /><br>
<input type="submit" value="登录">
</form>
OneServlet:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String username = request.getParameter("username");
String age = request.getParameter("age");
System.out.println("username = " + username);
System.out.println("age = " + age);
//手动进行拼接
response.sendRedirect("two.do?pname=" + username + "&page=" + age);
}
TwoServlet:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String pname = request.getParameter("pname");
String page = request.getParameter("page");
System.out.println("pname ==== " + pname);
System.out.println("page ==== " + page);
response.getWriter().println("This is TwoServlet...");
}
点击按钮后进行跳转,注意看地址栏:
5、重定向时的数据传递的中文乱码问题解决
关于Servlet的中文乱码问题可以看我的博文Servlet出现中文乱码的问题
以上个例子为例,用中文提交时,存在着乱码问题:
注意:Tomcat9已经解决了此问题,这里我们针对的是Tomcat8及以下版本
OneServlet:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String username = request.getParameter("username");
//如果是Tomcat8及以上版本的,则可不用写下行代码
username = new String(name.getBytes("IS0-8859-1"),"UTF-8");
//编码:打散
username = URLEncoder.encode(username,"UTF-8");
System.out.println("username = " + username);
//手动进行拼接
response.sendRedirect("two.do?pname=" + username + "&page=" + age);
}
TwoServlet:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String pname = request.getParameter("pname");
//解码:组装
pname = URLDecoder.decode(pname,"UTF-8");
//如果是Tomcat8及以上版本的,则可不用写下行代码
pname = new String(pname.getBytes("IS0-8859-1"),"UTF-8");
System.out.println("pname ==== " + pname);
response.getWriter().println("This is TwoServlet...");
}
此时提交后已解决了中文乱码问题:
6、重定向方案适用场景
(1)添加功能 Servlet 调用:查询功能 Servlet
(2)删除功能 Servlet 调用:查询功能 Servlet
(3)更新功能 Servelt 调用:查询功能 Servlet
三、请求转发
1、原理
在第一个 servelt 工作完毕后,代替当前浏览器向 Tomcat 申请调用第二个 Servlet。Tomcat 在接受到申请之后,调用第二个 servelt 来完成本次请求中的剩余任务
2、请求转发涉及的命令
//1.创建一个资源申请报告对象
RequestDispatcher report = request.getRequestDispatcher("第二个servelt地址");
//2.将申请报告推送给Tomcat,同时将OneServlet拥有的request和response一并交给Tomcat
report.forward (request,response);
//3.Tomcat在接受到报告之后,就会调用第二个servelt来完成剩余任务
注意:请求转发时不能从网站根目录名称开始写!
3、请求转发时为什么将第一个Servelt中的request和response交给Tomcat
(1)Tomcat 在接受到请求后,需要调用第二个 Servlet
(2)Tomcat 此时需要为第二个 Servelt 提供运行时需要的【request】和【response】
(3)但是,本次请求是由第一个 Servelt 来发送的,没有对应的【请求协议包】,因此导致 Tomcat 在接受到请求后,不会创建【request】和【response】
(4)为了解决这个问题,需要将第一个 servlet 使用的【request】和【response】通过 Tomcat 交给第二个 servlet 来使用
4、请求转发特征
(1)发生位置:发生在服务端
(2)浏览器发送请求次数:浏览器只向服务端发送了一次请求
(3)地址栏内容是否会发生变化:由于请求转发发生在服务端,因此浏览器地址内容保持在第一次请求内容中
(4)调用资源文件范围:只能访问同一个访问内部的资源文件
(5)通过请求转发调用的 servlet 接受的请求方式:与浏览器第一次发送请求时,使用的请求方式
保持一致,参与同一次请求转发的所有 servlet 接受的请求方式是一样的
5、参与同一次请求转发的所有 servlet 之间如何进行数据共享
(1)提供共享数据
a、可以使用全局作用域对象,提供共享数据
//将一段共享数据保存到全局作用域对象
ServletContext application = request.getServletContext();
application.setAttribute("key1","mike");
b、可以使用会话作用域对象,提供共享数据
//将一段共享数据保存到当前来访的浏览器的HttpSession对象
HttpSession session = request.getSession();
session.setAttribute("key2","北京");
c、可以使用请求作用域对象,提供共享数据
//将一段共享数据保存当前的请求对象
request.setAttribute("key3",23);
(2)拿到共享数据
a、在全局作用域对象当中
ServletContext application = request.getServletContext();
String name = (String)application.getAttribute("key1");
System.out.println("TwoServlet从全局作用域对象得到共享数据" + name);
b、在会话作用域对象当中
HttpSession session = request.getSession();
string address = (String)session.getAttribute("key2");
System.out.println("TwoServlet从会话作用域对象得到共享数据" + address);
c、在请求作用域对象当中
Integer age = (Integer)request.getAttribute("key3");
System.out.println("TwoServlet从请求作用域对象得到共享数据" + age);
6、请求转发不适合场景
(1)添加功能 Servlet 调用:查询功能 Servlet
(2)删除功能 Servlet 调用:查询功能 Servlet
(3)更新功能 Servelt 调用:查询功能 Servlet
7、请求转发适合场景:查询 Servlet 调用 JSP 时
四、请求转发与重定向对比
1、请求转发
- 浏览器只发出一次请求,收到一次响应
- 请求所转发到的资源中可以直接获取到请求中所携带的数据
- 浏览器地址栏显示的为用户所提交的请求路径
- 只能跳转到当前应用的资源中
2、重定向
- 浏览器发出两次请求,接收到两次响应
- 重定向到的资源不能直接获取到用户提交请求中所携带的数据
- 浏览器地址栏显示的为重定向的请求路径,而非用户提交请求的路径。也正因为如此,重定向的一个很重要作用是:防止表单重复提交
- 重定向不仅可以跳转到当前应用的其它资源,也可以跳转到到其它应用中资源
3、请求转发与重定向的选择
- 若需要跳转到其它应用,则使用重定向
- 若是处理表单数据的 Servlet 要跳转到其它 Servlet,则需要选择重定向。为了防止表单重复提交
- 若对某一请求进行处理的 Servlet 的执行需要消耗大量的服务器资源(CPU、内存),此时这个 Servlet 执行完毕后,也需要重定向
- 若请求转发与重定向都可使用的话,尽量使用重定向,因为可以防止恶意刷新
- 其它情况下,一般使用请求转发
五、RequestDispatcher
RequestDispatcher 是 javax.servlet 包下的一个接口,通过 HttpServletRequest 可以获取到
RequestDispatcher 的接口对象。顾名思义,该对象就是用于完成请求转发功能的。
1、forward()与include()
RequestDispatcher 接口中具有两个方法:forward() 与 include(),均可完成请求的转发。即可以将请求中所携带的参数由当前 Servlet 传递给下一下资源,如另一个Servlet。也就是说,这两个方法对于请求来说是相同的,都是请求转发。但它们的不同之处是响应,是标准响应输出流的开启时间不同。
TwoServlet:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("Two request = " + request);
System.out.println("Two response = " + response);
}
OneServlet:
forward()方式:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("One request = " + request);
System.out.println("One response = " + response);
request.getRequestDispatcher("two.do").forward(request,response);
}
include()方式:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("One request = " + request);
System.out.println("One response = " + response);
request.getRequestDispatcher("two.do").include(request,response);
}
从上图说明:无论是 forward() 还是 include(),对于请求来说,都是一样的,它们的不同点主要集中在响应对象。
2、代码测试及说明
(1)forward()
OneServlet:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html");
response.getWriter().println("OneServlet:forward() before...<br>");
request.getRequestDispatcher("two.do").forward(request,response);
response.getWriter().println("OneServlet:forward() after...<br>");
}
TwoServlet:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.getWriter().println("TwoServlet Data...<br>");
}
输出结果:
void forward(ServletRequest request, ServletResponse response)
使用该方法,则当前的 Servlet 中只能进行业务处理,而不能向浏览器发送要显示的数据。因为请求还未完成,需要继续向前(forward),当请求完成后,服务器才会开启标准响应输出流,向输出流中写入数据。
该方法的响应对象,使用的是第二个资源的响应对象。即第二个资源向浏览器回送的响应数据。
不过,需要注意的是,在以上测试中的OneServlet与TwoServlet中均添加对于Response对象的输出语句,会发现这两个Servlet中所使用的Response对象为同一个ResponseFacade对象。
那为什么在OneServlet中向输出流中的 print() 写入数据后,并不会显示到客户端浏览器?
原因就是Response对象在OneServlet中创建了,但标准输出流并未开启。输出流的开启是在TwoServlet中进行的。
(2)include():
OneServlet:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html");
response.getWriter().println("OneServlet:include() before...<br>");
request.getRequestDispatcher("two.do").include(request,response);
response.getWriter().println("OneServlet:include() after...<br>");
}
TwoServlet:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.getWriter().println("TwoServlet Data...<br>");
}
输出结果:
void include(ServletRequest request, ServletResponse response)
使用该方法,在将请求向后转发时,服务器会将标准响应输出流开启。当前Servlet可以向输出流中写入数据,并且服务器还会将要转向的资源的输出流中数据合并到当前的输出流中。
该方法的响应对象,使用的是第一个资源的响应对象,即当前Servlet向浏览器回送的响应数据。
需要注意的是,在以上测试中的OneServlet与TwoServlet中均添加对于Response对象的输出语句,会发现OneServlet输出的是ResponseFacade对象,而TwoServlet输出的则是ApplicationHttpResponse 对象。
ApplicationHttpResponse是HttpServletResponse接口的实现类ResponseFacade的装饰者类,其增强了ResponseFacade类的功能。ApplicationHttpResponse底层完成的一个工作是,将当前的TwoServlet中的输出流中的数据合并到了OneServlet的标准输出流中。
3、总结
forward( )与include( )的区别,主要表现在标准输出流的开启时间不同:
(1)forward()
forward这个单词表示的意思是“向前”,说明当前的请求还未结束,需要继续“向前”,所以服务器就不会在这里先打开标准输出流。所以此时写入到out中的数据是不会写入到客户端浏览器中的。
使用forward()方法的Servlet,其标准输出流还未开启。
对客户端的响应肯定不是使用forward( )方法的Servlet给出的。
(2)include()
include这个单词表示的意思是“包含”,说明当前的请求已经结束,可以对客户端进行响应了。其不仅将自己的数据写入到了标准输出流,还要将其它数据包含到自己的输出流中。
使用include()方法的Servlet,其标准输出流已经打开。
对客户端的响应是由使用include( )方法的Servlet给出的。
(3)结论
在使用forward()进行请求转发时,使用forward()的Servlet不应向Response中写入数据,若要写入数据,则应使用include()进行转发