背景
上世紀90年代,隨著Internet和閱讀器的飛速發展,基于閱讀器的B/S模式隨之火爆發展起來。
最初,用戶使用閱讀器向WEB服務器發送的要求都是要求靜態的資源,比如html、css等。
但是可以想象:根據用戶要求的不同動態的處理并返回資源是天經地義必須的要求。
CGI
必須要滿足上述需求,所以CGI(Common Gateway Interface)出現了。CGI程序使用C、Shell Script或Perl編寫,CGI是為特定操作系統編寫的(如UNIX或Windows),不可移植,CGI程序對每一個要求產生新的進程去處理。步驟以下:
Java
與此同時,Java語言也在迅速發展。必定的,Java要支持上述需求。
Java有兩種方案來實現動態需求,它們都屬于JavaEE技術的1部份。
applet
這是純客戶端(閱讀器)方案,applet就是閱讀器中的Java插件,閱讀器通過它就可以夠解釋履行WEB服務器發過來的Java代碼,從而實現動態。但是,明顯這類方案不好,既需要閱讀器必須安裝插件,又受限于閱讀器,所以Java代碼不能太多和太復雜。
比如,如果安裝了JRE,雖然IE閱讀器會自動啟用Java插件,但是你可以輕易制止。再比如Chrome還需要你手動去安裝插件才行,普通用戶連Java是甚么都不知道他怎樣會去裝呢?
IE以下圖:
Servlet
既然閱讀器不方便履行Java代碼,那自然還是服務端來履行了,所以Servlet出現了,Servlet就是server真個applet的意思。
其實Servlet的工作原理基本類似上面的CGI,不過Servlet比CGI更好。
Servlet誕生后,SUN公司很快發現了Servlet編程非常繁瑣,這是由于:
所以,SUN鑒戒了Microsoft的ASP,正式提出JSP(Servlet1.1),已期望能代替Servlet。但是很快,SUN發現JSP也有問題:
所以,Servlet1.2出現了,這個版本的Servlet提倡了MVC思想:
基本上到這里Servlet的大方向已固定了,隨之,成熟的發展至今 - 2016年5月26日…
↑以上,是關于Servlet的歷史部份。↓下面來說1講Servlet規范中重要知識點。
聲明:以下內容歸納自官方Servlet規范和JavaEE規范等文檔。
下載地址:
Servlet規范官方地址:JSR 340: Java Servlet 3.1 Specification(中文版網上有人翻譯了,可以自己搜索找找)
可以自己下載瀏覽,終究版final是2013年5月28發布的Servlet3.1。
Servlet有兩種意思:
廣義上是:基于Java技術的Web組件,被容器托管,用于生成動態內容。
再詳細點說,Servlet是JavaEE組件中的 -> Web組件的 -> 1種。
(其它兩種是JavaServer Faces和JavaServer Page)
狹義上說:是JavaEE API中的1個interface
,javax.servlet.Servlet
;
Servlet 容器/引擎:
Servlet容器也能夠叫引擎,Container/Engine,用于履行Servlet
。
容器本身(不依賴Web服務器)就提供了基于要求/響應發送模型的網絡服務,解碼基于MIME的要求,格式化基于MIME的響應。
所有容器必須實現HTTP協議的要求/響應模型。其它協議不強求,如HTTPS。
下面開始說1下規范的核心要點。
請注意:我不是要完全的論述Servlet規范,畢竟你可以直接看規范。這里我只是要記錄我認為重要的點。
為了方便描寫,先聲明1些名詞:
web.xml
= 部署描寫符(Deployment Descriptor )Servlet
生命周期:
Servlet
的生命(周期)是由容器管理的,換句話說,Servlet
程序員不能用代碼控制其生命。
加載和實例化:
時機取決于web.xml
的定義,如果有<load-on-startup>x</load-on-startup>
則在容器啟動時,反之則在第1次針對這個Servlet的要求產生時。
初始化:
實例化后會立馬進行初始化。也就是履行init
方法。
要求處理:
初始化后,Servlet
就能夠接受要求了。
基本方式是履行Servlet
接口中的service
方法。
固然,API也提供了HttpServlet
抽象類,其中有doGet
、doPost
等特殊方法。
注意:任意的容器依照規范必須實現上述幾種方法,所以你的代碼寫在這幾個方法中都可以。
終止服務:
容器會在適合的時候燒毀某個Servlet
對象,這個策略取決于容器的開發者/商。
在容器關閉的時候Servlet
對象1定會被燒毀。
當1或2產生時,也就是Servlet
對象被燒毀時,destroy
方法會被調用。
1. 要求路徑元素
Context Path
:
以'/'
開頭,但不以'/'
結尾
Servlet Path
:
以'/'
開頭
PathInfo
:
要末為null
要末以'/'
開頭
例如:
2. 要求編碼
要求會以甚么編碼情勢送給服務器端呢?
HTTP協議沒有強迫規定,所以實際上這是由閱讀器自己決定的,決定后閱讀器可以通過entity-body
中的Content-Type
項告知服務器自己使用了甚么編碼。但是!大部份情況下閱讀器不會這么做的。比如說,Get要求是沒有entity-body
的,自然也不會使用Content-Type
了。
在服務端,我們Servlet規范 規定了如果要求沒有指定編碼的話,容器必須使用IOS⑻859⑴
來解碼。為了讓開發人員知道要求給沒給出編碼,容器會在沒給的情況下通過getCharacterEncoding
返回null
來告知我們。
為了在我們明知道不是ISO⑻859⑴
編碼的情況下給我們自主權,ServletRequest
提供了setCharacterEncoding(String enc);
1個Web利用對應1個ServletContext
接口的實例。
1. 獲得資源:
ServletContext
接口提供了直接訪問Web利用中靜態內容(意思是說你獲得jsp
返回就是jsp源碼
)層次結構的文件的方法。
getResource
getResourceAsStream
這兩個方法需要的String參數必須是以'/'
開頭的,這個'/'
代表相對:
ServletContextPath
的路徑。
或WEB-INF/lib
中的jar
中的METE-INF/resources
路徑。
代表容器的響應,沒有特別需要注意的。
過濾器,是Java中1種代碼重用技術,通過攔截要求改變HTTP要求的內容、響應、Header信息。
其它沒甚么好說的,只需要注意的1點是:
匹配的Filter
極可能是多個而不是1個,所以Filter
是1個FilterChain
設計。
所有Filter
的doFilter
方法終究都需要調用chain.doFilter(request, response);
方法來觸發調用鏈的下1個Filter
或如果是最后1個Filter
那末直接訪問目標資源。
用于映照到Servlet
的路徑是:
客戶端要求的URL
- ServletContext上下文路徑
- 路徑參數
。
例如當客戶端要求的URL是:http://www.google.com/testproject/action/servlet1?param1=asd
時,那末需要映照的路徑是:/action/servlet1
。
重要!重要!重要!選擇映照到的Servlet的規則是,依照以下的順序查找,如果已選定1個就會匹配成功不會繼續往下:
先精確匹配<url-pattern>
,成功則選擇。(精確匹配)
遞歸遍歷路徑樹,選擇最長的路徑匹配。(最長匹配)
如果URL最后1個部份包括擴大名,比如/action/servlet1.jsp
,容器將選擇專門聲明了要處理此擴大名要求的Servlet
。(擴大名匹配)
如果123都沒有匹配,容器將提供1個后備方案,1般來講是提供1個"default" Servlet
。(低保匹配,優先級最低,提供1個最低保障)
映照規范
以'/'
字符開始,以'/*'
字符結束的字符串:用于路徑匹配。(這是1個準確嚴謹的定義而已)。
以'*.'
開始的字符串用于擴大名映照。
空字符串""
是特殊的URL,精確映照到利用的上下文根,即http://host:port/<context-root>/
,這類情況(""
)相當于<url-pattern>/<url-pattern>
。
只包括'/'
字符的字符串表示利用的"default" Servlet
。
隱式映照
容器可以為1些擴大名定義1些影射映照,比如來1個.jsp
的,那末如果上面的顯示映照沒有攔截.jsp
,此時這里應當發揮作用。
tomcat中是怎樣做的(我看了Jetty也是差不多)
tomcat在其頂級的web.xml
中定義了且開放了2個<servlet>
(這么說是由于其實不只2個,不過那幾個是注釋狀態)
<servlet>
<servlet-name>default</servlet-name>
<servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
...
<load-on-startup>1</load-on-startup>
</servlet>
<servlet>
<servlet-name>jsp</servlet-name>
<servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
...
<load-on-startup>3</load-on-startup>
</servlet>
<!-- default servlet mapping -->
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- The mappings for the JSP servlet -->
<servlet-mapping>
<servlet-name>jsp</servlet-name>
<url-pattern>*.jsp</url-pattern>
<url-pattern>*.jspx</url-pattern>
</servlet-mapping>
可以看到tomcat踐行了如上所述的Servlet規范。
jsp:org.apache.jasper.servlet.JspServlet
這個Servlet
終究會:
.jsp
文件變成java
文件(即1個Servlet
);compile
成.class
;default:org.apache.catalina.servlets.DefaultServlet
這個Servlet
終究會:
會話跟蹤機制
HTTP協議是無狀態的,但是記錄狀態,也就是說記錄來自同1客戶真個要求的需求是必須的。所以人們發明了會話跟蹤機制。
Servlet規范定義了1個簡單的HttpSession
接口,允許容器使用幾種方法來實現會話跟蹤,從而使得Web利用開發人員沒必要來關心和寫這塊的代碼。(容器只能幫我們實現單機會話跟蹤,散布式利用多機狀態下,我們需要自己寫代碼實現Session同步)。
幾種方法
Cookies
是最經常使用的機制,且所有Servlet容器必須支持。容器有能力(但是需要程序員顯式調用)向客戶端發送1個cookie
,用來記錄會話,標準名字必須是JSESSIONID
。
URL重寫
比如:http://www.sss.com/aa/bb.html;jessionid=1234
要注意,使用的是分號';'
,這個叫路徑參數,區分于查詢參數。
如何使用Session跟蹤機制
需要程序員手動調用
//a. 有session返回,沒有新生成1個
request.getSession();
request.getSession(true);
//b. 有session返回,沒有返回null
request.getSession(false);
如果使用的a方式,再加上使用cookie
機制的情況下,容器的第1次響應(Response)會向客戶端寫1個JSESSIONID
,客戶端從第2次要求(Request)開始會帶著JSESSIONID
,以下圖:
1. WEB-INF
目錄:
此目錄是1個特殊目錄,不能由容器直接提供給客戶端訪問。可以通過:
調用ServletContext
的getResource
和getResourceAsStream
來訪問。
還可以通過RequestDispatcher
來調用從而公然這些內容。
2. WEB-INF
目錄的內容:
/WEB-INF/web.xml
部署描寫文件。
Servlet
和其它類的目錄/WEB-INF/classes/
。
Java歸檔文件(jar)區域/WEB-INF/lib/*.jar
。
Servlet API為ServletContext
、HttpSession
、ServletRequest
這3個對象添加了事件。這可讓Servlet開發人員更好的控制上述3個對象生命周期。
ServletContext
事件類型 | 描寫 | 監聽器接口 |
---|---|---|
生命周期 | ServletContext剛創建并可用于服務它的第1個要求或行將關閉 | javax.servlet.ServletContextListener |
更改屬性 | ServletContext的屬性已添加、已刪除、已替換 | javax.servlet.ServletContextAttributeListener |
HttpSession
事件類型 | 描寫 | 監聽器接口 |
---|---|---|
生命周期 | 會話已創建、燒毀、超時 | javax.servlet.http.HttpSessionListener |
更改屬性 | HttpSession的屬性已添加、已刪除、已替換 | javax.servlet.http.HttpSessionAttributeListener |
改變ID | HttpSession的ID將被改變 | javax.servlet.http.HttpSessionIdListener |
會話遷移 | HttpSession已被激活或鈍化 | javax.servlet.http.HttpSessionActivationListener |
對象綁定 | 對象已從HttpSession綁定或解綁 | javax.servlet.http.HttpSeesionBindingListener |
ServletRequest
事件類型 | 描寫 | 監聽器接口 |
---|---|---|
生命周期 | 1個要求已開始由Web組件處理 | javax.servlet.ServletRequestListener |
更改屬性 | 已在servlet上添加、移除、替換屬性 | javax.servlet.ServletRequestAttributeListner |
異步事件 | 超時、連接終止或完成異步操作處理 | javax.servlet.AsyncListener |
實例化時機:
容器必須在開始履行進入利用的第1個要求之前完成Web利用中所有監聽器類的實例化。
1. 關于順序:
Servlet
加載順序
<load-on-startup>x</load-on-startup>
中的x指定加載順序,必須不小于0,越小越早加載。
Filter
的
加載順序:
應當是沒有要求,不會依照根據web.xml
中聲明的順序,但也不是隨機的,以某種規則固定,這個不重要。
過濾器鏈構造規則:
規范中6.2.4 節,待驗證總結。
Listener
調用順序
根據在web.xml
中注冊的順序來被調用。例外的,生命周期中的燒毀事件觸發的destroy
會被反方向的順次調用。
2. 關于初始化參數:
ServletContext
的初始化參數:
設值:由于1個利用只有1個ServletContext
,所以是直接聲明在根<web-app>
下的:
<context-param>
<param-name>contextParam1</param-name>
<param-value>11context11</param-value>
</context-param>
取值/設值:只要獲得到了ServletContext
對象,就能夠使用其getInitParameter(String name)
方法來取值,同時也能夠使用setInitParameter(String name, String value);
來設值,所以很多地方都可以取值/設值。
Listener
的初始化參數:
設值:監聽器沒有自己獨立的初始化參數配置,想要使用的話可以借助將參數配置在ServletContext
的初始化參數位置。
取值/設值:如上。
Filter
的初始化參數:
設值:只能在<filter>
中使用<init-param>
來設置值:
<filter>
<filter-name>FirstFilter</filter-name>
<filter-class>filter.FirstFilter</filter-class>
<init-param>
<param-name>firstFilterParam1</param-name>
<param-value>11filter11</param-value>
</init-param>
</filter>
取值:只有獲得了某1個Filter
的FilterConfig
對象以后,才能使用此對象的方法getInitParameter(String name)
方法來取值。
另外的:當你自己寫1個Filter
的時候,除實現Filter
接口以外,你還可以選擇學習類似GenericServlet
的方式,額外的實現FilterConfig
接口并覆蓋其getInitParameter(String name)
方法,從而可以在自己的Filter
的任何方法中都能調用取值方法而不必顯示獲得FilterConfig
對象。像下面這樣:
public class FirstFilter implements Filter, FilterConfig{
...
}
Servlet
的初始化參數:
設值:只能在<servlet>
中使用<init-param>
來設置值:
<servlet>
<servlet-name>FirstServlet</servlet-name>
<servlet-class>servlet.FirstServlet</servlet-class>
<init-param>
<param-name>FirstServletParam1</param-name>
<param-value>11FirstServlet11</param-value>
</init-param>
</servlet>
取值:跟Filter
類似,Servlet
有ServletConfig
對象,只要獲得到它就能夠使用其getInitParameter(String name)
方法來取值。
另外的:通常我們不會直接實現Servlet
接口而是使用繼承GenericServlet
/HttpServlet
的方式,那末(如我在上面Filter
“另外的”部份所說)我們就能夠利用它們的實現直接使用getInitParameter(String name)
方法來取值。