WebApp源码之web组件初始化
白羽 2018-06-08 来源 :网络 阅读 1361 评论 0

摘要:本文将带你了解 WebApp源码之web组件初始化,希望本文对大家学WEBAPP有所帮助



 

了解组件初始化调用序列

 

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

 

listener的初始化过程

 

servlet的初始化过程

 

filter的初始化过程

 

1.组件初始化序列

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

 WebApp源码之web组件初始化

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

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

代码参考org.apache.catalina.core.ContainerBase.startInternal

  List results = new ArrayList();
        for (int i = 0; i < children.length; i++) {
            results.add(startStopExecutor.submit(new StartChild(children[i])));
        }
 
        boolean fail = false;
        for (Future 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之间的初始化顺序,与中的字符排序规则有关,经测试与默认排序规则相反。
· 
· 
Listener之间的初始化顺序,与在web.xml声明的顺序一致。这一点非常重要,如果使用一些mvc框架,安全框架时,如果使用Listener来完成过滤拦截的话,一定要注意Listener的声明顺序。
· 
 2.1servlet中load-on-startup的规则说明
    
o 
标记容器是否在启动的时候就加载这个servlet。
o 
o 
当值为0或者大于0时,表示容器在应用启动时就加载这个servlet;
o 
o 
当是一个负数时或者没有指定时,则指示容器在该servlet被选择时才加载。
o 
o 
正数的值越小,启动该servlet的优先级越高。
o 
3.servlet的初始化过程
org.apache.catalina.core.StandardWrapper
   
   public boolean loadOnStartup(Container children[]) {
 
        // Collect "load on startup" servlets that need to be initialized
        TreeMap map =
            new TreeMap();
        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 list = map.get(key);
            if (list == null) {
                list = new ArrayList();
                map.put(key, list);
            }
            list.add(wrapper);
        }
 
        // Load the collected "load on startup" servlets
        for (ArrayList 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类结构图
  WebApp源码之web组件初始化

    

      4.2 listenerStart方法
   
   /**
     * Configure the set of instantiated application event listeners
     * for this Context.  Return true if all listeners wre
     * initialized successfully, or false otherwise.
     */
    public boolean listenerStart() {
 
 
        // Sort listeners in two arrays
        ArrayList eventListeners = new ArrayList();
        ArrayList lifecycleListeners = new ArrayList();
        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 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();
    }

   


 

最后,组件已经初始化了。


 


本文由职坐标整理并发布,希望对同学们有所帮助。了解更多详情请关注职坐标移动开发之WebApp频道


本文由 @白羽 发布于职坐标。未经许可,禁止转载。
喜欢 | 0 不喜欢 | 0
看完这篇文章有何感觉?已经有0人表态,0%的人喜欢 快给朋友分享吧~
评论(0)
后参与评论

您输入的评论内容中包含违禁敏感词

我知道了

助您圆梦职场 匹配合适岗位
验证码手机号,获得海同独家IT培训资料
选择就业方向:
人工智能物联网
大数据开发/分析
人工智能Python
Java全栈开发
WEB前端+H5

请输入正确的手机号码

请输入正确的验证码

获取验证码

您今天的短信下发次数太多了,明天再试试吧!

提交

我们会在第一时间安排职业规划师联系您!

您也可以联系我们的职业规划师咨询:

小职老师的微信号:z_zhizuobiao
小职老师的微信号:z_zhizuobiao

版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
 沪公网安备 31011502005948号    

©2015 www.zhizuobiao.com All Rights Reserved

208小时内训课程