Request和Response相关介绍
Request 和 Response 是什么?
- Request(请求对象):用来接收浏览器发过来的数据。
- Response(响应对象):用来把服务器处理后的结果返回给浏览器。
1. request 的作用(获取请求数据)
- 当你在浏览器访问网页或提交表单时,浏览器会把你的请求(比如你输入的账号、密码等)通过HTTP协议发给服务器(Tomcat)。
- 这些数据会被Tomcat解析,然后封装到request对象里。
- 在Servlet代码里,你可以通过request对象获取到这些数据,比如获取表单里的用户名、密码等。
- 拿到数据后,服务器就可以根据这些信息做后续的业务处理,比如登录验证。
2. response 的作用(设置响应数据)
- 服务器处理完业务后,需要把结果返回给浏览器,比如返回一个网页、提示信息等。
- 这些返回的数据会被封装到response对象里。
- Tomcat会把response对象里的内容解析出来,按照HTTP协议的格式发回给浏览器。
- 浏览器收到后,就会把内容展示出来,比如显示网页、弹出提示等。
通过一个案例来初步体验下request和response对象的使用。
写一个表单,请求方式为GET,当我们输入不同的username,并点击提交时,界面上就会出现username,欢迎访问
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<form action="http://localhost:8080/web_demo_war_exploded/demo" method="get">用户名:<input type="text" name="username"><input type="submit" value="提交">
</form>
</body>
</html>
启动成功后就可以通过浏览器来访问,并且根据传入参数的不同就可以在页面上展示不同的内容:
import javax.servlet.*;
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(urlPatterns = "/demo")
public class ServletDemo extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {String name = req.getParameter("username");//避免中文乱码问题resp.setHeader("content-type","text/html;charset=utf-8");resp.getWriter().write("<h1>"+name+",欢迎访问<h1>");}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {System.out.println("post...");}
}
总结一句话
- request:用来接收浏览器发来的数据。
- response:用来把服务器的处理结果返回给浏览器。
Request继承体系
一、Request继承体系是什么?
在Java Web开发中,Request对象用来封装浏览器发来的请求数据。
但这个Request对象其实有一套“继承体系”,也就是有父接口、子接口和实现类。
1. ServletRequest(接口)
- Java官方提供的最基础的请求对象接口,定义了所有请求对象都应该有的方法。
- 就像“交通工具”这个大类,规定了所有交通工具都应该有“启动、停止”等功能。
2. HttpServletRequest(接口)
- 继承自ServletRequest,专门针对HTTP协议的请求对象接口,增加了很多和HTTP相关的方法(如获取请求头、参数等)。
- 就像“汽车”是“交通工具”的一种,除了启动、停止,还能“开空调、开车窗”等。
3. RequestFacade(实现类)
- 这是Tomcat服务器自己写的一个实现类,真正实现了HttpServletRequest接口的所有方法。
- 就像“宝马汽车”是“汽车”的一种,宝马公司造出来的具体汽车。
二、Tomcat处理请求的过程
- 浏览器发请求(比如你在Chrome里访问一个网站)。
- Tomcat服务器接收到请求,会先解析请求数据。
- Tomcat用RequestFacade类,把请求数据封装成一个Request对象。
- Tomcat把Request对象传给Servlet的service方法,让你的Java代码可以方便地获取请求里的各种信息。
三、生活中的实际例子
例子1:快递包裹
- ServletRequest:快递包裹的“标准接口”,规定所有快递包裹都要有“收件人、寄件人、内容物”等信息。
- HttpServletRequest:专门针对“顺丰快递”的包裹,除了基本信息,还多了“顺丰单号、保价服务”等特殊功能。
- RequestFacade:顺丰公司实际生产出来的某个快递包裹,里面装着你的快递。
例子2:公司员工
- ServletRequest:公司规定的“员工”标准(必须有姓名、工号等)。
- HttpServletRequest:技术部员工,除了基本信息,还要有“技术等级、编程语言”等。
- RequestFacade:张三,技术部的一个具体员工。
ServletRequest和HttpServletRequest是继承关系,并且两个都是接口,接口是无法创建对象,那么方法里的HttpServletRequest参数是从哪儿来的呢?
//接口无法创建对象,那么这个参数是哪儿来的呢
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {String name = req.getParameter("username");resp.setHeader("content-type","text/html;charset=utf-8");resp.getWriter().write("<h1>"+name+",欢迎访问<h1>");
}@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {System.out.println("post...");
}
一、接口不能创建对象,参数对象从哪来的?
- ServletRequest 和 HttpServletRequest 都是接口,接口是不能直接用 new 创建对象的。
- 但是在 Servlet 的 doGet、doPost 方法里,参数却是 HttpServletRequest req,这个对象是谁创建的呢?
二、Request对象的创建过程
- Tomcat 服务器收到浏览器请求后,会先解析请求数据。
- Tomcat 会用 RequestFacade 这个类,来实现 HttpServletRequest 接口,把请求数据封装成一个对象。
- Tomcat 再把这个对象传给你的 Servlet 的 doGet/doPost 方法,你就能直接用 req.getParameter("username") 这样的方法获取请求参数了。
简单说:你用的 req 对象,其实是 Tomcat 帮你创建好的 RequestFacade 对象,只不过它的类型是 HttpServletRequest。
三、生活中的实际例子
例子1:快递公司和快递单
- 接口(HttpServletRequest):就像快递公司规定的“快递单标准”,规定了快递单必须有收件人、寄件人、地址等信息。
- RequestFacade:就像顺丰公司实际印出来的快递单,完全符合标准,但是顺丰自己造的。
- 你(Servlet):只管用快递单(req对象)填写和查信息,不用关心快递单是谁印的、怎么印的。
例子2:公司员工和岗位说明书
- 接口:公司规定的“岗位说明书”,比如“程序员”要会写代码、会调试。
- 实现类:张三是公司招聘的程序员,完全符合岗位说明书的要求。
- 你(项目经理):只管让张三干活(调用方法),不用管张三是怎么被招聘进来的
四、代码中的体现
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {// req其实是Tomcat创建的RequestFacade对象String name = req.getParameter("username");// ...
}
import javax.servlet.*;
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(urlPatterns = "/demo")
public class ServletDemo extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {System.out.println(req);}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {System.out.println("post...");}
}
启动服务器访问页面,控制台输出org.apache.catalina.connector.RequestFacade@36bf693c
五、总结
- ServletRequest:最基础的请求接口,规定了所有请求对象的基本功能。
- HttpServletRequest:专门针对HTTP协议的请求接口,功能更丰富。
- RequestFacade:Tomcat实现的具体类,真正用来封装和传递请求数据。
Tomcat会用RequestFacade把浏览器的请求数据封装好,然后传给你的Servlet代码,让你可以方便地获取和处理请求信息。
Request获取请求数据
请求行包含三块内容,分别是请求方式
、请求资源路径
、HTTP协议及版本
,例如
GET /tomcat_demo_war/index.html?username=suger1201&password=dsaasd HTTP/1.1
对于这三部分内容,request对象都提供了对应的API方法来获取,具体如下:
- 获取请求方式:
GET
String getMethod()
- 获取虚拟目录(项目访问路径):
/request-demo
String getContextPath()
- 获取URI(统一资源标识符):
/request-demo/req1
String getRequestURI()
介绍完上述方法后,咱们通过代码把上述方法都使用下:
import javax.servlet.*;
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(urlPatterns = "/demo")
public class ServletDemo extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {String method = req.getMethod();System.out.println("请求方式:" + method);String contextPath = req.getContextPath();System.out.println("项目访问路径:" + contextPath);StringBuffer requestURL = req.getRequestURL();System.out.println("URL:" + requestURL);String requestURI = req.getRequestURI();System.out.println("URI:" + requestURI);String queryString = req.getQueryString();System.out.println("请求参数:" + queryString);}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {System.out.println("post...");}
}
这里的请求方式是GET
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<form action="/web_demo_war_exploded/demo" method="get">用户名:<input type="text" name="username"><input type="submit" value="提交">
</form>
</body>
</html>
启动服务器,并向表单中随便输入一个username,然后点击提交,控制台输出如下
请求方式:GET
项目访问路径:/web_demo_war_exploded
URL:http://localhost:8080/web_demo_war_exploded/demo
URI:/web_demo_war_exploded/demo
请求参数:username=Cyderpunk2077%40gmail.com
获取请求头数据
对于请求头的数据,格式为key: value
所以根据请求头名称获取对应值的方法为
String getHeader(String name)
接下来,在代码中如果想要获取客户端浏览器的版本信息,则可以使用
import javax.servlet.*;
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(urlPatterns = "/demo")
public class ServletDemo extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {String agent = req.getHeader("user-agent");System.out.println(agent);}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {System.out.println("post...");}
}
启动服务器,控制台输出如下
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36
获取请求体数据
浏览器在发送GET请求的时候是没有请求体的,所以需要把请求方式变更为POST
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<form action="/web_demo_war_exploded/demo" method="post">用户名:<input type="text" name="username"><input type="submit" value="提交">
</form>
</body>
</html>
对于请求体中的数据,Request对象提供了如下两种方式来获取其中的数据,分别是:
- 获取字节输入流,如果前端发送的是字节数据,比如传递的是文件数据,则使用该方法
ServletInputStream getInputStream()
- 获取字符输入流,如果前端发送的是纯文本数据,则使用该方法
BufferedReader getReader()
下面我们在Servlet的doPost方法中获取数据
import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;@WebServlet(urlPatterns = "/demo")
public class ServletDemo extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {System.out.println("get??");}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {//由于获取的是纯文本数据,所以这里用的getReader()BufferedReader bufferedReader = req.getReader();String line = bufferedReader.readLine();System.out.println(line);}
}
form表单提交数据
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<form action="http://localhost:8080/request-demo/demo" method="post">用户名:<input type="text" name="username"><input type="submit" value="提交"></form>
</body>
</html>
BufferedReader流是通过request对象来获取的,当请求完成后request对象就会被销毁,request对象被销毁后,BufferedReader流就会自动关闭,所以此处就不需要手动关闭流了。
就像你收到一封信(POST请求),你用 getReader() 就是把信封拆开,直接读里面的信纸内容(请求体),而不是看信封上的地址(请求头或参数)。
启动服务器,访问 http://localhost:8080/requset-demo/index.html ,填入username并提交,在控制台就可以看到前端发送的请求数据了
小结
HTTP请求数据中包含了请求行
、请求头
和请求体
,针对这三部分内容,Request对象都提供了对应的API方法来获取对应的值:
- 请求行
- getMethod()获取请求方式
- getContextPath()获取项目访问路径
- getRequestURL()获取请求URL
- getRequestURI()获取请求URI
- getQueryString()获取GET请求方式的请求参数
- 请求头
- getHeader(String name)根据请求头名称获取其对应的值
- 请求体
- 注意: 浏览器发送的POST请求才有请求体
- 如果是纯文本数据:getReader()
- 如果是字节数据如文件数据:getInputStream()
获取请求参数的通用方式
请求参数获取方式
- GET方式:
String getQueryString()
- POST方式:
BufferedReader getReader();
思考:
GET请求方式和POST请求方式区别主要在于获取请求参数的方式不一样,是否可以提供一种统一获取请求参数的方式,从而统一doGet和doPost方法内的代码?
解决方式一:
import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;@WebServlet(urlPatterns = "/demo")
public class ServletDemo extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {//获取请求方式String method = req.getMethod();String param = "";//根据请求方式来获取请求参数if ("GET".equals(method)) {param = req.getQueryString();} else if ("POST".equals(method)) {BufferedReader bufferedReader = req.getReader();param = bufferedReader.readLine();}//将请求参数进行打印控制台System.out.println(param);}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {this.doGet(req, resp);}
}
代码中的一些疑问:
1. "GET".equals(method) 是什么意思?
- method 是一个字符串变量,保存了请求方式,比如 "GET" 或 "POST"。
- "GET".equals(method) 的意思是:判断 method 变量的值是不是 "GET"。
2. 为什么不写成 method.equals("GET")?
- 其实 method.equals("GET") 也可以用,功能是一样的。
- 但如果 method 变量是 null,method.equals("GET") 会报空指针异常(NullPointerException)。
- 而 "GET".equals(method) 就算 method 是 null,也不会报错,只会返回 false。
所以这种写法更安全!
3. 还能怎么用?
这种写法适用于任何字符串比较,比如:
if ("POST".equals(method)) { ... }
if ("admin".equals(role)) { ... }
if ("zhangsan".equals(username)) { ... }
4. 生活中的例子
就像你问别人“你是不是张三?”
- 你直接问“张三.equals(你)”,不管对方是谁(哪怕没人),你都不会出错。
- 但如果你问“你.equals(张三)”,对方要是没人(null),你就会出错。
5. 总结
- "GET".equals(method) 是安全的字符串比较写法,防止空指针异常。
- 这种写法在Java开发中非常常见,推荐你以后都这样写!
使用request的getMethod()来获取请求方式,根据请求方式的不同分别获取请求参数值,这样就可以解决上述问题,但是以后每个Servlet都需要这样写代码,实现起来比较麻烦,这种方案我们不采用
解决方式二:
request对象已经将上述获取请求参数的方法进行了封装,并且request提供的方法实现的功能更强大,以后只需要调用request提供的方法即可,在request的方法中都实现了哪些操作呢?
- 根据不同的请求方式获取请求参数,例如:
username=zhangsan&password=asd123&hobby=1&hobby=2
- 把获取到的内容进行分割,
username=zhangsan&password=asd123&hobby=1
->username=zhangsan password=asd123 hobby=1 hobby=2
->username zhangsan password asd123 hobby 1 hobby 2
- 把分割后端数据,存入到一个Map集合中,其中Map集合的泛型为
<String,String[]>
,因为参数的值可能是一个,也可能有多个,所以value的值的类型为String数组。
基于上述理论,request对象为我们提供了如下方法:
- 获取所有参数Map集合
Map<String,String[]> getParameterMap()
- 根据名称(Key)获取参数值(Value)(数组)
String[] getParameterValues(String name)
- 根据名称(Key)获取参数值(Value)(单个值)
String getParameter(String name)
接下来,我们通过案例来把上述的三个方法进行实例演示:
- 随便写一个表单,加上一个复选框,爱好可以多选,所以到时候的参数值就不止一个了
<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>Title</title> </head> <body> <form action="/web_demo_war_exploded/demo" method="post">用户名:<input type="text" name="username"><br>密码:<input type="password" name="password"><br>爱好:<input type="checkbox" name="hobby" value="1">Apex<input type="checkbox" name="hobby" value="2">Terraria<br><input type="submit" value="提交"> </form> </body> </html>
getParameterMap()
import javax.servlet.*; 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.util.Map;@WebServlet(urlPatterns = "/demo") public class ServletDemo extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {//获取所有参数的Map集合Map<String, String[]> parameterMap = req.getParameterMap();//遍历Mapfor (String key : parameterMap.keySet()) {System.out.print(key+":");String[] values = parameterMap.get(key);for (String v : values) {System.out.print(v+" ");}System.out.println();}}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {this.doGet(req, resp);} } /* 获取的结果如下 username:Cyderpunk2077@gmail.com password:dsaasd hobby:1 2 */
getParameterValues(String name)
@WebServlet(urlPatterns = "/demo") public class ServletDemo extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {String[] hobbies = req.getParameterValues("hobby");for (String s : hobbies) {System.out.println(s);}}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {this.doGet(req, resp);} } /* 将两个复选框都勾上,得到结果如下 1 2 */
getParameter(String name)
@WebServlet(urlPatterns = "/demo") public class ServletDemo extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {String username = req.getParameter("username");System.out.println(username);}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {this.doGet(req, resp);} } //输出结果就是你输入的username
如果你在post请求方式下输入了中文username并提交,控制台会输出乱码
解决方案:
package com.itheima.request;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("/req")
public class RequestDemo extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {String username = req.getParameter("username");
// 先用ISO-8859-1解码,再用UTF-8编码username = new String(username.getBytes("ISO-8859-1"), "UTF-8");System.out.println(username);}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {this.doGet(req,resp);}
}
遇到的问题
new String(username.getBytes("ISO-8859-1"), "UTF-8")是什么作用?
它的作用就是:把已经乱码的字符串“修正”回来,变成正常的中文。
详细分解
- username.getBytes("ISO-8859-1")
把“乱码”的字符串,按照ISO-8859-1编码方式转成字节数组。
这一步的意思是:把你看到的乱码,变回原始的字节数据。
- new String(..., "UTF-8")
用UTF-8编码方式,把上一步得到的字节数组重新解码成字符串。
这一步的意思是:用正确的方式重新“翻译”这些字节,得到正常的中文。
这行代码就是“先把乱码还原成字节,再用UTF-8重新解码,得到正常中文”。
方法 | 作用 | 缺点 |
---|---|---|
req.getParameterMap() | 获取所有参数的 Map(参数名→值数组 ) | 需遍历处理 |
req.getParameter("username") | 获取单个参数的第一个值 | 无法直接获取多值参数(如 hobby ) |
req.getParameterValues("hobby") | 获取单个参数的所有值(返回数组 ) | 需逐个参数调用 |
Request请求转发
请求转发(forward):一种在服务器内部的资源跳转方式。
- 浏览器发送请求给服务器,服务器中对应的资源A接收到请求
- 资源A处理完请求后将请求发给资源B
- 资源B处理完后将结果响应给浏览器
- 请求从资源A到资源B的过程就叫请求转发
测试步骤
- 创建一个RequestDemo1类,接收/req5的请求,在doGet方法中打印
这里是RequestDemo1
import javax.servlet.*; import javax.servlet.http.*; import javax.servlet.annotation.*; import java.io.IOException;@WebServlet("/RequestDemo1") public class RequestDemo1 extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {System.out.println("这里是RequestDemo1");}@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {this.doGet(request, response);} }
- 创建一个RequestDemo2类,接收/req6的请求,在doGet方法中打印
这里是RequestDemo2
import javax.servlet.*; import javax.servlet.http.*; import javax.servlet.annotation.*; import java.io.IOException;@WebServlet("/RequestDemo2") public class RequestDemo2 extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {System.out.println("这里是RequestDemo2");}@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {this.doGet(request, response);} }
- 在RequestDemo1的方法中使用
req.getRequestDispatcher("/RequestDemo2").forward(req,resp)
进行请求转发import javax.servlet.*; import javax.servlet.http.*; import javax.servlet.annotation.*; import java.io.IOException;@WebServlet("/RequestDemo1") public class RequestDemo1 extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {System.out.println("这里是RequestDemo1");//加上这行request.getRequestDispatcher("/RequestDemo2").forward(request, response);}@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {this.doGet(request, response);} }
- 启动测试
当我们访问RequestDemo1
时,控制台会得到如下输出
这里是RequestDemo1
这里是RequestDemo2
说明请求已经转发到了/RequestDemo2
在转发的同时我们还可以传递数据给/RequestDemo2
request对象提供了三个方法:
- 存储数据到request域[范围,数据是存储在request对象]中
void setAttribute(String name,Object o);
- 根据key获取值
Object getAttribute(String name);
- 根据key删除该键值对
void removeAttribute(String name);
接着上个需求来:
- 在RequestDemo1的doGet方法中转发请求之前,将数据存入request域对象中.
@WebServlet("/RequestDemo1") public class RequestDemo1 extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {System.out.println("这里是RequestDemo1");//存储数据request.setAttribute("msg","HELLO~");//请求转发request.getRequestDispatcher("/RequestDemo2").forward(request, response);}@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {this.doGet(request, response);} }
- 在RequestDemo2的doGet方法从request域对象中获取数据,并将数据打印到控制台.
@WebServlet("/RequestDemo2") public class RequestDemo2 extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {System.out.println("这里是RequestDemo2");Object msg = request.getAttribute("msg");System.out.println(msg);}@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {this.doGet(request, response);} }
- 启动访问测试,得到的输出结果如下
这里是RequestDemo1
这里是RequestDemo2
HELLO~
请求转发的特点
- 浏览器地址栏路径不发生变化
虽然后台从/这里是RequestDemo1
转发到/这里是RequestDemo2
,但是浏览器的地址一直是/这里是RequestDemo1
,未发生变化 - 只能转发到当前服务器的内部资源
不能从一个服务器通过转发访问另一台服务器 - 一次请求,可以在转发资源间使用request共享数据
虽然后台从/RequestDemo1
转发到/RequestDemo2
,但是这个只有一次请求
遇到的问题
1. request.setAttribute 是什么?
- 这是 HttpServletRequest 提供的方法,用来在一次请求的范围内存储数据。
- 你可以把它理解为“在本次请求的背包里放东西”,后续在请求转发(forward)到其他 Servlet 或 JSP 时,可以随时取出来用。
2. 语法和用法
request.setAttribute("键", 值);
- "msg" 是你给数据起的名字(key)。
- "HELLO~" 是你要存的数据(value),可以是任意对象。
3. 作用和场景
- 作用:在一次请求内,多个 Servlet/JSP 之间共享数据。
- 常见场景:请求转发(forward)时传递数据,比如表单校验、页面跳转时传递提示信息等。
4. 取出数据的方法
在被转发的 Servlet 或 JSP 中,可以这样取出数据:
Object msg = request.getAttribute("msg");
System.out.println(msg); // 输出:HELLO~
5. 注意事项
- request.setAttribute 存的数据只在本次请求有效,请求结束就没了。
- 如果用的是重定向(redirect),数据不会带过去,只有请求转发(forward)才可以。
6. 总结
- request.setAttribute("msg", "HELLO~"); 就是把数据存到本次请求的“背包”里,方便后续在同一次请求的其他地方取用。
- 常用于Servlet/JSP 之间的数据传递。
Response对象
Reponse的继承体系和Request的继承体系也非常相似:
Response设置响应数据功能介绍
HTTP响应数据总共分为三部分内容,分别是==响应行、响应头、响应体==,对于这三部分内容的数据,respone对象都提供了哪些方法来进行设置?
- 响应行
响应行包含三块内容,分别是 HTTP/1.1[HTTP协议及版本] 200[响应状态码] ok[状态码的描述]
对于响应头,比较常用的就是设置响应状态码:void setStatus(int sc);
- 响应头
响应头的格式为key:value形式
设置响应头键值对:void setHeader(String name,String value);
- 响应体
对于响应体,是通过字符、字节输出流的方式往浏览器写,
获取字符输出流:PrintWriter getWriter();
获取字节输出流
ServletOutputStream getOutputStream();
Response请求重定向
- 重定向的实现方式
response.setStatus(302); response.setHeader("location","资源B的访问路径"); //或 resposne.sendRedirect("资源B的访问路径");
- 创建一个ResponseDemo1类,接收/ResponseDemo1的请求,在doGet方法中打印
这里是ResponseDemo1
@WebServlet("/ResponseDemo1") public class ResponseDemo1 extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {System.out.println("这里是ResponseDemo1");}@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {this.doGet(request, response);} }
- 创建一个ResponseDemo2类,接收/ResponseDemo1的请求,在doGet方法中打印
这里是ResponseDemo2
@WebServlet("/ResponseDemo2") public class ResponseDemo2 extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {System.out.println("这里是ResponseDemo2");}@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {this.doGet(request, response);} }
- 在ResponseDemo1的方法中使用
response.sendRedirect("/web_demo_war_exploded/RequestDemo2");
@WebServlet("/ResponseDemo1") public class ResponseDemo1 extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {System.out.println("这里是ResponseDemo1");//重定向response.sendRedirect("/web_demo_war_exploded/RequestDemo2");}@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {this.doGet(request, response);} }
- 启动测试,访问
/ResponseDemo1
,控制台得到如下输出,同时地址栏的地址也变更为/RequestDemo2
这里是RequestDemo1
这里是RequestDemo2
重定向的特点
- 浏览器地址栏路径发送变化
当进行重定向访问的时候,由于是由浏览器发送的两次请求,所以地址会发生变化 - 可以重定向到任何位置的资源(服务内容、外部均可)
因为第一次响应结果中包含了浏览器下次要跳转的路径,所以这个路径是可以任意位置资源。 - 两次请求,不能在多个资源使用request共享数据
因为浏览器发送了两次请求,是两个不同的request对象,就无法通过request对象进行共享数据
简单解释重定向和请求转发的区别
一、重定向(Redirect)
1. 什么是重定向?
重定向就是服务器告诉浏览器“你去别的地方找”,然后浏览器自己重新发起请求。
2. 生活中的例子
场景:你去银行办业务
- 你走进银行A,问“我要办信用卡”。
- 银行A的工作人员说:“我们这里不办信用卡,请您去银行B,地址是XX路XX号。”
- 你走出银行A,自己走到银行B。
- 在银行B办完信用卡。
3. 重定向的特点
- 浏览器地址栏会变:因为你去了新的地方,地址栏显示银行B的地址。
- 可以跳转到任何地方:银行A可以让你去银行B,也可以让你去邮局、超市等。
- 两次请求:你去了银行A一次,又去了银行B一次。
- 不能带信息:银行A不能直接把你的资料传给银行B,你得重新说明。
4. 代码示例
// 重定向到另一个页面
resp.sendRedirect("/login.html");
二、请求转发(Forward)
1. 什么是请求转发?
请求转发就是服务器内部帮你转接,浏览器不知道发生了跳转。
2. 生活中的例子
场景:你打电话给客服
- 你打电话给客服A,问“我要投诉”。
- 客服A说:“这个问题我帮你转接给投诉部门,请稍等。”
- 客服A在内部把你的电话转给了投诉部门。
- 投诉部门直接和你通话,解决问题。
3. 请求转发的特点
- 浏览器地址栏不变:因为你一直以为在和客服A通话,地址栏还是显示客服A的号码。
- 只能转接到内部:客服A只能转给公司内部的部门,不能转给其他公司。
- 一次请求:你只打了一次电话,只是内部转接了。
- 可以带信息:客服A可以把你的问题、会员号等信息直接转给投诉部门。
4. 代码示例
// 请求转发到另一个Servlet
req.getRequestDispatcher("/demo2").forward(req, resp);
三、对比总结
| 方面 | 重定向 | 请求转发 |
|------|--------|----------|
| 地址栏 | 会变 | 不变 |
| 请求次数 | 两次 | 一次 |
| 跳转范围 | 任何地方 | 只能内部 |
| 数据传递 | 不能 | 可以 |
| 效率 | 较低 | 较高 |
四、什么时候用哪个?
一、什么时候用重定向?
1. 登录成功后跳转到首页
场景: 用户登录成功,需要跳转到网站首页。
为什么用重定向?
- 登录成功后,如果用户刷新页面,不会重复提交登录信息。
- 地址栏会变成首页地址,用户可以直接收藏或分享首页链接。
代码示例:
// 登录验证成功后
if (loginSuccess) {resp.sendRedirect("/index.html"); // 重定向到首页
}
2. 表单重复提交避免
场景: 用户提交订单后,如果刷新页面,不会重复提交订单。
为什么用重定向?
- 用户提交订单后,重定向到订单成功页面。
- 如果用户刷新页面,只会刷新订单成功页面,不会重复提交订单。
代码示例:
// 订单提交成功后
if (orderSuccess) {resp.sendRedirect("/order-success.html"); // 重定向到成功页面
}
二、什么时候用请求转发?
1. 页面内部模块跳转
场景: 在一个页面内部,根据不同的条件显示不同的内容。
为什么用请求转发?
- 保持同一个URL,用户不知道内部发生了跳转。
- 可以在不同模块间传递数据。
代码示例:
// // 用户访问 /dashboard
if (userRole.equals("admin")) {// 管理员看到管理面板req.getRequestDispatcher("/admin-dashboard.jsp").forward(req, resp);
} else if (userRole.equals("user")) {// 普通用户看到用户面板req.getRequestDispatcher("/user-dashboard.jsp").forward(req, resp);
} else {// 游客看到欢迎页面req.getRequestDispatcher("/guest-dashboard.jsp").forward(req, resp);
}
场景:根据商品类型显示不同的详情模板
// 用户访问 /product/detail?id=123
String productType = product.getType();
if (productType.equals("phone")) {// 手机显示手机专用模板req.getRequestDispatcher("/phone-detail.jsp").forward(req, resp);
} else if (productType.equals("laptop")) {// 笔记本显示笔记本专用模板req.getRequestDispatcher("/laptop-detail.jsp").forward(req, resp);
} else if (productType.equals("book")) {// 书籍显示书籍专用模板req.getRequestDispatcher("/book-detail.jsp").forward(req, resp);
}
效果: 用户访问的都是 /product/detail,但根据商品类型显示不同模板。
场景:根据订单状态显示不同的处理页面
// 用户访问 /order/status?id=456
String orderStatus = order.getStatus();
switch (orderStatus) {case "pending":// 待付款状态req.getRequestDispatcher("/order-pending.jsp").forward(req, resp);break;case "paid":// 已付款状态req.getRequestDispatcher("/order-paid.jsp").forward(req, resp);break;case "shipped":// 已发货状态req.getRequestDispatcher("/order-shipped.jsp").forward(req, resp);break;case "delivered":// 已送达状态req.getRequestDispatcher("/order-delivered.jsp").forward(req, resp);break;
}
效果: 用户访问的都是 /order/status,但根据订单状态显示不同内容。
路径问题
-
问题1:转发的时候路径上没有加虚拟目录
web_demo_war_exploded
,而重定向加了,那么到底什么时候需要加,什么时候不需要加呢? -
其实判断的依据很简单,只需要记住下面的规则即可:
- 浏览器使用:需要加虚拟目录(项目访问路径)
- 服务端使用:不需要加虚拟目录
解释为什么浏览器要加虚拟目录,服务端不加。
一、什么是虚拟目录?
虚拟目录就是项目在服务器上的访问路径,比如:
- 你的项目叫 web_demo
- 访问路径是 http://localhost:8080/web_demo/
- 这里的 /web_demo/ 就是虚拟目录
二、为什么浏览器要加虚拟目录?
// 重定向
resp.sendRedirect("/web_demo/login.html");
原因:
- 重定向是服务器告诉浏览器“你去访问这个地址”
- 浏览器收到指令后,自己重新发起请求
- 浏览器是外部客户端,不知道你的项目在服务器上的具体位置
- 所以必须告诉浏览器完整的访问路径(包含虚拟目录)
生活中的例子
- 你问路,别人告诉你“去银行”,但没说具体地址
- 你必须知道银行的完整地址(包含街道、门牌号),才能找到
三、为什么服务端不加虚拟目录?
请求转发的情况
// 请求转发
req.getRequestDispatcher("/login.html").forward(req, resp);
原因:
- 请求转发是服务器内部操作
- 服务器知道你的项目在文件系统中的真实位置
- 不需要通过外部的虚拟目录来定位资源
- 直接使用内部路径即可
生活中的例子
- 你在公司内部,同事说“去找张三”
- 你知道张三在哪个办公室,不需要知道公司的门牌号
问题2:在重定向的代码中,``web_demo_war_exploded`是固定编码的,如果后期通过Tomcat插件配置了项目的访问路径,那么所有需要重定向的地方都需要重新修改,该如何优化?
在代码中动态去获取项目访问的虚拟目录,request对象中提供了getContextPath()方法
@WebServlet("/ResponseDemo1")
public class ResponseDemo1 extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {System.out.println("这里是ResponseDemo1");//获取虚拟目录String contextPath = request.getContextPath();//把虚拟目录拼在前面response.sendRedirect(contextPath + "/RequestDemo2");}@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {this.doGet(request, response);}
}
Response响应字符数据
要想将字符数据写回到浏览器,我们需要两个步骤:
-
通过Response对象获取字符输出流: PrintWriter writer = resp.getWriter();
-
通过字符输出流写数据: writer.write(“aaa”);
接下来,我们实现通过些案例把响应字符数据给实际应用下:
- 返回一个简单的字符串
hello world
@WebServlet("/RequestDemo1") public class RequestDemo1 extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {PrintWriter printWriter = response.getWriter();printWriter.write("hello world");}@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {this.doGet(request, response);} }
返回一串html字符串,并且能被浏览器解析
- 返返回一串html字符串,并且能被浏览器解析,需要注意设置响应数据的编码为
utf-8
@WebServlet("/RequestDemo1") public class RequestDemo1 extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {//设置响应的数据格式及数据的编码response.setContentType("text/html;charset=utf-8");PrintWriter printWriter = response.getWriter();printWriter.write("<h1>你好<h1>");}@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {this.doGet(request, response);} }
Response响应字节数据
要想将字节数据写回到浏览器,我们需要两个步骤:
-
通过Response对象获取字节输出流:ServletOutputStream outputStream = resp.getOutputStream();
-
通过字节输出流写数据: outputStream.write(字节数据);
接下来,我们实现通过些案例把响应字符数据给实际应用下:
- 返回一个图片文件到浏览器
import javax.servlet.*; import javax.servlet.http.*; import javax.servlet.annotation.*; import java.io.FileInputStream; import java.io.IOException;@WebServlet("/RequestDemo1") public class RequestDemo1 extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {FileInputStream fis = new FileInputStream("D:\\background.jpg");ServletOutputStream os = response.getOutputStream();byte[] buffer = new byte[1024];int len = 0;while ((len = fis.read(buffer)) != -1){os.write(buffer,0,len);}fis.close();//不需要关闭ServletOutputStream,response会帮我们关闭}@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {this.doGet(request, response);} }