JavaWeb--ServletContext

說起ServletContext,一些人會產生誤解,以爲一個servlet對應一個ServletContext。其實不是這樣的,事實是一個web應用對應一個ServletContext,所以ServletContext的作用範圍是整個應用,明確這點很重要,這是基礎中的基礎。

我曾經想,爲什麼不起名叫WebContext或者ApplicationContext或者WebApplicationContext?這樣見名知意多好。後來我想這也可能是有歷史原因的:最初的客戶端-服務端的架構模型非常簡單,服務端運行着一些servlet用來處理客戶端的請求。那個時候服務器很輕量級,運行一個應用,應用就由一堆servlet組成。所以這樣簡單的服務器也被稱作servlet容器,主要作用就是運行servlet的。那麼提供給應用的上下文就叫做ServletContext。(這個純屬個人意淫^_^,不對勿噴)

一個web應用對應一個ServletContext實例,這個實例是應用部署啓動後,servlet容器爲應用創建的。ServletContext實例包含了所有servlet共享的資源信息。通過提供一組方法給servlet使用,用來和servlet容器通訊,比如獲取文件的MIME類型、分發請求、記錄日誌等。

這裏需要注意一點,如果你的應用是分佈式部署的,那麼每臺服務器實例上部署的應用實例都各自擁有一個ServletContext實例。

二、源碼分析

1 ServletContext(上)

下面我們先逐個分析ServletContext中servlet3.0之前的規範定義的方法(其中有三個方法是servlet3.0規範定義的,放在一起講是出於方便的考慮,講到的時候會特別說明)。

複製代碼

public interface ServletContext {
    public String getContextPath();

    public ServletContext getContext(String uripath);

    public int getMajorVersion();
    public int getMinorVersion();
    public int getEffectiveMajorVersion();
    public int getEffectiveMinorVersion();
    
    public String getServerInfo();
    public String getServletContextName();

    public String getMimeType(String file);
    
    public void log(String msg);
    public void log(String message, Throwable throwable);

    public Set<String> getResourcePaths(String path);
 
    public URL getResource(String path) throws MalformedURLException;
    public InputStream getResourceAsStream(String path);
    
    public RequestDispatcher getRequestDispatcher(String path);
    public RequestDispatcher getNamedDispatcher(String name);

    public String getRealPath(String path);

    public String getInitParameter(String name);
    public Enumeration<String> getInitParameterNames();
    public boolean setInitParameter(String name, String value);

    public Object getAttribute(String name);
    public Enumeration<String> getAttributeNames();
    public void setAttribute(String name, Object object);
    public void removeAttribute(String name);

    // 省略servlet3.0及3.1規範定義的方法
}

複製代碼

1.1 getContextPath

方法返回web應用的上下文路徑。就是我們部署的應用的根目錄名稱。拿Tomcat舉例,我們在webapps部署了應用demo。那麼方法返 回"/demo"。如果是部署在ROOT下,那麼方法返回空字符串""。這裏的路徑可以再server.xml裏面修改,比如我們的demo應用路徑修改 爲"/test":

<Context docBase="demo" path="/test" reloadable="true" source="org.eclipse.jst.j2ee.server:demo"/>

那麼方法按理說會返回"/test"。

但是經過試驗,tomcat會初始化2個web應用,路徑分別是 "/demo" 和  "/test",且都可以訪問成功,若有大神知道原由,還請在評論區指點

1.2 getContext

 

getContext(String path)(1,參數爲 server.xml 中  <context>元素 的 path 屬性值

                                           2,不能在 當前servlet 的 init 方法中調用,否則始終都會返回 null

                                           3,可以在 service 方法中 調用,並且一定要在 tomcat 的 server.xml 中 找到代表當前web應用的

                                                 <context>元素,增加 crossContext 屬性 ,設置爲 true,默認值爲false

傳入的參數是一個資源定位符的路徑。返回一個ServletContext實例。我們說過一個web應用對應一個ServletContext實例,那麼這個方法根據資源的路徑返回其servlet上下文。

比如說我們當前應用是demo,這個時候我們要訪問servlet容器中的另外一個應用test中的資源index.jsp,假使資源的路徑爲/test/index.jsp。那麼我們就可用通過調用getContext("/test/index.jsp")去獲取test應用的上下文。如果在servlet容器中找不到該資源或者該資源限制了外部的訪問,那麼方法返回null。(這個方法一般配合RequestDispatcher使用,實現請求轉發)。

1.3 getMajorVersion、getMinorVersion、getEffectiveMajorVersion、getEffectiveMinorVersion

getMajorVersion和getMinorVersion分別返回當前servlet容器支持的Servlet規範最高版本和最低版本。

getEffectiveMajorVersion和getEffectiveMinorVersion分別返回當前應用基於的Servlet規範最高版本和最低版本,是servlet3.0規範增加的新特性。

所以一般情況下:getMajorVersion>=getEffectiveMajorVersion>getEffectiveMinorVersion>=getMinorVersion。

1.4 getServerInfo、getServletContextName

getServerInfo返回servlet容器的名稱和版本,格式爲servername/versionnumber。比如我在Tomcat下測試,輸出的信息是:Apache Tomcat/9.0.0.M10。當然容器也可以多返回些額外的信息,這個就看各個servlet容器的實現了。

getServletContextName返回應用的名稱,這裏的名稱是web.xml裏面配置的display-name,如果沒配置則返回null。

<display-name>Archetype Created Web Application</display-name>

1.5 getMimeType

方法返回文件的MIME類型,MIME類型是容器配置的。可用通過web.xml進行配置,比如:

<mime-mapping>  
    <extension>doc</extension>  
    <mime-type>application/vnd.ms-word</mime-type>  
</mime-mapping>

那麼我們用瀏覽器打開文件的時候發現如果是doc文件,則會調用相應的word程序去打開。

1.6 log

兩個重載的log方法都是記錄日誌到servlet日誌文件,這個對於有編程經驗的來說沒什麼好解釋的。需要注意的是servlet日誌文件的路徑由具體的 servlet容器自己去決定。如果你是在MyEclipse、STS這類的IDE中跑應用的話,那麼日誌信息將在控制檯(Console)輸出。如果是 發佈到Tomcat下的話,日誌文件是Tomcat目錄下的/logs/localhost.yyyy-MM-dd.log。

1.7 getResourcePaths

根據傳入的路徑,列出該路徑下的所有資源路徑。返回的路徑是相對於web應用的上下文根或者相對於/WEB-INF/lib目錄下的各個JAR包裏面的/META-INF/resources目錄。

比如我們的web應用下有這些資源:/welcome.html,/catalog/index.html,/catalog/products.html, /catalog/offers/books.html,/catalog/offers/music.html,/customer /login.jsp,/WEB-INF/web.xml,/WEB-INF/classes /com.acme.OrderServlet.class,/WEB-INF/lib/catalog.jar!/META-INF/resources/catalog/moreOffers/books.html。

如果調用方法getResourcePaths("/"),那麼返回的是{"/welcome.html", "/catalog/", "/customer/", "/WEB-INF/"}。

如果調用方法getResourcePaths("/catalog/"),那麼返回的是{"/catalog/index.html", "/catalog/products.html", "/catalog/offers/"}。

這裏需要注意的是:1.路徑一定要以"/"開頭,結尾的"/"可要可不要。2.路徑要從應用根目錄下開始,如果調用方法getResourcePaths("/offers/"),此時返回的是null。

1.8 getResource和getResourceAsStream

getResource將指定路徑的資源封裝成URL實例並返回,getResourceAsStream獲取指定路徑資源的輸入流InputStream並返回。關於URL和InputStream的解釋和使用,不在本篇博文的關注點。

這裏和上面一樣,資源的路徑以"/"開頭,這個路徑是相對於應用上下文根目錄或者相對於/WEB-INF/lib目錄下的各個JAR包裏面的/META-INF/resources目錄。在搜索資源的時候,servlet容器先是從應用上下文根目錄下搜索再從JAR文件搜索,對於JAR文件的搜索順序(哪個JAR先,哪個JAR後),servlet規範沒有規定。

1.9 getRequestDispatcher和getNamedDispatcher

將指定的資源包裝成RequestDispatcher實例並返回。區別是前者根據資源路徑(該路徑相對於當前應用上下文根),後者根據資源的名稱(通過服務器控制檯或者web.xml裏面配置的,比如web.xml裏面配置servlet的名稱)。

RequestDispatcher這個接口,看名字就知道主要用來進行分發的。所有的資源都可以包裝成RequestDispatcher實例(主要是用於包裝servlet),然後調用它的方法進行轉發和包含。

public interface RequestDispatcher {
    public void forward(ServletRequest request, ServletResponse response) throws ServletException, IOException;     
    public void include(ServletRequest request, ServletResponse response) throws ServletException, IOException;
}

都表示要跳轉到其他資源,不同的是,如果使用forward跳轉則後面的response輸出則不會執行,而用include
來跳轉,則include的servlet執行完後,再返回到原來的servlet執行response的輸出(如果有)。如:
servlet A
RequestDispatcher disp = request.getRequestDispatcher("B");
disp.forward(request, response);
System.out.println("servlet A completed");
PrintWriter pw = response.getWriter();
pw.println("servlet A");

servlet B
PrintWriter pw = response.getWriter();
pw.println("servlet B");

輸出結果:
控制檯:servlet A completed
頁面:servlet B
如果將forward換成include的話,則結果爲:
控制檯:servlet A completed
頁面:servlet B servlet A
PS:如果在servlet B裏吧pw給close掉了的話,那servlet A 這裏就無法輸出了,則結果就和第一個一樣,原由是因爲2個PrintWriter pw對象是同一個(hash值相同);

 

還拿我們上面的demo應用例子來說:客戶端訪問http://xxx.xxx.xxx.xxx:xxxx/demo/test,這個時候訪問我們的 TestServlet。這個時候如果要把請求轉發到另外一個servlet,假使這個servlet的資源路徑是/demo/test2。那麼我們可以 調用getRequestDispatcher("/demo/test2")把資源包裝成RequestDispatcher實例,再調用forward的方法。就實現了請求的轉發。

請求的轉發使用起來很簡單,因爲servlet容器提供了ServletContext實例,我們在應用中只需要調用它的API就行。這個時候容器爲我們做了很多事情,容器會根據資源的路徑去獲取ServletContext實例,正如上面的getContext方法。這裏不一定就是當前 ServletContext,可以使其他應用的。如果找到了ServletContext,容器再將資源包裝成RequestDispatcher實例進行轉發。

1.10 getRealPath

根據資源虛擬路徑,返回實際路徑。

比如說應用中有個JSP頁面index.jsp,調用getRealPath("index.jsp"),則返回index.jsp文件在文件系統中的絕對路徑。在windows下或許是這樣:D:\xxx\xxx\index.jsp,在linux下或許是這樣:/root/xxx/index.jsp。

這裏可能存在應用中有多個index.jsp,它們在不同的路徑下。這時候servlet容器是先從應用根目錄下向下查找,再從/WEB-INF/lib目錄下的各個JAR包裏面的/META-INF/resources目錄查找(前提是servlet容器解壓了這些JAR),把找到的第一個資源絕對路徑返回。

1.11 getInitParameter、getInitParameterNames、setInitParameter

getInitParameter和getInitParameterNames是用來獲取應用的初始化參數相關數據的,參數的作用域是整個應用。這個參數是在web.xml裏面配置的(如下所示)或者使用setInitParameter方法設置。getInitParameter是根據參數名獲取參數值,getInitParameterNames獲取參數名集合。對於setInitParameter需要注意的是,如果設置的參數名已經存在,在 init 方法裏面調用,不會生效,也不報錯,仍然以 web.xml 爲準;如果在service 方法裏面 調用 ,會設置失敗並且報錯 ,這是servlet3.0規範增加的新特性。

這裏需要注意的是在web.xml配置多個初始化參數時,應該寫多個<context-param></context-param>對,而不是在一個<context-param></context-param>對裏面寫多個<param-name></param-name>和<param-value></param-value>對。

複製代碼

<context-param>
    <param-name>param1</param-name>
    <param-value>1</param-value>
</context-param>    
<context-param>
    <param-name>param2</param-name>
    <param-value>2</param-value>
</context-param>

複製代碼

1.12 getAttribute、getAttributeNames、setAttribute、removeAttribute

應用的屬性相關操作,建議屬性名遵循java包名的風格。這裏需要講的是setAttribute,當設置的屬性名已經存在,則會替換掉就的屬性值。如果設置屬性值爲null,那麼效果和removeAttribute是一樣的。

這裏需要注意的是,這些屬性都是應用級的,在一個地方設置的屬性可以被應用中其他地方使用。

2 ServletContext(下)

下面我們逐個分析ServletContext中servlet3.0及3.1的規範中定義的方法。

複製代碼

public interface ServletContext {
    // 省略servlet3.0之前的規範定義的方法
    
    public static final String TEMPDIR = "javax.servlet.context.tempdir";
    public static final String ORDERED_LIBS = "javax.servlet.context.orderedLibs";

    public ServletRegistration.Dynamic addServlet(String servletName, String className);
    public ServletRegistration.Dynamic addServlet(String servletName, Servlet servlet);
    public ServletRegistration.Dynamic addServlet(String servletName, Class <? extends Servlet> servletClass);
    public <T extends Servlet> T createServlet(Class<T> clazz) throws ServletException;
    public ServletRegistration getServletRegistration(String servletName);
    public Map<String, ? extends ServletRegistration> getServletRegistrations();

    public FilterRegistration.Dynamic addFilter(String filterName, String className);
    public FilterRegistration.Dynamic addFilter(String filterName, Filter filter);
    public FilterRegistration.Dynamic addFilter(String filterName, Class <? extends Filter> filterClass);
    public <T extends Filter> T createFilter(Class<T> clazz) throws ServletException;
    public FilterRegistration getFilterRegistration(String filterName);
    public Map<String, ? extends FilterRegistration> getFilterRegistrations();
    
    public void addListener(String className);
    public <T extends EventListener> void addListener(T t);
    public void addListener(Class <? extends EventListener> listenerClass);
    public <T extends EventListener> T createListener(Class<T> clazz) throws ServletException; 

    public SessionCookieConfig getSessionCookieConfig();
    public void setSessionTrackingModes(Set<SessionTrackingMode> sessionTrackingModes);
    public Set<SessionTrackingMode> getDefaultSessionTrackingModes();
    public Set<SessionTrackingMode> getEffectiveSessionTrackingModes();

    public JspConfigDescriptor getJspConfigDescriptor();

    public ClassLoader getClassLoader();

    public void declareRoles(String... roleNames);

    public String getVirtualServerName();
}

複製代碼

2.1 TEMPDIR和ORDERED_LIBS

這是兩個ServletContext的屬性名,Servlet規範建議屬性名遵循java包名的風格。

TEMPDIR

File f = (File) scontext.getAttribute("javax.servlet.context.tempdir");

對應的屬性值是個java.io.File對象,表示該ServletContext對應的臨時目錄。拿tomcat來說,比如有個應用叫demo,那麼 demo對應的ServletContext臨時目錄就是{Tomcat目錄}\work\Catalina\localhost\demo。

ORDERED_LIBS

scontext.getAttribute("javax.servlet.context.orderedLibs");

對應的屬性值是個java.util.List<java.lang.String>對象,每個元素表示WEB-INF/lib目錄下的 JAR包名稱。這些名稱的排序是根據它們的web-fragment名稱排序的,如果<absolute-ordering>裏面沒有配 置<others>的話,可能會存在衝突。如果沒有在web.xml裏面配置<absolute-ordering>或在web-fragment.xml裏面配置<ordering>,那麼屬性值爲null。這裏涉及到servlet3.0增加的新特性————web模塊化,感興趣的讀者自行去了解,這裏不做引申。

2.2 addServlet、createServlet、getServletRegistration、getServletRegistrations

三個重載方法addServlet提供了編程式的向servlet容器中注入servlet的方式,其達到的效果和在web.xml中配置或者使用WebServlet註解配置servlet是一樣的。只不過這種方式更加靈活、動態。addServlet方法返回的是ServletRegistration實例(servletRegistration.Dynamic集成ServletRegistration),使用這個實例可以進一步配置servlet的註冊信息,比如配置url-pattern。

createServlet的所用是實例化一個servlet,得到一個servlet實例。這個傳入的表示servlet類的Class實例必須要有個無參構造函數,因爲這個方法的底層實現就是利用反射機制調用默認的無參構造函數進行實例化。這個方法返回的servlet實例沒有多大的使用意義,可能還是需要調用相應的addServlet進行註冊。

getServletRegistration根據servlet名稱查找其註冊信息,即ServletRegistration實例。

getServletRegistrations是查詢當前servlet上下文中所有的servlet的註冊信息。

至於這幾個方法的使用,這裏不作詳解。

2.3 addFilter、createFilter、getFilterRegistration、getFilterRegistrations

這裏的幾個方法和上面的servlet的幾個方法很類似,也是提供編程的方式實現filter。不作贅述。

2.4 addListener、createListener

這裏的幾個方法和上面的servlet的幾個方法很類似,也是提供編程的方式實現listener。不作贅述。注意觀察可以發現,這裏沒有類似getXxxRegistration的方法,這是因爲listener不需要像url-pattern的這類註冊信息。

2.5 getSessionCookieConfig、setSessionTrackingModes、getDefaultSessionTrackingModes、getEffectiveSessionTrackingModes

getSessionCookieConfig方法返回SessionCookieConfig實例,這個實例可以用於獲取和設置會話跟蹤的cookie的屬性。多次調用getSessionCookieConfig方法返回的SessionCookieConfig實例是同一個,說明SessionCookieConfig是單例的。

setSessionTrackingModes用於設置會話的跟蹤模式,getDefaultSessionTrackingModes用於獲取默認的會話跟蹤模式,getEffectiveSessionTrackingModes用於獲取有效的會話跟蹤模式。默認情況下,getDefaultSessionTrackingModes返回的會話跟蹤模式就是有效的。

關於session和cookie的知識這裏不做擴展。

2.6 getJspConfigDescriptor

獲取web.xml和web-fragment.xml中配置的<jsp-config>數據,關於<jsp-config>配置這裏不做擴展。

2.7 getClassLoader

獲取當前servlet上文的類加載器,關於類加載器知識這裏不做擴展。

2.8 declareRoles

該方法用於申明安全角色,至於servlet3.0規範中關於應用安全模型的知識這裏不做擴展。

2.9 getVirtualServerName

方法返回servlet上下文(即應用)部署的邏輯主機名,比如在本機跑Tomcat,那麼這個方法返回"Catalina/localhost"。

三、總結

至此,ServletContext的相關屬性和方法大致講解完了。ServletContext是個很重要的東西,在每次的servlet規範更新中,這個接口都有較大的變化。因爲ServletContext是容器和應用溝通的橋樑,從一定程度上講ServletContext就是servlet規範的體現。

這篇博文中關於servlet3.0和3.1提供的新特性,大都沒有細講,這裏請讀者勿噴,實在是因爲每個點擴展開來都可以單獨寫一篇博文。如果後續有機會,我會單獨去介紹。

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