死磕Tomcat7源码之二:web组件初始化

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了死磕Tomcat7源码之二:web组件初始化相关的知识,希望对你有一定的参考价值。

经过死磕Tomcat7源码之一:解析web.xml,已经知道webapp的配置信息是如何解析到内存中。接下来,就是如何将对应的组件对象初始化化。分析所有的组件初始化过程,根本不可能。本文重点针对阐明3个主要组件的初始化过程,分别是:servlet,listener,filter。通过本文,你可以掌握以下知识点

  • 了解组件初始化调用序列

  • 组件servlet,listener,filter组件的初始化顺序

  • listener的初始化过程

  • servlet的初始化过程

  • filter的初始化过程

1.组件初始化序列

通过《解析web.xml》,我们可以了解,tomcat在自动完成webapp应用的部署时,完成了web.xml信息的解析,也就是说webapp组件配置元信息,tomcat已经拿到了。接下来,就是根据配置的元信息规则,初始化组件。

技术分享

通过上图,知道组件对象的初始化主要从StandardContext完成的, StandardContext对应着耳熟能详的应用,比如webapp目录下的 docs,ROOT,manager...。

1组件初始化过程使用的是线程启动的。

代码参考org.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组件的初始化顺序

笔者,创建了1个名为"helloapp"的webapp应用,分别声明了多个lister,多个servlet,filter。通过多次调整各组件在web.xml中的相对顺序。得出如下结论。

  • 组件执行顺序按Listener,Filter,Servlet进行。

  • Servlet的load-on-startup影响Servlet的启动顺序,详情见2.1节说明

  • Filter之间的初始化顺序,与<filter-name>中的字符排序规则有关,经测试与默认排序规则相反。

  • Listener之间的初始化顺序,与在web.xml声明的顺序一致。这一点非常重要,如果使用一些mvc框架,安全框架时,如果使用Listener来完成过滤拦截的话,一定要注意Listener的声明顺序。

 2.1servlet中load-on-startup的规则说明

    

  • 标记容器是否在启动的时候就加载这个servlet。

  • 当值为0或者大于0时,表示容器在应用启动时就加载这个servlet;

  • 当是一个负数时或者没有指定时,则指示容器在该servlet被选择时才加载。

  • 正数的值越小,启动该servlet的优先级越高。

3.servlet的初始化过程

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,跳过
            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;

    }

通过源码分析,我们清楚了load-on-startup 小于0时,表示servlet不需要在启动时初始化。

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

        if (isJspServlet) {
                    //
        }
    }

通过分析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方法,主要调用servlet的init方法,完成servlet组件的初始化工作,以及触发beforeInit和afterInit事件,触发操作org.apache.catalina.InstanceListener.instanceEvent(InstanceEvent event)。


4.listener的初始化过程

  4.1 listener类结构图

    技术分享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);

    }

通过分析listenerStart方法片段,可以知道tomcat将listener分为2类,分别是eventListener和lifecycleListener两大类。并将listener加入到StandardHost中,并触发beforeContextInitialized事件,和afterContextInitialized事件。

类型Listener名称
eventListenerServletContextAttributeListener
eventListenerServletRequestAttributeListener
eventListenerServletRequestListener
eventListenerHttpSessionAttributeListener
lifecycleListenerHttpSessionListener

lifecycleListener

noPluggabilityListener

ServletContextListener


5.filter 初始化过程


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

这儿维护的filterConfigsMap,将来会在StandardWrapperValve.invoke方法中调用。而StandardWrapperValve作为servlet为pipeline模式中处理用户请求流程中的一个节点,所以也就实现了filter拦截请求的目的。

    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构造函数

    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();
        //构造filter对象
        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);//调用filter的初始化
            } finally {
                String capturedlog = SystemLogHandler.stopCapture();
                if (capturedlog != null && capturedlog.length() > 0) {
                    getServletContext().log(capturedlog);
                }
            }
        } else {
            filter.init(this);
        }
        
        // Expose filter via JMX
        registerJMX();
    }


最后,组件已经初始化了。现在理想当然应该可以处理用户请求了。都知道我们用户请求信息都在HttpServletRequest对象中。那么用户的 HTTP socket流信息,怎么一步步转换成HttpServletRequest对象的呢。下次详聊。


本文出自 “简单” 博客,请务必保留此出处http://dba10g.blog.51cto.com/764602/1775827

以上是关于死磕Tomcat7源码之二:web组件初始化的主要内容,如果未能解决你的问题,请参考以下文章

preact 源码学习系列之二:组件的渲染与更新

vuejs和webpack项目(VueComponent)初尝试——瀑布流组件

Spring MVC源码 ----- 启动过程与组件初始化

死磕Spring源码系列

tomcat学习系列---tomcat事件处理机制

flume-ng源码分析-整体架构之二常用架构篇