Servlet是Server Applet的縮寫,即在服務器端運行的小程序,而Servlet框架則是對HTTP服務器(Servlet Container)和用戶小程序中間層的標準化和抽象。這一層抽象隔離了HTTP服務器的實現細節,而Servlet規范定義了各個類的行為,從而保證了這些“服務器端運行的小程序”對服務器實現的無關性(即提升了其可移植性)。
:所有“服務器小程序”要實現了接口,這些“服務器小程序”重寫doGet、doPost、doPut、doHead、doDelete、doOption、doTrace等方法(HttpServlet)以實現響應請求的相關邏輯。
Context在這里是指一個Web Application的上下文(Web Application是一個Server子URL下的Servlet和資源的集合),即它包含了這個Web Application級別的信息,如當前Web Application對應的根路徑、使用的Servlet版本、使用的ClassLoader等,在一個JVM中的一個Web Application只能有一個Context(一個JVM可以包含多個Web Application,它們包含不同的根路徑,即不同的Context路徑,Context路徑可以是空("/")即這個JVM只能包含一個Web Application)。ServletContext則是對這個Context的抽象,它還定義了一些和Servlet Container交互的方法,如獲取文件的MINE Type、Dispatch請求到另一個URL或Context、將日志寫入文件、根據提供的路徑獲取Resource實例、向這個ServletContext注冊并獲取Servlet或Filter、向這個ServletContext注冊并獲取Attribute或初始參數、向這個ServletContext注冊或獲取相關Listener等。對Distributed的Web Application來說,每個JVM下的Web Application都有一個獨立的ServletContext,因而ServletContext不可以作為全局信息存儲的地方,因而它并沒有分布式信息同步的功能,即它只是本地的ServletContext。在Servlet中,使用ServletConfig實例可以獲取ServletContext實例。
public interface ServletContext {
// Servlet Container為當前Web Application設置臨時目錄,并將該臨時目錄的值存儲到當前ServletContext的屬性中使用的屬性名。
// Jetty使用WebInfConfiguration(在preConfig
ure()方法中)來設置該值,設置temp目錄的規則:
// 1. 如果存在WEB-INF/work目錄,則temp目錄的值為:WEB-INF/work/Jetty_<host>_<port>__<resourceBase>_<context>_<virtualhost+base36_hashcode_of_whole_string>
// 2. 如果"javax.servlet.context.tempdir"已經在外部被設置,并且該目錄存在且可寫,則temp目錄直接設置為該目錄實例。
// 3. 如果系統變量中"jetty.home"目錄下存在"work"目錄且可寫,則設置temp目錄的值為:${jetty.home}/work/Jetty_<host>_<port>__<resourceBase>_<context>_<virtualhost+base36_hashcode_of_whole_string>
// 4. 如果存在"org.eclipse.jetty.webapp.basetempdir"的屬性,且該目錄存在并可寫,設置temp目錄為:${org.eclipse.jetty.webapp.basetempdir}/Jetty_<host>_<port>__<resourceBase>_<context>_<virtualhost+base36_hashcode_of_whole_string>
// 5. 如果以上條件都不成立,則設置temp目錄為:${java.io.tmpdir}/Jetty_<host>_<port>__<resourceBase>_<context>_<virtualhost+base36_hashcode_of_whole_string>,且刪除已存在臨時目錄。
// 注:對temp目錄的父目錄不是work,會注冊在JVM退出時刪除該temp目錄,并在temp目錄下創建.active目錄。
public static final String TEMPDIR = "javax.servlet.context.tempdir";
// Servlet 3.0中新引入的特性,即可以在WEB-INF/lib下的jar包中定義/META-INF/web-fragment配置響應的Servlet等。
// 如果在web.xml文件中定義了absolute-ordering,或者在jar包中存在/META-INF/web-fragment.xml文件,且定義了ordering,
// 則該屬性的值即為根據規范中定義的規則計算出來的讀取jar包中web-fragment.xml文件的jar包名順序,它時一個List<String>類型,包含jar包名字。
// 在Jetty中使用Ordering來表示這種順序,它有兩個實現類:AbsoluteOrdering和RelativeOrdering用來分別表示在web.xml和web-fragment.xml中定義的absolute-ordering和ordering定義,
// 并且將最終的解析結果匯總到Metadata類中,并根據規范中定義的規則以及metadata-complete的定義來計算實際的解析順序,
// 而對這兩種配置文件的解析由WebDescriptor和FragmentDescriptor來實現,它們都包含了metadata-complete解析,而真正的解析入口在WebXmlConfiguration和FragmentConfiguration中。
// 該規范的定義參考:https://blogs.oracle.com/swchan/entry/servlet_3_0_web_fragment
public static final String ORDERED_LIBS = "javax.servlet.context.orderedLibs";
// 返回當前Web Application的Context Path,即當前Web Application的根路徑,Servlet Container根據該路徑以及一個Request URL的匹配情況來選擇一個Request應該交給那個Web Application處理該請求。
// Context Path以"/"開始,但是不能以"/"結尾,對默認的根Context,它返回"",而不是"/"。該值在配置Jetty的ContextHandler中設置。
// 有些Servlet Container支持多個Context Path指向同一個Context,此時可以使用HttpServletRequest中的getContextPath()來獲取該Request實際對應的Context Path,此時這兩個Context Path的值可能會不同,但是ServletContext中返回的Context Path值是主要的值。另外Jetty也不支持這種特性。
public String getContextPath();
// 通過給定一個Context Path以在當前Servlet Container中找到其對應的ServletContext實例。
// 可以通過該方法獲取Servlet Container中定義的另一個Web Application的ServletContext實例,并獲得其RequestDispatcher,并將當前請求Dispatch到那個Web Application中做進一步的處理。這里的uripath必須以"/"開頭,且其路徑相對于當前Server的根路徑。出于安全考慮,該方法可能會返回null。
// 在Jetty的實現中,這里uripath可以是一個具體的路徑,并且支持查找最準確的匹配。如:對uripath為/foo/goo/abc.html,在Server中由以下Context Path定義:"/", "/foo", "/foo/goo",則最終查找到的ServletContext為"/foo/goo"作為Context Path對應的ServletContext實例。
public ServletContext getContext(String uripath);
//返回Servlet規范的主版本,如3
public int getMajorVersion();
// 返回Servlet規范的次版本,如0
public int getMinorVersion();
// 返回當前Web Application基于的Servlet規范的主版本,如在web.xml文件中定義的version(<web-app version="..." ...>...</web-app>,即Jetty中的實現)
public int getEffectiveMajorVersion();
// 返回當前Web Application基于的Servlet規范的次版本,如在web.xml文件中定義的version(<web-app version="..." ...>...</web-app>,即Jetty中的實現)
public int getEffectiveMinorVersion();
// 返回給定file的MIME type,返回null如果無法計算出其MIME type。這個映射關系由Servlet Container定義或在web.xml文件中定義(mime-mapping, extension, mine-type)。
// 常見的MIME type有:text/html, image/gif等。Jetty使用MimeTypes類來封裝所有和MIME type相關的操作,MimeTypes類中定義了所有默認支持的MIME type以及編碼類型,
// 并且默認從org/eclipse/jetty/http/mime.properties文件中加載默認的映射,如css=text/css, doc=application/msword等,使用addMimeMapping()方法向該類注冊web.xml中定義的文件名擴展名到MIME type的映射。
// 而從org/eclipse/jetty/http/encoding.properties文件中加載MIME type的默認編碼類型,如text/xml=UTF-8等。
// 在使用文件名查找MIME type時,根據文件名的擴展名查找已注冊或默認注冊的MIME type。用戶自定義的映射優先。用戶定義的MIME type映射支持extension為"*",表示任意擴展名。
public String getMimeType(String file);
// 對于給定目錄,返回該目錄下所有的資源以及目錄。path必須以"/"開頭,如果它不是指向一個目錄,則返回空的Set。所有返回的路徑都相對當前Web Application的根目錄,
// 或對于/WEB-INF/lib中jar包中的/META-INF/resources/目錄,如一個Web Application包含以下資源:/catalog/offers/music.html, /WEB-INF/web.xml,
// 以及/WEB-INF/lib/catalog.jar!/META-INF/resources/catalog/moreOffers/books.html,則getResourcePaths("/catalog")返回{"/catalog/offers/", /catalog/moreOffers/"}
// Jetty的實現中,在MetaInfConfiguration中,它會掃描WEB-INF/lib目錄下所有的jar包,如果發現在某個jar包中存在META-INF/resources/目錄,
// 就會將該目錄資源作為baseResource在WebInfConfiguration中注冊到ContextHandler(WebAppContext)中。從而實現jar包中的META-INF/resources/目錄作為根目錄的查找。
public Set<String> getResourcePaths(String path);
// 返回給定path的URL,path必須以"/"開頭,它相對當前Web Application的根目錄或相對/WEB-INF/lib中jar包中的/META-INF/resources/目錄。
// 其中查找順序前者優先于后者,但是在/WEB-INF/lib/目錄下的jar包的查找順序未定義。該方法不同于Class.getResource()在于它不使用ClassLoader,如果沒有找到給定資源,返回null。
// 在WebAppContext實現中,它還支持Alias查找,并且如果其extractWAR的變量為false,給定的資源在WAR包中,則該URL返回WAR包中的URL。
public URL getResource(String path)
throws MalformedURLException;
// 參考getResource(path)的查找實現,如果其返回的URL為null,則該方法返回null,否則返回URL對應的InputStream。
public InputStream getResourceAsStream(String path);
// 創建一個RequestDispatcher用于將一個Request、Response分發到path對應的URL中,這里path必須以"/"開頭,且它相對于當前Context Path。如果無法創建RequestDispatcher,返回null。
// path可以包含query數據用于傳遞參數:uriInContext?param1=abc¶m2=123....該方法可以和getContext(uripath)一起使用,以將當前請求分發到另一個Web Application中。
// 該方法的另一種用法是先有一個Servlet或Filter處理基本的邏輯,然后使用這個RequestDispatcher將當前請求forward到另一個URL中或include一個JSP文件生成響應頁面,如果在處理過程中出錯,則將其當前請求分發到錯誤處理的流程中。
// RequestDispatcher支持兩種類型的分發:forward和include,唯一的區別是include只可以改變Response的內容,不可以改變其Header信息,forward則沒有這種限制。
public RequestDispatcher getRequestDispatcher(String path);
// 創建一個RequestDispatcher用于將一個Request、Response分發到name對應的Servlet(JSP)中。如果沒能找到響應的Servlet,返回null。
public RequestDispatcher getNamedDispatcher(String name);
// 將msg打印到Servlet對應的log文件中,在Jetty中,使用INFO級別打印,logger名稱為web.xml定義的display-name,或者context path。
// Jetty默認使用SLF4J作為日志打印框架,可以使用"org.eclipse.jetty.util.log.class"系統屬性改變其日志打印框架。
public void log(String msg);
// 打印message和throwable到Servlet對應的log文件中,在Jetty中使用WARN級別打印該信息。
public void log(String message, Throwable throwable);
// 返回給定path在當前機器上操作系統對應的位置。對/META-INF/resources下的資源,除非他們已經解壓到本地目錄,否則對那些資源該方法返回null。
// 在Jetty實現中,使用getResource()方法相同的實現獲取Resource實例,如果其getFile()返回不為null,則返回該File的canonical路徑。
public String getRealPath(String path);
// 返回Servlet Container的名稱和版本號,其格式為:<servername>/<versionnumber>,如:Jetty/8.1.9.v20130131。
public String getServerInfo();
// ServletContext級別的初始參數操作,可以在web.xml中使用context-param定義,也可以手動設置。在get中如果找不到對應的項,返回null,在set時,如果已存在name,則返回false,并且不更新相應的值。
public String getInitParameter(String name);
public Enumeration<String> getInitParameterNames();
public boolean setInitParameter(String name, String value);
// ServletContext級別的屬性操作,其中屬性名遵循包命名規則。在set中,如果object為null表示移除該屬性,如果name以存在,則會替換原有的值,如果注冊了ServletContextAttributeListener,則會出發相應的attributeRemoved、attributeReplaced、attributeAdded事件。在remove中,如果name存在且被移除了,則會觸發attributeRemoved事件。
// 在Jetty中使用ContextHandler中的Context內部類實現ServletContext,在ContextHandler中定義了兩個相關字段:_attributes以及_contextAttributes,其中_attributes表示在Jetty內部通過ContextHandler設置的屬性,而_contextAttributes表示用戶設置的屬性,但是在獲取屬性值時,兩個字段的屬性都會考慮進去,在移除屬性時,如果是移除_attributes字段中的值,則不會觸發attributeRemoved事件。
public Object getAttribute(String name);
public Enumeration<String> getAttributeNames();
public void setAttribute(String name, Object object);
public void removeAttribute(String name);
// 返回當前Web Application在web.xml中的display-name定義的ServletContext名字,在Jetty實現中,如果該值為null,則返回context path。
public String getServletContextName();
// 該部分具體的使用可以參考:http://www.tkk7.com/yongboy/archive/2010/12/30/346209.html
// 動態的向ServletContext中注冊Servlet,注冊的Servlet還可以通過返回的ServletRegistration繼續配置,如addMapping、setInitParameter等。
// 在使用className實例話Servlet時,使用當前ServletContext相關聯的ClassLoader。在創建Servlet實例時,會根據該類中定義的以下Annotation做相應的配置:
// javax.servlet.annotation.ServletSecurity、javax.servlet.annotation.MultipartConfig、javax.annotation.security.RunAs、javax.annotation.security.DeclareRoles
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);
// 創建給定Servlet類的Servlet實例,并且會根據該類中定義的以下Annotation做相應的配置:
// javax.servlet.annotation.ServletSecurity、javax.servlet.annotation.MultipartConfig、javax.annotation.security.RunAs、javax.annotation.security.DeclareRoles
// 在創建Servlet實例后,一般還要調用addServlet()方法將其注冊到ServletContext中。
public <T
extends Servlet> T createServlet(Class<T> clazz)
throws ServletException;
// 根據servletName獲取ServletRegistration實例
public ServletRegistration getServletRegistration(String servletName);
// 獲取所有在ServletContext中注冊的servletName到ServletRegistration映射的Map。所有動態注冊和使用配置注冊的映射。
public Map<String, ?
extends ServletRegistration> getServletRegistrations();
// 動態的向ServletContext中注冊Filter,注冊的Filter可以通過返回的FilterRegistration進一步配置,如addMappingForUrlPatterns、setInitParameter等
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);
// 創建給定Filter類實例的Filter實例,一般都會后繼調用addFilter將該實例注冊到ServletContext中。
public <T
extends Filter> T createFilter(Class<T> clazz)
throws ServletException;
// 根據filterName獲取FilterRegistration實例。
public FilterRegistration getFilterRegistration(String filterName);
// 返回所有filterName到FilterRegistration映射的Map,包括所有動態注冊和使用配置注冊的映射。
public Map<String, ?
extends FilterRegistration> getFilterRegistrations();
// 返回SessionCookieConfig實例,用于session tracking的cookie屬性,多次調用該方法返回相同的實例。
public SessionCookieConfig getSessionCookieConfig();
// 設置session tracking模式,可以是URL、COOKIE、SSL。Jetty8只支持URL和COOKIE。
public void setSessionTrackingModes(Set<SessionTrackingMode> sessionTrackingModes);
// 返回當前ServletContext默認支持的session tracking模式。
public Set<SessionTrackingMode> getDefaultSessionTrackingModes();
// 返回當前ServletContext目前使用的session tracking模式。
public Set<SessionTrackingMode> getEffectiveSessionTrackingModes();
// 向ServletContext動態的注冊Listener,該Listener類或實例必須實現以下的一個或多個接口:
// ServletContextAttributeListener、ServletRequestListener、ServletRequestAttributeListener、HttpSessionListener、HttpSessionAttributeListener}</tt>
// 如果這個ServletContext傳入ServletContainerInitializer的onStartup方法,那么這個Listener類或實例也可以實現ServletContextListener接口。
// 注:動態注冊的ServletContextListener中的contextInitialized方法中不可以調用Servlet 3.0中定義的這些動態注冊Servlet、Filter、Listener等方法,不然會拋出UnsupportedOperationException,看起來像是出于一致性、安全性或是兼容性的考慮,但是具體是什么原因一直想不出來。而且在Jetty實現中,它在注冊EventListener實例是確取消了這種限制,而對注冊EventListener類實例和類名確有這種限制,不知道這是Jetty的bug還是其他什么原因。。。。。
// 對于調用順序按定義順序來的EventListener(如ServletRequestListener、ServletContextListener、HttpSessionListener),那這個新的EventListener會添加到相應列表末尾。
public void addListener(String className);
public <T
extends EventListener>
void addListener(T t);
public void addListener(Class <?
extends EventListener> listenerClass);
// 創建clazz對應的EventListener實例,一般這個新創建的EventListener會之后注冊到ServletContext中。
public <T
extends EventListener> T createListener(Class<T> clazz)
throws ServletException;
// 返回web.xml和web-fragment.xml配置文件中定義<jsp-config>的匯總,或返回null如果沒有相關配置。看起來像Jetty并沒有實現該方法。
public JspConfigDescriptor getJspConfigDescriptor();
// 返回當前Web Application對應的ClassLoader實例。
public ClassLoader getClassLoader();
// 定義角色名。角色名在ServletRegistration.Dynamic.setServletSecurity()和ServletRegistration.Dynamic.setRunAsRole()中默認定義,因而不需要重新使用這個方法定義。
public void declareRoles(String

roleNames);
}
ServletContext的初始化從ContextHandler的doStart()方法開始,在其startContext()方法快結束時,會調用注冊的ServletContextListener中的contextInitialized()方法,因而這里是用戶對ServletContext初始化時做一些自定義邏輯的擴展點。
在Servlet 3.0中還引入了ServletContainerInitializer接口,它定義了onStartup()方法,該方法會在WebAppContext中的startContext方法中的configure方法中通過ContainerInitializerConfiguration.configure()中被調用,該方法的調用要早于所有ServletContextListener
.contextInitialized()事件的觸發。