溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

死磕Tomcat7源碼之二:web組件初始化

發(fā)布時(shí)間:2020-07-12 21:58:14 來(lái)源:網(wǎng)絡(luò) 閱讀:4581 作者:randy_shandong 欄目:開(kāi)發(fā)技術(shù)

經(jīng)過(guò)死磕Tomcat7源碼之一:解析web.xml,已經(jīng)知道webapp的配置信息是如何解析到內(nèi)存中。接下來(lái),就是如何將對(duì)應(yīng)的組件對(duì)象初始化化。分析所有的組件初始化過(guò)程,根本不可能。本文重點(diǎn)針對(duì)闡明3個(gè)主要組件的初始化過(guò)程,分別是:servlet,listener,filter。通過(guò)本文,你可以掌握以下知識(shí)點(diǎn)

  • 了解組件初始化調(diào)用序列

  • 組件servlet,listener,filter組件的初始化順序

  • listener的初始化過(guò)程

  • servlet的初始化過(guò)程

  • filter的初始化過(guò)程

1.組件初始化序列

通過(guò)《解析web.xml》,我們可以了解,tomcat在自動(dòng)完成webapp應(yīng)用的部署時(shí),完成了web.xml信息的解析,也就是說(shuō)webapp組件配置元信息,tomcat已經(jīng)拿到了。接下來(lái),就是根據(jù)配置的元信息規(guī)則,初始化組件。

死磕Tomcat7源碼之二:web組件初始化

通過(guò)上圖,知道組件對(duì)象的初始化主要從StandardContext完成的, StandardContext對(duì)應(yīng)著耳熟能詳?shù)膽?yīng)用,比如webapp目錄下的 docs,ROOT,manager...。

1組件初始化過(guò)程使用的是線程啟動(dòng)的。

代碼參考o(jì)rg.apache.catalina.core.ContainerBase.startInternal

        List<Future<Void>> results = new ArrayList<Future<Void>>();
        for (int i = 0; i < children.length; i++) {
            results.add(startStopExecutor.submit(new StartChild(children[i])));
        }

        boolean fail = false;
        for (Future<Void> result : results) {
            try {
                result.get();
            } catch (Exception e) {
                log.error(sm.getString("containerBase.threadedStartFailed"), e);
                fail = true;
            }

        }

2.組件servlet,listener,filter組件的初始化順序

筆者,創(chuàng)建了1個(gè)名為"helloapp"的webapp應(yīng)用,分別聲明了多個(gè)lister,多個(gè)servlet,filter。通過(guò)多次調(diào)整各組件在web.xml中的相對(duì)順序。得出如下結(jié)論。

  • 組件執(zhí)行順序按Listener,F(xiàn)ilter,Servlet進(jìn)行。

  • Servlet的load-on-startup影響Servlet的啟動(dòng)順序,詳情見(jiàn)2.1節(jié)說(shuō)明

  • Filter之間的初始化順序,與<filter-name>中的字符排序規(guī)則有關(guān),經(jīng)測(cè)試與默認(rèn)排序規(guī)則相反。

  • Listener之間的初始化順序,與在web.xml聲明的順序一致。這一點(diǎn)非常重要,如果使用一些mvc框架,安全框架時(shí),如果使用Listener來(lái)完成過(guò)濾攔截的話,一定要注意Listener的聲明順序。

 2.1servlet中l(wèi)oad-on-startup的規(guī)則說(shuō)明

    

  • 標(biāo)記容器是否在啟動(dòng)的時(shí)候就加載這個(gè)servlet。

  • 當(dāng)值為0或者大于0時(shí),表示容器在應(yīng)用啟動(dòng)時(shí)就加載這個(gè)servlet;

  • 當(dāng)是一個(gè)負(fù)數(shù)時(shí)或者沒(méi)有指定時(shí),則指示容器在該servlet被選擇時(shí)才加載。

  • 正數(shù)的值越小,啟動(dòng)該servlet的優(yōu)先級(jí)越高。

3.servlet的初始化過(guò)程

org.apache.catalina.core.StandardWrapper

   public boolean loadOnStartup(Container children[]) {

        // Collect "load on startup" servlets that need to be initialized
        TreeMap<Integer, ArrayList<Wrapper>> map =
            new TreeMap<Integer, ArrayList<Wrapper>>();
        for (int i = 0; i < children.length; i++) {
            Wrapper wrapper = (Wrapper) children[i];
            int loadOnStartup = wrapper.getLoadOnStartup();
            //如果小于0,跳過(guò)
            if (loadOnStartup < 0)
                continue;
            Integer key = Integer.valueOf(loadOnStartup);
            ArrayList<Wrapper> list = map.get(key);
            if (list == null) {
                list = new ArrayList<Wrapper>();
                map.put(key, list);
            }
            list.add(wrapper);
        }

        // Load the collected "load on startup" servlets
        for (ArrayList<Wrapper> list : map.values()) {
            for (Wrapper wrapper : list) {
                try {
                    //完成加載
                    wrapper.load();
                } catch (ServletException e) {
                    getLogger().error(sm.getString("standardContext.loadOnStartup.loadException",
                          getName(), wrapper.getName()), StandardWrapper.getRootCause(e));
                    // NOTE: load errors (including a servlet that throws
                    // UnavailableException from the init() method) are NOT
                    // fatal to application startup
                    // unless failCtxIfServletStartFails="true" is specified
                    if(getComputedFailCtxIfServletStartFails()) {
                        return false;
                    }
                }
            }
        }
        return true;

    }

通過(guò)源碼分析,我們清楚了load-on-startup 小于0時(shí),表示servlet不需要在啟動(dòng)時(shí)初始化。

       protected volatile boolean instanceInitialized = false;
    public synchronized void load() throws ServletException {
        instance = loadServlet();
        
        if (!instanceInitialized) {
            initServlet(instance);
        }

        if (isJspServlet) {
                    //
        }
    }

通過(guò)分析load()方法, 主要邏輯保證Servlet初始化一次。注意instanceInitialized 變量聲明為volatile類型,保證線程安全。

    private synchronized void initServlet(Servlet servlet)
            throws ServletException {
        // Call the initialization method of this servlet
        try {
            instanceSupport.fireInstanceEvent(InstanceEvent.BEFORE_INIT_EVENT,
                                              servlet);

            if( Globals.IS_SECURITY_ENABLED) {
                boolean success = false;
                try {
                    Object[] args = new Object[] { facade };
                    SecurityUtil.doAsPrivilege("init",
                                               servlet,
                                               classType,
                                               args);
                    success = true;
                } finally {
                    if (!success) {
                        // destroy() will not be called, thus clear the reference now
                        SecurityUtil.remove(servlet);
                    }
                }
            } else {
                servlet.init(facade);
            }

            instanceInitialized = true;

            instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT,
                                              servlet);   
                                              
           } catch (UnavailableException f) {     
              ...
           }                                        
  }

initServlet方法,主要調(diào)用servlet的init方法,完成servlet組件的初始化工作,以及觸發(fā)beforeInit和afterInit事件,觸發(fā)操作org.apache.catalina.InstanceListener.instanceEvent(InstanceEvent event)。


4.listener的初始化過(guò)程

  4.1 listener類結(jié)構(gòu)圖

    死磕Tomcat7源碼之二:web組件初始化4.2 listenerStart方法

   /**
     * Configure the set of instantiated application event listeners
     * for this Context.  Return <code>true</code> if all listeners wre
     * initialized successfully, or <code>false</code> otherwise.
     */
    public boolean listenerStart() {


        // Sort listeners in two arrays
        ArrayList<Object> eventListeners = new ArrayList<Object>();
        ArrayList<Object> lifecycleListeners = new ArrayList<Object>();
        for (int i = 0; i < results.length; i++) {
            if ((results[i] instanceof ServletContextAttributeListener)
                || (results[i] instanceof ServletRequestAttributeListener)
                || (results[i] instanceof ServletRequestListener)
                || (results[i] instanceof HttpSessionAttributeListener)) {
                eventListeners.add(results[i]);
            }
            if ((results[i] instanceof ServletContextListener)
                || (results[i] instanceof HttpSessionListener)) {
                lifecycleListeners.add(results[i]);
            }
        }
...
        for (int i = 0; i < instances.length; i++) {
            if (instances[i] == null)
                continue;
            if (!(instances[i] instanceof ServletContextListener))
                continue;
            ServletContextListener listener =
                (ServletContextListener) instances[i];
            try {
                fireContainerEvent("beforeContextInitialized", listener);
                if (noPluggabilityListeners.contains(listener)) {
                    listener.contextInitialized(tldEvent);
                } else {
                    listener.contextInitialized(event);
                }
                fireContainerEvent("afterContextInitialized", listener);
            } catch (Throwable t) {
                ExceptionUtils.handleThrowable(t);
                fireContainerEvent("afterContextInitialized", listener);
                getLogger().error
                    (sm.getString("standardContext.listenerStart",
                                  instances[i].getClass().getName()), t);
                ok = false;
            }
        }
        return (ok);

    }

通過(guò)分析listenerStart方法片段,可以知道tomcat將listener分為2類,分別是eventListener和lifecycleListener兩大類。并將listener加入到StandardHost中,并觸發(fā)beforeContextInitialized事件,和afterContextInitialized事件。

類型Listener名稱
eventListenerServletContextAttributeListener
eventListenerServletRequestAttributeListener
eventListenerServletRequestListener
eventListenerHttpSessionAttributeListener
lifecycleListenerHttpSessionListener

lifecycleListener

noPluggabilityListener

ServletContextListener


5.filter 初始化過(guò)程


5.1 filterStart方法,遍歷FilterDefs,初始化Filter,并放入filterConfigsMap。

這兒維護(hù)的filterConfigsMap,將來(lái)會(huì)在StandardWrapperValve.invoke方法中調(diào)用。而StandardWrapperValve作為servlet為pipeline模式中處理用戶請(qǐng)求流程中的一個(gè)節(jié)點(diǎn),所以也就實(shí)現(xiàn)了filter攔截請(qǐng)求的目的。

    public boolean filterStart() {

        if (getLogger().isDebugEnabled())
            getLogger().debug("Starting filters");
        // Instantiate and record a FilterConfig for each defined filter
        boolean ok = true;
        synchronized (filterConfigs) {
            filterConfigs.clear();
            for (Entry<String, FilterDef> entry : filterDefs.entrySet()) {
                String name = entry.getKey();
                if (getLogger().isDebugEnabled())
                    getLogger().debug(" Starting filter '" + name + "'");
                ApplicationFilterConfig filterConfig = null;
                try {
                    filterConfig =
                        new ApplicationFilterConfig(this, entry.getValue());
                    filterConfigs.put(name, filterConfig);
                } catch (Throwable t) {
                    t = ExceptionUtils.unwrapInvocationTargetException(t);
                    ExceptionUtils.handleThrowable(t);
                    getLogger().error
                        (sm.getString("standardContext.filterStart", name), t);
                    ok = false;
                }
            }
        }

        return (ok);

    }

5.2 ApplicationFilterConfig構(gòu)造函數(shù)

    ApplicationFilterConfig(Context context, FilterDef filterDef)
        throws ClassCastException, ClassNotFoundException,
               IllegalAccessException, InstantiationException,
               ServletException, InvocationTargetException, NamingException {

        super();

        this.context = context;
        this.filterDef = filterDef;
        // Allocate a new filter instance if necessary
        if (filterDef.getFilter() == null) {
            getFilter();
        } else {
            this.filter = filterDef.getFilter();
            getInstanceManager().newInstance(filter);
            initFilter();
        }
    }
 
    Filter getFilter() throws ClassCastException, ClassNotFoundException,
        IllegalAccessException, InstantiationException, ServletException,
        InvocationTargetException, NamingException {

        // Return the existing filter instance, if any
        if (this.filter != null)
            return (this.filter);

        // Identify the class loader we will be using
        String filterClass = filterDef.getFilterClass();
        //構(gòu)造filter對(duì)象
        this.filter = (Filter) getInstanceManager().newInstance(filterClass);
        
        initFilter();
        
        return (this.filter);

    }    
    
    
        private void initFilter() throws ServletException {
        if (context instanceof StandardContext &&
                context.getSwallowOutput()) {
            try {
                SystemLogHandler.startCapture();
                filter.init(this);//調(diào)用filter的初始化
            } finally {
                String capturedlog = SystemLogHandler.stopCapture();
                if (capturedlog != null && capturedlog.length() > 0) {
                    getServletContext().log(capturedlog);
                }
            }
        } else {
            filter.init(this);
        }
        
        // Expose filter via JMX
        registerJMX();
    }


最后,組件已經(jīng)初始化了。現(xiàn)在理所當(dāng)然應(yīng)該可以處理用戶請(qǐng)求了。都知道我們用戶請(qǐng)求信息都在HttpServletRequest對(duì)象中。那么用戶的 HTTP socket流信息,怎么一步步轉(zhuǎn)換成HttpServletRequest對(duì)象的呢。下次詳聊。


向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI