Servlet概述
Servlet 是Java Server Applet的簡稱,稱爲小服務器程序,用Java編寫的服務器端程序,主要功能交互式地瀏覽和修改數據,生成動態Web內容。
Servlet運行於支持Java的應用服務器中。從實現上講,Servlet可以響應任何類型的請求,但絕大多數情況下Servlet只用來擴展基於HTTP協議的Web服務器。
Servlet編程需要使用到javax.servlet 和 javax.servlet.http兩個包下面的類和接口,在所有的類和接口中,javax.servlet.Servlet 接口最爲重要。所有的servlet程序都必須實現該接口或者繼承實現了該接口的類。javax.servlet.ServletConfig;
javax.servlet.ServletException;
javax.servlet.http.HttpServlet;
javax.servlet.http.HttpServletRequest;
javax.servlet.http.HttpServletResponse;
javax.servlet.http.HttpSession;
javax.servlet.http.Cookie;
HTTP協議
什麼是HTTP協議
超文本傳輸協議(HTTP,HyperText Transfer Protocol)是互聯網上應用最爲廣泛的一種網絡協議,是一個基於請求與響應模式的、無狀態的、應用層的協議,常基於TCP的連接方式。
HTTP協議的主要特點如下:
1.支持客戶端/服務器模式。
2.簡單快速: 客戶向服務器請求服務時,只需傳送請求方法和路徑。請求方法常用的有GET、POST。每種方法規定了客戶與服務器聯繫的類型不同。由於HTTP協議簡單,使得HTTP服務器的程序規模小,因而通信速度很快。
3.靈活: HTTP允許傳輸任意類型的數據對象。傳輸的類型由Content-Type加以標記。
4.無連接: 無連接的含義是限制每次連接只處理一個請求。服務器處理完客戶的請求,並收到客戶的應答後,即斷開連接。採用這種方式可以節省傳輸時間。
5.無狀態: HTTP協議是無狀態協議。無狀態是指協議對於事務處理沒有記憶能力。缺少狀態意味着如果後續處理需要前面的信息,則它必須重傳,這樣可能導致每次連接傳送的數據量增大。另一方面,在服務器不需要先前信息時它的應答就較快。
Http協議的通信
HTTP通信機制是在一次完整的HTTP通信過程中,Web瀏覽器與Web服務器之間將完成下列7個步驟:
1、 建立TCP連接
在HTTP工作開始之前,Web瀏覽器首先要通過網絡與Web服務器建立連接,該連接是通過TCP來完成的,該協議與IP協議共同構建Internet,即著名的TCP/IP協議族,因此Internet又被稱作是TCP/IP網絡。HTTP是比TCP更高層次的應用層協議,根據規則,只有低層協議建立之後才能進行更高層協議的連接。因此,首先要建立TCP連接,一般TCP連接的端口號是80
2、 瀏覽器向Web服務器發送請求命令
一旦建立了TCP連接,Web瀏覽器就會向Web服務器發送請求命令
例如:GET /sample/hello.html HTTP/1.1
3、 瀏覽器發送請求頭信息
瀏覽器發送其請求命令之後,還要以頭信息的形式向Web服務器發送一些別的信息,之後瀏覽器發送了一空白行來通知服務器,它已經結束了該頭信息的發送。
4、 Web服務器應答
客戶機向服務器發出請求後,服務器會客戶機回送應答,
HTTP/1.1 200 OK
應答的第一部分是協議的版本號和應答狀態碼
5、 Web服務器發送應答頭信息
正如客戶端會隨同請求發送關於自身的信息一樣,服務器也會隨同應答向用戶發送關於它自己的數據及被請求的文檔。
6、 Web服務器向瀏覽器發送數據
Web服務器向瀏覽器發送頭信息後,它會發送一個空白行來表示頭信息的發送到此爲結束,接着,它就以Content-Type應答頭信息所描述的格式發送用戶所請求的實際數據
7、 Web服務器關閉TCP連接
一般情況下,一旦Web服務器向瀏覽器發送了請求數據,它就要關閉TCP連接,然後如果瀏覽器或者服務器在其頭信息加入了這行代碼
Connection:keep-alive
TCP連接在發送後將仍然保持打開狀態,於是,瀏覽器可以繼續通過相同的連接發送請求。保持連接節省了爲每個請求建立新連接所需的時間,還節約了網絡帶寬
請求和響應數據格式
HTTP請求報文
當瀏覽器向Web服務器發出請求時,它向服務器傳遞了一個數據塊,也就是請求信息(請求報文),HTTP請求信息由4部分組成:
1 請求行 請求方法/地址 URI協議/版本
2 請求頭(Request Header)
3 空行
4 請求正文
HTTP請求的例子:
POST /hello HTTP/1.1
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Language:zh-CN,zh;q=0.8,en-GB;q=0.6,en;q=0.4
Connection:Keep-Alive
Host:localhost:8080
User-Agent:Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36
Accept-Encoding:gzip, deflate, br
username=zhangsan&age=20&add=beijing
HTTP響應報文
HTTP應答與HTTP請求相似,HTTP響應也由4個部分構成,分別是:
1、狀態行
2、響應頭(Response Header)
3、空行
4、響應正文
HTTP/1.1 200 OK //狀態行
Server: nginx
Date: Tue, 31 May 2016 02:09:24 GMT
Content-Type: text/html;charset=UTF-8
Connection: keep-alive
Vary: Accept-Encoding
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: X-Requested-With,access_token,access-token,content-type,multipart/form-data,application/x-www-form-urlencoded
Access-Control-Allow-Methods: GET,POST,OPTIONS
Content-Length: 49
<!DOCTYPE html> //正文
<html>
<head>
<title>網頁標題</title>
<meta charset="utf-8">
</head>
<body>
網頁內容
</body>
</html>
Servlet使用
Servlet接口
在ServletAPI中最重要的是Servlet接口,所有Servlet都會直接或間接的與該接口發生聯繫,或是直接實現該接口,或間接繼承自實現了該接口的類。
該接口包括以下五個方法:
//初始化servlet方法
init(ServletConfig config)
//獲取Servlet配置
ServletConfig getServletConfig()
//服務方法:執行處理瀏覽器請求的方法
service(ServletRequest req,ServletResponse res)
//獲取servlet信息:作者版權
String getServletInfo()
//銷燬方法
destroy( )
處理方式:
(1)第一次訪問Servlet時,服務器會創建Servlet對象,並調用init方法,再調用service方法
(2)第二次再訪問時,Servlet對象已經存在,不再創建,執行service方法
(3)當服務器停止,會釋放Servlet,調用destroy方法。
HttpServlet類
是繼承GenericServlet的基礎上進一步的擴展。
提供將要被子類化以創建適用於 Web 站點的 HTTP servlet 的抽象類。HttpServlet 的子類至少必須重寫一個方法,該方法通常是以下這些方法之一:
doGet
,如果 servlet 支持 HTTP GET 請求
doPost
,用於 HTTP POST 請求
doPut
,用於 HTTP PUT 請求
doDelete
,用於 HTTP DELETE 請求
init
和 destroy
,用於管理 servlet 的生命週期內保存的資源
getServletInfo
,servlet
使用它提供有關其自身的信息
Servlet的兩種創建方式
Servlet的第一種創建方式:繼承HttpServlet
Servlet創建的第二種方式:實現接口Servlet
Servlet的兩種配置方式
第一種註解式配置 Servlet3.0及以後 :
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
使用註解方式:
註解類 WebServlet
name:
serlvet名字 (可選)
value
: 配置url路徑
urlPatterns
:配置url路徑 ,和value作用一樣,不能同時使用
loadOnStartup
:配置Servlet的創建的時機, 如果是0或者正數 啓動程序時創建,如果是負數,則訪問時創建。數字越小優先級越高。
initParams
:配置Servlet的初始化參數
第二種web.xml配置 Servlet所有版本都支持:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1">
<display-name>Web_Day11</display-name>
<!--Servlet的第二種配置 -->
<!--Servlet配置 -->
<servlet>
<!--名稱 -->
<servlet-name>hello2</servlet-name>
<!--Servlet的全稱類名 -->
<servlet-class>com.qf.web.servlet.HelloServlet</servlet-class>
<!--啓動的優先級,數字越小越先起作用 -->
<load-on-startup>1</load-on-startup>
</servlet>
<!--映射配置 -->
<servlet-mapping>
<!--名稱 -->
<servlet-name>hello2</servlet-name>
<!--資源的匹配規則:精確匹配 -->
<url-pattern>/hello2</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>login.html</welcome-file>
</welcome-file-list>
</web-app>
容器在進行url-pattern配置的時候是遵循一定的匹配原則的
url-pattern
定義匹配規則,取值說明:
精確匹配 /具體的名稱 只有url路徑是具體的名稱的時候纔會觸發Servlet
後綴匹配 .xxx 只要是以xxx結尾的就匹配觸發Servlet
通配符匹配 / 匹配所有請求,包含服務器的所有資源
通配符匹配 / 匹配所有請求,包含服務器的所有資源,不包括.jsp
load-on-startup
1元素標記容器是否應該在web應用程序啓動的時候就加載這個servlet。
2它的值必須是一個整數,表示servlet被加載的先後順序。
3如果該元素的值爲負數或者沒有設置,則容器會當Servlet被請求時再加載。
4如果值爲正整數或者0時,表示容器在應用啓動時就加載並初始化這個servlet,值越小,servlet的優先級越高,就越先被加載。值相同時,容器就會自己選擇順序來加載。
<init-param>
<param-name>name</param-name>
<param-value>張三</param-value>
</init-param>
1 init-param
元素用來定義Servlet啓動的參數,可以定義多個
2 param-name
表示參數名稱
3 param-value
表示參數值
Servlet生命週期
階段一、實例化(調用構造方法)
階段二、初始化(init方法)
階段三、就緒/服務
階段四、銷燬
獲取請求參數
GET請求
GET提交的數據會放在URL之後,以?分割URL和傳輸數據,參數之間以&相連
GET提交的數據大小有限制(因爲瀏覽器對URL的長度有限制)
GET方式提交數據,會帶來安全問題
效率高
對應的Servlet的方法是doGet
POST請求
POST方法是把提交的數據放在HTTP包的Body中
POST方法提交的數據沒有限制
POST提交的數據相對安全
效率相對沒有GET高
對應的Servlet的方法是doPost
中文亂碼處理
產生亂碼,就是因爲服務器和客戶端溝通的編碼不一致造成的,因此解決的辦法是:在客戶端和服務器之間設置一個統一的編碼,之後就按照此編碼進行數據的傳輸和接收
GET中文亂碼
在Tomcat7及以下
客戶端以UTF-8的編碼傳輸數據到服務器端,而服務器端的request對象使用的是ISO8859-1這個字符編碼來接收數據,服務器和客戶端溝通的編碼不一致因此纔會產生中文亂碼的。解決辦法:在接收到數據後,先獲取request對象以ISO8859-1字符編碼接收到的原始數據的字節數組,然後通過字節數組以指定的編碼構建字符串,解決亂碼問題。
Tomcat8的版本中GET基本就不會亂碼了,因爲服務器對url的編碼格式可以進行自動轉換
POST亂碼
由於客戶端是以UTF-8字符編碼將表單數據傳輸到服務器端的,因此服務器也需要設置以UTF-8字符編碼進行接收,要想完成此操作,服務器可以直接使用從ServletRequest接口繼承而來的"setCharacterEncoding(charset)
"方法進行統一的編碼設置。
Servlet輸出中文內容
瀏覽器識別不到返回的中文是什麼編碼格式,就會默認使用GB2312,如果返回的是UTF-8格式的那麼在瀏覽器上就會顯示亂碼的問題
解決內容中的亂碼
response.setContentType("text/html;charset=UTF-8");//方式一
response.setCharacterEncoding("UTF-8");//方式二:輸出一個完整的網頁
線程安全問題
因爲每次請求都會創建一個線程,如果多人同時請求,那麼就會存在多個線程操作同一個Servlet對象,那麼如果在對應的方法中操作了成員變量,就有可能產生線程安全的問題。
如何保證線程安全
1、synchronized
將存在線程安全問題的代碼放到同步代碼塊中
2、實現SingleThreadModel接口
servlet實現SingleThreadModel接口後,每個線程都會創建servlet實例,這樣每個客戶端請求就不存在共享資源的問題,但是servlet響應客戶端請求的效率太低,所以已經淘汰。
3、儘可能只使用局部變量
表單數據請求響應示例:
package com.tomcat;
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;
import java.io.PrintWriter;
@WebServlet("/ServletDemo")
public class ServletDemo extends HttpServlet {
//request請求,response響應
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//設置接收到的請求體信息爲utf-8編碼
request.setCharacterEncoding("utf-8");
//沒有在響應頭聲明編碼,使用時還需要在輸出時輸出整個html聲明
//response.setCharacterEncoding("utf-8");
//設置響應體文本格式和編碼
response.setContentType("text/html;charset=utf-8");
//獲取請求體的內容
String username = request.getParameter("username");
String phone = request.getParameter("phone");
System.out.println(username+"->"+phone);
//給請求體響應
PrintWriter writer = response.getWriter();
writer.print("<h2>提交成功!!!</h2>");
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request,response);
}
}
<meta charset="UTF-8">
<form action="ServletDemo" method="post">
用戶名:<input type="text" name="username">
<br />
聯繫方式:<input type="text" name="phone">
<br />
<input type="submit" value="提交">
</form>
頁面跳轉
Java Web服務端控制頁面跳轉主要有兩種:重定向和轉發
重定向
重定向就是通過各種方法將網絡請求重新定個方向轉到其它位置。
實現原理:
客戶瀏覽器發送http請求----》web服務器接受後發送302狀態碼響應及對應新的location給客戶瀏覽器–》客戶瀏覽器發現是302響應,則自動再發送一個新的http請求,請求url是新的location地址----》服務器根據此請求尋找資源併發送給客戶。
特點:
1、重定向是客戶端行爲。
2、重定向是瀏覽器做了至少兩次的訪問請求。
3、重定向瀏覽器地址改變。
4、重定向兩次跳轉之間傳輸的信息會丟失(request範圍)。
5、重定向可以指向任何的資源,包括當前應用程序中的其他資源,同一個站點上的其他應用程序中的資源,其他站點的資源。注意:傳遞給HttpServletResponse.sendRedirect 方法的相對URL以“/”開頭,它是相對於整個WEB站點的根目錄
示例:
301跳轉:
@WebServlet("/ServletDemo4")
public class ServletDemo4 extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//設置響應狀態碼
response.setStatus(301);
//設置響應頭信息設置響應信息頭
response.setHeader("Location","ServletDemo");
System.out.println("不想處理");
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request,response);
}
}
302跳轉:
@WebServlet("/ServletDemo")
public class ServletDemo extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.sendRedirect("http://www.baidu.com");
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request,response);
}
}
請求轉發
原理:
客戶瀏覽器發送http請求----》web服務器接受此請求–》調用內部的一個方法在容器內部完成請求處理和轉發動作----》將目標資源發送給客戶。在這裏,轉發的路徑必須是同一個web容器下的url,其不能轉向到其他的web路徑上去,中間傳遞的是自己的容器內的request。在客戶瀏覽器路徑欄顯示的仍然是其第一次訪問的路徑,也就是說客戶是感覺不到服務器做了轉發的。
特點:
1,轉發是服務器行爲
2,轉發是瀏覽器只做了一次訪問請求
3,轉發瀏覽器地址不變
4,轉發兩次跳轉之間傳輸的信息不會丟失,所以可以通過request進行數據的傳遞
5,轉發只能將請求轉發給同一個WEB應用中的組件
注意:如果創建RequestDispatcher 對象時指定的相對URL以“/”開頭,它是相對於當前WEB應用程序的根目錄。
網絡路徑
絕對路徑: 用在不同網站之間的跳轉,比如:http://www.baidu.com/aaa/1.jpg
相對路徑: 用在同一個網站中, aaa/1.jpg,僅限靜態資源,如果頁面比較多,並且使用框架,會出現混亂。
根路徑:根指定就是主機名(服務器) /day12web1/loginservlet (/ 表示 http://localhost:8080/)
/day12web1/loginservlet 如果在瀏覽器中使用 / 表示 http://localhost:8080/
/listservlet 如果是在服務器中使用 / 表示 /day12web1
response對象
在Servlet中可以使用的內置對象主要有:request、response、application、session、out(PrintWriter)。
ServletResponse簡介
定義輔助 servlet 將響應發送到客戶端的對象。servlet 容器創建 ServletResponse 對象,並將它作爲參數傳遞給 servlet 的 service 方法。 要發送 MIME 正文響應中的二進制數據,請使用 getOutputStream 返回的 ServletOutputStream。要發送字符數據,請使用 getWriter 返回的 PrintWriter 對象。
2.2 HttpServletResponse介紹
擴展 ServletResponse 接口以提供特定於 HTTP 的發送響應功能。例如,該接口擁有訪問 HTTP 頭和 cookie 的方法。 客戶端向服務器發起的都是HTTP協議操作,所以我們大部分使用HttpServletResponse對象作爲直接操作對象!
HttpServletResponse 常用API介紹
方法名稱 | 作用 |
---|---|
setStatus(int code) | 設置響應狀態碼:200 成功 302 臨時重定向 304 處理緩存 404 Not Found沒有找到資源,500 服務器錯誤 |
setHeader(name,value) | 設置響應信息頭 |
setCharacterEncoding(String); | 設置編碼格式 |
setContentType(String) | 設置返回數據mimetype |
getWriter() | 獲取字符輸出流 |
getOutputStream() | 獲取字節輸出流 |
設置返回字符編碼格式
方法一:
可以解決返回字符串亂碼問題,但是需要將返回的字符串封裝到html代碼中.操作繁瑣!
response.setCharacterEncoding("utf-8");
方法二
方案按相對簡單,通過設置響應頭告知瀏覽器解析字符串的編碼格式!
response.setHeader("Content-type","text/html;charset=UTF-8")
方法三(推薦)
利用setContentType這種綜合性的寫法解決問題!此方法也是開發中常用的方法!方便!
response.setContentType("text/html;charset=UTF-8")
request對象
ServletRequest介紹
定義將客戶端請求信息提供給某個 servlet 的對象。servlet 容器創建ServletRequest 對象,並將該對象作爲參數傳遞給該 servlet 的service方法。
3.2 HttpServletRequest介紹
HttpServletRequest對象代表客戶端的請求,當客戶端通過HTTP協議訪問服務器時,HTTP請求頭中的所有信息都封裝在這個對象中,開發人員通過這個對象的方法,可以獲得客戶這些信息。
小結: 同響應相同,客戶端請求協議都是基於HTTP所以我們選用HttpServletRequest來操作用戶發送過來的請求的數據!
3.3 HttpServletRequest常用API
URL :Uniform Resource Location (統一資源定位符) 網址
URI :Uniform Resource Identifier (統一資源標識符) URI包含URL
美國總統:—》特朗普 URI
他二大爺:—》特朗普 URI
美國白宮10號樓99號房間–> 特朗普的地址 URL
//獲取請求路徑相關參數
**
getRequestURL方法返回客戶端發出請求時的完整URL。
getRequestURI方法返回請求行中的資源名部分。
getQueryString 方法返回請求行中的參數部分。
getRemoteAddr方法返回發出請求的客戶機的IP地址
getRemoteHost方法返回發出請求的客戶機的完整主機名
getRemotePort方法返回客戶機所使用的網絡端口號
getLocalAddr方法返回WEB服務器的IP地址。
getLocalName方法返回WEB服務器的主機名
getMethod得到客戶機請求方式
//獲取請求頭信息
getHead(name)方法
getHeaders(String name)方法
getHeaderNames方法
//獲取請求正文參數
**getParameter(name)方法
**getParameterValues(String name)方法
getParameterNames方法
getParameterMap方法 //做框架用,非常實用
getInputStream方法 獲取輸入流
封裝請求參數
將數據封裝到實體類上
創建一個對應的實體類!
實體類要變量命名和變量類型都有相應的要求,要求變量名跟提交參數的key相同,變量跟參數類型相同!
public class User {
private String username;
private String password;
private String sex;
private String [] hobby;
//getter/setter
}
第一種方式:使用getParameter獲取
String username = req.getParameter("username");
String password=req.getParameter("password");
String gender=req.getParameter("gender");
String[] hobby = req.getParameterValues("hobby");
User user=new User(username,password,gender,hobby);
System.out.println(user.toString());
第二種方式:使用反射進行解析
//表單的name值和value值
//key name值 value values值
Map<String, String[]> parameterMap = request.getParameterMap();
User bean = new User();
Set<Entry<String, String[]>> entrySet = parameterMap.entrySet();
for (Entry<String, String[]> entry : entrySet) {
//entry map中的一條
//username password sex
String key = entry.getKey();
String[] value = entry.getValue();
try {
PropertyDescriptor descriptor =
new PropertyDescriptor(key, User.class);
Method set = descriptor.getWriteMethod();
/**
* 參數:哪個對象的set方法
* password username sex
*/
if (value.length == 1) {
set.invoke(bean, value[0]);
}else{
set.invoke(bean, (Object)value);
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
第三種方式:使用Apache BeanUtils進行快速映射
1.導入beanutils對應jar包、logging日誌、commons-collections-3.2.1.jar
2.映射
BeanUtils.populate(bean2, request.getParameterMap());
System.out.println(bean2);