Servlet原理解析
文章目錄
- 1 Servlet介紹
- 1.1 簡介
- 1.2 Servlet生命周期
- 1.2.1 生命周期
- 1.2.2 servlet配置
- 1.2.3 servlet中load-on-startup作用
- 1.3 Servlet接口
- 1.3.1 HttpServlet
- 1.3.1.1 service,doGet,doPost區別
- 1.3.2 表單提交中get和post方式區別
- 1.3.2.1 表單中區別
- 1.3.2.2 http中區別
- 2 Servlet原理
- 2.1 工作原理
- 2.1.1 問題引入
- 2.1.2 Servlet容器啟動
- 2.1.3 HttpServletRequest 與 HttpServletResponse
- 2.1.4 HttpSession
- 2.1.5 ServletContext
- 2.1.6 線程安全
- 2.2 Servlet單例多線程
- 2.2.1 Servlet如何處理多個請求訪問
- 2.2.2 Servlet容器如何采用單實例多線程的方式來處理請求
- 2.2.3 Servlet多實例
- 2.2.4 容器概念理解
1 Servlet介紹
1.1 簡介
Servlet是一種運行于服務器端的Java應用程序,具有獨立于平臺和協議的特性,可以生成動態的Web頁面,它擔當客戶請求與服務器響應的中間層 ,有特殊的技術規范:必須繼承某個特定父類;必須配置之后才能執行;有特定的生命周期
Servlet是在Java代碼中嵌入頁面代碼,JSP是在頁面代碼中嵌入Java代碼
JSP不能執行,必須轉譯成Servlet并編譯成class后才能執行,Servlet是學好JSP的基礎,能了解JSP的底層運作方式
1.2 Servlet生命周期
1.2.1 生命周期
每個servlet實例的生命周期中只有三種類型的事情,分別對應于由servlet容器所調用的三個方法:
- init() 初始化時期:
當servlet第一次被裝載的時候由servlet容器調用init(),且只調用一次,默認情況下調用超類的init()方法。 - service()運行時期:
接受客戶請求并決定調用何種doXXX方法,并將處理結果返回到客戶端。 - destroy()結束時期:
為可選方法,釋放不用的servlet實例所占內存和資源。
Servlet的生命周期可以歸納為以下幾步:
1.2.2 servlet配置
<servlet><servlet-name>Servlet的名稱</servlet-name><servlet-class>該servlet類的路徑</servlet-class><init-param><param-name>參數名</param-name><param-value>參數值</param-value></init-param><jsp-file>/路徑/XXX.JSP </jsp-file><load-on-startup>表示web站臺被啟動時,自動加載該servlet的優先級別,越小越優先加載</load-on-startup> </servlet> <servlet-mapping><servlet-name>對應的servlet-name名稱</servlet-name><url-pattern>在項目運行時用以訪問的URL </url-pattern> </servlet-mapping>url-pattern 取值:1、可以與具體的某個Servlet對應。2、也可以用/* 來指定所有的頁面,更多關于servlet路徑配置可以點此連接查看
1.2.3 servlet中load-on-startup作用
像上面的servlet配置中的標簽<load-on-startup>,注意到它里面包含了這段配置:<load-on-startup>1</load-on-startup>,那么這個配置有什么作用呢?
所以,<load-on-startup>x</load-on-startup>,中x的取值1,2,3,4,5代表的是優先級,而非啟動延遲時間
1.3 Servlet接口
1.3.1 HttpServlet
HttpServlet類,httpServlet中各種接受請求處理的方法
| doGet() | 處理http的get請求 |
| doPost() | 處理http的post請求,主要用于發送HTML文本中FORM的內容 |
| doHead() | 用于處理HEADER請求 |
| doPut() | 處理http的put請求,模仿 ftp發送 |
| doDelete() | 處理http的delete請求 |
| doOptions() | 該操作自動決定支持什么HTTP方法 |
| doTrace() | 處理HTTP的trace請求 |
發送請求的三種基本方式
地址欄直接鍵入地址(默認get方式) 例如:在地址欄輸入:http://127.0.0.1/jsp2/index.jsp?a=2
使用表單提交(默認get方式)只有使用表單的method=post才是post提交
點擊超鏈接(默認get方式)
1.3.1.1 service,doGet,doPost區別
HttpServlet 里的三個方法service(HttpServletRequest req, HttpServletResponse resp) ,doGet(HttpServletRequest req, HttpServletResponse resp), doPost(HttpServletRequest req, HttpServletResponse res)的區別和聯系
在servlet中默認情況下,無論是get還是post提交過來都會經過service()方法來處理,然后轉向到doGet或是doPost方法,可以查看HttpServlet 類的service方法
protected void service(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException{String method = req.getMethod();if(method.equals("GET")){long lastModified = getLastModified(req);if(lastModified == -1L){doGet(req, resp);} else{long ifModifiedSince = req.getDateHeader("If-Modified-Since");if(ifModifiedSince < (lastModified / 1000L) * 1000L){maybeSetLastModified(resp, lastModified);doGet(req, resp);} else{resp.setStatus(304);}}} elseif(method.equals("HEAD")){long lastModified = getLastModified(req);maybeSetLastModified(resp, lastModified);doHead(req, resp);} elseif(method.equals("POST"))doPost(req, resp);elseif(method.equals("PUT"))doPut(req, resp);elseif(method.equals("DELETE"))doDelete(req, resp);elseif(method.equals("OPTIONS"))doOptions(req, resp);elseif(method.equals("TRACE")){doTrace(req, resp);} else{String errMsg = lStrings.getString("http.method_not_implemented");Object errArgs[] = new Object[1];errArgs[0] = method;errMsg = MessageFormat.format(errMsg, errArgs);resp.sendError(501, errMsg);}}從上面可以看出 這里的service是用來轉向的,但是如果在自己的servlet類中覆蓋了service方法,比如說你的service是下面這樣的,那么這時service就不是用來轉向的,而是用來處理業務的,現在不論客戶端是用pos還是get來請求此servlet,都會執行service方法也只能執行servlet方法,不會去執行doPost或是doGet方法
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { res.getOutputStream().print( "image is <img src='images/downcoin.gif'></img><br>"); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request,response); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { ServletOutputStream out=response.getOutputStream(); String[] args=(String[])request.getParameterValues("fruit"); for(int i=0;i<args.length;i++){ out.print(args[i]+"<br>"); } }所以,我們在寫servlet的時候,一般都是重寫doGet或doPost方法,不會管service方法
1.3.2 表單提交中get和post方式區別
1.3.2.1 表單中區別
表單提交中get和post方式的區別有5點
1.3.2.2 http中區別
HTTP請求:get與post方法的區別:
HTTP定義了與服務器交互的不同方法,最基本的方法是 get 和 post。事實上 get 適用于多數請求,而保留 post僅用于更新站點。根據 HTTP規范,get 用于信息獲取,而且應該是安全的和冪等的。所謂安全的意味著該操作用于獲取信息而非修改信息。換句話說,get 請求一般不應產生副作用。冪等的意味著對同一URL的多個請求應該返回同樣的結果。完整的定義并不像看起來那樣嚴格。從根本上講,其目標是當用戶打開一個鏈接時,可以確信從自身的角度來看沒有改變資源。比如,新聞站點的頭版不斷更新。雖然第二次請求會返回不同的一批新聞,該操作仍然被認為是安全的和冪等的,因為它總是返回當前的新聞。反之亦然。但 post請求就不那么輕松了。post表示可能改變服務器上的資源的請求。
在FORM提交的時候,如果不指定Method,則默認為get請求,Form中提交的數據將會附加在url之后,以?分開與url分開。字母數字字符原樣發送,但空格轉換為+號,其它符號轉換為%XX, 其中XX為該符號以16進制表示的ASCII(或ISOLatin-1)值。get請求請提交的數據放置在HTTP請求協議頭中,而post提交的數據則 放在實體數據中;
get方式提交的數據最多只能有1024字節,而post則沒有此限制
在表單里使用post和get有什么區別
在Form里面,可以使用post也可以使用get。它們都是method的合法取值。但是,post和get方法在使用上至少有兩點不同:
2 Servlet原理
2.1 工作原理
2.1.1 問題引入
Servlet是如何工作的?Servlet 如何實例化、共享變量、并進行多線程處理?
假設有一個運行了大量 Servlet 的 web 服務器。通過 Servlet 之間傳輸信息得到 Servlet 上下文,并設置 session 變量。現在,如果有兩名或更多使用者向這個服務發送請求,接下來 session 變量會發生什么變化?究竟是所有用戶都是用共同的變量?還是不同的用戶使用的變量都不一樣?如果是后者,服務器如何區分不同用戶?
如果有n名用戶訪問一個特定的 Servlet,那么該 Servlet 是僅在第一個用戶首次訪問的時候實例化,還是分別為每個用戶實例化?
2.1.2 Servlet容器啟動
當 Servlet 容器(比如 Apache Tomcat)啟動后,會部署和加載所有 web 應用。當web應用被加載,Servlet 容器會創建一次ServletContext,然后將其保存在服務器的內存中。
web應用的web.xml被解析,找到其中所有 servlet、filter 和 Listener 或 @WebServlet、@WebFilter 和@WebListener 注解的內容,創建一次并保存到服務器的內存中。對于所有過濾器會立即調用init()。當 Servlet 容器停止,將卸載所有 web 應用,調用所有初始化的 Servlet 和過濾器的destroy() 方法,最后回收 ServletContext 和所有 Servlet、Filter 與 Listener實例。
當Servlet 配置的load-on-startup或者 @WebServlet(loadOnStartup) 設置了一個大于 0 的值,則同樣會在啟動的時候立即調用 init() 方法。
load-on-startup中的值表示那些 Servlet 會以相同順序初始化。如果配置的值相同,會遵循 web.xml 中指定的順序或 @WebServlet類加載的順序。另外,如果不設置 load-on-startup 值,init() 方法只在第一次HTTP請求命中問題中的Servlet時才被調用
2.1.3 HttpServletRequest 與 HttpServletResponse
Servlet 容器附加在一個 web 服務上,這個 web 服務會在某個端口號上監聽 HTTP 請求,在開發環境中這個端口通常為 8080,生產環境中通常為 80。當客戶端(web 瀏覽器)發送了一個 HTTP 請求,Servlet 容器會創建新的 HttpServletRequest 和 HttpServletResponse 對象,傳遞給已創建好并且請求的 URL 匹配 url-pattern 的 Filter 和 Servlet 實例中的方法,所有工作都在同一個線程中處理。
request 對象可以訪問所有該 HTTP 請求中的信息,例如 request header 和 request body。response 對象提供需要的控制和發送 HTTP 響應方法,例如設置 header 和 body(通常會帶有 JSP 文件中的 HTML 內容)。提交并完成HTTP 響應后,將回收 request 和 response 對象。
2.1.4 HttpSession
當用戶第一次訪問該 web 應用時,會通過 request.getSession() 第一次獲得 HttpSession。之后 Servlet 容器將會創建 HttpSession,生成一個唯一的 ID(可以通過 session.getId() 獲取)并儲存在服務器內存中。然后 Servlet 容器在該次 HTTP 響應的 Set-Cookie 頭部設置一個 Cookie,以JSESSIONID 作為 Cookie 名字,那個唯一的 session ID 作為 Cookie 的值。
按照 HTTP cookie 規則(正常 web 瀏覽器和 web 服務端必須遵循的標準),當 cookie 有效時,要求客戶端(瀏覽器)在后續請求的 Cookie 頭中返回這個 cookie。
使用瀏覽器內置的HTTP流量監控器,Servlet容器將會確定每個進入的 HTTP 請求的 Cookie 頭中是否存在名為JSESSIONID 的 cookie,然后用它的值(session ID)從服務端內存中找到關聯的 HttpSession。
可以在 web.xml 中設置 session-timeout ,默認值為 30 分鐘。超時到達之前 HttpSession 會一直存活。所以當客戶端不再訪問該 web 應用超過 30 分鐘后,Servlet 容器就會回收這個 session。后續每個請求,即使指定 cookie 名稱也不能再訪問到相同的 session。Servlet 容器會創建一個新的 Cookie。
另一方面,客戶端上的 session cookie 有一個默認存活時間,該事件和該瀏覽器實例運行時間一樣長。所以,當客戶端關閉該瀏覽器實例(所有標簽和窗口)后,這個 session 就會被客戶端回收。新瀏覽器實例不再發送與該 session 關聯的 cookie。一個新的 request.getSession() 將會返回新的HttpSession 并設置一個擁有新 session ID 的 cookie
2.1.5 ServletContext
ServletContext 與 web 應用存活時間一樣長。它被所有 session 中的所有請求共享。
只要客戶端一直與相同瀏覽器實例的web應用交互并且沒有超時HttpSession就會存在。
HttpServletRequest和 HttpServletResponse 的存活時間為客戶端發送完成到完整的響應(web頁面)到達的這段時間。不會被其他地方共享。
所有 Servlet、Filter 和 Listener 對象在web 應用運行時都是活躍的。它們被所有 session 中的請求共享。
設置在 HttpServletRequest、HttpServletResponse 和 HttpSession 中的所有屬性在問題中的對象存活時都會一直保持存活。
2.1.6 線程安全
即便如此,最關心的可能是線程安全。現在應該學習到 Servlet 和 filter 被所有請求共享。那是 Java 的一個優點,使得多個不同線程(讀取 HTTP 請求)可以使用同一個實例。否則為每個請求重新創建線程的開銷實在過于昂貴。
但應該也意識到永遠不要將任何 request 或 session 域中的數據賦值給 servlet 或 filter 的實例變量。它將會被所有其他 session 中的所有請求共享。那是非線程安全的
下面的示例對這種情況進行了展示:
由于當客戶端第一次請求某一個JSP文件時,服務端把該JSP編譯成一個CLASS文件,并創建一個該類的實例,然后創建一個線程處理CLIENT端的請求。如果有多個客戶端同時請求該JSP文件,則服務端會創建多個線程。每個客戶端請求對應一個線程。以多線程方式執行可大大降低對系統的資源需求,提高系統的并發量及響應時間.
對JSP中可能用的的變量說明如下:
- 實例變量: 實例變量是在堆中分配的,并被屬于該實例的所有線程共享,所以不是線程安全的. JSP系統提供的8個類變量:JSP中用到的OUT,REQUEST,RESPONSE,CONFIG,PAGE,PAGECONXT是線程安全的(因為每個線程對應的request,respone對象都是不一樣的,不存在共享問題), SESSION,APPLICATION在整個系統內被使用,所以不是線程安全的
ServletContext:(線程是不安全的) ServletContext是可以多線程同時讀/寫屬性的,線程是不安全的。要對屬性的讀寫進行同步處理或者進行深度Clone()。所以在Servlet上下文中盡可能少量保存會被修改(寫)的數據,可以采取其他方式在多個Servlet中共享,比方我們可以使用單例模式來處理共享數據。
HttpSession:(線程是不安全的) HttpSession對象在用戶會話期間存在,只能在處理屬于同一個Session的請求的線程中被訪問,因此Session對象的屬性訪問理論上是線程安全的。 當用戶打開多個同屬于一個進程的瀏覽器窗口,在這些窗口的訪問屬于同一個Session,會出現多次請求,需要多個工作線程來處理請求,可能造成同時多線程讀寫屬性。這時我們需要對屬性的讀寫進行同步處理:使用同步塊Synchronized和使用讀/寫器來解決。 ServletRequest:(線程是安全的) 對于每一個請求,由一個工作線程來執行,都會創建有一個新的ServletRequest對象,所以ServletRequest對象只能在一個線程中被訪問。ServletRequest是線程安全的。
注意:ServletRequest對象在service方法的范圍內是有效的,不要試圖在service方法結束后仍然保存請求對象的引用 - 局部變量: 局部變量在堆棧中分配,因為每個線程都有它自己的堆棧空間,所以是線程安全的.
- 靜態類: 靜態類不用被實例化,就可直接使用,也不是線程安全的.
- 外部資源: 在程序中可能會有多個線程或進程同時操作同一個資源(如:多個線程或進程同時對一個文件進行寫操作).此時也要注意同步問題. 使它以單線程方式執行,這時,仍然只有一個實例,所有客戶端的請求以串行方式執行。這樣會降低系統的性能
2.2 Servlet單例多線程
2.2.1 Servlet如何處理多個請求訪問
Servlet容器默認是采用單實例多線程的方式處理多個請求的:
從上面可以看出:
2.2.2 Servlet容器如何采用單實例多線程的方式來處理請求
Java的內存模型JMM(Java Memory Model) JMM主要是為了規定了線程和內存之間的一些關系。根據JMM的設計,系統存在一個主內存(Main Memory),Java中所有實例變量都儲存在主存中,對于所有線程都是共享的。每條線程都有自己的工作內存(Working Memory),工作內存由緩存和堆棧兩部分組成,緩存中保存的是主存中變量的拷貝,緩存可能并不總和主存同步,也就是緩存中變量的修改可能沒有立刻寫到主存中;堆棧中保存的是線程的局部變量,線程之間無法相互直接訪問堆棧中的變量。根據JMM,我們可以將論文中所討論的Servlet實例的內存模型抽象為圖所示的模型。
工作者線程Work Thread:執行代碼的一組線程。
調度線程Dispatcher Thread:每個線程都具有分配給它的線程優先級,線程是根據優先級調度執行的。
Servlet采用多線程來處理多個請求同時訪問。servlet依賴于一個線程池來服務請求。線程池實際上是一系列的工作者線程集合。Servlet使用一個調度線程來管理工作者線程。 當容器收到一個Servlet請求,調度線程從線程池中選出一個工作者線程,將請求傳遞給該工作者線程,然后由該線程來執行Servlet的service方法。當這個線程正在執行的時候,容器收到另外一個請求,調度線程同樣從線程池中選出另一個工作者線程來服務新的請求,容器并不關心這個請求是否訪問的是同一個Servlet.當容器同時收到對同一個Servlet的多個請求的時候,那么這個Servlet的service()方法將在多線程中并發執行。 Servlet容器默認采用單實例多線程的方式來處理請求,這樣減少產生Servlet實例的開銷,提升了對請求的響應時間,對于Tomcat可以在server.xml中通過<Connector>元素設置線程池中線程的數目。
就實現來說,調度者線程類所擔負的責任如其名字,該類的責任是調度線程,只需要利用自己的屬性完成自己的責任。所以該類是承擔了責任的,并且該類的責任又集中到唯一的單體對象中。而其他對象又依賴于該特定對象所承擔的責任,我們就需要得到該特定對象。那該類就是一個單例模式的實現了
2.2.3 Servlet多實例
服務器可以使用多個實例來處理請求,代替單個實例的請求排隊帶來的效益問題。服務器創建一個Servlet類的多個Servlet實例組成的實例池,對于每個請求分配Servlet實例進行響應處理,之后放回到實例池中等待下此請求。這樣就造成并發訪問的問題。此時,局部變量(字段)也是安全的,但對于全局變量和共享數據是不安全的,需要進行同步處理。而對于這樣多實例的情況SingleThreadModel接口并不能解決并發訪問問題。 SingleThreadModel接口在servlet規范中已經被廢棄了。
Servlet并非只是單例的. 當container開始啟動,或是客戶端發出請求服務時,Container會按照容器的配置負責加載和實例化一個Servlet(也可以配置為多個,不過一般不這么干).不過一般來說一個servlet只會有一個實例。
2.2.4 容器概念理解
Servlet容器,Web容器,應用服務器
Servlet容器的主要任務就是管理Servlet的生命周期;
Web容器也稱之為web服務器,主要任務就是管理和部署web應用的; 應用服務器的功能非常強大,不僅可以管理和部署web應用,也可以部署EJB應用,實現容器管理的事務等等。。。
Web服務器就是跟基于HTTP的請求打交道,而EJB容器更多是跟數據庫,事務管理等服務接口交互,所以應用服務器的功能是很多的。 常見的web服務器就是Tomcat,但Tomcat同樣也是Servlet服務器; 常見的應用服務器有WebLogic,WebSphere,但都是收費的; 沒有Servlet容器,可以用Web容器直接訪問靜態Html頁面,比如安裝了apache等;如果需要顯示Jsp/Servlet,就需要安裝一個Servlet容器;但是光有servlet容器也是不夠的,它需要被解析為html顯示,所以仍需要一個web容器;所以,我們常把web容器和Servlet容器視為一體,因為他們兩個容器都有對方的功能實現了,都沒有獨立的存在了,比如tomcat)
總結
以上是生活随笔為你收集整理的Servlet原理解析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 基于JAVA高校实习实训管理系统计算机毕
- 下一篇: html5 逐帧播放 代码,html5-