(一) Setvlet基本概述
(1) 什么是Servlet ?
Servlet(Server Applet)是JavaServlet的簡稱,稱為小服務程序或服務連接器,用Java編寫的服務器端程序,具有獨立于平臺和協(xié)議的特性,主要功能在于交互式地瀏覽和生成數據,生成動態(tài)Web內容是
JavaWeb中,我們將會接觸到三大組件(Servlet、Filter、Listener),Servlet由服務器調用,處理服務器接收到的請求,即完成,接受請求數據 --> 處理請求 --> 完成響應,其本質就是一個實現(xiàn)了Servlet接口的java類
Servlet類由我們來寫,但對象由服務器來創(chuàng)建,并且由服務器來調用相應的方法
(2) Servlet用來做什么?
網絡中比較常見的一些功能,例如登錄,注冊,這樣存在交互的功能,而Servlet就可以幫助我們處理這些請求,可以說Servlet是JavaWeb知識中重要的知識點之一
(二) 實現(xiàn)Servlet的方式
實現(xiàn)Servlet有三種方式:
- 實現(xiàn) javax.servlet.Servlet 接口;
- 繼承 javax.servlet.GenericServlet類;
- 繼承 javax.servlet.http.HttpServlet類;
實際開發(fā)中,我們通常會選擇繼承HttpServlet類來完成我們的Servlet,但認識Servlet接口這種方式也是很重要的,是我們入門知識中不可或缺的部分
(1) 創(chuàng)建我們的第一個Servelt
我們創(chuàng)建一個web項目,選擇對應的參數,我們所裝的jdk為1.8版本,可以選擇到 JavaEE8的版本,對應versions也就是4.0,不過我們在這里選擇市面上用的還是比較多的7版本
創(chuàng)建一個Demo類實現(xiàn)Servlet接口,然后我們快速生成這個接口中未實現(xiàn)的方法,我們先暫時忽略Servlet中其他四個方法,只關心service()方法,因為它是用來處理請求的方法,我們在該方法內給出一條輸出語句
package cn.ideal.web.servlet;
import javax.servlet.*;
import java.io.IOException;
public class ServeltDemo1 implements Servlet {
//初始化方法
@Override
public void init(ServletConfig servletConfig) throws ServletException {
}
//Servlet配置方法
@Override
public ServletConfig getServletConfig() {
return null;
}
//提供服務方法
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("理想二旬不止");
}
//Servlet信息方法
@Override
public String getServletInfo() {
return null;
}
//銷毀方法
@Override
public void destroy() {
}
}
寫完了一個最簡單Servlet代碼,但是如何在瀏覽器中可以訪問到呢?我們就需要對web/WEB-INF下的web.xml進行配置,我們在<web-app></web-app>中加入以下代碼(雖然后期有優(yōu)化的方法,但是很推薦大家記憶下來)
<servlet>
<!--給這個Servlet起一個名字,一般與類名相同-->
<servlet-name>ServletDemo1</servlet-name>
<!--全類名-->
<servlet-class>cn.ideal.web.servlet.ServeltDemo1</servlet-class>
</servlet>
<!--配置映射路徑-->
<servlet-mapping>
<servlet-name>ServletDemo1</servlet-name>
<!--外界訪問的路徑-->
<url-pattern>/Demo1</url-pattern>
</servlet-mapping>
現(xiàn)在我們根據我們在url-pattern中配置的路徑來訪問一下,在控制臺中果然輸出了,理想二旬不止這個字符串
(2) web.xml的作用
趁熱打鐵,我們來簡單分析一下這個web.xml的因由,其實在web.xml中配置Servlet的目的,就是把在瀏覽器中的訪問路徑與對應Servlet綁到一起,上面的例子中就是把訪問路徑:“/Demo1” 與 “cn.ideal.web.servlet.ServeltDemo1” 綁定到了一起
1、<servlet></servlet> :指定ServletDemo1這個Servlet的名字為ServletDemo1,一般此處與對應類同名
2、<servlet-mapping></servlet=mapping> :設定訪問的具體路徑
而這兩者又通過<servlet-name></servlet-name> 關聯(lián)在一起
執(zhí)行過程:
1、當服務器中接受到了瀏覽器的請求,解析URL路徑,獲取到Servlet的資源路徑
2、尋找web.xml文件,找到 <url-pattern> 標簽,尋找對應的全類名<servlet-class>
3、Tomcat將字節(jié)碼文件加載進內存,并且創(chuàng)建對象,調用其中的方法
所以我們需要知道:Servlet中的大多數方法均不由我們來創(chuàng)建和調用,均由Tomcat完成
(三) Servlet 接口
(1) 生命周期簡單概述
我將生命周期簡單理解為這樣幾個過程:
生前——出生——服務——死亡——埋葬
1、生前:當Tomcat第一次訪問Servlet,Tomcat會創(chuàng)建Servlet的實例
2、出生:Tomcat會調用init()方法初始化這個對象
3、服務: 客戶端訪問Servlet的時候,service()方法會被調用
4、死亡: 當Tomcat被關閉或者Servlet長時間不被使用,destroy()方法會被調用
5、埋葬: destroy()方法被調用后,Servlet就等待垃圾回收(不輕易),有需要則用init()方法重新初始化
(2) 生命周期詳解
1、生前
服務器會在Servlet第一次被訪問時,或者是在服務器啟動時創(chuàng)建Servlet。如果服務器啟動時就創(chuàng)建Servlet,那么還需要在web.xml文件中進行配置,也就是說默認情況下,Servlet是在第一次訪問時由服務器創(chuàng)建的
一個Servlet類型,服務器只創(chuàng)建一個實例對象:例如我們第一次訪問<http://localhost:8080/Demo1>,服務器通過/Demo1就找到了cn.ideal.web.servlet.ServeltDemo1 ,服務器就會判斷這個類型的Servlet是否創(chuàng)建過,若沒有才通過反射來創(chuàng)建ServletDmoe1的實例,否則就直接使用已經存在的實例
2、出生
在Servlet被創(chuàng)建后,服務器會立即調用Servlet的void init(ServletConfig)方法,而且一個Servlet的一生,這個方法只會被調用一次,我們可以把一些對Servlet的初始化工作放到方法中!
3、服務
當服務器每次接收到請求時,都會去調用Servlet的service()方法來處理請求。service()方法是會被調用多次的,服務器接收到一次請求,就會調用service() 方法一次,也正因為如此,所以我們才需要把處理請求的代碼寫到service()方法中!
4、死亡及埋葬
當服務器關閉時Servlet也需要被銷毀了,但是銷毀之前,服務器會先調用Servlet中的destroy()方法,我們可以把一些釋放資源的代碼放到此處
(3) Servlet接口的三個類型
在這五個方法中,我們可以在參數中看到三個我們沒有接觸過的類型
public void init(ServletConfig servletConfig) throws ServletException {}
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {}
也就是這個三個類型:ServletConfig、ServletRequest、ServletResponse
A:ServletConfig
ServletConfig是服務器創(chuàng)建的一個對象,然后傳遞到Servlet的init()方法中
下述方法中我們簡單使用一下第一個 getServletName() 就可以了,后面的方法等我們學寫了Context以及其他知識才能更好的理解
//獲取Servlet在web.xml文件中的配置名稱,即<servlet-name>指定的名稱
String getServletName()
//用來獲取ServletContext對象
ServletContext getServletContext()
//用來獲取在web.xml中配置的初始化參數,通過參數名來獲取參數值;
String getInitParameter(String name)
//用來獲取在web.xml中配置的所有初始化參數名稱
Enumeration getInitParameterNames()
B:ServletRequest & ServletResponse
這兩個類型出現(xiàn)在Servlet的service()方法中,分別代表著請求與響應對象,并且兩者的實例也都是由服務器創(chuàng)建的
但是我們想要做一個web應用,歸根結底要和HTTP相掛鉤,如果我們希望在service() 方法中使用HTTP相關的功能,可以把 ServletRequest 和 ServletResponse 強轉成 HttpServletRequest 和HttpServletResponse
HttpServletRequest方法:
//獲取指定請求參數的值;
String getParameter(String paramName)
//獲取請求方法,例如GET或POST
String getMethod()
//獲取指定請求頭的值;
String getHeader(String name)
//設置請求體的編碼!
/*
GET沒有請求體,所以這個方法只只對POST請求有效當調用
這個方法必須在調用getParameter()方法之前調用!
使用request.setCharacterEncoding(“utf-8”)之后,再通過getParameter()方法獲取參數
時,參數值都已經通過了轉碼,即轉換成了UTF-8編碼
*/
void setCharacterEncoding(String encoding)
HttpServletResponse方法:
//獲取字符響應流,使用該流可以向客戶端輸出響應信息。
PrintWriter getWriter()
Eg:response.getWriter().print(“<h1>Just for test</h1>”);
//獲取字節(jié)響應流,例如可實現(xiàn)向客戶端響應一張圖片
ServletOutputStream getOutputStream()
//用來設置字符響應流的編碼
void setCharacterEncoding(String encoding)
//向客戶端添加響應頭信息
void setHeader(String name, String value)
Eg:setHeader(“Refresh”, “3;url=http://www.xxx.com”) 表示三秒后自動刷新到該網址
//該方法是setHeader(“content-type”, “xxx”)的簡便方法,即用來添加名為content-type響應頭的方法
/*
content-type響應頭用來設置響應數據的MIME類型,例如要向客戶端響應jpg的圖片,那么
可以setContentType(“image/jepg”),如果響應數據為文本類型,那么還要臺同時設置編
碼,例如setContentType(“text/html;chartset=utf-8”)表示響應數據類型為文本類型
中的html類型,并且該方法會調用setCharacterEncoding(“utf-8”)方法;
*/
void setContentType(String contentType)
//向客戶端發(fā)送狀態(tài)碼,以及錯誤消息
void sendError(int code, String errorMsg)
(四) GenericServlet 類
A:通過查看這個類的源碼可以知道,該類中只有
public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
一個方法需要實現(xiàn),其他的方法已經均在源碼中有了定義
B:GenericServlet的init()方法
還需要提一提的兩個方法就是
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
public void init() throws ServletException {
}
GenericServlet 類實現(xiàn)了Servlet的init(ServletConfig)方法,把參數config賦給了本類的成員config,然后再調用本類自己的無參的init()方法
這個方法是 GenericServlet 自己的方法,而不是從Servlet繼承下來的。當我們自定義Servlet時,如果想完成初始化作用就不要再重復 init(ServletConfig) 方法了,而是應該去重寫init()方法。因為在 GenericServlet中的 init(ServletConfig) 方法中保存了 ServletConfig 對象,如果覆蓋了保存 ServletConfig 的代碼,那么就不能再使用 ServletConfig 了
C:實現(xiàn)了ServletConfig接口
GenericServlet還實現(xiàn)了ServletConfig接口,所以可以直接調用getInitParameter()、getServletContext()等ServletConfig的方法。
但是這個類我們仍然不是我們要講的重點,我們接著看一下下一個類
(五) HttpServlet 類
(1) 概述
在上面我們實現(xiàn) Servlet 接口,需要實現(xiàn)5個方法,十分麻煩,而HttpServlet類已經實現(xiàn)了Servlet接口的所有方法,編寫Servlet時,只需要繼承HttpServlet,重寫你需要的方法即可,并且它提供了對HTTP請求的特殊支持,更加強大
(2) service()方法
在 HttpServlet 的 service(ServletRequest,ServletResponse)方法中會把 ServletRequest 和ServletResponse 強轉成 HttpServletRequest 和 HttpServletResponse
//HttpServlet 源碼節(jié)選
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
HttpServletRequest request;
HttpServletResponse response;
try {
request = (HttpServletRequest)req;
response = (HttpServletResponse)res;
} catch (ClassCastException var6) {
throw new ServletException("non-HTTP request or response");
}
this.service(request, response);
}
強轉過后,然后調用 HttpServlet 類中提供的 service(HttpServletRequest,HttpServletResponse)方法,這是這個類本身的方法,而不是繼承而來的,這說明我們在使用的時候,只需要覆蓋 service(HttpServletRequest,HttpServletResponse) 就可以了,不需要再進行強轉這個兩個對象了
注意:其實還有更一步簡化的步驟,也不必使用 service(HttpServletRequest,HttpServletResponse)
(3) doGet() 和 doPost()
在HttpServlet的service(HttpServletRequest,HttpServletResponse) 方法會去判斷這個請求是GET還是POST,如果是GET請求,就去調用類中的doGet()方法,如果是POST請求,就去調用doPost()方法,這說明我們在子類中去覆蓋doGet()或doPost()方法就可以了
(六) Servlet細節(jié)
(1) 線程安全問題
Servlet只會被服務器創(chuàng)建一個實例對象,很多情況下,一個Servlet需要處理多個請求,顯然,Servlet雖然效率高,但也不是線程安全的
所以我們不應該在Servlet中輕易創(chuàng)建成員變量,因為可能會存在多個線程同時對這個成員變量進行不同的操作
結論:不要在Servlet中創(chuàng)建成員!創(chuàng)建局部變量即可,可以創(chuàng)建無狀態(tài)成員量,或者狀態(tài)只為可讀的成員
(2) 服務器啟動時就創(chuàng)建Servlet
之前我們將生命周期的時候有說過,Servlet是在第一次訪問時由服務器創(chuàng)建的,但我們可以通過在web.xml中對Servlet進行配置,使服務器啟動時就創(chuàng)建Servlet
<servlet>
<servlet-name>ServletDemo1</servlet-name>
<servlet-class>cn.ideal.web.ServletDemo1</servlet-class>
<!--在<servlet>中配置<load-on-startup>,其中給出一個非負整數!-->
<load-on-startup>0</load-on-startup>
</servlet>
它的作用是確定服務器啟動時創(chuàng)建Servlet的順序
(3) 一個Servlet可以綁定多個URL
<servlet-mapping>
<servlet-name>Servlet</servlet-name>
<url-pattern>/AServlet</url-pattern>
<url-pattern>/BServlet</url-pattern>
</servlet-mapping>
這樣配置后無論訪問/AServlet還是/BServlet,訪問的都是AServlet
(4) 通配符匹配問題
在<url-pattern>中可以使用通配符,也就是 “ * ” ,它可以匹配任何前綴或者后綴
<!--路徑匹配-->
<url-pattern>/servlet/*<url-patter>:/servlet/a、/servlet/b,都匹配/servlet/*;
<!--擴展名匹配-->
<url-pattern>*.xx</url-pattern>:/abc/de.xx、/a.xx,都匹配*.xx;
<!--什么都匹配-->
<url-pattern>/*<url-pattern>:匹配所有URL;
通配符要么為前綴,要么為后綴,不能出現(xiàn)在URL中間位置,并且一個URL中最多只能出現(xiàn)一個通配符,如果存在更具體的地址,會優(yōu)先訪問具體的地址
(七) ServletContext
(1) 概述
服務器會為每個web應用創(chuàng)建一個 ServletContext 對象,可以說它就代表著這個web站點,并且這個對象,在Tomcat啟動時就創(chuàng)建,在Tomcat關閉時才會銷毀
(2) 功能
所有Servlet都共享著一個ServletContext對象,所以ServletContext對象的作用是在整個Web應用的動態(tài)資源之間共享數據,也就是說不同的Servlet之間可以通過ServletContext進行通訊,從而共享數據
(3) 獲取ServletContext對象
GenericServlet類有getServletContext()方法,所以可以直接使用this.getServletContext()來獲取
public class MyServlet implements Servlet {
public void init(ServletConfig config) {
ServletContext context = config.getServletContext();
}
}
public class MyServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response) {
ServletContext context = this.getServletContext();
}
}
(4) 域對象的功能
所有域對象都有存取數據的功能,可以將這種存儲數據的方式看做,Map的方式
我們來看幾個常見的用來操作數據的方法
存儲
//用來存儲一個對象,也可以稱之為存儲一個域屬性
void setAttribute(String name, Object value)
Eg:servletContext.setAttribute(“xxx”, “XXX”)
//在ServletContext中保存了一個域屬性,域屬性名稱為xxx,域屬性的值為XXX
獲取
//用來獲取ServletContext中的數據
Object getAttribute(String name)
//獲取名為xx的域屬性
Eg:String value = (String)servletContext.getAttribute(“xxx”);
//獲取所有域屬性的名稱;
Enumeration getAttributeNames()
移除
//用來移除ServletContext中的域屬性
void removeAttribute(String name)
訪問量統(tǒng)計的小案例
package cn.ideal.web.servlet;
import javax.servlet.*;
import java.io.IOException;
public class ServletDemo2 extends GenericServlet {
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
//獲取ServletContext對象
ServletContext servletContext = this.getServletContext();
//獲取ServletContext對象中的count屬性
Integer count = (Integer) servletContext.getAttribute("count");
if (count == null) {
//如果在ServletContext中不存在count屬性,name設置為count的值為1,表示第一次訪問
count = 1;
} else {
//如果在Servlet中存在count屬性,說明以前被訪問過,name讓count在原來的基礎上加1
count++;
}
servletResponse.setContentType("text/html;charset=UTF-8");
//向客戶端響應本頁面被訪問的次數
servletResponse.getWriter().print("<h1>本頁面一共訪問" + count + "次</h1>");
//保存count的值到ServletContext對象中
servletContext.setAttribute("count", count);
}
}
(八) 獲取資源相關方法
(1) 獲取路徑
使用ServletContext對象可以用來獲取Web應用下的資源,例如在一個web應用的根目錄下創(chuàng)建aaa.txt文件,WEB-INF目錄下創(chuàng)建bbb.txt文件,如果我們想要通過Servlet獲取這兩者的路徑就可以這樣來寫
//獲取aaa.txt的路徑
String realPath = servletContext.getRealPath(“/aaa.txt”)
//獲取bbb.txt的路徑
?String realPath = servletContext.getRealPath(“/WEB-INF/b.txt”)
獲取單個文件路徑是這樣,我們還有一種方式,可以獲取到指定目錄下所有的資源路徑,例如獲取/WEB-INF下的所有資源路徑
Set set = context.getResourcePaths("/WEB-INF");
System.out.println(set);
(2) 獲取資源流
不僅我們可以使用ServletContext獲取路徑,我們還可以獲取資源流,以上面假設的兩個文件為例
//獲取aaa.txt
InputStream in = servletContext.getResourceAsStream(“/aaa.txt”);
//獲取bbb.txt
InputStream in = servletContext.getResourceAsStream(“/WEB-INF/b.txt”);
(3) 獲取類路徑下資源
InputStream in = this.getClass().getClassLoader().getResourceAsStream("xxx.txt");
System.out.println(IOUtils.toString(in));
(九) 使用注解,不再配置web.xml
每創(chuàng)建一個Servlet我們就需要在web.xml中配置,但是如果我們的Servlet版本在3.0以上,就可以選擇不創(chuàng)建web.xml,而使用注解來解決,十分簡單方便
例如我們創(chuàng)建一個Servlet,配置web.xml如下
<servlet>
<servlet-name>ServletDemo2</servlet-name>
<servlet-class>cn.ideal.web.servlet.ServletDemo2</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ServletDemo2</servlet-name>
<url-pattern>/Demo2</url-pattern>
</servlet-mapping>
//在類名的上方寫入這樣一句代碼,引號內為外部訪問路徑
@WebServlet("/Demo2")
是不是很簡單方便,我們看一下其中的原理:
//WebServlet 源碼節(jié)選
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface WebServlet {
String name() default "";
String[] value() default {};
String[] urlPatterns() default {};
int loadOnStartup() default -1;
這個注解可以看到,@Target({ElementType.TYPE})作用范圍為類上,@Retention(RetentionPolicy.RUNTIME)保留在運行期,name()方法反而在這里沒有那么重要,因為在web.xml中,name主要起一個關聯(lián)的作用,其中我們最重要的就是這個String[] urlPatterns() default {};配置一個地址,它的定義為一個數組,當然配置一個也是可以的,即urlPatterns = "/Demo2"而其中value所代表的最重要的值,其實也就代表這個地址,所以可以寫為 Value = "/Demo2" ,而 Value又可以省略,所以可以寫成 "/Demo2"
結尾:
如果內容中有什么不足,或者錯誤的地方,歡迎大家給我留言提出意見, 蟹蟹大家 !_
如果能幫到你的話,那就來關注我吧?。ㄏ盗形恼戮鶗诠娞柕谝粫r間更新)
在這里的我們素不相識,卻都在為了自己的夢而努力 ?
一個堅持推送原創(chuàng)Java技術的公眾號:理想二旬不止