1. 程式人生 > >【Java.Web】Servlet —— Servlet容器的啟動及Servlet建立及初始化,容器預設的Servlet

【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 (上面的部分)