說明:
Servlet3.0及之后版本都是基于注解配置開發(fā)辕坝。
Servlet2.5及之前版本基于web.xml進(jìn)行相關(guān)配置仙蛉。
本次教程基于Servlet2.5版本材彪。
什么是Servlet?
Servlet是使用Java語言編寫的運行在Web服務(wù)器上的小型應(yīng)用程序觉痛,用于接收客戶端發(fā)送的請求并響應(yīng)數(shù)據(jù)給客戶端役衡。
Servlet是一個接口,也是開發(fā)Web項目的規(guī)范之一薪棒,任何實現(xiàn)了Servlet接口的自定義類都可以實現(xiàn)請求響應(yīng)的效果手蝎。通常,Servlet處理一般基于HTTP協(xié)議的Web服務(wù)器上的請求俐芯。它是JavaWeb三大組件之一棵介。
Servlet的主要功能包括:
- 接收客戶端發(fā)送的請求:當(dāng)用戶在Web瀏覽器中輸入URL或點擊鏈接時,Web服務(wù)器會調(diào)用相應(yīng)的Servlet來處理請求泼各。
- 生成動態(tài)Web內(nèi)容:Servlet可以根據(jù)請求參數(shù)鞍时、會話信息或其他數(shù)據(jù)動態(tài)生成HTML、XML或其他格式的響應(yīng)內(nèi)容扣蜻,并將其發(fā)送回客戶端。
- 會話管理:Servlet可以維護(hù)客戶端與服務(wù)器之間的會話狀態(tài)及塘,以便跟蹤用戶的活動和數(shù)據(jù)莽使。
- 集成數(shù)據(jù)庫:Servlet可以與數(shù)據(jù)庫進(jìn)行交互,執(zhí)行CRUD(創(chuàng)建笙僚、讀取芳肌、更新、刪除)操作,并將結(jié)果呈現(xiàn)給用戶亿笤。
- 過濾和攔截請求:Servlet可以用于實現(xiàn)請求過濾和攔截功能翎迁,例如對請求進(jìn)行身份驗證、授權(quán)净薛、壓縮等處理汪榔。
- 集成其他Web組件:Servlet可以與其他Web組件(如JSP、JSTL等)集成肃拜,以實現(xiàn)更復(fù)雜的Web應(yīng)用程序痴腌。
總之,Servlet是Java Web開發(fā)中的重要組件燃领,用于處理網(wǎng)絡(luò)請求士聪、生成動態(tài)Web內(nèi)容和實現(xiàn)各種Web應(yīng)用程序功能。
案例實操:
先創(chuàng)建web工程猛蔽,創(chuàng)建過程不在此次教程范圍剥悟,基于不同IDE創(chuàng)建web工程請自行g(shù)oogle。
① 自定義類實現(xiàn)Servlet接口
public class HelloServlet implements Servlet {
/**
* service 方法是專門用來處理請求和響應(yīng)的
* @param servletRequest
* @param servletResponse
* @throws ServletException
* @throws IOException
*/
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("Hello Servlet 被訪問了");
}
}
② 配置web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!-- servlet 標(biāo)簽給 Tomcat 配置 Servlet 程序 -->
<servlet>
<!--servlet-name 標(biāo)簽 Servlet 程序起一個別名(一般是類名) -->
<servlet-name>helloServlet</servlet-name>
<!--servlet-class 是 Servlet 程序的全類名-->
<servlet-class>com.evan.java.HelloServlet</servlet-class>
</servlet>
<!--servlet-mapping 標(biāo)簽給 servlet 程序配置訪問地址-->
<servlet-mapping>
<!--servlet-name 標(biāo)簽的作用是告訴服務(wù)器曼库,我當(dāng)前配置的地址給哪個 Servlet 程序使用-->
<servlet-name>helloServlet</servlet-name>
<!--url-pattern 標(biāo)簽配置訪問地址 <br/>
/ 斜杠在服務(wù)器解析的時候区岗,表示地址為:http://ip:port/工程路徑 <br/>
/hello 表示地址為:http://ip:port/工程路徑/hello <br/>
-->
<url-pattern>/hello</url-pattern>
</servlet-mapping>
</web-app>
瀏覽器url執(zhí)行Servlet程序的過程:
- 用戶在瀏覽器地址欄輸入http://localhost:8080/day02/hello發(fā)送請求
- Tomcat服務(wù)器接收到請求讀取web.xml中<url-pattern>標(biāo)簽
- 然后再通過<servlet-name>找到<servlet-class>
- 再通過<servlet-class>標(biāo)簽中的全類名定位到Servlet接口實現(xiàn)類
- 請求到達(dá)Servlet接口實現(xiàn)類會被Tomcat加載到內(nèi)存中,同時創(chuàng)建一個請求對象凉泄,處理客戶端請求躏尉;創(chuàng)建一個響應(yīng)對象,響應(yīng)客戶端請求后众。
- Servlet 調(diào)用 service() 方法胀糜,傳遞請求和響應(yīng)對象作為參數(shù)。
- service() 方法獲取請求對象的信息蒂誉,根據(jù)請求信息獲取請求資源教藻。
- 然后service()方法使用響應(yīng)對象的方法,將請求資源響應(yīng)給Servlet右锨,最后回傳給瀏覽器括堤。
-
對于更多的客戶端請求,Tomcat創(chuàng)建新的請求和響應(yīng)對象绍移,仍然調(diào)用此 Servlet 的 service() 方法悄窃,將這兩個對象作為參數(shù)傳遞給它。如此重復(fù)以上的循環(huán)蹂窖,但無需再次調(diào)用 init() 方法轧抗。一般 Servlet 只初始化一次(只有一個對象),當(dāng) Tomcat不再需要 Servlet 時(一般當(dāng) Server 關(guān)閉時)瞬测,Tomcat調(diào)用 Servlet 的 destroy() 方法横媚。
Servlet程序的生命周期
實現(xiàn)Servlet規(guī)范接口演示Servlet程序的生命周期
public class HelloServlet implements Servlet {
public HelloServlet(){
System.out.println("1.執(zhí)行Servlet程序的構(gòu)造器");
}
@Override
public void init(ServletConfig servletConfig) throws ServletException {
System.out.println("2.執(zhí)行Servlet程序的初始化方法");
}
/**
* service方法專門處理請求和響應(yīng)的
* @param servletRequest
* @param servletResponse
* @throws ServletException
* @throws IOException
*/
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("3.執(zhí)行Servlet程序的service方法:Hello Servlet訪問了...");
}
@Override
public void destroy() {
System.out.println("4.執(zhí)行Servlet程序的銷毀方法");
}
}
Servlet程序的生命周期的執(zhí)行流程:
- 執(zhí)行空參構(gòu)造器
- 執(zhí)行Servlet程序初始化方法
- 執(zhí)行service方法(對請求和響應(yīng)進(jìn)行處理)
- 執(zhí)行Servlet程序的銷毀方法(銷毀方法在Tomcat服務(wù)器停止的時候執(zhí)行)
結(jié)論:
生命周期:從出生到消亡的過程就是生命周期纠炮。對應(yīng)Servlet中的三個方法:init(),service(),destroy()。
默認(rèn)情況下:
第一次接收請求時灯蝴,這個Servlet會進(jìn)行實例化(調(diào)用構(gòu)造方法)恢口、初始化(調(diào)用init())、然后調(diào)用服務(wù)(調(diào)用service())穷躁。
從第二次請求開始耕肩,每一次都是調(diào)用服務(wù)。
當(dāng)容器關(guān)閉時折砸,其中的所有的servlet實例會被銷毀看疗,調(diào)用銷毀方法。
Servlet程序的初始化時機
- Servlet實例Tomcat服務(wù)器只會創(chuàng)建一個睦授,所有的請求都是這個實例去響應(yīng)两芳。
- 默認(rèn)情況下,第一次請求時去枷,tomcat才會去實例化怖辆,初始化,然后再服務(wù).這樣的好處是什么删顶? 提高系統(tǒng)的啟動速度 竖螃。 這樣的缺點是什么? 第一次請求時逗余,耗時較長特咆。
- 因此得出結(jié)論: 如果需要提高系統(tǒng)的啟動速度,當(dāng)前默認(rèn)情況就是這樣录粱。如果需要提高響應(yīng)速度腻格,我們應(yīng)該設(shè)置Servlet的初始化時機。
- Servlet的初始化時機設(shè)置:
默認(rèn)是第一次接收請求時啥繁,實例化菜职,初始化。
我們可以通過<load-on-startup>
來設(shè)置servlet啟動的先后順序,將加載Servlet程序的初始化過程提前到項目啟動時旗闽,數(shù)字越小酬核,啟動越靠前,最小值0适室。
<servlet>
<servlet-name>helloServlet</servlet-name>
<servlet-class>com.evan.java.HelloServlet</servlet-class>
<!-- 設(shè)置Servlet程序的啟動時機 -->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>helloServlet</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
Servlet的線程不安全問題
在Web應(yīng)用程序中嫡意,一個Servlet可能同時被多個用戶訪問,此時Web容器會為每個用戶創(chuàng)建一個線程來執(zhí)行Servlet捣辆。如果Servlet不涉及共享資源鹅很,則無需擔(dān)心多線程問題。但如果Servlet需要訪問共享資源罪帖,必須確保其線程安全促煮。
當(dāng)Web容器處理多個線程產(chǎn)生的請求時,每個線程會執(zhí)行單一的Servlet實例的service()方法整袁。如果一個線程需要根據(jù)某個成員變量的值進(jìn)行邏輯判斷菠齿,但在判斷過程中另一個線程改變了該變量的值,這可能導(dǎo)致第一個線程的執(zhí)行路徑發(fā)生變化坐昙。
由于Servlet是線程不安全的绳匀,應(yīng)盡量避免在Servlet中定義成員變量。如果必須定義成員變量炸客,應(yīng)遵循以下原則:
不要修改成員變量的值疾棵。
不要根據(jù)成員變量的值進(jìn)行邏輯判斷。
盡量減少在doGet()或doPost()方法中使用同步代碼痹仙,并在最小代碼塊范圍內(nèi)實施同步是尔。
HttpServlet的使用
自定義類繼承HttpServlet類,實現(xiàn)請求方式分發(fā)處理开仰。
① 自定義子類繼承HttpServlet類重寫doGet()與doPost()
public class HelloServlet2 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("get請求...");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("post請求...");
}
}
② 配置web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!-- servlet 標(biāo)簽給 Tomcat 配置 Servlet 程序 -->
<servlet>
<!--servlet-name 標(biāo)簽 Servlet 程序起一個別名(一般是類名) -->
<servlet-name>helloServlet2</servlet-name>
<!--servlet-class 是 Servlet 程序的全類名-->
<servlet-class>com.evan.java.HelloServlet2</servlet-class>
</servlet>
<!--servlet-mapping 標(biāo)簽給 servlet 程序配置訪問地址-->
<servlet-mapping>
<!--servlet-name 標(biāo)簽的作用是告訴服務(wù)器拟枚,我當(dāng)前配置的地址給哪個 Servlet 程序使用-->
<servlet-name>helloServlet2</servlet-name>
<!--url-pattern 標(biāo)簽配置訪問地址 <br/>
/ 斜杠在服務(wù)器解析的時候,表示地址為:http://ip:port/工程路徑 <br/>
/hello 表示地址為:http://ip:port/工程路徑/hello <br/>
-->
<url-pattern>/hello2</url-pattern>
</servlet-mapping>
</web-app>
③ 測試
方式1:使用form表單
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="http://localhost:8080/day02/hello2" method="post">
<input type="text" name="uname"><br/>
<input type="text" name="price"><br/>
<input type="submit" value="提交">
</form>
</body>
</html>
方式2:使用Postman工具
請求分發(fā)的執(zhí)行流程:
- 用戶點擊form表單提交請求 <form action="xxx" method="post">
- Tomcat服務(wù)器收到請求众弓,去web.xml中找到<url-pattern>標(biāo)簽value
- 找<servlet-mapping>標(biāo)簽<servlet-name>里面的value
- 通過<servlet-mapping>標(biāo)簽<servlet-name>里面的value找<servlet>標(biāo)簽中一致的<servlet-name>
- 然后再根據(jù)servlet找到<servlet-class>的value
- 用戶發(fā)送的是 method="post" 請求恩溅,因此Tomcat找到HelloServlet2類中的doPost方法執(zhí)行。
Servlet類的繼承體系
ServletConfig類
概述
- ServletConfig 是Servlet程序的配置信息類谓娃。
- Servlet程序和ServletConfig對象都是由Tomcat創(chuàng)建脚乡,我們調(diào)用需要使用的相關(guān)信息即可。
- Servlet程序默認(rèn)只在第一次訪問的時候創(chuàng)建滨达,ServletConfig是每個Servlet程序創(chuàng)建時奶稠,就創(chuàng)建一個對應(yīng)的ServletConfig對象。
作用
- 可以獲取Servlet程序的別名(web.xml中<servlet-name>的值)弦悉。
- 獲取初始化參數(shù)<init-param>窒典。
- 獲取ServletContext對象。
public class HelloServlet3 implements Servlet {
@Override
public void init(ServletConfig servletConfig) throws ServletException {
System.out.println("執(zhí)行初始化方法....");
//獲取web.xml中<servlet-name>的值稽莉,即Servlet程序別名
System.out.println("Servlet程序別名:" + servletConfig.getServletName());
//獲取初始化參數(shù)值,即:<init-param>中<param-name>的參數(shù)值<param-value>
String username = servletConfig.getInitParameter("username");
System.out.println("初始化參數(shù)值:" + username);
//獲取servletContext對象,即Application上下文環(huán)境
System.out.println("Servlet程序的上下文對象:" + servletConfig.getServletContext());
}
}
瀏覽器地址欄輸入:http://localhost:8080/day02/hello3
控制臺輸出:
執(zhí)行初始化方法....
Servlet程序別名:helloServlet3
初始化參數(shù)值:lisi
Servlet程序的上下文路徑org.apache.catalina.core.ApplicationContextFacade@79c8f2b3
注意點:
子類繼承HttpServlet類重寫init方法時瀑志,必須顯式的調(diào)用父類的init方法
//子類繼承HttpServlet類重寫其初始化方法時,必須顯式調(diào)用父類的init方法
public class HelloServlet2 extends HttpServlet {
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
System.out.println("重寫初始化操作...");
}
}
ServletContext類
概述
- ServletContext是一個接口污秆,表示Servlet程序上下文對象
- 一個web工程劈猪,只有一個ServletContext對象實例
- ServletContext對象是一個==域?qū)ο?/strong>==
- ServletContext是在web工程部署啟動時創(chuàng)建的,在web工程停止時銷毀.
什么是域?qū)ο?
- 這里的域?qū)ο笾竪eb工程中的Servlet程序在執(zhí)行期間存取的數(shù)據(jù)都保存在一個對象中。這個對象就是域?qū)ο?類似Map集合存取數(shù)據(jù)的概念)良拼。
- 這里的域值存取數(shù)據(jù)的操作范圍战得;范圍指整個web工程。
域?qū)ο笈cMap對比
對象 | 存數(shù)據(jù) | 取數(shù)據(jù) | 刪除數(shù)據(jù) |
---|---|---|---|
Map | put() | get() | remove() |
域?qū)ο?/td> | setAttribute() | getAttribute() | removeAttribute() |
作用
- 獲取web.xml中配置的上下文參數(shù)context-param
- 獲取當(dāng)前的工程路徑庸推,格式:/工程路徑
- 獲取工程部署后在服務(wù)器磁盤上的絕對路徑
- 跟Map一樣存取數(shù)據(jù)
//獲取ServletContext參數(shù)
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("接收到GET請求...");
//獲取ServletConfig對象
ServletConfig config = getServletConfig();
//通過ServletConfig對象獲取ServletContext對象,即web.xml中的上下文參數(shù)<context-param>
ServletContext context = config.getServletContext();
//獲取<context-param>參數(shù)值
String username = context.getInitParameter("username");
System.out.println(username);
//獲取<context-param>參數(shù)值
String password = context.getInitParameter("password");
System.out.println(password);
//獲取當(dāng)前Servlet程序上下文(即:/工程名 或 Servlet實例)
String contextPath = context.getContextPath(); // /day01_hello
System.out.println(contextPath);
/**
* 獲取工程部署(編譯)后在服務(wù)器磁盤上的絕對路徑
* /斜杠被服務(wù)器解析地址為:http://IP:port/工程名/ 映射地址:IDEA中的web目錄下
* D:\java-workspace\javaweb-pro\out\artifacts\day01_hello_war_exploded\
* D:\java-workspace\javaweb-pro\out\artifacts\day01_hello_war_exploded\imgs
* day01_hello_war_exploded相當(dāng)于web工程的web目錄
*/
System.out.println(context.getRealPath("/"));
System.out.println(context.getRealPath("/imgs"));
}
<!-- context-param是上下文參數(shù)(屬于整個web工程),可配置多個 -->
<context-param>
<param-name>username</param-name>
<param-value>context</param-value>
</context-param>
<!-- context-param是上下文參數(shù)(屬于整個web工程) -->
<context-param>
<param-name>password</param-name>
<param-value>123456</param-value>
</context-param>
輸出結(jié)果:
接收到GET請求...
hangman
123456
/day01_hello
D:\java-workspace\javaweb-pro\out\artifacts\day01_hello_war_exploded
D:\java-workspace\javaweb-pro\out\artifacts\day01_hello_war_exploded\imgs
在ServletContext中存取數(shù)據(jù)
public class ContextServlet1 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletConfig servletConfig = getServletConfig();
ServletContext context = servletConfig.getServletContext();
System.out.println("保存之前的數(shù)據(jù):" + context.getAttribute("key1"));
context.setAttribute("key1","value1");
System.out.println("context1中獲取的值:" + context.getAttribute("key1"));
}
}
public class ContextServlet2 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext servletContext = getServletContext();
System.out.println("context2獲取域中的key1的值:" + servletContext.getAttribute("key1"));
}
}
<servlet>
<servlet-name>contextServlet1</servlet-name>
<servlet-class>com.evan.java.ContextServlet1</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>contextServlet1</servlet-name>
<url-pattern>/context1</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>contextServlet2</servlet-name>
<servlet-class>com.evan.java.ContextServlet2</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>contextServlet2</servlet-name>
<url-pattern>/context2</url-pattern>
</servlet-mapping>
結(jié)論:
當(dāng)首次獲取域?qū)ο笾械臄?shù)據(jù)常侦,域?qū)ο鬄閚ull浇冰,當(dāng)向域?qū)ο笾刑砑恿藬?shù)據(jù),可以獲取指定參數(shù)的值聋亡,添加的數(shù)據(jù)會一直保存在整個web工程中肘习,其它Servlet程序也可以獲取域?qū)ο笾械膮?shù)值,直到web工程停止坡倔。
HttpServletRequest類
客戶端每次發(fā)送請求進(jìn)入 Web容器中漂佩,Web容器就會把請求過來的 HTTP 協(xié)議信息解析好封裝到 Request 對象中。 然后傳遞到 service 方法(doGet 和 doPost)中給我們使用罪塔。我們可以通過 HttpServletRequest 對象投蝉,獲取到所有請求的信息。
常用方法
getRequestURI() 獲取請求的資源路徑
getRequestURL() 獲取請求的統(tǒng)一資源定位符(絕對路徑)
getRemoteHost() 獲取客戶端的 ip 地址
getHeader() 獲取請求頭
getParameter() 獲取請求的參數(shù)
getParameterValues() 獲取請求的參數(shù)(多個值的時候使用)
getMethod() 獲取請求的方式 GET 或 POST
setAttribute(key, value); 設(shè)置域數(shù)據(jù)
getAttribute(key); 獲取域數(shù)據(jù)
getRequestDispatcher() 獲取請求轉(zhuǎn)發(fā)對象
public class RequestAPIServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("請求相對路徑:" + req.getRequestURI());
System.out.println("請求絕對路徑:" + req.getRequestURL());
System.out.println("客戶端IP:" + req.getRemoteHost());
System.out.println("請求頭:" + req.getHeader("user-Agent"));
// 獲取請求參數(shù)
String username = req.getParameter("username");
//解決get請求的中文亂碼
//1 先以 iso8859-1 進(jìn)行編碼
//2 再以 utf-8 進(jìn)行解碼
//username = new String(username.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
System.out.println(username);
var pwd = req.getParameter("pwd");
System.out.println(pwd);
System.out.println("請求方式:" + req.getMethod());
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("請求相對路徑:" + req.getRequestURI());
System.out.println("請求絕對路徑:" + req.getRequestURL());
System.out.println("客戶端IP:" + req.getRemoteHost());
System.out.println("請求頭:" + req.getHeader("user-Agent"));
//設(shè)置請求體的字符集為UTF-8,解決POST請求中文亂碼
//字符集設(shè)置要在獲取請求參數(shù)前
req.setCharacterEncoding("UTF-8");
var username = req.getParameter("username");
/*byte[] bytes = username.getBytes("ISO-8857-1");
var data = new String(bytes, StandardCharsets.UTF_8);
System.out.println(data);*/
System.out.println(username);
var pwd = req.getParameter("pwd");
System.out.println(pwd);
System.out.println("請求方式:" + req.getMethod());
}
}
HttpServletResponse類
HttpServletResponse類和HttpServletRequest類一樣征堪,都會在瀏覽器發(fā)送請求之后瘩缆,Web容器會創(chuàng)建一個對象傳給Servlet程序中的service(doGet和doPost)方法使用。只不過HttpServletResponse表示將請求信息進(jìn)行處理后響應(yīng)給瀏覽器请契,HttpServletRequest表示獲取瀏覽器發(fā)送的請求信息咳榜。
輸出流的說明
字節(jié)流 getOutputStream(); 常用于下載二進(jìn)制數(shù)據(jù)文件(傳遞二進(jìn)制數(shù)據(jù))
字符流 getWriter(); 常用于回傳字符串?dāng)?shù)據(jù)(常用)
使用了字符流,就不能使用字節(jié)流爽锥;反之亦然涌韩,否則報錯java.lang.IllegalStateException
public class MyHttpServletResponse extends HttpServlet {
//獲取字符流響應(yīng)數(shù)據(jù)
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//解決響應(yīng)數(shù)據(jù)中文亂碼
//設(shè)置瀏覽器與服務(wù)器都使用utf-8字符集,并設(shè)置了響應(yīng)頭
//此方法要在獲取流對象之前使用
resp.setContentType("text/html;charset=UTF-8");
//獲取字符流對象
var writer = resp.getWriter();
//服務(wù)端向客戶端響應(yīng)數(shù)據(jù)
writer.write("Hello,Browser,我是服務(wù)端!");
}
//獲取字節(jié)流響應(yīng)數(shù)據(jù)
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//resp.setContentType("text/html;charset=UTF-8");
//var os = resp.getOutputStream();
//os.write("哈哈哈哈哈".getBytes(StandardCharsets.UTF_8));
String str = "我是ABC";
var os = resp.getOutputStream();
byte[] buff = str.getBytes(StandardCharsets.UTF_8);
var data = new String(buff, StandardCharsets.ISO_8859_1);
os.print(data);
System.out.println(data);
}
}
響應(yīng)報文給客戶端的中文亂碼問題:
解決中文亂碼必須放到獲取流之前氯夷。
//推薦使用
// 它會同時設(shè)置服務(wù)器和客戶端都使用 UTF-8 字符集臣樱,還設(shè)置了響應(yīng)頭
// 此方法一定要在獲取流對象之前調(diào)用才有效
resp.setContentType("text/html;charset=UTF-8");
// 不推薦使用
// 設(shè)置服務(wù)器字符集為 UTF-8
resp.setCharacterEncoding("UTF-8");
// 通過響應(yīng)頭,設(shè)置瀏覽器也使用 UTF-8 字符集
resp.setHeader("Content-Type", "text/html; charset=UTF-8");
請求轉(zhuǎn)發(fā)與重定向
請求轉(zhuǎn)發(fā)
請求轉(zhuǎn)發(fā)指把客戶端發(fā)送過來的請求交給指定Servlet程序腮考,而內(nèi)部把該請求轉(zhuǎn)交給另外一個Servlet程序進(jìn)行處理雇毫,完成之后響應(yīng)給客戶端(響應(yīng)的過程中會路過轉(zhuǎn)交的Servlet)。
特點:
因為請求轉(zhuǎn)發(fā)是內(nèi)部處理請求信息踩蔚,所以客戶端看不到處理請求的方式棚放;客戶端發(fā)送一次請求,內(nèi)部會把請求信息存儲到共享域中馅闽,然后轉(zhuǎn)交給其他Servlet處理飘蚯,該請求只能轉(zhuǎn)發(fā)到web目錄下,不可以訪問web工程外的資源福也。
- 地址欄不會改變
- 發(fā)送一次請求
- 可以訪問WEB-INF下的資源
- 不可以訪問web工程外的資源
- 可以獲取共享Request域中的數(shù)據(jù)
//Servlet程序1
public class Servlet1 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("servlet1:請求處理");
//獲取請求參數(shù)
var username = req.getParameter("username");
System.out.println(username);
//向Servlet共享域中添加數(shù)據(jù)
req.setAttribute("key1","李四");
//告訴請求->轉(zhuǎn)發(fā)到servlet2程序地址:請求轉(zhuǎn)發(fā)必須以/開頭局骤,/表示上下文路徑,映射到web目錄
var requestDispatcher = req.getRequestDispatcher("/servlet2");
//將請求信息定位到轉(zhuǎn)發(fā)信息處暴凑,即跳轉(zhuǎn)至Servlet2
requestDispatcher.forward(req,resp);
}
}
//Servlet程序2
public class Servlet2 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//獲取請求參數(shù)
var username = req.getParameter("username");
//獲取Servlet共享域中的數(shù)據(jù)
var key1 = req.getAttribute("key1");
System.out.println(key1);
System.out.println("servlet2:處理請求信息...");
}
}
<!-- Servlet程序1 -->
<servlet>
<servlet-name>servlet1</servlet-name>
<servlet-class>com.evan.java.Servlet1</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>servlet1</servlet-name>
<url-pattern>/servlet1</url-pattern>
</servlet-mapping>
<!-- Servlet程序2 -->
<servlet>
<servlet-name>servlet2</servlet-name>
<servlet-class>com.evan.java.Servlet2</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>servlet2</servlet-name>
<url-pattern>/servlet2</url-pattern>
</servlet-mapping>
請求轉(zhuǎn)發(fā)使用相等路徑的問題
使用相對路徑在工作時都會參照瀏覽器地址欄中的地址來進(jìn)行跳轉(zhuǎn)峦甩;所以當(dāng)瀏覽器發(fā)送請求跳轉(zhuǎn)的路徑是相對路徑時,會找地址欄中的地址现喳;但使用請求轉(zhuǎn)發(fā)時凯傲,地址欄中的地址始終都是請求跳轉(zhuǎn)前的路徑犬辰,此時相對路徑去找地址欄中地址進(jìn)行參照時,會出現(xiàn)路徑錯誤情況泣洞。
<!DOCTYPE html>
<html lang="zh_CN">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
這是 a 下的 b 下的 c.html 頁面<br/>
<a href="../../index.html">跳回首頁</a><br/>
</body>
</html>
請求跳轉(zhuǎn)路徑的問題演示
public class Servlet3 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("servlet3:請求轉(zhuǎn)發(fā)");
req.getRequestDispatcher("a/b/c.html").forward(req,resp);
}
}
<servlet>
<servlet-name>servlet3</servlet-name>
<servlet-class>com.evan.servlet.Servlet3</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>servlet3</servlet-name>
<url-pattern>/s3</url-pattern>
</servlet-mapping>
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>c.html</title>
<base href="http://localhost:8080/day02/a/b/" hidden="hidden"/>
</head>
<body>
這是 a目錄下的 b目錄下的 c.html 頁面<br/>
<a href="../../index.jsp">跳回首頁</a><br/>
</body>
</html>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>index.jsp</title>
</head>
<body>
<a href="a/b/c.html">a/b/c.html</a><br/>
<a href="http://localhost:8080/day02/s3">請求轉(zhuǎn)發(fā):a/b/c.html</a>
</body>
</html>
可以看到根據(jù)地址欄轉(zhuǎn)發(fā)報錯:
解決方案
此時可以使用bose標(biāo)簽設(shè)置相對路徑工作時參照的地址忧风。
<!DOCTYPE html>
<html lang="zh_CN">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!--base標(biāo)簽設(shè)置頁面相對路徑工作時參照的地址
href 屬性就是參數(shù)的地址值
-->
<base href="http://localhost:8080/day01-hello/a/b/">
</head>
<body>
這是 a 下的 b 下的 c.html 頁面<br/>
<a href="../../index.html">跳回首頁</a><br/>
</body>
</html>
說明:
. 表示當(dāng)前路徑
.. 表示上一級路徑
在實際開發(fā)中,盡量使用絕對路徑球凰。
請求重定向
指客戶端(瀏覽器)給服務(wù)器發(fā)送請求,服務(wù)器收到請求后告訴客戶端腿宰,該請求有了新地址呕诉,請到新地址去訪問,叫請求重定向(之前的地址可能廢棄吃度,需要重新訪問新地址)甩挫。
特點:
- 瀏覽器地址欄發(fā)生改變
- 發(fā)送兩次請求
- 不能共享Request域數(shù)據(jù)
- 不能訪問WEB-INF目錄下的資源
- 可以訪問工程外部資源
public class Request1 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("r1:請求地址已廢棄!");
//方式一
//設(shè)置響應(yīng)狀態(tài)碼,302表示重定向
//resp.setStatus(302);
//設(shè)置響應(yīng)頭椿每,說明新地址在哪里
//resp.setHeader("location","http://localhost:8080/day03-servlet/r2");
//方式二
//設(shè)置重定向地址r2,內(nèi)部封裝重定向狀態(tài)碼302
//
resp.sendRedirect("https://www.baidu.com");
resp.sendRedirect("http://localhost:8080/day03-servlet/r2");
}
}
public class Request2 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("r2:正在處理請求...");
//響應(yīng)數(shù)據(jù)到客戶端
resp.getWriter().write("r2:請求已處理");
}
}
<!-- request1 -->
<servlet>
<servlet-name>request1</servlet-name>
<servlet-class>com.evan.java.Request1</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>request1</servlet-name>
<url-pattern>/r1</url-pattern>
</servlet-mapping>
<!-- request2 -->
<servlet>
<servlet-name>request2</servlet-name>
<servlet-class>com.evan.java.Request2</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>request2</servlet-name>
<url-pattern>/r2</url-pattern>
</servlet-mapping>