【Java.Web】Servlet —— Servlet容器的啟動及Servlet建立及初始化,容器預設的Servlet
Servlet容器 —— 以tomcat為例
在tomcat容器等級中,context容器直接管理servlet在容器中的包裝類Wrapper,所以Context容器如何執行將直接影響servlet的工作方式。
tomcat容器模型如下:
一個context對應一個web工程,在tomcat的配置檔案server.xml中,可以發現context的配置(在eclipse工程中,可在部署路徑的conf資料夾zhoing找到)
Context docBase="/Users/wongrobin/all/projects/tech-test/java-test/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/wtpwebapps/base-webapp" path="/base-webapp" reloadable="true" source="org.eclipse.jst.j2ee.server:base-webapp"/>
Servlet容器的啟動過程——以tomcat為例
Tomcat7增加了一個啟動類:
org.apache.catalina.startup.Tomcat
建立一個Tomcat的一個例項物件並呼叫start方法就可以啟動tomcat。
還可以通過這個物件來增加和修改tomcat的配置引數,如可以動態增加context,servlet等。
在tomcat7中提供的example中,看是如何新增到context容器中:
Tomcat tomcat = getTomcatInstance(); File appDir = new File(getBuildDirectory(), "webapps/examples"); tomcat.addWebapp(null, "/examples", appDir.getAbsolutePath()); tomcat.start(); ByteChunk res = getUrl("http://localhost:" + getPort() + "/examples/servlets/servlet/HelloWorldExample"); assertTrue(res.toString().indexOf("<h1>Hello World!</h1>") > 0);
這段程式碼建立了一個Tomcat例項並新增了一個WEB應用,然後啟動Tomcat並呼叫其中的一個HelloWorldExampleServlet。
Tomcat的addWebap方法的程式碼如下:
public Context addWebapp(Host host, String url, String path) { silence(url); Context ctx = new StandardContext(); ctx.setPath( url ); ctx.setDocBase(path); if (defaultRealm == null) { initSimpleAuth(); } ctx.setRealm(defaultRealm); ctx.addLifecycleListener(new DefaultWebXmlListener()); ContextConfig ctxCfg = new ContextConfig(); ctx.addLifecycleListener(ctxCfg); ctxCfg.setDefaultWebXml("org/apache/catalin/startup/NO_DEFAULT_XML"); if (host == null) { getHost().addChild(ctx); } else { host.addChild(ctx); } return ctx; }
一個WEB應用對應一個context容器,也就是servlet執行時的servlet容器。新增一個web應用時將會建立一個StandardContext容器,並且給這個context容器設定必要的引數,url和path分別代表這個應用在tomcat中的訪問路徑和這個應用實際的物理路徑,這兩個引數與tomcat配置中的兩個引數是一致的。其中一個最重要的一個配置是ContextConfig,這個類會負責整個web應用配置的解析工作。
最後將這個context容器加入到父容器host中。
接下來會呼叫tomcat的start方法啟動tomcat。
Tomcat的啟動邏輯是基於觀察者模式的,所有的容器都會繼承Lifecycle介面,它管理著容器的整個生命週期,所有容器的修改和狀態改變都會由它通知已經註冊的觀察者。
Tomcat啟動的時序如下:
當context容器初始狀態設定Init時,新增到context容器的listener將會被呼叫。ContextConfig繼承了LifecycleListener介面,它是在呼叫Tomcat.addWebapp時被加入到StandardContext容器中的。ContextConfig類會負責整個WEB應用的配置檔案的解析工作。
ContextConfig的init方法將會主要完成一下工作:
- 建立用於解析XML配置檔案的contextDigester物件
- 讀取預設的context.xml檔案,如果存在則解析它
- 讀取預設的Host配置檔案,如果存在則解析它
- 讀取預設的Context自身的配置檔案,如果存在則解析它
- 設定Context的DocBase
ContextConfig的init方法完成後,Context容器會執行startInternal方法,這個方法包括如下幾個部分:
- 建立讀取資原始檔的物件
- 建立ClassLoader物件
- 設定應用的工作目錄
- 啟動相關的輔助類,如logger,realm,resources等
- 修改啟動狀態,通知感興趣的觀察者
- 子容器的初始化
- 獲取ServletContext並設定必要的引數
- 初始化“load on startuo”的Servlet
Web應用的初始化工作——以tomcat為例
WEB應用的初始化工作是在ContextConfig的configureStart方法中實現的,應用的初始化工作主要是解析web.xml檔案,這個檔案是一個WEB應用的入口。
Tomcat首先會找globalWebXml,這個檔案的搜尋路徑是engine的工作目錄下的org/apache/catalina/startup/NO-DEFAULT_XML或conf/web.xml。接著會找hostWebXml,這個檔案可能會在System.getProperty("catalina.base")/conf/${EngineName}/${HostName}/web.xml.default中。接著尋找應用的配置檔案examples/WEB-INF/web.xml,web.xml檔案中的各個配置項將會被解析成相應的屬性儲存在WebXml物件中。接下來會講WebXml物件中的屬性設定到context容器中,這裡包括建立servlet物件,filter,listerner等,這些在WebXml的configureContext方法中。
下面是解析servlet的程式碼物件:
for (ServletDef servlet : servlets.values()) {
Wrapper wrapper = context.createWrapper();
String jspFile = servlet.getJspFile();
if (jspFile != null) {
wrapper.setJspFile(jspFile);
}
if (servlet.getLoadOnStartup() != null) {
wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue());
}
if (servlet.getEnabled() != null) {
wrapper.setEnabled(servlet.getEnabled().booleanValue());
}
wrapper.setName(servlet.getServletName());
Map<String,String> params = servlet.getParameterMap();
for (Entry<String, String> entry : params.entrySet()) {
wrapper.addInitParameter(entry.getKey(), entry.getValue());
}
wrapper.setRunAs(servlet.getRunAs());
Set<SecurityRoleRef> roleRefs = servlet.getSecurityRoleRefs();
for (SecurityRoleRef roleRef : roleRefs) {
wrapper.addSecurityReference(roleRef.getName(), roleRef.getLink());
}
wrapper.setServletClass(servlet.getServletClass());
MultipartDef multipartdef = servlet.getMultipartDef();
if (multipartdef != null) {
if (multipartdef.getMaxFileSize() != null &&
multipartdef.getMaxRequestSize()!= null &&
multipartdef.getFileSizeThreshold() != null) {
wrapper.setMultipartConfigElement(new MultipartConfigElement(
multipartdef.getLocation(),
Long.parseLong(multipartdef.getMaxFileSize()),
Long.parseLong(multipartdef.getMaxRequestSize()),
Integer.parseInt(
multipartdef.getFileSizeThreshold())));
} else {
wrapper.setMultipartConfigElement(new MultipartConfigElement(
multipartdef.getLocation()));
}
}
if (servlet.getAsyncSupported() != null) {
wrapper.setAsyncSupported(
servlet.getAsyncSupported().booleanValue());
}
context.addChild(wrapper);
}
上面的程式碼將servlet容器包裝成context容器中的StandardWrapper。StandardWrapper是tomcat容器中的一部分,它具有容器的特徵,而Servlet作為一個獨立的web開發標準,不應該強制耦合在tomcat中。
除了將Servlet包裝成StandardWrapper並作為子容器新增到Context中外,其他所有的web.xml屬性都被解析到Context中。
建立Servlet例項——以tomcat為例
前面完成了servlet的解析工作,並且被包裝成了StandardWrapper新增到Context容器中,但是它仍然不能為我們工作,它還沒有被例項化。
建立Servlet物件
如果Servlet的load-on-startup配置項大於0,那麼在Context容器啟動時就會被例項化。
前面提到的在解析配置檔案時會讀取預設的globalWebXml,在conf下的web.xml檔案中定義了一些預設的配置項,其中定義了兩個Servlet,分別是org.apache.catalina.servlets.DefaultServlet和org.apache.jsper.servlet.JspServelt,它們的load-on-startup分別是1和3,也就是當tomcat啟動時這兩個servlet就會被啟動。
建立Servlet例項的方式是從Wrapper.loadServlet開始的,loadServlet方法要完成的就是獲取servletClass,然後把它交給InstanceManager去建立一個基於servletClass.class的物件。如果這個Servlet配置了jsp-file,那麼這個servletClass就是在conf/web.xml中定義的org.apache.jasper.servlet.JspServlet。
初始化Servlet物件
初始化Servlet在StandardWrapper的initServlet方法中,這個方法很簡單,就是呼叫Servlet的init()方法,同時把包裝了StandardWrapper物件的StandardWrapperFacade作為ServletConfig傳給Servlet。
如果該Servlet關聯的是一個JSP檔案,那麼前面初始化的就是JspServlet,接下來會模擬一次簡單請求,請求呼叫這個JSP檔案,以便編譯這個JSP檔案為類,並初始化這個類。
這樣Servlet物件的初始化就完成了。
容器預設Servlet
每個servlet容器都有一個預設的servlet,一般都叫做default。
例如:tomcat中的 DefaultServlet 和 JspServlet (上面的部分)