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"。

1.2 getContext

方法入參為uriPath(String),是一個資源定位符的路徑。返回一個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規范最高版本和最低版本。

getEffectiveMajorVersiongetEffectiveMinorVersion分別返回當前應用基於的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),然后調用它的方法進行轉發和包含。這里比較簡單,不過過多贅述,直接上源碼。(這里只列關鍵的兩個方法,簡單介紹下forward。如有興趣深入,可自行去研究RequestDispatcher相關知識

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

還拿我們上面的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需要注意的是,如果設置的參數名已經存在會設置失敗,這是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 對應的屬性值是個java.io.File對象,表示該ServletContext對應的臨時目錄。拿tomcat來說,比如有個應用叫demo,那么 demo對應的ServletContext臨時目錄就是{Tomcat目錄}\work\Catalina\localhost\demo。

ORDERED_LIBS 對應的屬性值是個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 addServletcreateServletgetServletRegistrationgetServletRegistrations

三個重載方法addServlet提供了編程式的向servlet容器中注入servlet的方式,其達到的效果和在web.xml中配置或者使用WebServlet注解配置servlet是一樣的。只不過這種方式更加靈活動態。addServlet方法返回的是ServletRegistration實例(ervletRegistration.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、getDefaultSessionTrackingModesgetEffectiveSessionTrackingModes

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提供的新特性,大都沒有細講,這里請讀者勿噴,實在是因為每個點擴展開來都可以單獨寫一篇博文。如果后續有機會,我會單獨去介紹。

作者:南唐三少
出處:http://www.cnblogs.com/nantang
如果您覺得閱讀本文對您有幫助,請點一下“推薦”按鈕,您的“推薦”將是我們最大的寫作動力!歡迎各位轉載,但是未經作者本人同意,轉載文章之后必須在文章頁面明顯位置給出作者和原文鏈接,否則保留追究法律責任的權利。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM