Servlet工作原理解析(一)Servlet容器

請結合這篇文章閱讀Tomcat生命周期

Servlet是Java Web技術的核心基礎,簡單來說,處理請求和發(fā)送響應的過程是由一種叫做Servlet的程序來完成的,并且Servlet是為了解決實現(xiàn)動態(tài)頁面而衍生的東西。

Servlet容器

要介紹Servlet,必須要先了解Servlet容器,他們是 相互依存的,但是又是獨立發(fā)展的,從技術來說是為了解耦合,通過標準的接口(Servlet-API)來相互協(xié)作,在javax.servlet包的文檔中是這樣描述的:

The javax.servlet package contains a number of classes and interfaces that describe and define the contracts between a servlet class and the runtime environment provided for an instance of such a class by a conforming servlet container.

我以Tomcat為例來介紹Servlet容器的,Tomcat的容器體系如下:

Tomcat容器

真正管理Servlet的容器是Context,一個Context對應一個Web工程,也就是webapps下的一個項目,Context中的Wrapper也就是包裝后的servlet。

Servlet容器的啟動過程

Tomcat 7開始支持嵌入式功能,增加了一個啟動類,我們可以很容易的通過一個實例對象來日東Tomcat(Spring Boot),還可以通過這個對象來配置Tomcat的參數(shù),我給出一段示例代碼:

    private void startTomcat(int port, String contextPath, String baseDir)
            throws ServletException, LifecycleException {
        tomcat = new Tomcat();
        //  端口號
        tomcat.setPort(port);
        //項目物理路徑
        tomcat.setBaseDir(".");
        StandardServer server = (StandardServer) tomcat.getServer();
        //容器生命周期監(jiān)聽
        AprLifecycleListener listener = new AprLifecycleListener();
        server.addLifecycleListener(listener);
        //添加web應用
        tomcat.addWebapp(contextPath, baseDir);
        tomcat.start();
    }

前面說過一個web應用對應一個Context容器,當添加一個web應用的時候會創(chuàng)建一個StandardContext容器,并且做相關配置,其中最重要的是ContextConfig(實際上是一個監(jiān)聽器),這個類將負責整個web應用的解析工作,當context容器的狀態(tài)被更改時,ContextConfig會被通知,做相關解析工作,最后將這個容器加到父容器Host中。
我們來看一下addWebApp的代碼:

 public Context addWebapp(Host host, String contextPath, String docBase,
            LifecycleListener config) {
        //設置日志級別
        silence(host, contextPath);
        //創(chuàng)建一個Context容器
        Context ctx = createContext(host, contextPath);
        //做相關配置
        ctx.setPath(contextPath);
        ctx.setDocBase(docBase);
        ctx.addLifecycleListener(getDefaultWebXmlListener());
        ctx.setConfigFile(getWebappConfigFile(docBase, contextPath));
        //添加生命周期監(jiān)聽
        ctx.addLifecycleListener(config);

        if (config instanceof ContextConfig) {
            // prevent it from looking ( if it finds one - it'll have dup error )
            ((ContextConfig) config).setDefaultWebXml(noDefaultWebXmlPath());
        }
        //將context容器添加到Host容器中
        if (host == null) {
            getHost().addChild(ctx);
        } else {
            host.addChild(ctx);
        }

        return ctx;
    }

接著,我們看一下Context容器狀態(tài)變化時,ContextConfig作出的相應工作。

public void lifecycleEvent(LifecycleEvent event) {

    // Identify the context we are associated with
    try {
    //  從事件對象中獲取context對象
    context = (Context) event.getLifecycle();
    } catch (ClassCastException e) {
    log.error(sm.getString("contextConfig.cce", event.getLifecycle()), e);
    return;
    }
    //  處理發(fā)生的事件
    // Process the event that has occurred
    if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
    //  Web應用的初始化工作
    configureStart();
    } else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {
    beforeStart();
    } else if (event.getType().equals(Lifecycle.AFTER_START_EVENT)) {
    // Restore docBase for management tools
    if (originalDocBase != null) {
    context.setDocBase(originalDocBase);
    }
    } else if (event.getType().equals(Lifecycle.CONFIGURE_STOP_EVENT)) {
    configureStop();
    } else if (event.getType().equals(Lifecycle.AFTER_INIT_EVENT)) {
    //  主要解析context.xml(例如springMVC的配置文件)、Host配置文件、Context自身配置文件、DocBase
    init();
    } else if (event.getType().equals(Lifecycle.AFTER_DESTROY_EVENT)) {
    destroy();
    }

    }
Web應用的初始化工作

Web應用的初始化工作主要是在上面代碼中的 configureStart();中進行的,當Context容器的狀態(tài)變?yōu)長ifecycle.CONFIGURE_START_EVENT),會調用ContextConfig的configureStart()進行Web應用的初始化工作(主要是對web.xml的解析工作,這里的web.xml包括全局、host、應用以及fragment,原本一個web應用的任何配置都需要在web.xml中進行,因此會使得web.xml變得很混亂,而且靈活性差,因此Servlet 3.0可以將每個Servlet、Filter、Listener打成jar包,然后放在WEB-INF\lib中;注意各自的模塊都有各自的配置文件,這個配置文件的名稱為 web-fragment.xml)。

    protected synchronized void configureStart() {
        // Called from StandardContext.start()
        //該方法會在Context啟動時被調用
        if (log.isDebugEnabled()) {
            log.debug(sm.getString("contextConfig.start"));
        }

        if (log.isDebugEnabled()) {
            log.debug(sm.getString("contextConfig.xmlSettings",
                    context.getName(),
                    Boolean.valueOf(context.getXmlValidation()),
                    Boolean.valueOf(context.getXmlNamespaceAware())));
        }
        //解析XXweb.xml
        webConfig();
        //解析注解
        if (!context.getIgnoreAnnotations()) {
            applicationAnnotationsConfig();
        }
        //驗證角色
        if (ok) {
            validateSecurityRoles();
        }
         //配置認證器
        // Configure an authenticator if we need one
        if (ok) {
            authenticatorConfig();
        }

        // Dump the contents of this pipeline if requested
        if (log.isDebugEnabled()) {
            log.debug("Pipeline Configuration:");
            Pipeline pipeline = context.getPipeline();
            Valve valves[] = null;
            if (pipeline != null) {
                valves = pipeline.getValves();
            }
            if (valves != null) {
                for (int i = 0; i < valves.length; i++) {
                    log.debug("  " + valves[i].getClass().getName());
                }
            }
            log.debug("======================");
        }

        // Make our application available if no problems were encountered
        if (ok) {
            context.setConfigured(true);
        } else {
            log.error(sm.getString("contextConfig.unavailable"));
            context.setConfigured(false);
        }

    }

在這里我們主要來看webConfig()中的代碼:

 /**
     * Scan the web.xml files that apply to the web application and merge them
     * using the rules defined in the spec. For the global web.xml files,
     * where there is duplicate configuration, the most specific level wins. ie
     * an application's web.xml takes precedence over the host level or global
     * web.xml file.
     *掃描web.xml(包括globalWebXml,hostWebXml以及應用中配置的webXml,如果存在重復,則以具體級別的為準)
     */

這里我不貼代碼,給出一個具體步驟:
Parse global level web.xml(解析默認全局配置web.xml)
Parse context level web.xml(解析應用中配置的web.xml)
Step 1. Identify all the JARs packaged with the application and those provided by the container. If any of the application JARs have a web-fragment.xml it will be parsed at this point. web-fragment.xml files are ignored for container provided JARs.(識別應用程序以及容器中所有的jar包,解析web-fragment.xml,提供該jar包的容器將忽略這個xml文件,對于容器來說應用級別的web.xml才是真正的配置文件)
Step 2. Order the fragments.(對這些fragments排序,Servlet、Filter、Listener等,并且保存這些fragments
Step 3. Look for ServletContainerInitializer implementations(查找servlet容器的初始化器)
Step 4. Process /WEB-INF/classes for annotations and @HandlesTypes matches(Skip the META-INF directory)(處理/WEB-INF/classes下的@HandlesTypes 注解的匹配類,跳過了META-INF 目錄)
Step 5. Process JARs for annotations and @HandlesTypes matches - only need to process those fragments we are going to use (處理jar包中@HandlesTypes 注解的匹配類,只處理我們需要使用的fragment所在的jar包)
Step 6. Merge web-fragment.xml files into the main web.xml file.(合并web-fragment.xml到應用中配置的web.xml中)
Step 7. Apply global defaults Have to merge defaults before JSP conversion since defaults provide JSP servlet definition.(應用默認全局配置,因為默認全局配置中提供了JSP servlet的定義)
Step 8. Convert explicitly mentioned jsps to servlets(轉換顯示聲明的jsp到servlet)
Step 9. Apply merged web.xml to Context(應用合并后的web.xml到容器)
Step 10. Look for static resources packaged in JARs(尋找jar包中的靜態(tài)資源)
Step 11. Apply the ServletContainerInitializer config to the context(配置容器中的servlet初始化器)

Step5,6,11主要是處理Servlet3.0所支持的動態(tài)Servlet,這里我們重點關注Step 9,Step 11.
先說一下Step9 ,這一步的代碼主要在configureContext(WebXml webxml)中,
由于代碼太長就不放了,簡單說一下,主要是創(chuàng)建Servlet對象、Filter、Listener等,處理工作是按照字母順序來做的,主要是為了防止遺漏

  • 設置context容器的PublicID、版本號、ContextParams等參數(shù)
  • 設置context的environment
  • 設置errorPage
  • 設置filter是否為異步
  • 設置filter Mapping
  • 設置jsp配置描述符
  • 設置lister
  • 設置LocaleEncodingMappings
  • 設置登錄配置
  • 設置安全角色SecurityRoles
  • 設置service
  • 設置servlet
  • 設置servlet Mapping
  • 設置session(cookie)配置
  • 設置歡迎頁
  • 設置jsp的屬性組(放在最后是因為依賴servlets)
    好吧,這么多夠煩人的,我們來看一下servlet的創(chuàng)建過程。這個過程呢,是將Servlet包裝成StandardWrapper的一個過程,實際上Context容器中運行的是StandardWrapper對象而不是Servlet對象為啥這么做?
    開局我們說過,Servlet容器和Servlet是相互獨立的,StandardWrapper是Tomcat的一部分,它具有容器的特征,而Servlet作為一個獨立的web開發(fā)標準,不應該強耦合在Tomcat中。所以將Servlet包裝成StandardWrapper,作為context容器的子容器添加到context容器中,由StandardWrapper負責Servlet的管理工作。有興趣的伙伴可以看一下響應的源碼。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容