Tomcat源码分析(四):ServletContext应用启动之核心组件初始化

概述

  • 由我的上篇文章Tomcat源码分析(三):ServletContext应用启动之配置解析可知,在Tomcat启动应用StandardContext时,是先通过listener事件机制的方式,交给ContextConfig,解析web.xml类来获取应用的配置信息,包含过滤器filter,监听器listener,如spring的ContextLoaderListener,servlet,如spring的DispatchServlet,以及session配置等。但是配置解析这边只是创建一个包装类,如过滤器的FilterDef,servlet的StandardWrapper,获取这些组件对应的类的全限定名以及init params等数据保存到包装类中,而不进行实际类对象的创建。

  • 其次在Servlet3.0之后,提供了WebApplicationInitializer接口来支持以编程方式配置web.xml的内容,故在ContextConfig中也会查找应用中的WebApplicationInitializer的实例类,然后保存到StandardContext的initializers中。注意在ContextConfig并不会对WebApplicationInitializer进行解析,即调用其onStartup方法,而是留在StardardContext中执行。

    /**
     * The ordered set of ServletContainerInitializers for this web application.
     */
     // map的value为WebApplicationInitializer的集合
    private Map<ServletContainerInitializer,Set<Class<?>>> initializers =
        new LinkedHashMap<>();
    
  • 具体以上对象实例的创建,如servlet, filter,listener等,是之后在StandardContext中继续完成。即在StandardContext的startInternal方法中,以如下顺序继续完成启动和相关类对象实例的创建。

1. context初始化参数加载

  • context初始化参数主要包括两种,一种是在web.xml(或者WebApplicationInitializer,以下提到web.xml的类似)配置context-param设置,如下:

    <context-param>  
        <param-name>contextConfigLocation</param-name>  
        <param-value>classpath:spring/applicationContext.xml</param-value>  
    </context-param>
    
  • 另外一种则是在Server.xml的Context节点下面的Parameter节点配置,或者在Context.xml中配置,这些是提供某个param默认值的方式,可以被web.xml中的覆盖。然后在Digester解析规则会添加Parameter解析器,具体为在ContextRuleSet中定义,如下:

    digester.addObjectCreate(prefix + "Context/Parameter",
                             "org.apache.tomcat.util.descriptor.web.ApplicationParameter");
    digester.addSetProperties(prefix + "Context/Parameter");
    digester.addSetNext(prefix + "Context/Parameter",
                        "addApplicationParameter",
                        "org.apache.tomcat.util.descriptor.web.ApplicationParameter");
    
  • 在ContextConfig中已经解析好了在web.xml中配置的param,然后接着在StandardContext中,将以上两种方式获取的param填充到servletContext中:

    /**
     * Merge the context initialization parameters specified in the application
     * deployment descriptor with the application parameters described in the
     * server configuration, respecting the <code>override</code> property of
     * the application parameters appropriately.
     */
    private void mergeParameters() {
        Map<String,String> mergedParams = new HashMap<>();
        
        // web.xml
        String names[] = findParameters();
        for (int i = 0; i < names.length; i++) {
            mergedParams.put(names[i], findParameter(names[i]));
        }
    
        // Parameter节点
        ApplicationParameter params[] = findApplicationParameters();
        for (int i = 0; i < params.length; i++) {
            if (params[i].getOverride()) {
                if (mergedParams.get(params[i].getName()) == null) {
                    mergedParams.put(params[i].getName(),
                            params[i].getValue());
                }
            } else {
                mergedParams.put(params[i].getName(), params[i].getValue());
            }
        }
    
        // 填充到ServletContext中
        ServletContext sc = getServletContext();
        for (Map.Entry<String,String> entry : mergedParams.entrySet()) {
            sc.setInitParameter(entry.getKey(), entry.getValue());
        }
    
    }
    

2. SCI(ServletContainerInitializers)启动onStartup

  • 在ContextConfig查找到应用中的WebApplicationInitializer实现类中,填充保存在StandardContext的initializers集合中,然后在这步调用WebApplicationInitializer的onStartup方法,获取编程方式的应用配置信息。
    // Call ServletContainerInitializers
    for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry :
        initializers.entrySet()) {
        try {
        
        // 在spring中,则是遍历WebApplicationInitializer集合,
        // 即entry.getValue(),
        // 调用WebApplicationInitializer的onStartup方法;
        // getServletContext()为传递当前应用的servletContext
        
            entry.getKey().onStartup(entry.getValue(),
                    getServletContext());
        } catch (ServletException e) {
            log.error(sm.getString("standardContext.sciFail"), e);
            ok = false;
            break;
        }
    }
    

3. Listener监听器配置与调用

  • 这些一般是应用在web.xml和WebApplicationInitializer中配置的监听器,而不是Tomcat自身添加的监听器,如ContextConfig是Tomcat自身使用Digester解析server.xml时添加的监听器,用于解析配置文件。而这里说的监听器是应用在web.xml或者WebApplicationInitializer中,即配置文件中添加的,类型包括ServletContextListener,HttpSessionListener,ServletRequestListener等,如spring的ContextLoaderListener就是一个ServletContextListener接口的实现类。

  • 实现在StandardContext的listenerStart方法,核心实现如下:即包括:

    1. EventListener和LifeCycleListener的创建
    2. 生命周期监听器LifeCycleListener的调用
    /**
     * 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() {
    
        ...
    
        // 1. 创建listener对象实例
        // Instantiate the required listeners
        String listeners[] = findApplicationListeners();
        Object results[] = new Object[listeners.length];
        boolean ok = true;
        for (int i = 0; i < results.length; i++) {
            
            ...
            
            String listener = listeners[i];
            results[i] = getInstanceManager().newInstance(listener);
            
            ...
        }
        
        ...
    
        // 2. 分类:分为EventListener和LifeCycleListener,
        // 并保存到StandardContext中
        // EventListener是在应用运行过程中调用的
        // LifetCycleListener是应用启动,关闭等中调用的
        
        // Sort listeners in two arrays
        ArrayList<Object> eventListeners = new ArrayList<>();
        ArrayList<Object> 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 HttpSessionIdListener)
                || (results[i] instanceof HttpSessionAttributeListener)) {
                eventListeners.add(results[i]);
            }
            if ((results[i] instanceof ServletContextListener)
                || (results[i] instanceof HttpSessionListener)) {
                lifecycleListeners.add(results[i]);
            }
        }
    
        // Listener instances may have been added directly to this Context by
        // ServletContextInitializers and other code via the pluggability APIs.
        // Put them these listeners after the ones defined in web.xml and/or
        // annotations then overwrite the list of instances with the new, full
        // list.
        for (Object eventListener: getApplicationEventListeners()) {
            eventListeners.add(eventListener);
        }
        setApplicationEventListeners(eventListeners.toArray());
        for (Object lifecycleListener: getApplicationLifecycleListeners()) {
            lifecycleListeners.add(lifecycleListener);
            if (lifecycleListener instanceof ServletContextListener) {
                noPluggabilityListeners.add(lifecycleListener);
            }
        }
        setApplicationLifecycleListeners(lifecycleListeners.toArray());
    
        ...
        
        // 3. 调用LifetCycleListener,处理应用启动初始化事件
        // 这里只调用LifetCycleListener,
        // 不调用EventListener
        
        // Send application start events
        // Ensure context is not null
        getServletContext();
        context.setNewServletContextListenerAllowed(false);
    
        Object instances[] = getApplicationLifecycleListeners();
        if (instances == null || instances.length == 0) {
            return ok;
        }
    
        ServletContextEvent event = new ServletContextEvent(getServletContext());
        ServletContextEvent tldEvent = null;
        if (noPluggabilityListeners.size() > 0) {
            noPluggabilityServletContext = new NoPluggabilityServletContext(getServletContext());
            tldEvent = new ServletContextEvent(noPluggabilityServletContext);
        }
        for (int i = 0; i < instances.length; i++) {
            // 只调用ServletContextListener
            // spring的ContextLoaderListener就是
            // ServletContextListener的实现类
            if (!(instances[i] instanceof ServletContextListener))
                continue;
            ServletContextListener listener =
                (ServletContextListener) instances[i];
            try {
                fireContainerEvent("beforeContextInitialized", listener);
                if (noPluggabilityListeners.contains(listener)) {
                    listener.contextInitialized(tldEvent);
                } else {
                    // 调用contextInitialized方法
                    // 可以通过event获取ServletContext
                    // spring的ContextLoaderListener
                    // 是在这里加载spring的root WebApplicationContet
                    // 并作为一个attribute填充到ServletContext中
                    
                    listener.contextInitialized(event);
                }
                fireContainerEvent("afterContextInitialized", listener);
            }
            
            ...
            
        }
        return (ok);
    }
    

4. 过滤器Filter的实例化

  • 遍历StandardContext的filterDefs集合,创建ApplicationFilterConfig,然后填充到filterConfigs中。其中在创建ApplicationFilterConfig时,会创建filter实例并进行初始化。
    /**
     * Configure and initialize the set of filters for this Context.
     * @return <code>true</code> if all filter initialization completed
     * successfully, or <code>false</code> otherwise.
     */
    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 + "'");
                }
                try {
                
                // 创建ApplicationFilterConfig实例,
                // 并放到StandardContext的filterConfigs中
                    ApplicationFilterConfig 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;
    }
    
    
    /**
     * Construct a new ApplicationFilterConfig for the specified filter
     * definition.
     *
     * @param context The context with which we are associated
     * @param filterDef Filter definition for which a FilterConfig is to be
     *  constructed
     *
     * @exception ClassCastException if the specified class does not implement
     *  the <code>javax.servlet.Filter</code> interface
     * @exception ClassNotFoundException if the filter class cannot be found
     * @exception IllegalAccessException if the filter class cannot be
     *  publicly instantiated
     * @exception InstantiationException if an exception occurs while
     *  instantiating the filter object
     * @exception ServletException if thrown by the filter's init() method
     * @throws NamingException
     * @throws InvocationTargetException
     * @throws SecurityException
     * @throws NoSuchMethodException
     * @throws IllegalArgumentException
     */
    ApplicationFilterConfig(Context context, FilterDef filterDef)
            throws ClassCastException, ClassNotFoundException, IllegalAccessException,
            InstantiationException, ServletException, InvocationTargetException, NamingException,
            IllegalArgumentException, NoSuchMethodException, SecurityException {
    
        super();
    
        this.context = context;
        this.filterDef = filterDef;
        // Allocate a new filter instance if necessary
        if (filterDef.getFilter() == null) {
            getFilter();
        } else {
            this.filter = filterDef.getFilter();
            
            // 创建filter实例
            getInstanceManager().newInstance(filter);
            // 初始化filter,如添加filter配置的param等
            initFilter();
        }
    }
    

5. Servlet的实例化

  • 主要为查找配置的Servlet中onStartup > 0的serlvet,然后加载Servlet对应的类,创建servlet实例并初始化。具体为通过StandardWrapper的load方法来完成。
    /**
     * Load and initialize all servlets marked "load on startup" in the
     * web application deployment descriptor.
     *
     * @param children Array of wrappers for all currently defined
     *  servlets (including those not declared load on startup)
     * @return <code>true</code> if load on startup was considered successful
     */
    public boolean loadOnStartup(Container children[]) {
    
        // 筛选loadOnStartup > 0的servlet
        
        // Collect "load on startup" servlets that need to be initialized
        TreeMap<Integer, ArrayList<Wrapper>> map = new TreeMap<>();
        for (int i = 0; i < children.length; i++) {
            Wrapper wrapper = (Wrapper) children[i];
            int loadOnStartup = wrapper.getLoadOnStartup();
            if (loadOnStartup < 0)
                continue;
            Integer key = Integer.valueOf(loadOnStartup);
            ArrayList<Wrapper> 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<Wrapper> list : map.values()) {
            for (Wrapper wrapper : list) {
                try {
                
                // 通过StandardWrapper的load方法来完成
                // 类加载,实例创建,实例初始化
                
                    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 and initialize an instance of this servlet, if there is not already
     * at least one initialized instance.  This can be used, for example, to
     * load servlets that are marked in the deployment descriptor to be loaded
     * at server startup time.
     * <p>
     * <b>IMPLEMENTATION NOTE</b>:  Servlets whose classnames begin with
     * <code>org.apache.catalina.</code> (so-called "container" servlets)
     * are loaded by the same classloader that loaded this class, rather than
     * the classloader for the current web application.
     * This gives such classes access to Catalina internals, which are
     * prevented for classes loaded for web applications.
     *
     * @exception ServletException if the servlet init() method threw
     *  an exception
     * @exception ServletException if some other loading problem occurs
     */
    @Override
    public synchronized void load() throws ServletException {
        instance = loadServlet();
    
        if (!instanceInitialized) {
            initServlet(instance);
        }
    
        if (isJspServlet) {
            StringBuilder oname = new StringBuilder(getDomain());
    
            oname.append(":type=JspMonitor");
    
            oname.append(getWebModuleKeyProperties());
    
            oname.append(",name=");
            oname.append(getName());
    
            oname.append(getJ2EEKeyProperties());
    
            try {
                jspMonitorON = new ObjectName(oname.toString());
                Registry.getRegistry(null, null)
                    .registerComponent(instance, jspMonitorON, null);
            } catch( Exception ex ) {
                log.info("Error registering JSP monitoring with jmx " +
                         instance);
            }
        }
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值