根據Servlet-API源碼分析學習Servlet

Servlet

介於Servlet是Java Web開發的基礎,因此好好看了一下Servlet3.0.1的源碼,於是有了這篇記錄。

 

Servlet架構圖

Servlet和JSP是衆多java EE定義的技術當中的兩種,其他還有JMS,EJB等等,運行JEE程序需要一個JEE容器,如GlassFish、JBOSS、WebLogic等,Servlet\JSP也可以部署在JEE容器中,不過用Servlet/JSP容器已經足夠了,而且比JEE容器更加輕量化,Tomcat和Jetty不屬於JEE容器,不能運行EJB或JMS。

Servlet API概述

首先,下載Servlet-api源碼,可以使用IDE,maven等方式,本文使用maven下載,命令如下(或者自行搜索下載源碼):


maven dependency:sources

servlet API中有4個Java包,包括:

  • javax.servlet 定義Servlet和Servlet容器之間的七月類和接口。

  • javax.servlet.http 定義HTTP Servlet與Serlvet容器之間的契約和接口

  • javax.servlet.annotation 包含對Servlet、Filter和Listener進行標註的註解。還爲標註元件指定元數據。

  • javax.servlet.descriptor 包含爲Web應用程序的配置星系提供編程式訪問的類型。

javax.servlet包

不完整截圖:

javax.servlet方法一覽

Servlet包中的主要成員:

Servlet主要成員

Servlet接口是核心接口,是所有Serlvet都必須直接或間接實現的一個接口,Servlet接口定義了Servlet與Servlet容器之間的約定,總的來說就是Servlet容器會把Servlet類加載到內存中,並在Servlet實例中調用特定方法,在一個應用程序中,每個Servlet類型只能有一個實例。當用戶的請求引發service方法,並給這個方法傳入一個ServletRequest實例和一個ServletResponse實例。ServletRequest封裝當前的HTTP請求,讓開發者不必去解析和操作原始的HTTP數據,同理,ServletResponse表示當前用戶的HTTP響應,它的作用是使得將響應回傳給用戶更容易。Servlet容器還爲每個應用程序創建一個ServletContext實例。這個對象封裝context(應用程序)的環境細節,而每個context只有一個ServletContext。每個Servlet實例還有一個封裝Servlet配置信息的ServletConfig。

Servlet

下面先看看Servlet接口,(如果別人問你什麼是Servlet,你可以告訴他,就是一個java接口,分分鐘定義出來給你看,不過人們常說的Servlet應該是指任何實現了Servlet接口的類)看源碼發現有234行(3.0.1版),仔細一看也就5個抽象方法,其他的全是註釋,所以兄弟們,看源碼不要虛,這就是傳說中的和Servlet容器之間的約定(有沒有很熟悉的感覺):

Servlet接口

  • init 第一次請求我們編寫的Serlvet時,Servlet容器調用此方法,後續不在調用,可以利用這個方法做一些初始化的工作。在調用這個方法時,Servlet容器會傳遞一個ServletConfig。一般會將這個ServletConfig賦給一個類級變量,以方便其他方法也可以使用這個對象。

  • service 每次用戶請求service時,servlet容器都會調用這個方法,我們對請求的處理就是在這裏完成的。

  • destroy 要銷燬Servlet時,Servlet容器就會調用這個方法,它通常發生在卸載應用程序,或者關閉Servlet容器的時候,這裏一般我們會寫一些資源清理相關的代碼。

Servlet中另外另個費生命週期的方法:getServletInfo和getServletConfig

  • getServletInfo 就是字面意思,返回Servlet的描述

  • getServletConfig 這個方法返回由Servlet容器傳給init方法的ServletConfig,上面說了,一般在init方法中將ServletConfig賦給一個類級變量,免得本方法返回null。

PS:由於Servlet不是線程安全的,一個應用程序中所有的用戶公用一個Servlet實例,因此不建議使用類級別的變量(只使用局部變量最好),除非是隻讀的或者java.utilconcurrent.atomic包中的成員。

Servlet基礎應用程序

現在來寫一個Servlet應用程序,寫起來很簡單,只要創建一個目錄,並將Servlet類放在指定目錄中就可以了,同時,如果要運行這個應用程序,你還需要安裝一個Servlet容器,如Tomcat或者Jetty(安裝方法自行搜索)。

 

編寫Servlet應用

Servlet應用目錄結構

以上爲Servlet的目錄結構,要編譯servlet,類路徑中還要有servlet API,tomcat容器中已經自帶了servlet-api.jar。另外珍愛生命,還是用IDE來創建吧,博主試過,自己去一步一步創建配置雖說也可以,但是確實會花費不少時間,如果你就是要自己一步一步創建,覺得這樣能學到更多東西,我只能說加油騷年!

應用程序中一般會有JSP、HTML、圖像等其他資源,這些都應該放在應用程序的目錄下面,並且經常放在子目錄下,如上圖,html放在html文件下,jsp放在jsp目錄下。放在應用程序目錄下的任何資源,用戶可以通過資源的URL直接訪問(放在應用程序目錄下當然要可以訪問了),如果希望某個資源可以被Servlet訪問,但不能被用戶訪問,那就應該放在WEB-INF目錄下(是不是找到該目錄的作用了)。


import java.io.IOException;

import java.io.PrintWriter;



import javax.servlet.Servlet;

import javax.servlet.ServletConfig;

import javax.servlet.ServletException;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

import javax.servlet.annotation.WebServlet;



//name可選,提供servlet名,關鍵urlPatterns指定匹配當前servlet的模式

@WebServlet(name="/FirstServlet",urlPatterns={ "/myapp" })

public class FirstServlet implements Servlet {

    private transient ServletConfig config;

    @Override

    public void init(ServletConfig config) throws ServletException {

        //將config給類變量,擴大使用範圍

        this.config=config;

    }

    @Override

    public ServletConfig getServletConfig() {

        return config;

    }

    @Override

    public String getServletInfo() {

        return "My Servlet";

    }

    @Override

    public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {

        String servletName=config.getServletName();

        response.setContentType("text/html");

        PrintWriter pw=response.getWriter();

        pw.write("hello from "+servletName);

    }

    @Override

    public void destroy() {}

}

以上程序部署完成,啓動Servlet容器,就可以通過url在瀏覽器中訪問了:

通過訪問 http://localhost:8080/App01/FirstServlet

效果圖

ServletRequest & ServletResponse

對於每一個HTTP請求,servlet容器會創建一個封裝了HTTP請求的ServletRequest實例傳遞給servlet的service方法,ServletResponse則表示一個Servlet響應,其影藏了將響應發給瀏覽器的複雜性。通過ServletRequest的方法你可以獲取一些請求相關的參數,而ServletResponse則可以將設置一些返回參數信息,並且設置返回內容。返回內容之前一般會調用setContentType方法設置響應的內容類型,如果沒有設置,大多數瀏覽器會默認以html的形式響應,不過爲了避免出問題,我們一般都設置該項。

值得注意的是ServletResponse中定義的getWriter方法,它返回可以將文本傳給客戶端的java.io.PrintWriter。在默認的情況下,PrintWriter對象使用ISO-8859-1編碼,這有可能引起亂碼。

以下爲ServletRequest和ServletResponse的大部分方法:

ServletRequest

ServletResponse

ServletConfig

ServletConfig封裝可以通過@WebServlet或者web.xml傳給一個Servlet的配置信息,以這種方式傳遞的每一條信息都稱做初始化信息,初始化信息就是一個個K-V鍵值對。爲了從一個Servlet內部獲取某個初始參數的值,init方法中調用ServletConfig的getinitParameter方法或getinitParameterNames方法獲取,除此之外,還可以通過getServletContext獲取ServletContext對象。方法簽名:

ServletConfig

通過WebServlet傳遞配置信息示例:

 

WebServlet初始化參數

ServletContext

ServletContext是代表了Servlet應用程序。每個Web應用程序只有一個context。在分佈式環境中,一個應用程序同時部署到多個容器中,並且每臺Java虛擬機都有一個ServletContext對象。有了ServletContext對象後,就可以共享能通過應用程序的所有資源訪問的信息,促進Web對象的動態註冊,共享的信息通過一個內部Map中的對象保存在ServiceContext中來實現。保存在ServletContext中的對象稱作屬性。操作屬性的方法:

ServletContext

GenericServlet

前面編寫的Servlet應用中通過實現Servlet接口來編寫Servlet,但是我們每次都必須爲Servlet中的所有方法都提供實現,還需要將ServletConfig對象保存到一個類級別的變量中,GenericServlet抽象類就是爲了爲我們省略一些模板代碼,實現了Servlet和ServletConfig,完成了一下幾個工作:

  • 將init方法中的ServletConfig賦給一個類級變量,使的可以通過getServletConfig來獲取。

public void init(ServletConfig config) throws ServletException {
        this.config = config;
        this.init();
  }

同時爲避免覆蓋init方法後在子類中必須調用super.init(servletConfig),GenericServlet還提供了一個不帶參數的init方法,當ServletConfig賦值完成就會被第帶參數的init方法調用。這樣就可以通過覆蓋不帶參數的init方法編寫初始化代碼,而ServletConfig實例依然得以保存(這難道不是適配器模式嗎?)

  • 爲Servlet接口中的所有方法提供默認實現。

  • 提供方法來包裝ServletConfig中的方法。

用GenericServlet實現Servlet應用


import java.io.IOException;

import java.io.PrintWriter;



import javax.servlet.GenericServlet;

import javax.servlet.ServletConfig;

import javax.servlet.ServletException;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

import javax.servlet.annotation.WebInitParam;

import javax.servlet.annotation.WebServlet;



@WebServlet(name="SecondServlet",

         urlPatterns={"/generic"},

         initParams={

             @WebInitParam(name="user",value="xiaobai"),

             @WebInitParam(name="email",value="[email protected]")

         }

)

public class SecondServlet extends GenericServlet {

        @Override

        public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {

             ServletConfig config=getServletConfig();

             String user=config.getInitParameter("user");

             String email=config.getInitParameter("email");

             response.setContentType("text/html");

             PrintWriter pw=response.getWriter();

             pw.write("User:"+user+"<br>email:"+email);

        }

}

在編寫Servlet應用程序時,大多數都要用到HTTP,也就是說可以利用HTTP提供的特性,javax.servlet.http包含了編寫Servlet應用程序的類和接口,其中很多覆蓋了javax.servlet中的類型,我們自己在編寫應用時大多時候也是繼承的HttpServlet,以下爲其中的重要成員:

HttpServelt主要成員

從上圖看,HttpServlet繼承了GenericServlet,HttpServletRequest/Response繼承了覆蓋了ServletRequest/Response,成爲了新的Servlet請求和響應的代表。在HttpServlet中覆蓋了GenericServlet的service方法,並用新的Servlet請求和響應代表作爲參數添加了一個service方法:


//覆蓋GenereicServlet中的service

public void service(ServletRequest req, ServletResponse res)

       throws ServletException, IOException
   {
           HttpServletRequest  request;
           HttpServletResponse response;       
           if (!(req instanceof HttpServletRequest &&
                   res instanceof HttpServletResponse)) {
               throw new ServletException("non-HTTP request or response");
           }
           request = (HttpServletRequest) req;
           response = (HttpServletResponse) res;
           service(request, response);
       }
}

//新service方法簽名
protected void service(HttpServletRequest req, HttpServletResponse resp)

原始的service方法將請求和響應進行向下轉換,分別爲HttpServletRequest和HttpServletResponse,並調用新的service方法。看了下2.5版本中的實現,發現沒有加以上代碼是中的instanceof判斷,恩,看來2.5中直接向下轉型確實暴力了點,考慮容器不一定總是把請求當做HTTP請求,這樣做看起來穩妥了些。新的service方法會查尋HTTP請求的方法從而調用do{Method}來處理請求。

總之HttpServlet中有兩項特性是GenericServlet中沒有的:

  • 不覆蓋service方法,而是覆蓋doGet、doPost等。

  • 用HttpServletRequest\Response 替代ServletRequest\Response

HttpServletRequest,HttpServletResponse由於帶有了HTTP的特性,因此除了ServletRequest,ServletResponse中的方法之外還增加了幾個可以獲取HTTP特性信息的方法。

 


//獲取context的請求URI部分

java.lang.String getContextPath()

//獲取Cookie對象數組

Cookie [] getCookies()

//返回指定HTTP標頭的值

java.lang.String getHeader(java.lang.String name)

//返回發出這條請求的HTTP方法的名稱

java.lang.String getMethod()

//返回請求URL中的查詢字符串

java.lang.String getQueryString()

//獲取session對象,沒找到就新創建

HttpSession getSession()

//返回與這個請求相關的session對象,如果沒有,並且create參數爲true,創建新的session對象

//響應對象添加cookie

void addCookie(Cookie cookie)

//添加標頭

void addheader(String name,String value)

//重定向

void sendRedirect(String location)

使用web.xml配置Servlet應用

在Servlet3.x中可以使用@WebServlet來部署應用,可以不必在WEB-INF目錄下放一個web.xml配置文件,當然也可以同時使用,前者優先級更高,這是annotation爲我們帶來的好處,介於使用web.xml來配置Servlet也有其有點,就簡單介紹下,使用web.xml配置優點:

  • 不用更改代碼,也就意味着不需要重新編譯

  • 可包含@WebServlet中沒有的元素,如load-on-startup元素,init方法比較費時間這個就很有幫助了。


<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
        version="3.1">

   <servlet>
       <servlet-name>SimpleServlet</servlet-name>
       <servlet-class>app01.SimpleServlet</servlet-class>
       <load-on-startup>2</load-on-startup>
   </servlet>
   <servlet-mapping>
       <servlet-name>SimpleServlet</servlet-name>
       <url-pattern>/simple</url-pattern>
   </servlet-mapping>

</web-app>

現在再來看下我們我們通常寫的Servlet,看完上文的客官,看到下面Servlet是不是感覺自己看到了更多的東西呢?反正我是看到了。講真,如果想要縷下Servlet的話,真的可以試一試下載Servlet-api的源碼看看,結合本文,或許風味更佳哦!!

 


import java.io.IOException;

import javax.servlet.ServletException;

import javax.servlet.annotation.WebServlet;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;



@WebServlet("/normal")

public class NormalServlet extends HttpServlet {

    private static final long serialVersionUID = 1L;



    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        response.getWriter().append("Served at: ").append(request.getContextPath());

    }



    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        doGet(request, response);

    }

}

小結

Servlet技術是Java EE技術的重要組成,Servlet容器中運行的所有Servlet,以及容器與Servlet之間的約定,都採用了javax.servlet.Servlet接口的形式。javax.servlet包也提供了實現Servlet接口的GenericServlet抽象類。這是一個比較方便的類,可以通過擴展它來創建Servlet。但是大多數的現代Servlet都在HTTP環境中處理請求,因此提供了javax.servlet.http.HttpServlet來繼承GenericServlet並且加入HTTP特性。



作者:BigfaceMonster
鏈接:https://www.jianshu.com/p/e9f31c783ff1
來源:簡書
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章