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);} }
?