Java Web(一) Servlet詳解!!

一杯涼茶發表於2017-02-17

    這篇文章到上一篇,距離的有點遙遠呀,隔了大概有兩個月把,中間在家過了個年,哈哈~ 現在重新開始拾起,最近在看一本個人覺得很棒的書,《Java Web 整合開發王者歸來》,現在寫的這一系列基本上是從該本書上總結出來了,個人認為覺得比較重要的和需要理解的,因為我發現,隔了這麼久沒學習,以前的很多東西都忘光了,比如最簡單的,什麼是Servlet?我都回答不上來,以前也只是簡單的會建立servlet,知道它其中的一些什麼轉發重定向到別的頁面,但是具體要說說,便大腦一片空白,啥也不清楚,一方面可能是學的不紮實,只會按葫蘆畫瓢,另一方面可能是這種東西需要記錄下來,以後在可能忘記時,回過頭來看看自己摘記的一些東西,以此來回顧,所以開啟了老路,java web的學習記錄。

                                        --WH

 

一、什麼是servlet?

    處理請求和傳送響應的過程是由一種叫做Servlet的程式來完成的,並且Servlet是為了解決實現動態頁面而衍生的東西。理解這個的前提是瞭解一些http協議的東西,並且知道B/S模式(瀏覽器/伺服器)。

    B/S:瀏覽器/伺服器。 瀏覽器通過網址來訪問伺服器,比如訪問百度,在瀏覽器中輸入www.baidu.com,這個時候瀏覽器就會顯示百度的首頁,那麼這個具體的過程,步驟是怎樣的呢?這個就瞭解一下http請求和響應

                  

    請求,響應:通過給的連結應該可以知道這兩個具體的內容

 

二、tomcat和servlet的關係

    Tomcat 是Web應用伺服器,是一個Servlet/JSP容器. Tomcat 作為Servlet容器,負責處理客戶請求,把請求傳送給Servlet,並將Servlet的響應傳送回給客戶.而Servlet是一種執行在支援Java語言的伺服器上的元件. Servlet最常見的用途是擴充套件Java Web伺服器功能,提供非常安全的,可移植的,易於使用的CGI替代品.

    從http協議中的請求和響應可以得知,瀏覽器發出的請求是一個請求文字,而瀏覽器接收到的也應該是一個響應文字。但是在上面這個圖中,並不知道是如何轉變的,只知道瀏覽器傳送過來的請求也就是request,我們響應回去的就用response。忽略了其中的細節,現在就來探究一下。

              

      ①:Tomcat將http請求文字接收並解析,然後封裝成HttpServletRequest型別的request物件,所有的HTTP頭資料讀可以通過request物件呼叫對應的方法查詢到。

      ②:Tomcat同時會要響應的資訊封裝為HttpServletResponse型別的response物件,通過設定response屬性就可以控制要輸出到瀏覽器的內容,然後將response交給tomcat,tomcat就會將其變成響應文字的格式傳送給瀏覽器

 

    Java Servlet API 是Servlet容器(tomcat)和servlet之間的介面,它定義了serlvet的各種方法,還定義了Servlet容器傳送給Servlet的物件類,其中最重要的就是ServletRequest和ServletResponse。所以說我們在編寫servlet時,需要實現Servlet介面,按照其規範進行操作。

 

三、編寫Servlet

    在前面,我們已經知道了servlet是什麼,為什麼需要servlet?(為了實現動態網頁,而不是顯示靜態網頁,具體情況可以百度查查),tomcat和servlet的關係?等問題。現在來手動編寫一個Servlet。

    3.1、手動編寫servlet。

      1、建立一個MyServlet繼承HttpServlet,重寫doGet和doPost方法,也就是看請求的方式是get還是post,然後用不同的處理方式來處理請求,

            

      2、在web.xml中配置MyServlet,為什麼需要配置?讓瀏覽器發出的請求知道到達哪個servlet,也就是讓tomcat將封裝好的request找到對應的servlet讓其使用。

            配置四個東西。

                

            配置之後,瀏覽器是如何通過我們配置的資訊來找到對應的servlet的。

                

            按照步驟,首先瀏覽器通過http://localhost:8080/test01/MyServlet來找到web.xml中的url-pattern,這就是第一步,匹配到了url-pattern後,就會找到第二步servlet的名字MyServlet,知道了名字,就可以通過servlet-name找到第三步,到了第三步,也就能夠知道servlet的位置了。然後到其中找到對應的處理方式進行處理。

       3、實驗,驗證上面配置成功。

               

            

    3.2、利用嚮導新建MyServlet

        這個就相對簡單了,web.xml不用我們手動配置,工具直接幫我們自動配置了

            1、右擊專案,在new選項中有直接新建servlet的選項

            2、配置MyServlet類中的資訊

                

          3、配置web.xml中的servlet資訊

                  

        4、檢視MyServle01類中的程式碼和web.xml,其中的配置跟手動的配置是一樣的,只是用圖形化介面,讓我們更方便的建立servlet而產生的。

 

    3.3、詳解建立servlet的原理

        1、servlet的生命週期是什麼?

        2、為什麼建立的servlet是繼承自httpServlet,而不是直接實現Servlet介面?

        3、servlet的生命週期中,可以看出,執行的是service方法,為什麼我們就只需要寫doGet和doPost方法呢?

        等這一系列的問題,我們都應該知道,而不應該就單純的知道如何配置和使用servlet?上面的問題,一一來解答。

        1、servlet的生命週期是什麼?

          伺服器啟動時(web.xml中配置load-on-startup=1,預設為0)或者第一次請求該servlet時,就會初始化一個Servlet物件,也就是會執行初始化方法init(ServletConfig conf)

          該servlet物件去處理所有客戶端請求,在service(ServletRequest req,ServletResponse res)方法中執行

          最後伺服器關閉時,才會銷燬這個servlet物件,執行destroy()方法。

              

        2、為什麼建立的servlet是繼承自httpServlet,而不是直接實現Servlet介面?

        3、servlet的生命週期中,可以看出,執行的是service方法,為什麼我們就只需要寫doGet和doPost方法呢?

          檢視原始碼,httpServlet的繼承結構。

             httpServlet繼承GenericServlet。懂的人立馬就應該知道,GenericServlet(通用Servlet)的作用是什麼?大概的就是將實現Servlet介面的方法,簡化編寫servlet的步驟。具體下面詳解

                

             GenericServlet的繼承結構,實現了Servlet介面和ServletConfig介面,

                    

             Servlet介面內容

                  

                從這裡可以看到,Servlet生命週期的三個關鍵方法,init、service、destroy。還有另外兩個方法,一個getServletConfig()方法來獲取ServletConfig物件,ServletConfig物件可以獲取到Servlet的一些資訊,ServletName、ServletContext、InitParameter、InitParameterNames、通過檢視ServletConfig這個介面就可以知道

             ServletConfig介面內容

                 

                其中ServletContext物件是servlet上下文物件,功能有很多,獲得了ServletContext物件,就能獲取大部分我們需要的資訊,比如獲取servlet的路徑,等方法。

               到此,就知道了Servlet介面中的內容和作用,總結起來就是,三個生命週期執行的方法,獲取ServletConfig,而通過ServletConfig又可以獲取到ServletContext。而GenericServlet實現了Servlet介面後,也就說明我們可以直接繼承GenericServlet,就可以使用上面我們所介紹Servlet介面中的那幾個方法了,能拿到ServletConfig,也可以拿到ServletContext,不過那樣太麻煩,不能直接獲取ServletContext,所以GenericServlet除了實現Servlet介面外,還實現了ServletConfig介面,那樣,就可以直接獲取ServletContext了。

              GenericServlet類的內容詳解

                     

                看上圖,用紅色框框起來的就是實現Servlet和ServletConfig介面所實現的方法,有9個,這很正常,但是我們可以發現,init方法有兩個,一個是帶有引數ServletConfig的,一個有無參的方法,為什麼這樣設計?這裡需要知道其中做了什麼事情,來看看這兩個方法分別做了什麼事?

                init(ServletConfig config)

                      

                init()

                      

                一個成員變數config

                      

                getServletConfig()

                      

                通過這幾個方法一起來講解,首先看init(ServletConfig config)方法,因為只有init(ServletConfig config)中帶有ServletConfig物件,為了方便能夠在其他地方也能直接使用ServletConfig物件,而不僅僅侷限在init(ServletConfig config)方法中,所以建立一個私有的成員變數config,在init(ServletConfig config)方法中就將其賦值給config,然後通過getServletConfig()方法就能夠獲取ServletConfig物件了,這個可以理解,但是在init(ServletConfig config)中,158行,還呼叫了一個init()方法,並且這個init()方法是空的,什麼讀沒有,這是為什麼呢?這個原因是為了防止一件事情,當我們需要在init方法中做一點別的事情,我們想到的方法就是繼承GenericServlet並且重寫了init(ServletConfig config)方法,這樣依賴,就破壞了原本在GenericServlet類中init(ServletConfig config)寫的程式碼了,也就是在GenericServlet類中的成員變數config會一直是null,無法得到賦值,因為被重寫了,就不會在執行GenericServlet中init(ServletConfig config)方法中的程式碼。要想賦值,就必須在重寫的init(ServletConfig config)方法中呼叫父類的init(ServletConfig config)方法,也就是super.init(ServletConfig config),這樣一來,就很不方便,怕有時候會忘了寫這句程式碼,所以在GenericServlet類中增加一個init()方法,以後需要在init方法中需要初始化別的資料,只需要重寫init()這個方法,而不需要去覆蓋init(ServletConfig config)這個方法,這樣設計,就好很多,不用在管init(ServletConfig config)這個其中的內容了。也不用出現其他的問題。

                service(ServletRequest req, ServletResponse res)

                      

                   一個抽象方法,說明在GenericServlet類中並沒有實現該內容,那麼我們想到的是,在它上面肯定還有一層,也就是還有一個子類繼承它,實現該方法,要是讓我們自己寫的Servlet繼承GenericServlet,需要自己寫service方法,那豈不是累死,並且我們可以看到,service方法中的引數還是ServletRequest,ServletResponse。並沒有跟http相關物件掛鉤,所以我們接著往下面看。

           HttpServlet類詳解

               繼承了GenericServlet類,通過我們上面的推測,這個類主要的功能肯定是實現service方法的各種細節和設計。並且通過類名可以知道,該類就跟http掛鉤了。

                     

                關注service(HttpServletRequest req, HttpServletResponse resp)方法和service(ServletRequest req, ServletResponse res)方法。

                  service(ServletRequest req, ServletResponse res)方法

                        

                    該方法中就做一件事情,就是將ServletRequest和ServletResponse這兩個物件強轉為HttpServletRequest和HttpServletResponse物件。為什麼能這樣轉?

                      首先要知道req、res是什麼型別,通過列印System.out.println(req),可以知道,req實際上的型別是org.apache.catalina.connector.RequestFacade                         Tomcat中的原始碼。

                        

                          

                    通過圖可以得知,req的繼承結構:RequestFacade、httpServletRequest、ServletRequest,我們知道本身req是ServletRequest,那麼從繼承結構上看,它也可以看成HttpServletRequest,也可以看成ServletRequest,所以強轉為HttpServletRequest是可以的,如果不明白,我舉個例子,ArrayList、List、Object 這個,Object obj = new ArrayList();  List list = new ArrayList();  一個ArrayList物件可以看成List物件, 也可以看成一個Object物件,現在obj是不是可以堪稱List物件呢?答案是可以的,因為obj就是ArrayList物件,既然是ArrayList物件,那麼就可以看成是List物件。一樣的道理,RequestFacade 對應 ArrayList、httpServleRequest對應 List、 ServletRequest 對應 Object。

                    轉換為httpServletRequest和HttpServletResponse物件之後,在呼叫service(HttpServletRequest req, HttpServletResponse resp)方法。

                service(HttpServletRequest req, HttpServletResponse resp)

                    這個方法就是判斷瀏覽器過來的請求方式是哪種,每種的處理方式不一樣,我們常用的就是get,post,並且,我們處理的方式可能有很多的內容,所以,在該方法內會將get,post等其他5種請求方式提取出來,變成單個的方法,然後我們需要編寫servlet時,就可以直接重寫doGet或者doPost方法就行了,而不是重寫service方法,更加有針對性。所以這裡就回到了我們上面編寫servlet時的情況,繼承httpServlet,而只要重寫兩個方法,一個doGet,一個doPost,其實就是service方法會呼叫這兩個方法中的一個(看請求方式)。所以也就解答了我們一開始提的問題3。  

                     

四、幾個重點的物件。ServletConfig、ServletContext,request、response

       講解四大類,ServletConfig物件,ServletContext物件、request物件,response物件

    ServletConfig物件

        獲取途徑:getServletConfig(); 

        功能:上面大概提及了一下,能得到四個東西,

              

            getServletName();  //獲取servlet的名稱,也就是我們在web.xml中配置的servlet-name

            getServletContext(); //獲取ServletContext物件,該物件的作用看下面講解

            getInitParameter(String); //獲取在servlet中初始化引數的值。這裡注意與全域性初始化引數的區分。這個獲取的只是在該servlet下的初始化引數

                

             getInitParameterNames(); //獲取在Servlet中所有初始化引數的名字,也就是key值,可以通過key值,來找到各個初始化引數的value值。注意返回的是列舉型別

                

                

                

           注意:在上面我們所分析的原始碼過程中,我們就知道,其實可以不用先獲得ServletConfig,然後在獲取其各種引數,可以直接使用其方法,比如上面我們用的ServletConfig().getServletName();可以直接寫成getServletName();而不用在先獲取ServletConfig();了,原因就是在GenericServlet中,已經幫我們獲取了這些資料,我們只需要直接拿就行。

 

      ServletContext物件

           獲取途徑:getServletContext(); 、getServletConfig().getServletContext();  //這兩種獲取方式的區別就跟上面的解釋一樣,第一種是直接拿,在GenericServlet中已經幫我們用getServletConfig().getServletContext();拿到了ServletContext。我們只需要直接獲取就行了,第二種就相當於我們自己在獲取一遍,兩種讀是一樣的。

           功能:tomcat為每個web專案都建立一個ServletContext例項,tomcat在啟動時建立,伺服器關閉時銷燬,在一個web專案中共享資料,管理web專案資源,為整個web配置公共資訊等,通俗點講,就是一個web專案,就存在一個ServletContext例項,每個Servlet讀可以訪問到它。

              1、web專案中共享資料,getAttribute(String name)、setAttribute(String name, Object obj)、removeAttribute(String name)

                  setAttribute(String name, Object obj) 在web專案範圍記憶體放內容,以便讓在web專案中所有的servlet讀能訪問到

                  getAttribute(String name) 通過指定名稱獲得內容

                  removeAttribute(String name) 通過指定名稱移除內容   

                   

                     

                    

              2、整個web專案初始化引數 //這個就是全域性初始化引數,每個Servlet中都能獲取到該初始化值

                  getInitPatameter(String name)  //通過指定名稱獲取初始化值

                  getInitParameterNames()  //獲得列舉型別

                   web.xml 配置 整個web專案的初始化

                      

                      

                      

              3、獲取web專案資源

                  3.1獲取web專案下指定資源的路徑:getServletContext().getRealPath("/WEB-INF/web.xml")

                    

                    

                  3.2獲取web專案下指定資源的內容,返回的是位元組輸入流。InputStream getResourceAsStream(java.lang.String path)

                    前提知識:需要了解流。不知道的可以去看看IO流總結的文章

                    

                    輸出內容截圖一部分

                    

 

               4、getResourcePaths(java.lang.String path)  指定路徑下的所有內容。

                    

                    

                    

                5還有很多別的方法,暫時用到的就這幾個了,以後需要在用的,就檢視原始碼,看API。

      request物件

          我們知道,request就是將請求文字封裝而成的物件,所以通過request能獲得請求文字中的所有內容,請求頭、請求體、請求行 。

                

            1、請求行內容的獲取。

              

              

                

            2請求頭的獲取

                隨便百度一個東西,然後檢視的請求頭,包括以下這些內容,稍作了解。

                

                String getHeader(java.lang.String name) 獲得指定頭內容String【】

                  

                  

                long getDateHeader(java.lang.String name) 獲得指定頭內容Date

                int getIntHeader(java.lang.String name)  獲得指定頭內容int

                Enumeration getHeaders(java.lang.String name) 獲得指定名稱所有內容

            3請求體的獲取 -- 請求引數的獲取

               分兩種,一種get請求,一種post請求

               get請求引數:http://localhost:8080/test01/MyServlet?username=jack&password=1234

               post請求引數: <form method="post"><input type="text" name="username">

                String request.getParameter(String) 獲得指定名稱,一個請求引數值。

                String[] request.getParameterValues(String) 獲得指定名稱,所有請求引數值。例如:checkbox、select等

                Map<String , String[]> request.getParameterMap() 獲得所有的請求引數  

    

            4請求轉發

                request.getRequestDispatcher(String path).forward(request,response);  //path:轉發後跳轉的頁面,這裡不管用不用"/"開頭,都是以web專案根開始,因為這是請求轉發,請求轉發只侷限與在同一個web專案下使用,所以這裡一直都是從web專案根下開始的,

                web專案根:

                    開發:G:\Workspaces\test01\WebRoot\..

                    執行時:D:\java\tomcat\apache-tomcat-7.0.53\webapps\test01\..

                 web站點根:

                    執行時:D:\java\tomcat\apache-tomcat-7.0.53\webapps\..

                從這裡可以看出,web專案根就是從該web專案名開始,所以我們請求轉發時,只需要接著專案名後面需要訪問的路徑寫就行了,

                特點:瀏覽器中url不會改變,也就是瀏覽器不知道伺服器做了什麼,是伺服器幫我們跳轉頁面的,並且在轉發後的頁面,能夠繼續使用原先的request,因為是原先的request,所以request域中的屬性都可以繼續獲取到。

                

      response物件

          

        常用的一個方法:response.setHeader(java.lang.String name, java.lang.String value) 設定指定的頭,一般常用。

             例如:設定每隔3秒就自動重新整理一次,

              response.setHeader("Refresh",3);

              

              

              這樣可以看到現在時間的秒數,會發現每隔三秒就會自動重新整理一次頁面。

        這個最重要的一個就是重定向,其他的一些操作都被封裝到response物件中了,重點講解重定向

          重定向(頁面跳轉)

            方式一:手動方案

                response.setStatus(302);  //狀態碼302就代表重定向

                response.setHeader("location","http://www.baidu.com");

            方式二:使用封裝好的,通過response.sendRedirect("http://www.baidu.com");

          特點:伺服器告訴瀏覽器要跳轉的頁面,是瀏覽器主動去跳轉的頁面,瀏覽器知道,也瀏覽器的位址列中url會變,是瀏覽器重新發起一個請求到另外一個頁面,所以request是重新發起的,跟請求轉發不一樣。

            注意:response.sendRedirect(path);  //

            第一種:response.sendRedirect("/test01/MyServlet01");  //使用了"/"開頭,說明是從web站點根開始,所以需要寫test01/MyServlet01

            第二種:response.sendRedirect("MyServlet01");  //沒有使用"/"開頭,說明是從web專案根開始,那麼就無需寫test01了。

            重定向沒有任何侷限,可以重定向web專案內的任何路徑,也可以訪問別的web專案中的路徑,並且這裡就用"/"區分開來,如果使用了"/"開頭,就說明我要重新開始定位了,不訪問剛才的web專案,自己寫專案名,如果沒有使用"/"開始,那麼就知道是訪問剛才那個web專案下的servlet,就可以省略專案名了。就是這樣來區別。

 

 

五、總結

      這一章節篇幅較長,不過理清很多知識點

          1、什麼是servlet?如果編寫servlet?

          2、分析了servlet的部分原始碼,知道了其中的一些設計巧妙的東西,比如,本來編寫servlet是能看到其生命週期的,但是在其設計下,我們只關注doGet和doPost方法,為什麼能這樣呢?就可以通過原始碼中得知。

          3、servlet的生命週期,web.xml的配置

          4、servlet中的ServletConfig物件,ServletContext物件,request物件,response物件的詳細講解。包括其中的一些常用的方法。

          5、下一篇講解一下request、response的中文亂碼問題的解決

            

 

 

 

 

    

 

相關文章