## Servlet 簡介
Servlet 在本質上就是 Java 類,編寫 Servlet 需要遵循 Java 的基本語法,但是與一般 Java 類所不同的是,Servlet 是只能運行在服務器端的 Java 類,而且必需遵循特殊的規范,在運行的過程中有自己的生命周期,這些特性都是 Servlet 所獨有的。另外 Servlet 是和 HTTP 協議是緊密聯系的,所以使用 Servlet幾乎可以處理 HTTP 協議各個方面的內容,這也正是 Servlet 收到開發人員青睞的最大原因。
## Servlet 工作原理
Servlet 需要在特定的容器中才能運行,在這里所說的容器即 Servlet 運行的時候所需的運行環境,一般情況下,市面上常見的 Java Web Server 都可以支持 Servlet,例如 Tomcat、Resin、Weblogic、WebSphere等,在本書中采用 Tomcat 作為 Servlet 的容器,由 Tomcat 為 Servlet 提供基本的運行環境。
Servlet 容器環境在 HTTP 通信和 web 服務器平臺之間實現了一個抽象層。Servlet 容器負責把請求傳遞給 Servlet,并把結果返回結客戶。容器環境也提供了配置 Servlet 應用的簡單方法,并且也提供用XML 文件配置 Servlet 的方法。當 Servlet 容器收到對用戶對 Servlet 請求的時候,Servlet 引擎就會判斷這個 Servlet 是否是第一次被訪問,如果是第一次訪問,Servlet 引擎就會初始化這個 Servlet,即調用 Servlet 中的init()方法完成必要的初始化工作,當后續的客戶請求 Servlet 服務的時候,就不再調用 init()方法,而是直接調用 service()方法,也就是說每個 Servlet 只被初始化一次,后續的請求只是新建一個線程,調用 Servlet 中的 service()方法。
在使用 Servlet 的過程中,并發訪問的問題由 Servlet 容器處理,當多個用戶請求同一個 Servlet 的時候,Servlet 容器負責為每個用戶啟動一個線程,這些線程的運行和銷毀由 Servlet 容器負責,而在傳統的 CGI 程序中,是為每一個用戶啟動一個進程,因此 Servlet 的運行效率就要比 CGI 的高出很多。
## Servlet 生命周期
Servlet 是運行在服務器端的程序,所以 Servlet 的運行狀態完全由 Servlet 容器維護,一個 Servlet 的生命周期一般有三個過程。
**1.初始化**
當一個 Servlet 被第一請求的時候,Servlet 引擎就初始化這個 Servlet,在這里是調用 init()方法完成必需的初始化工作。而且這個對象一致在內存中活動,Servlet 為后續的客戶請求新建線程,直接調用Servlet 中的 service()方法提供服務,不再初始化 Servlet。
**2.提供服務**
當 Servlet 對象被創建以后,就可以調用具體的 service()方法為用戶提供服務。
**3.銷毀**
Servlet 被初始化以后一直再內存中保存,后續的訪問可以不再進行初始化工作,當服務器遇到問題需要重新啟動的時候,這些對象就需要被銷毀,這時候 Servlet 引擎就會調用 Servlet 的 destroy()方法把內存中的 Servlet 對象銷毀。
## Servlet 完整示例
```java
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("/helloServlet") // 請求路徑,這里是注解開發,這樣寫就不需要寫web.xml中的servlet-mapping了
public class HelloServlet extends HttpServlet {
@Override
public void init() throws ServletException {
System.out.println("HelloServlet 初始化");
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("HelloServlet doGet 處理get請求");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("HelloServlet doPost 處理post請求");
}
@Override
public void destroy() {
System.out.println("HelloServlet 銷毀");
}
}
```
## doGet、doPost區別
**常見方式**
get方式:直接在URL地址欄中輸入URL、網頁中的超鏈接、form中method為get、form中method為空時,默認是get提交。
post:form中method屬性為post。
**數據傳送方式**
get方式:表單數據存放在URL地址后面。所有get方式提交時HTTP中沒有消息體。
post方式:表單數據存放在HTTP協議的消息體中以實體的方式傳送到服務器。
**服務器獲取數據方式**
GET方式:服務器采用request.QueryString來獲取變量的值。
POST方式:服務器采用request.Form來獲取數據。
**傳送的數據量**
GET方式:數據量長度有限制,一般不超過2kb。因為是參數傳遞,且在地址欄中,故數據量有限制。
POST方式:適合大規模的數據傳送。因為是以實體的方式傳送的。
**安全性**
GET方式:安全性差。因為是直接將數據顯示在地址欄中,瀏覽器有緩沖,可記錄用戶信息。所以安全性低。
POST方式:安全性高。因為post方式提交數據時是采用的HTTP post機制,是將表單中的字段與值放置在HTTP HEADER內一起傳送到ACTION所指的URL中,用戶是看不見的。
**在用戶刷新時**
GET方式:不會有任何提示、
POST方式:會彈出提示框,問用戶是否重新提交
## Servlet 發送請求
使用servlet最常見的操作就是頁面傳值給后臺,后臺接收處理后臺,返回執行結果給前端。接收前端請求的值的方法為:**req.getParameter("輸入框的name的名字");**
例子:下面我們從前端傳遞用戶名和密碼到后臺,后臺驗證是否正確,然后將結果輸出到控制臺。前端輸入對應的值,就可以測試結果了。
```java
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("/login")
public class LoginServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 獲得用戶名
String userName = req.getParameter("userName");
// 獲得密碼
String userPassword = req.getParameter("userPassword");
if (userName.equals("admin") && userPassword.equals("123456")) {
System.out.println("驗證通過");
} else {
System.out.println("驗證失敗");
}
}
}
```
```html
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>index</title>
</head>
<body>
<form action="/login" method="post">
用戶名:<input type="text" name="userName" /><br>
密碼:<input type="password" name="userPassword" /><br>
<input type="submit" value="提交" />
</form>
</body>
</html>
```
## Servlet 響應數據
我們對上一個例子進行修改,將響應的結果告訴前端的用戶,通常是通過**req.setAttribute("消息名稱", "消息內容");**,然后頁面使用**${消息名稱}**就能取到值。
```java
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("/login")
public class LoginServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 獲得用戶名
String userName = req.getParameter("userName");
// 獲得密碼
String userPassword = req.getParameter("userPassword");
if (userName.equals("admin") && userPassword.equals("123456")) {
// 把結果保存到請求中
req.setAttribute("message", "驗證通過");
} else {
// 把結果保存到請求中
req.setAttribute("message", "驗證失敗");
}
// 轉向
req.getRequestDispatcher("index.jsp").forward(req, resp);
}
}
```
```html
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>index</title>
</head>
<body>
${message}
<form action="/login" method="post">
用戶名:<input type="text" name="userName" /><br>
密碼:<input type="password" name="userPassword" /><br>
<input type="submit" value="提交" />
</form>
</body>
</html>
```
我輸入的是用戶名是admin,密碼是123456,測試結果如下:

## Servlet 重定向與轉發
servlet中,頁面跳轉有兩種方式,重定向與轉發。
**區別一**
重定向時瀏覽器上的網址改變
轉發是瀏覽器上的網址不變
**區別二**
重定向實際上產生了兩次請求
轉發只有一次請求
**區別三**
重定向時的網址可以是任何網址
轉發的網址必須是本站點的網址
**重定向**
發送請求 -->服務器運行-->響應請求,返回給瀏覽器一個新的地址與響應碼-->瀏覽器根據響應碼,判定該響應為重定向,自動發送一個新的請求給服務器,請求地址為之前返回的地址-->服務器運行-->響應請求給瀏覽器
**轉發**:
發送請求 -->服務器運行-->進行請求的重新設置,例如通過request.setAttribute(name,value)-->根據轉發的地址,獲取該地址的網頁-->響應請求給瀏覽器
**詳解**
重定向:以前的request中存放的變量全部失效,并進入一個新的request作用域。
轉發:以前的request中存放的變量不會失效,就像把兩個頁面拼到了一起。
**書寫區別**
```java
req.getRequestDispatcher("index.jsp").forward(req, resp); // 轉發
resp.sendRedirect("index.jsp"); // 重定向
```
## Servlet 注解開發
**Servlet的傳統配置方式**
在JavaWeb開發中, 每次編寫一個Servlet都需要在web.xml文件中進行配置,如下所示:
```xml
<servlet>
<servlet-name>LoginServlet</servlet-name>
<servlet-class>com.sponge.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LoginServlet</servlet-name>
<url-pattern>/login</url-pattern>
</servlet-mapping>
```
每開發一個Servlet,都要在web.xml中配置Servlet才能夠使用,這實在是很頭疼的事情,所以Servlet3.0之后提供了注解(annotation),使得不再需要在web.xml文件中進行Servlet的部署描述,簡化開發流程。
**Servlet基于注解的配置方式**
在直接在servlet類名上面寫以下注解,能達到同樣的效果:
```java
@WebServlet("/login")
```
```java
package com.sponge.servlet;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("doGet");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("doPost");
}
}
```