死磕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名称
eventListener ServletContextAttributeListener
eventListener ServletRequestAttributeListener
eventListener ServletRequestListener
eventListener HttpSessionAttributeListener
lifecycleListener HttpSessionListener

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对象的呢。下次详聊。

时间: 2024-10-26 20:51:56

死磕Tomcat7源码之二:web组件初始化的相关文章

死磕Tomcat7源码之一:解析web.xml

熟悉java web开发的同学都清楚,tomcat作为一款非常流行的servlet容器,开源,流行,配置简单,不需要赘述.个人认为,web.xml作为webapp的入口,弄清楚该文件的底层解析过程,进而可以窥探tomcat的底层工作机制,搞明白tomcat对servlert规范的实现机理. 通过本文,可以知道以下部分内容 webapp部署3种部署方式 webapp web.xml解析流程 webapp Context对象信息的生成(不包括对象的生成) 总体来说,webapp部署有三种方式:XML

死磕itchat源码--core.py

core.py文件中的Core类定义了itchat的所有接口.且,仅仅是定义了接口,全部在component包中实现重构.其用法如下表述: 缺省 源码如下: # -*- encoding: utf-8 -*- import logging import requests from . import config, storage, utils, log from .components import load_components class Core(object): """

死磕itchat源码--config.py

itchat的配置文件,源码: import os, platform # 版本及微信的url,二维码等 VERSION = '1.3.10' BASE_URL = 'https://login.weixin.qq.com' OS = platform.system() # Windows, Linux, Darwin DIR = os.getcwd() DEFAULT_QR = 'QR.png' TIMEOUT = (10, 60) # 代理配置 USER_AGENT = 'Mozilla/5

【死磕jeestie源码】类型后面三个点(String...)和数组(String[])的区别

类型后面三个点(String...),是从Java 5开始,Java语言对方法参数支持一种新写法,叫可变长度参数列表,其语法就是类型后跟...,表示此处接受的参数为0到多个Object类型的对象,或者是一个Object[]. 例如我们有一个方法叫做test(String...strings),那么你还可以写方法test(),但你不能写test(String[] strings),这样会出编译错误,系统提示出现重复的方法. 在使用的时候,对于test(String...strings),你可以直接

死磕JDK源码之LinkedList

LinkedList LinkedList底层是基于双向链表实现的 内置插入删除方法 linkFirst & linkLast unlinkFirst & unlinkLast linkBefore unlink 源码分析 1 package java.util; 2 import java.util.function.Consumer; 3 public class LinkedList<E> extends AbstractSequentialList<E> i

死磕Spring源码系列

一.Spring总体架构 1.架构图 2.SpringIOC:核心容器提供 Spring 框架的基本功能.核心容器的主要组件是 BeanFactory,它是工厂模式的实现.BeanFactory 使用控制反转(IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开. 3.SpringAOP:通过配置管理特性,Spring AOP 模块直接将面向方面的编程功能集成到了 Spring 框架中.所以,可以很容易地使 Spring 框架管理的任何对象支持 AOP.Spring AOP 模块为

死磕JDK源码之ArrayList

ArrayList即动态数组,实现了动态的添加和减少元素 RandomAccess接口 标记接口,实现RandomAccess接口的类支持快速随机访问 Cloneable接口 没有实现Cloneable接口的类调用clone方法会抛出CloneNotSupportedException Object提供的clone方法是浅度复制 Serializable接口 标记接口,实现Serializable接口的类可以被序列化 Iterable接口 实现Iterable接口的类支持for-each循环 A

[tomcat7源码学习]初始化之catalina.home和catalina.base(转)

我们在代码中为了获取某个配置文件路径下的文件经常会这么写 String tomcatPath = System.getProperty("catalina.home") + "/webapps/axis2/WEB-INF/conf/"; tomcatPath = tomcatPath.replace("/", File.separator); //使用此方法是为了区分unix系统与windows, //File.separator UNIX中为/

Spring 源码解析之HandlerAdapter源码解析(二)

Spring 源码解析之HandlerAdapter源码解析(二) 前言 看这篇之前需要有Spring 源码解析之HandlerMapping源码解析(一)这篇的基础,这篇主要是把请求流程中的调用controller流程单独拿出来了 解决上篇文章遗留的问题 getHandler(processedRequest) 这个方法是如何查找到对应处理的HandlerExecutionChain和HandlerMapping的,比如说静态资源的处理和请求的处理肯定是不同的HandlerMapping ge