# Servlet入門
[TOC]
## 導學
在本節課程中需要了解什么是b/s架構模式,瀏覽器與服務器。掌握servlet初步開發技巧,掌握servlet開發原理。
## 軟件結構發展史
* 單機時代(桌面應用)
特點:沒有互聯網(92)和局域網(83),所用數據的保存是放在本地的硬盤中
優點:易于使用、結構簡單、安裝方便
缺點:數據難以共享、安全性差、更新不及時
比如:Word/Eclipse
* 聯機時代(`Client-Server`模式)
特點:`Client/Server`結構(`C/S架構`)是指客戶端和服務器結構,最大的特點是需要安裝客戶端,大部分的數據保存在遠程服務器上,在本地客戶端只保留有少量的不太重要的數據。
優點:數據方便共享、安全性高
缺點:必須安裝客戶端,升級和維護困難
比如:QQ/微信/支付寶
* 互聯網模式(`Brower-Server`模式)
特點:`Brower-Server`(`B/S架構`)是指瀏覽器和服務器結構,使用瀏覽器代替客戶端,程序運行在瀏覽器上,動態生成一個個的網頁,在本地不會留有任何的數據。
優點:開發簡單,無需安裝客戶端,數據易于共享
缺點:執行速度慢一點、用戶體驗差一點
比如:手機淘寶(像是APP,本質上嵌套了一個瀏覽器,用的是HTML進行的開發)
## `B/S`模式的執行流程
### 什么是服務器
>[info]服務器:向終端(客戶端)提供服務的計算機。簡單來說就是安裝了WEB服務器程序的電腦
**服務器一般的特點:**
* 7\*24 全天候都在運行的計算機,當然也會出現維護的時候;
* 服務器的性能要出色,包括CPU/內存/磁盤讀寫;
* 服務器是要連接在一個穩定的網絡中的,沒有連接網絡的服務器可以說沒有任何意義。
**服務器的種類:**
1. 刀片式服務器:刀片式服務器應用于大型的數據中心或者需要大規模度計算的領域。
2. 塔式服務器:塔式服務器適合常見的入門級和工作組級服務器應用,性能能滿足大部分中小企業用戶的要求問。
3. 機架式服務器:機架式服務器多用于服務器數量較多的大型企業使用。
### 廣域網與局域網
廣域網:可以理解為全世界互聯的一個網絡。
局域網:是一個由有限的計算機組成的網絡,一般比如說公司的內網,學校的內網都屬于局域網。
>[info]IP地址:計算機在網絡中的唯一標識,進入`cmd`,`ipconfig`顯示自身電腦的ip地址
但是IP V4地址是一串10進制數字組成,IP V6的地址是一串16進制的數字組成,非常難以記憶。于是,便有了DNS(域名服務協議),設置域名服務器用于將域名和IP進行轉換。
比如,百度的域名:[https://www.baidu.com:80](https://www.baidu.com/)
* https:協議
* [baidu.com](http://baidu.com/):域名
* www:在此域名下解析的主機服務器
* 80:解析到的服務器的端口(服務器設置不同的端口用于提供不同的服務)
其實對于這樣的地址,我們不僅僅可以采用域名進行訪問,還可以使用ip地址進行訪問。比如在cmd中,使用如下代碼:`nslookup www.baidu.com`去查看對應域名的ip地址,再使用`ping`語句,檢查是否能夠連接對應地址。
### 運行流程
>[success]**靜態Web服務:** 訪問的就是最簡單的文件系統,在文件系統中只存儲了html/css/javascript/img這些靜態資源
**動態Web服務:** 網頁可以和數據庫進行交互

在這張圖片中,是一次請求與響應的動態加載過程。那么什么是請求與響應呢?
>[info]從瀏覽器發出送給服務區的數據包稱為**請求**(`Request`)
從服務器返回給瀏覽器的結果稱為**響應**(`Response` )
二者通常是**成對出現**的。
## Tomcat與Servlet簡介
再來回憶一下什么是`J2EE`,它是Java開發的平臺企業版。在它內部和標準版最大的不同就是它內置了很多企業開發的組件和功能。其中開發`B/S(WEB)`應用程序就是`J2EE`最核心的功能。這些功能模塊有:

現在Java已經為我們的WEB開發提供了如此多的支持,那么還需要什么呢?這個時候就需要介紹大名鼎鼎的Apache Tomcat。
Apache Tomcat是一款WEB應用服務器程序,它能夠使我們的WEB應用程序和靜態資源能夠被公開的使用。它是Apache軟件基金會旗下一款免費的開放源代碼的WEB應用服務器程序。
**J2EE與Tomcat的關系:**
* J2EE是一組技術規范與指南,具體實現由軟件廠商來決定。
* Tomcat是J2EE Web (Servlet與JSP)標準的實現者。
* J2SE是J2EE的運行基石,運行Tomcat離不開J2SE
此時雖然有了Tomcat,可以使電腦變成服務器,但是在服務器內部程序是如何處理的,Tomcat自己也不清楚。所以,此時需要J2EE的核心組件Servlet來進行編碼,Tomcat與Servlet是相輔相成的。Tomcat提供運行的基礎,Servlet提供軟件的實現,共同組建形成軟件。
>[info]Servlet(Server Applet)服務器小程序,主要功能用于生成動態Web內容
## 安裝配置Tomcat
### 安裝流程
1. 安裝JDK,Tomcat依托于JDK
2. 選擇與JDK大版本號相同的Tomcat版本
3. 訪問[https://tomcat.apache.org/download-80.cgi](https://tomcat.apache.org/download-80.cgi)下載 8.X.X 版本。
4. 解壓到本地,即完成安裝
### 啟動
Tomcat 目錄說明
~~~
+---bin Tomcat的命令行工具,主要有啟動和關閉命令
+---conf Tomcat相關配置的屬性,如修改端口號,設置部署目錄等
+---lib Tomcat用到的一些CLASSPATH,如果程序里有用到第三方的jar也可以放在其中,但是不建議
+---logs 相關日志文件
+---temp
+---webapps 項放置所有的web應用程序
+---work 所有的jsp被編譯后的文件,臨時生成的文件
~~~
進入 「bin」目錄 → startup.bat
關閉:可以直接關閉啟動窗口(比較暴力),或者進入 「bin」目錄 → shutdown.bat

**ROOT 目錄**
webapps/ROOT 是一個特殊的應用程序,請求在訪問的時候默認進去的應用。
**關于 hosts 文件**
位置:C:\\Windows\\System32\\drivers\\etc
hosts文件中存儲了本機的域名解析服務,瀏覽器在輸入域名的時候,第一步是在host中找到解析的ip地址,如果找不到則到外網的域名解析服務器中查詢。
**修改端口號**
在conf/server.xml,搜索8080字符,將其替換為80,并重啟服務

**部署項目**
* 直接部署在webapps下面,此時應用的上下文路徑就是webapps下面文件夾名稱
* 服務器項目部署
1. 直接復制法:比較開發工具中Tomcat的項目發布路徑的內容與工作空間的內容,可以將開發工具中Tomcat中的項目(或者修改工作空間中項目源碼輸出位置)直接復制到外部Tomcat中
2. 導出war包法:可以通過eclipse等開發工具導出war包,部署到webapps文件夾下
3. 使用下文的虛擬路徑的方式部署
* 通過虛擬目錄的方式進行配置
在 conf/server.xml 文件中的`</Host>`內部的最后配置一下內容
`<Context path="/abc" docBase="D:\work\Trade\inv\ntqn" />`
其中`path`為應用的上下文路徑,`docBase`為應用在服務器中的路徑
**字符編碼的統一**
在開發環境中(Eclipse)中,相關的類、文件都使用 UTF-8 編碼。
創建數據庫的時候,數據庫編碼使用 UTF-8。
在 Tomcat 中,設置 URIEncoding=UTF-8,server.xml 中的 Connector 節點。在Tomcat 8之后,默認字符編碼為UTF-8。
>[danger] 不要使用中文和帶有空格的名稱做為文件路徑。
### Eclipse集成配置Tomcat
* 打開Servers視圖便簽

* 添加本地的 Tomcat 至 Eclipse,并設置對應JDK


* 設置項目發布路徑為本地安裝Tomcat中

## Servlet入門
### 創建動態WEB項目
1. 新建Dynamic Web Project

2. 填寫項目基本信息

3. 創建完的項目目錄

**說明:**
build:默認的編譯完成的class文件和相關的配置文件(xml/properties/json..)
src:源文件
WebContent:web項目路徑
WebContent 下的 WEB-INF:放置服務器端文件的目錄,例如 lib/classes,當然也可以放置 jsp 文件,但是在此處放置的 jsp 文件,是不能通過路徑直接訪問的。

### 部署項目
* 直接部署在webapps下面,此時應用的上下文路徑就是webapps下面文件夾名稱
* 服務器項目部署
1. 直接復制法:比較開發工具中Tomcat的項目發布路徑的內容與工作空間的內容,可以將開發工具中Tomcat中的項目(或者修改工作空間中項目源碼輸出位置)直接復制到外部Tomcat中
2. 導出war包法:可以通過eclipse等開發工具導出war包,部署到webapps文件夾下
3. 使用下文的虛擬路徑的方式部署
* 通過虛擬目錄的方式進行配置
在 conf/server.xml 文件中的`</Host>`內部的最后配置一下內容
`<Context path="/abc" docBase="D:\work\Trade\inv\firstservlet" />`
其中`path`為應用的上下文路徑,`docBase`為應用在服務器中的路徑
### Servlet
~~~java
package com.dodoke.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 此時Tomcat還不認識這個servlet,需要在項目的配置文件中配置servlet
* @author LiXinRong
*
*/
public class MyFirstServlet extends HttpServlet {//HttpServlet是所有服務器小程序的父類
@Override//重寫service方法,為servlet的請求與響應提供支持。
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//接收請求的參數,暫時先記住
String name = request.getParameter("name");
String html = "<h1 style='color:red'>hi," + name + "!</h1><hr/>";
System.out.println("返回給瀏覽器的響應數據為:" + html);
//創建服務器對瀏覽器的輸出流,可以將數據從服務器發送到瀏覽器
PrintWriter out = response.getWriter();
out.println(html);
}
}
~~~
~~~xml
<?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" id="WebApp_ID" version="3.1">
<display-name>FirstServlet</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
<servlet>
<!-- 創建servlet的別名,就是給真正創建的servlet起了個名字 -->
<servlet-name>first</servlet-name>
<!-- 使用servlet-class指向一個具體的servlet的實現類 -->
<servlet-class>com.dodoke.servlet.MyFirstServlet</servlet-class>
</servlet>
<!-- 構建一個servlet的映射,servlet可以有其具體的地址,這個地址可以被瀏覽器訪問,此處就來設置對應的地址 -->
<servlet-mapping>
<!-- 需要和上方的first同名,這樣地址映射才能和具體實現類對應 -->
<servlet-name>first</servlet-name>
<url-pattern>/hi</url-pattern>
</servlet-mapping>
</web-app>
~~~

### Servlet開發與基本配置
**Servlet開發步驟:**
* 創建Servlet類,繼承HttpServlet
* 重寫service方法,編寫程序代碼
* 配置web.xml,綁定URL

### 請求參數的發送與接收
**請求參數:**
* 請求參數是指瀏覽器通過請求向Tomcat請求的數據
* 請求參數通常是用戶輸入的數據,待servlet進行處理
* 請求參數的形式:參數名=值1&參數名2=值2&參數名n=
示例:
~~~
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>學員信息登記表</title>
</head>
<body>
<h1>學員信息登記表</h1>
<form action="/FirstServlet/sample">
姓名:<input name="name"/>
<br/>
電話:<input name="telphone"/>
<br/>
性別:
<select name="sex" style="width:100px;padding:5px">
<option value="man">男</option>
<option value="women">女</option>
</select>
<br/>
特長:
<input type="checkbox" name="spec" value="English"/>英語
<input type="checkbox" name="spec" value="Program"/>編程
<input type="checkbox" name="spec" value="Speech"/>演講
<input type="checkbox" name="spec" value="Swimming"/>游泳
<br/>
<input type="submit" value="提交"/>
<br/>
</form>
</body>
</html>
~~~
~~~
package com.dodoke.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class SampleServlet extends HttpServlet{
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
PrintWriter out = response.getWriter();
String html = "<h1>hello,world</h1>";
out.println(html);
}
}
~~~
在servlet中,有兩個方法去接收參數:
* `request.getParameter() 接收單個參數`
* `request.getParameterValues() 接收多個同名參數`
修改后:
~~~
public class SampleServlet extends HttpServlet{
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String name = request.getParameter("name");
String telphone = request.getParameter("telphone");
String sex = request.getParameter("sex");
String[] specs = request.getParameterValues("spec");
PrintWriter out = response.getWriter();
out.println("<h1>姓名:"+ name +"</h1>");
out.println("<h1>手機:"+ telphone +"</h1>");
out.println("<h1>性別:"+ sex +"</h1>");
for(String str : specs) {
out.println("<h2>特長:"+ str +"</h2>");
}
}
}
~~~
## GET和POST請求
**get和post請求:**
get是將數據通過url附加數據顯性的向服務器發送數據
post 是將數據放置在`請求體`中隱性的向服務器發生數據
請求體:name=zhangsan,放置在請求流中,專門用于存放數據
比如:在action 屬性添加 method="post" ,我們會發現在地址欄中的數據已經沒有了,那么這樣的數據如何去查看呢?我們可以在瀏覽器的調試工具中查看。

其實,在請求體中的數據,和get請求的數據表現是一樣的。
~~~
//service()是請求處理的核心方法 無論get和post都會被處理,service方法不區別對待
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String method = request.getMethod();//獲取請求的請求方式
String name = request.getParameter("name");
String telphone = request.getParameter("telphone");
String sex = request.getParameter("sex");
String[] specs = request.getParameterValues("spec");
PrintWriter out = response.getWriter();
out.println("<h1>method:"+ method +"</h1>");
out.println("<h1>name:"+ name +"</h1>");
out.println("<h1>mobile:"+ telphone +"</h1>");
out.println("<h1>sex:"+ sex +"</h1>");
for(String str : specs) {
out.println("<h2>spec:"+ str +"</h2>");
}
}
~~~
在實際工作中,對于get和post請求是需要分開進行處理的,在servlet中提供了doGet和doPost方法,方便我們進行處理。
~~~
package com.dodoke.servlet;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class RuquestMethodServlet extends HttpServlet{
//處理get請求
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String name = request.getParameter("name");
response.getWriter().println("<h1 style='color:green'>"+ name +"</h1>");
}
//處理post請求
@Override
protected void doHead(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String name = request.getParameter("name");
response.getWriter().println("<h1 style='color:red'>"+ name +"</h1>");
}
}
~~~
>[info]doGet() 常用于不包含敏感詞信息查詢
doPost() 用于安全性較高的功能和服務器的寫操作
Service()方法如果不進行重寫的話,就相當于一個分發器,是doGet()和doPost()的上級,將不同請求方式的請求交給不同的方法進行處理。
## Servlet的生命周期
Servlet從創建到死亡經歷了哪些過程呢?我們首先來看看它的底層實現。
1. 裝載(Tomcat解析XML)此時Tomcat只是解析,并未對其做什么操作
2. 創建 第一次訪問的時候,創建servlet對象執行構造函數
3. 初始化 調用init()方法初始化
4. 提供服務 service( )/doGet( )/doPost( )
5. 銷毀 tomcat關閉或重啟的時候執行destory( )
~~~
public class MyFirstServlet extends HttpServlet {
public MyFirstServlet() {
System.out.println("正在創建Servlet");
}
@Override
public void init(ServletConfig config) throws ServletException {
System.out.println("正在初始化servlet對象");
}
@Override//重寫service方法
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("servlet正在提供服務");
//接收請求的參數,暫時先記住
String name = request.getParameter("name");
String html = "<h1 style='color:red'>hi," + name + "!</h1><hr/>";
System.out.println("返回給瀏覽器的響應數據為:" + html);
//創建服務器對瀏覽器的輸出流,可以將數據從服務器發送到瀏覽器
PrintWriter out = response.getWriter();
out.println(html);
}
@Override
public void destroy() {
System.out.println("正在銷毀servlet對象");
}
}
~~~
創建與初始化servlet對象,只在第一次訪問servlet時進行,隨后的訪問不會再去執行,所以在整個系統中,servlet對象有且只有一個。
## 使用注解簡化配置servlet
之前我們創建了好幾個servlet,但是在使用這些servlet的時候,我們發現需要在web.xml中進行很多的配置。這顯然來說,對于我們的開發是很不友好的,需要在Java文件和xml文件之間進行大量的跳轉,那么有沒有一種簡便的方法可以使我們的開發變得更容易呢?
在jdk 1.5之后,提供了注解,注解可以簡化程序的配置。同樣的,在Servlet 3.X版本之后也引入了注解的特性。在Servlet中,有且只有一個核心的注解:
>[success]`@WebServlet`
這個注解可以用于簡化Servlet在web.xml中的配置工作。
~~~
@WebServlet("/sample")
public class SampleServlet extends HttpServlet{
//service()是請求處理的核心方法 無論get和post都會被處理,service方法不區別對待
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String method = request.getMethod();//獲取請求的請求方式
String name = request.getParameter("name");
String telphone = request.getParameter("telphone");
String sex = request.getParameter("sex");
String[] specs = request.getParameterValues("spec");
PrintWriter out = response.getWriter();
out.println("<h1>method:"+ method +"</h1>");
out.println("<h1>name:"+ name +"</h1>");
out.println("<h1>mobile:"+ telphone +"</h1>");
out.println("<h1>sex:"+ sex +"</h1>");
for(String str : specs) {
out.println("<h2>spec:"+ str +"</h2>");
}
}
}
~~~
## 啟動時加載Servlet
就如同很多程序開機啟動一樣,servlet可以在Tomcat被啟動的使用就進行加載,啟動時加載在工作中常用于系統的預處理。我們可以在web.xml中進行設置,配置我們的啟動加載服務。
>[info]`<load-on-startup>`設置該節點可以控制啟動時加載
~~~xml
<servlet>
<servlet-name>first</servlet-name>
<servlet-class>com.dodoke.servlet.MyFirstServlet</servlet-class>
<!-- 設置啟動加載,值越小優先級越高 -->
<load-on-startup>0</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>first</servlet-name>
<url-pattern>/hi</url-pattern>
</servlet-mapping>
~~~
當然我們也可以使用注解的形式來指定啟動時加載
~~~java
//可以使用屬性指定不同的功能
//在@WebServlet注解中,如果只指定了loadOnStartup屬性,而沒有指定urlPatterns屬性,該注解是不起作用的
//因為@WebServlet注解強制要求必須指定urlPatterns屬性
@WebServlet(urlPatterns="/first" , loadOnStartup=0)
public class MyFirstServlet extends HttpServlet {}
~~~