一.容器簡介
在tomcat容器等級中,context容器直接管理servlet在容器中的包裝類Wrapper,所以Context容器如何運行將直接影響servlet的工作方式。
tomcat容器模型如下:
一個context對應一個web工程,在tomcat的配置文件server.xml中,可以發現context的配置(在eclipse工程中,可在部署路徑的conf文件夾zhoing找到)
1 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
Tomcat7增加了一個啟動類:
1 org.apache.catalina.startup.Tomcat
創建一個Tomcat的一個實例對象並調用start方法就可以啟動tomcat。
還可以通過這個對象來增加和修改tomcat的配置參數,如可以動態增加context,servlet等。
在tomcat7中提供的example中,看是如何添加到context容器中:
1 Tomcat tomcat = getTomcatInstance(); 2 File appDir = new File(getBuildDirectory(), "webapps/examples"); 3 tomcat.addWebapp(null, "/examples", appDir.getAbsolutePath()); 4 tomcat.start(); 5 ByteChunk res = getUrl("http://localhost:" + getPort() + 6 "/examples/servlets/servlet/HelloWorldExample"); 7 assertTrue(res.toString().indexOf("<h1>Hello World!</h1>") > 0);
這段代碼創建了一個Tomcat實例並新增了一個WEB應用,然后啟動Tomcat並調用其中的一個HelloWorldExampleServlet。
Tomcat的addWebap方法的代碼如下:
1 public Context addWebapp(Host host, String url, String path) { 2 silence(url); 3 Context ctx = new StandardContext(); 4 ctx.setPath( url ); 5 ctx.setDocBase(path); 6 if (defaultRealm == null) { 7 initSimpleAuth(); 8 } 9 ctx.setRealm(defaultRealm); 10 ctx.addLifecycleListener(new DefaultWebXmlListener()); 11 ContextConfig ctxCfg = new ContextConfig(); 12 ctx.addLifecycleListener(ctxCfg); 13 ctxCfg.setDefaultWebXml("org/apache/catalin/startup/NO_DEFAULT_XML"); 14 if (host == null) { 15 getHost().addChild(ctx); 16 } else { 17 host.addChild(ctx); 18 } 19 return ctx; 20 }
一個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應用的初始化
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的代碼對象:
1 for (ServletDef servlet : servlets.values()) { 2 Wrapper wrapper = context.createWrapper(); 3 String jspFile = servlet.getJspFile(); 4 if (jspFile != null) { 5 wrapper.setJspFile(jspFile); 6 } 7 if (servlet.getLoadOnStartup() != null) { 8 wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue()); 9 } 10 if (servlet.getEnabled() != null) { 11 wrapper.setEnabled(servlet.getEnabled().booleanValue()); 12 } 13 wrapper.setName(servlet.getServletName()); 14 Map<String,String> params = servlet.getParameterMap(); 15 for (Entry<String, String> entry : params.entrySet()) { 16 wrapper.addInitParameter(entry.getKey(), entry.getValue()); 17 } 18 wrapper.setRunAs(servlet.getRunAs()); 19 Set<SecurityRoleRef> roleRefs = servlet.getSecurityRoleRefs(); 20 for (SecurityRoleRef roleRef : roleRefs) { 21 wrapper.addSecurityReference(roleRef.getName(), roleRef.getLink()); 22 } 23 wrapper.setServletClass(servlet.getServletClass()); 24 MultipartDef multipartdef = servlet.getMultipartDef(); 25 if (multipartdef != null) { 26 if (multipartdef.getMaxFileSize() != null && 27 multipartdef.getMaxRequestSize()!= null && 28 multipartdef.getFileSizeThreshold() != null) { 29 wrapper.setMultipartConfigElement(new MultipartConfigElement( 30 multipartdef.getLocation(), 31 Long.parseLong(multipartdef.getMaxFileSize()), 32 Long.parseLong(multipartdef.getMaxRequestSize()), 33 Integer.parseInt( 34 multipartdef.getFileSizeThreshold()))); 35 } else { 36 wrapper.setMultipartConfigElement(new MultipartConfigElement( 37 multipartdef.getLocation())); 38 } 39 } 40 if (servlet.getAsyncSupported() != null) { 41 wrapper.setAsyncSupported( 42 servlet.getAsyncSupported().booleanValue()); 43 } 44 context.addChild(wrapper); 45 }
上面的代碼將servlet容器包裝成context容器中的StandardWrapper。StandardWrapper是tomcat容器中的一部分,它具有容器的特征,而Servlet作為一個獨立的web開發標准,不應該強制耦合在tomcat中。
除了將Servlet包裝成StandardWrapper並作為子容器添加到Context中外,其他所有的web.xml屬性都被解析到Context中。
四.創建Servlet實例
前面完成了servlet的解析工作,並且被包裝成了StandardWrapper添加到Context容器中,但是它仍然不能為我們工作,它還沒有被實例化。
1.創建
如果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。
2.初始化
初始化Servlet在StandardWrapper的initServlet方法中,這個方法很簡單,就是調用Servlet的init()方法,同時把包裝了StandardWrapper對象的StandardWrapperFacade作為ServletConfig傳給Servlet。
如果該Servlet關聯的是一個JSP文件,那么前面初始化的就是JspServlet,接下來會模擬一次簡單請求,請求調用這個JSP文件,以便編譯這個JSP文件為類,並初始化這個類。
這樣Servlet對象的初始化就完成了。
3.容器默認Servlet
每個servlet容器都有一個默認的servlet,一般都叫做default。
例如:tomcat中的 DefaultServlet 和 JspServlet (上面的部分)