Servlet(Server Applet),全稱Java Servlet。 對這個Servlet一直不瞭解,也沒有學習過,如今進行各基礎瞭解學習。
html
Servlet是用Java編寫的服務端程序,其主要功能在於交互式地瀏覽和修改數據,生成動態的Web內容。Servlet運行於支持Java的應用服務器中。從實現上講,Servlet能夠響應任何類型的請求,但絕大多數狀況下Servlet只用來擴展基於HTTP協議的Web服務器。java
Servlet的工做模式:程序員
1:客戶端發送求情到服務器web
2:服務器啓動並調用Servlet, Servlet根據客戶端請求生成響應內容並將其傳給服務器數據庫
3:服務器將響應返回給客戶端安全
Servlet的主要相關類服務器
其中GenericServlet下面還有一個HttpServlet class.網絡
Servlet接口中定義的方法多線程
1 public interface Servlet { 2 3 public void init(ServletConfig config) throws ServletException; 4 5 public ServletConfig getServletConfig(); 6 7 public void service(ServletRequest req, ServletResponse res) 8 throws ServletException, IOException; 9 10 public String getServletInfo(); 11 12 public void destroy(); 13 14 }
根據上面Servlet的接口函數,描述一下Servlet的運行過程:併發
Servlet程序是由WEB服務器調用,web服務器收到客戶端的Servlet請求訪問後,
1:web服務器首先檢查是否已經裝在並建立了該Servlet的實例對象,若是已經建立,則直接到第四步;不然執行第二步;
2:裝載並建立該servlet的一個實例對象;
3:調用Servlet實例對象的init()方法;
4:建立一個用於封裝Http請求消息的HttpServletRequest對象和一個表明HttpServletResponse對象,而後調用Servlet的Service()方法並將請求和響應對象做爲參數傳進去。
5:web應用程序被停職或從新啓動以前,servlet引擎將卸載servlet,並在卸載以前調用Servlet的destory()方法。
Servlet 的生命週期
其中,init(), service(),destory()是servlet生命週期的方法。表明了Servlet從出生到工做,再到死亡的過程。Servlet容器(例如Tomcat)會根據下面的規則來調用這三個方法。
1,init(),當Servlet第一次被請求時,Servlet容器就會開始調用這個方法來初始化一個Servlet對象出來,可是這個方法不會再後續請求中被Servlet容器調用,就像人是能出生一次同樣。咱們能夠利用init()方法來執行相應的初始化工做。調用這個方法時,Servlet容器會傳入一個ServletConfig對象進來從而對Servlet對象進行初始化。
2,Service()方法,每當請求Servlet時,Servlet就會調用這個方法。就像人同樣,須要不停的接受老闆的指令而且工做。第一次請求時,Servlet容器會先調用init()方法初始化一個Servlet對象出來,而後調用它的service()方法進行工做。但在後續請求中,servlet容器只會調用service方法了。
3,destory, 當要銷燬Servlet時,Servlet容器就會調用這個方法,就如人同樣。到時候就會死亡。在卸載應用程序或者關閉Servlet容器時,就會發生這種狀況,通常在這個方法中會寫一些清除代碼。
getServletInfo( ),這個方法會返回Servlet的一段描述,能夠返回一段字符串。
getServletConfig( ),這個方法會返回由Servlet容器傳給init( )方法的ServletConfig對象。
此處應該增長例子,可是在我添加例子過程當中,常常出現找不到servlet,出現404錯誤。
後面找到真正緣由後,再添加例子
按照博客中給的例子進行驗證,經常出現http404錯誤。
前面咱們編寫Servlet一直是經過實現Servlet接口來編寫的,可是,這種方法必須實現接口servlet中定義的全部方法。即便有一些方法中沒有任何東西也要實現。而且還須要本身手動的維護ServletConfig這個對象的引用。所以,這樣去實現Servlet是比較麻煩的。幸虧,GenericServlet抽象類的出現很好的解決了這個問題。本着儘量使代碼簡潔的原則,GenericServlet實現了Servlet和ServletConfig接口。 GenericServlet類實現:
1 public abstract class GenericServlet implements Servlet, ServletConfig, 2 java.io.Serializable { 3 4 @Override 5 public void destroy() { 6 // NOOP by default 7 } 8 9 @Override 10 public ServletConfig getServletConfig() { 11 return config; 12 } 13 14 @Override 15 public String getServletInfo() { 16 return ""; 17 } 18 19 @Override 20 public void init(ServletConfig config) throws ServletException { 21 this.config = config; 22 this.init(); 23 } 24 25 public void init() throws ServletException { 26 // NOOP by default 27 } 28 29 @Override 30 public abstract void service(ServletRequest req, ServletResponse res) 31 throws ServletException, IOException; 32 33 }
其中,GenericServlet抽象類相比於直接實現Servlet接口,有如下幾個好處:
1.爲Servlet接口中的大部分方法提供了默認的實現,則程序員須要什麼就直接改什麼,再也不須要把全部的方法都本身實現了。
2.將init( )方法中的ServletConfig參數賦給了一個內部的ServletConfig引用從而來保存ServletConfig對象,不須要程序員本身去維護ServletConfig了。
可是,咱們發如今GenericServlet抽象類中還存在着另外一個沒有任何參數的Init()方法:
public void init() throws ServletException {
}
設計者的初衷究竟是爲了什麼呢?在第一個帶參數的init()方法中就已經把ServletConfig對象傳入而且經過引用保存好了,完成了Servlet的初始化過程,那麼爲何後面還要加上一個不帶任何參數的init()方法呢?
抽象類是沒法直接產生實例的,須要另外一個類去繼承這個抽象類,那麼就會發生方法覆蓋的問題,若是在類中覆蓋了GenericServlet抽象類的init()方法,那麼程序員就必須手動的去維護ServletConfig對象了,還得調用super.init(servletConfig)方法去調用父類GenericServlet的初始化方法來保存ServletConfig對象,這樣會給程序員帶來很大的麻煩。GenericServlet提供的第二個不帶參數的init( )方法,就是爲了解決上述問題的。這個不帶參數的init()方法,是在ServletConfig對象被賦給ServletConfig引用後,由第一個帶參數的init(ServletConfig servletconfig)方法調用的,那麼這意味着,當程序員若是須要覆蓋這個GenericServlet的初始化方法,則只須要覆蓋那個不帶參數的init( )方法就行了,此時,servletConfig對象仍然有GenericServlet保存着。簡而言之一句話,就是覆蓋的時候能夠不用管servletConfig。
可是GenericServlet沒有處理Service(), HttpServlet()纔是主角。
之因此所HttpServlet要比GenericServlet強大,其實也是有道理的。HttpServlet是由GenericServlet抽象類擴展而來的,HttpServlet抽象類的聲明以下所示:
1 public abstract class HttpServlet extends GenericServlet {}
HttpServlet之因此運用普遍的另外一個緣由是如今大部分的應用程序都要與HTTP結合起來使用。這意味着咱們能夠利用HTTP的特性完成更多更強大的任務。Javax。servlet.http包是Servlet API中的第二個包,其中包含了用於編寫Servlet應用程序的類和接口。Javax.servlet.http中的許多類型都覆蓋了Javax.servlet中的類型。
HttpServlet抽象類是繼承於GenericServlet抽象類而來的。使用HttpServlet抽象類時,還須要藉助分別表明Servlet請求和Servlet響應的HttpServletRequest和HttpServletResponse對象。
1 public interface HttpServletRequest extends ServletRequest {} 2 public interface HttpServletResponse extends ServletResponse {}
先看一下GenericServlet中service定義:
1 @Override 2 public abstract void service(ServletRequest req, ServletResponse res) 3 throws ServletException, IOException;
再來看看HttpServlet()是怎麼來覆蓋的:
1 @Override 2 public void service(ServletRequest req, ServletResponse res) 3 throws ServletException, IOException { 4 5 HttpServletRequest request; 6 HttpServletResponse response; 7 8 try { 9 request = (HttpServletRequest) req; 10 response = (HttpServletResponse) res; 11 } catch (ClassCastException e) { 12 throw new ServletException("non-HTTP request or response"); 13 } 14 service(request, response); 15 } 16 17 protected void service(HttpServletRequest req, HttpServletResponse resp) 18 throws ServletException, IOException { 19 20 String method = req.getMethod(); 21 22 if (method.equals(METHOD_GET)) { 23 long lastModified = getLastModified(req); 24 if (lastModified == -1) { 25 // servlet doesn't support if-modified-since, no reason 26 // to go through further expensive logic 27 doGet(req, resp); 28 } else { 29 long ifModifiedSince; 30 try { 31 ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE); 32 } catch (IllegalArgumentException iae) { 33 // Invalid date header - proceed as if none was set 34 ifModifiedSince = -1; 35 } 36 if (ifModifiedSince < (lastModified / 1000 * 1000)) { 37 // If the servlet mod time is later, call doGet() 38 // Round down to the nearest second for a proper compare 39 // A ifModifiedSince of -1 will always be less 40 maybeSetLastModified(resp, lastModified); 41 doGet(req, resp); 42 } else { 43 resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED); 44 } 45 } 46 47 } else if (method.equals(METHOD_HEAD)) { 48 long lastModified = getLastModified(req); 49 maybeSetLastModified(resp, lastModified); 50 doHead(req, resp); 51 52 } else if (method.equals(METHOD_POST)) { 53 doPost(req, resp); 54 55 } else if (method.equals(METHOD_PUT)) { 56 doPut(req, resp); 57 58 } else if (method.equals(METHOD_DELETE)) { 59 doDelete(req, resp); 60 61 } else if (method.equals(METHOD_OPTIONS)) { 62 doOptions(req,resp); 63 64 } else if (method.equals(METHOD_TRACE)) { 65 doTrace(req,resp); 66 67 } else { 68 // 69 // Note that this means NO servlet supports whatever 70 // method was requested, anywhere on this server. 71 // 72 73 String errMsg = lStrings.getString("http.method_not_implemented"); 74 Object[] errArgs = new Object[1]; 75 errArgs[0] = method; 76 errMsg = MessageFormat.format(errMsg, errArgs); 77 78 resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg); 79 } 80 }
咱們發現,HttpServlet中的service方法把接收到的ServletRequsest類型的對象轉換成了HttpServletRequest類型的對象,把ServletResponse類型的對象轉換成了HttpServletResponse類型的對象。之因此可以這樣強制的轉換,是由於在調用Servlet的Service方法時,Servlet容器總會傳入一個HttpServletRequest對象和HttpServletResponse對象,預備使用HTTP。所以,轉換類型固然不會出錯了。
轉換以後,service方法把兩個轉換後的對象傳入了本身定義的service方法進行運行。下面看怎麼運行的:
咱們會發如今service方法中仍是沒有任何的服務邏輯,可是卻在解析HttpServletRequest中的方法參數,並調用如下方法之
一:doGet,doPost,doHead,doPut,doTrace,doOptions和doDelete。這7種方法中,每一種方法都表示一個Http方法。doGet和doPost是最經常使用的。因此,若是咱們須要實現具體的服務邏輯,再也不須要覆蓋service方法了,只須要覆蓋doGet或者doPost就行了。
總之,HttpServlet有兩個特性是GenericServlet所不具有的:
1.不用覆蓋service方法,而是覆蓋doGet或者doPost方法。在少數狀況,還會覆蓋其餘的5個方法。
2.使用的是HttpServletRequest和HttpServletResponse對象
下面講述如何解析HttpServletRequest以及如何組裝HttpServletResponse
再次先省略這兩部份內容。
Servlet線程安全
Servlet多線程體系結構是創建在Java多線程機制之上的,它的生命週期是由Web容器負責的。
當客戶端第一次請求某個Servlet時,Servlet容器將會根據web.xml配置文件實例化這個Servlet類,此時它貯存於內存中。。當有新的客戶端請求該Servlet時,通常不會再實例化該Servlet類,也就是有多個線程在使用這個實例。 這樣,當兩個或多個線程同時訪問同一個Servlet時,可能會發生多個線程同時訪問同一資源的狀況,數據可能會變得不一致。因此在用Servlet構建的Web應用時要注意線程安全的問題。每個請求都是一個線程,而不是進程,所以,Servlet對請求的處理的性能很是高。
對於Servlet,它被設計爲多線程的(若是它是單線程的,你就能夠想象,當1000我的同時請求一個網頁時,在第一我的得到請求結果以前,其它999我的都在鬱悶地等待),若是爲每一個用戶的每一次請求都建立 一個新的線程對象來運行的話,系統就會在建立線程和銷燬線程上耗費很大的開銷,大大下降系統的效率。
所以,Servlet多線程機制背後有一個線程池在支持,線程池在初始化初期就建立了必定數量的線程對象,經過提升對這些對象的利用率,避免高頻率地建立對象,從而達到提升程序的效率的目的。(由線程來執行Servlet的service方法,servlet在Tomcat中是以單例模式存在的, Servlet的線程安全問題只有在大量的併發訪問時纔會顯現出來,而且很難發現,所以在編寫Servlet程序時要特別注意。線程安全問題主要是由實例變量形成的,所以在Servlet中應避免使用實例變量。若是應用程設計沒法避免使用實例變量,那麼使用同步來保護要使用的實例變量,但爲保證系統的最佳性能,應該同步可用性最小的代碼路徑)
從創建工程,到配置web.xml文件。編寫Servlet,啓動Tomcat等基本都沒有問題。可是常常會出現http404錯誤。這個過程後面會在前面補例子。
Servlet的生命週期是由Tomcat容器管理。
a)客戶發出請求->web服務器轉發到web容器Tomcat;
b)Tomcat主線程對轉發來用戶的請求作出響應建立兩個對象:HttpServletRequset和HttpServletResponse;
c)從請求中的URL中找到正確的Servlet,Tomcat 爲其建立或者分配一個線程,同時把2建立的兩個對象傳遞給該線程。
d)Tomcat調用Servlet的Service()方法,根據請求參數的不一樣調用doGet()或者doPost()方法;
e)假設是HTTP GET請求,doGet()方法生成靜態頁面。並組合到響應對象裏;
Servlet線程結束後,Tomcat將響應對象轉換爲Http響應發回給客戶,同時刪去請求和響應對象。
從該過程當中,咱們能夠理解Servlet的生命週期:Servlet類加載(對應第3步);調用init方法(對應第3步);調用service方法(對應第四、5步);調用destory()方法(對應第6步)。
Servlet的生命週期包含了下面4個階段:
(1)加載和實例化
Servlet容器負責加載和實例化Servlet。當Servlet容器啓動時,或者在容器檢測到須要這個Servlet來響應第一個請求時,建立Servlet實例。當Servlet容器啓動後,它必需要知道所需的Servlet類在什麼位置,Servlet容器能夠從本地文件系統、遠程文件系統或者其餘的網絡服務中經過類加載器加載Servlet類,成功加載後,容器建立Servlet的實例。由於容器是經過Java的反射API來建立Servlet實例,調用的是Servlet的默認構造方法(即不帶參數的構造方法),因此咱們在編寫Servlet類的時候,不該該提供帶參數的構造方法。
(2)初始化
在Servlet實例化以後,容器將調用Servlet的init()方法初始化這個對象。初始化的目的是爲了讓Servlet對象在處理客戶端請求前完成一些初始化的工做,如創建數據庫的鏈接,獲取配置信息等。對於每個Servlet實例,init()方法只被調用一次。在初始化期間,Servlet實例可使用容器爲它準備的ServletConfig對象從Web應用程序的配置信息(在web.xml中配置)中獲取初始化的參數信息。在初始化期間,若是發生錯誤,Servlet實例能夠拋出ServletException異常或者UnavailableException異常來通知容器。ServletException異經常使用於指明通常的初始化失敗,例如沒有找到初始化參數;而UnavailableException異經常使用於通知容器該Servlet實例不可用。例如,數據庫服務器沒有啓動,數據庫鏈接沒法創建,Servlet就能夠拋出UnavailableException異常向容器指出它暫時或永久不可用。
(3)請求處理
Servlet容器調用Servlet的service()方法對請求進行處理。要注意的是,在service()方法調用以前,init()方法必須成功執行。在service()方法中,Servlet實例經過ServletRequest對象獲得客戶端的相關信息和請求信息,在對請求進行處理後,調用ServletResponse對象的方法設置響應信息。在service()方法執行期間,若是發生錯誤,Servlet實例能夠拋出ServletException異常或者UnavailableException異常。若是UnavailableException異常指示了該實例永久不可用,Servlet容器將調用實例的destroy()方法,釋放該實例。此後對該實例的任何請求,都將收到容器發送的HTTP 404(請求的資源不可用)響應。若是UnavailableException異常指示了該實例暫時不可用,那麼在暫時不可用的時間段內,對該實例的任何請求,都將收到容器發送的HTTP 503(服務器暫時忙,不能處理請求)響應。
(4)服務終止
當容器檢測到一個Servlet實例應該從服務中被移除的時候,容器就會調用實例的destroy()方法,以便讓該實例能夠釋放它所使用的資源,保存數據到持久存儲設備中。當須要釋放內存或者容器關閉時,容器就會調用Servlet實例的destroy()方法。在destroy()方法調用以後,容器會釋放這個Servlet實例,該實例隨後會被Java的垃圾收集器所回收。若是再次須要這個Servlet處理請求,Servlet容器會建立一個新的Servlet實例。
在整個Servlet的生命週期過程當中,建立Servlet實例、調用實例的init()和destroy()方法都只進行一次,當初始化完成後,Servlet容器會將該實例保存在內存中,經過調用它的service()方法,爲接收到的請求服務。下面給出Servlet整個生命週期過程的UML序列圖
本節Servlet學習在這。看了好多博客,貼的有點亂,總體對serclet有了理解。後面再加一貼針對具體例子。
https://www.cnblogs.com/gaoxiangde/p/4339571.html-----後面部分主要參考
https://blog.csdn.net/qq_19782019/article/details/80292110------主要參考很是好,後面一部分ServletContext沒有理解。
https://www.cnblogs.com/whgk/p/6399262.html-----源碼解釋的很清楚
https://www.cnblogs.com/xdp-gacl/p/3729033.html ------Java web開發入門