Tomcat源码分析(十) -- Context

215 阅读5分钟

Tomcat 中的 Context

  • Context 定义内容太多代码就不贴了,自己看源码
  • 从 Context 的方法可以看出很多都是关于 web 方面的
  • Context 实现了 Container 接口和 ContextBind 接口
  • Context 的实现类是 StandardContext

StandardContext 的构造方法

public StandardContext() {

    super();
    //为 pipeline 设置基础阀 StandardContextValve
    pipeline.setBasic(new StandardContextValve());
    broadcaster = new NotificationBroadcasterSupport();
    // Set defaults
    //这里默认false 表示支持jsp
    if (!Globals.STRICT_SERVLET_COMPLIANCE) {
        // Strict servlet compliance requires all extension mapped servlets
        // to be checked against welcome files
        resourceOnlyServlets.add("jsp");
    }
}

StandardContext 的 initInternal 方法

protected void initInternal() throws LifecycleException {
    super.initInternal();

    // Register the naming resources
    if (namingResources != null) {
        //初始化命名资源
        namingResources.init();
    }

    // Send j2ee.object.created notification
    if (this.getObjectName() != null) {
        Notification notification = new Notification("j2ee.object.created",
                                                     this.getObjectName(), sequenceNumber.getAndIncrement());
        //发送广播通知
        broadcaster.sendNotification(notification);
    }
}

Standard 的 startInternal 方法

protected synchronized void startInternal() throws LifecycleException {

    if(log.isDebugEnabled())
        log.debug("Starting " + getBaseName());

    // Send j2ee.state.starting notification
    if (this.getObjectName() != null) {
        Notification notification = new Notification("j2ee.state.starting",
                                                     this.getObjectName(), sequenceNumber.getAndIncrement());
        broadcaster.sendNotification(notification);
    }

    setConfigured(false);
    boolean ok = true;

    // Currently this is effectively a NO-OP but needs to be called to
    // ensure the NamingResources follows the correct lifecycle
    if (namingResources != null) {
        namingResources.start();
    }

    // Post work directory
    //发布工作目录
    postWorkDirectory();

    // Add missing components as necessary
    if (getResources() == null) {   // (1) Required by Loader
        if (log.isDebugEnabled())
            log.debug("Configuring default Resources");

        try {
            setResources(new StandardRoot(this));
        } catch (IllegalArgumentException e) {
            log.error(sm.getString("standardContext.resourcesInit"), e);
            ok = false;
        }
    }
    if (ok) {
        resourcesStart();
    }

    if (getLoader() == null) {
        WebappLoader webappLoader = new WebappLoader();
        webappLoader.setDelegate(getDelegate());
        setLoader(webappLoader);
    }

    // An explicit cookie processor hasn't been specified; use the default
    if (cookieProcessor == null) {
        cookieProcessor = new Rfc6265CookieProcessor();
    }

    // Initialize character set mapper
    getCharsetMapper();

    // Validate required extensions
    boolean dependencyCheck = true;
    try {
        dependencyCheck = ExtensionValidator.validateApplication
            (getResources(), this);
    } catch (IOException ioe) {
        log.error(sm.getString("standardContext.extensionValidationError"), ioe);
        dependencyCheck = false;
    }

    if (!dependencyCheck) {
        // do not make application available if dependency check fails
        ok = false;
    }

    // Reading the "catalina.useNaming" environment variable
    String useNamingProperty = System.getProperty("catalina.useNaming");
    if ((useNamingProperty != null)
        && (useNamingProperty.equals("false"))) {
        useNaming = false;
    }

    if (ok && isUseNaming()) {
        if (getNamingContextListener() == null) {
            NamingContextListener ncl = new NamingContextListener();
            ncl.setName(getNamingContextName());
            ncl.setExceptionOnFailedWrite(getJndiExceptionOnFailedWrite());
            addLifecycleListener(ncl);
            setNamingContextListener(ncl);
        }
    }

    // Standard container startup
    if (log.isDebugEnabled())
        log.debug("Processing standard container startup");


    // Binding thread
    ClassLoader oldCCL = bindThread();

    try {
        if (ok) {
            // Start our subordinate components, if any
            Loader loader = getLoader();
            if (loader instanceof Lifecycle) {
                ((Lifecycle) loader).start();
            }

            // since the loader just started, the webapp classloader is now
            // created.
            setClassLoaderProperty("clearReferencesRmiTargets",
                                   getClearReferencesRmiTargets());
            setClassLoaderProperty("clearReferencesStopThreads",
                                   getClearReferencesStopThreads());
            setClassLoaderProperty("clearReferencesStopTimerThreads",
                                   getClearReferencesStopTimerThreads());
            setClassLoaderProperty("clearReferencesHttpClientKeepAliveThread",
                                   getClearReferencesHttpClientKeepAliveThread());
            setClassLoaderProperty("clearReferencesObjectStreamClassCaches",
                                   getClearReferencesObjectStreamClassCaches());
            setClassLoaderProperty("clearReferencesThreadLocals",
                                   getClearReferencesThreadLocals());

            // By calling unbindThread and bindThread in a row, we setup the
            // current Thread CCL to be the webapp classloader
            unbindThread(oldCCL);
            oldCCL = bindThread();

            // Initialize logger again. Other components might have used it
            // too early, so it should be reset.
            logger = null;
            getLogger();

            Realm realm = getRealmInternal();
            if(null != realm) {
                if (realm instanceof Lifecycle) {
                    ((Lifecycle) realm).start();
                }

                // Place the CredentialHandler into the ServletContext so
                // applications can have access to it. Wrap it in a "safe"
                // handler so application's can't modify it.
                CredentialHandler safeHandler = new CredentialHandler() {
                    @Override
                    public boolean matches(String inputCredentials, String storedCredentials) {
                        return getRealmInternal().getCredentialHandler().matches(inputCredentials, storedCredentials);
                    }

                    @Override
                    public String mutate(String inputCredentials) {
                        return getRealmInternal().getCredentialHandler().mutate(inputCredentials);
                    }
                };
                context.setAttribute(Globals.CREDENTIAL_HANDLER, safeHandler);
            }

            // Notify our interested LifecycleListeners
            fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);

            // Start our child containers, if not already started
            for (Container child : findChildren()) {
                if (!child.getState().isAvailable()) {
                    //调用 Wrapper 的 start 方法
                    child.start();
                }
            }

            // Start the Valves in our pipeline (including the basic),
            // if any
            if (pipeline instanceof Lifecycle) {
                //调用 pipeline 的 start 方法
                ((Lifecycle) pipeline).start();
            }

            // Acquire clustered manager
            Manager contextManager = null;
            Manager manager = getManager();
            if (manager == null) {
                if (log.isDebugEnabled()) {
                    log.debug(sm.getString("standardContext.cluster.noManager",
                                           Boolean.valueOf((getCluster() != null)),
                                           Boolean.valueOf(distributable)));
                }
                if ((getCluster() != null) && distributable) {
                    try {
                        contextManager = getCluster().createManager(getName());
                    } catch (Exception ex) {
                        log.error("standardContext.clusterFail", ex);
                        ok = false;
                    }
                } else {
                    contextManager = new StandardManager();
                }
            }

            // Configure default manager if none was specified
            if (contextManager != null) {
                if (log.isDebugEnabled()) {
                    log.debug(sm.getString("standardContext.manager",
                                           contextManager.getClass().getName()));
                }
                setManager(contextManager);
            }

            if (manager!=null && (getCluster() != null) && distributable) {
                //let the cluster know that there is a context that is distributable
                //and that it has its own manager
                getCluster().registerManager(manager);
            }
        }

        if (!getConfigured()) {
            log.error(sm.getString("standardContext.configurationFail"));
            ok = false;
        }

        // We put the resources into the servlet context
        if (ok) {
            getServletContext().setAttribute
                (Globals.RESOURCES_ATTR, getResources());

            if (getInstanceManager() == null) {
                javax.naming.Context context = null;
                if (isUseNaming() && getNamingContextListener() != null) {
                    context = getNamingContextListener().getEnvContext();
                }
                Map<String, Map<String, String>> injectionMap = buildInjectionMap(
                    getIgnoreAnnotations() ? new NamingResourcesImpl(): getNamingResources());
                setInstanceManager(new DefaultInstanceManager(context,
                                                              injectionMap, this, this.getClass().getClassLoader()));
            }
            getServletContext().setAttribute(
                InstanceManager.class.getName(), getInstanceManager());
            InstanceManagerBindings.bind(getLoader().getClassLoader(), getInstanceManager());

            // Create context attributes that will be required
            getServletContext().setAttribute(
                JarScanner.class.getName(), getJarScanner());

            // Make the version info available
            getServletContext().setAttribute(Globals.WEBAPP_VERSION, getWebappVersion());
        }

        // Set up the context init params
        mergeParameters();

        // Call ServletContainerInitializers
        for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry :
             initializers.entrySet()) {
            try {
                entry.getKey().onStartup(entry.getValue(),
                                         getServletContext());
            } catch (ServletException e) {
                log.error(sm.getString("standardContext.sciFail"), e);
                ok = false;
                break;
            }
        }

        // Configure and call application event listeners
        if (ok) {
            if (!listenerStart()) {
                log.error(sm.getString("standardContext.listenerFail"));
                ok = false;
            }
        }

        // Check constraints for uncovered HTTP methods
        // Needs to be after SCIs and listeners as they may programmatically
        // change constraints
        if (ok) {
            checkConstraintsForUncoveredMethods(findConstraints());
        }

        try {
            // Start manager
            Manager manager = getManager();
            if (manager instanceof Lifecycle) {
                ((Lifecycle) manager).start();
            }
        } catch(Exception e) {
            log.error(sm.getString("standardContext.managerFail"), e);
            ok = false;
        }

        // Configure and call application filters
        if (ok) {
            if (!filterStart()) {
                log.error(sm.getString("standardContext.filterFail"));
                ok = false;
            }
        }

        // Load and initialize all "load on startup" servlets
        if (ok) {
            if (!loadOnStartup(findChildren())){
                log.error(sm.getString("standardContext.servletFail"));
                ok = false;
            }
        }

        // Start ContainerBackgroundProcessor thread
        super.threadStart();
    } finally {
        // Unbinding thread
        unbindThread(oldCCL);
    }

    // Set available status depending upon startup success
    if (ok) {
        if (log.isDebugEnabled())
            log.debug("Starting completed");
    } else {
        log.error(sm.getString("standardContext.startFailed", getName()));
    }

    startTime=System.currentTimeMillis();

    // Send j2ee.state.running notification
    if (ok && (this.getObjectName() != null)) {
        Notification notification =
            new Notification("j2ee.state.running", this.getObjectName(),
                             sequenceNumber.getAndIncrement());
        broadcaster.sendNotification(notification);
    }

    // The WebResources implementation caches references to JAR files. On
    // some platforms these references may lock the JAR files. Since web
    // application start is likely to have read from lots of JARs, trigger
    // a clean-up now.
    getResources().gc();

    // Reinitializing if something went wrong
    if (!ok) {
        setState(LifecycleState.FAILED);
    } else {
        setState(LifecycleState.STARTING);
    }
}

StandardContext 的 stopInternal 方法

protected synchronized void stopInternal() throws LifecycleException {

    // Send j2ee.state.stopping notification
    if (this.getObjectName() != null) {
        Notification notification =
            new Notification("j2ee.state.stopping", this.getObjectName(),
                             sequenceNumber.getAndIncrement());
        broadcaster.sendNotification(notification);
    }

    // Context has been removed from Mapper at this point (so no new
    // requests will be mapped) but is still available.

    // Give the in progress async requests a chance to complete
    long limit = System.currentTimeMillis() + unloadDelay;
    while (inProgressAsyncCount.get() > 0 && System.currentTimeMillis() < limit) {
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            log.info(sm.getString("standardContext.stop.asyncWaitInterrupted"), e);
            break;
        }
    }

    // Once the state is set to STOPPING, the Context will report itself as
    // not available and any in progress async requests will timeout
    setState(LifecycleState.STOPPING);

    // Binding thread
    ClassLoader oldCCL = bindThread();

    try {
        // Stop our child containers, if any
        final Container[] children = findChildren();

        // Stop ContainerBackgroundProcessor thread
        threadStop();

        for (int i = 0; i < children.length; i++) {
            //调用子容器的stop方法
            children[i].stop();
        }

        // Stop our filters
        filterStop();

        Manager manager = getManager();
        if (manager instanceof Lifecycle && ((Lifecycle) manager).getState().isAvailable()) {
            ((Lifecycle) manager).stop();
        }

        // Stop our application listeners
        listenerStop();

        // Finalize our character set mapper
        setCharsetMapper(null);

        // Normal container shutdown processing
        if (log.isDebugEnabled())
            log.debug("Processing standard container shutdown");

        // JNDI resources are unbound in CONFIGURE_STOP_EVENT so stop
        // naming resources before they are unbound since NamingResources
        // does a JNDI lookup to retrieve the resource. This needs to be
        // after the application has finished with the resource
        if (namingResources != null) {
            namingResources.stop();
        }

        fireLifecycleEvent(Lifecycle.CONFIGURE_STOP_EVENT, null);

        // Stop the Valves in our pipeline (including the basic), if any
        if (pipeline instanceof Lifecycle &&
            ((Lifecycle) pipeline).getState().isAvailable()) {
            ((Lifecycle) pipeline).stop();
        }

        // Clear all application-originated servlet context attributes
        if (context != null)
            context.clearAttributes();

        Realm realm = getRealmInternal();
        if (realm instanceof Lifecycle) {
            ((Lifecycle) realm).stop();
        }
        Loader loader = getLoader();
        if (loader instanceof Lifecycle) {
            ClassLoader classLoader = loader.getClassLoader();
            ((Lifecycle) loader).stop();
            if (classLoader != null) {
                InstanceManagerBindings.unbind(classLoader);
            }
        }

        // Stop resources
        resourcesStop();

    } finally {

        // Unbinding thread
        unbindThread(oldCCL);

    }

    // Send j2ee.state.stopped notification
    if (this.getObjectName() != null) {
        Notification notification =
            new Notification("j2ee.state.stopped", this.getObjectName(),
                             sequenceNumber.getAndIncrement());
        broadcaster.sendNotification(notification);
    }

    // Reset application context
    context = null;

    // This object will no longer be visible or used.
    try {
        resetContext();
    } catch( Exception ex ) {
        log.error( "Error resetting context " + this + " " + ex, ex );
    }

    //reset the instance manager
    setInstanceManager(null);

    if (log.isDebugEnabled())
        log.debug("Stopping complete");

}

StandardContext 的 destroyInternal 方法

protected void destroyInternal() throws LifecycleException {

    // If in state NEW when destroy is called, the object name will never
    // have been set so the notification can't be created
    if (getObjectName() != null) {
        // Send j2ee.object.deleted notification
        Notification notification =
            new Notification("j2ee.object.deleted", this.getObjectName(),
                             sequenceNumber.getAndIncrement());
        broadcaster.sendNotification(notification);
    }

    if (namingResources != null) {
        namingResources.destroy();
    }

    Loader loader = getLoader();
    if (loader instanceof Lifecycle) {
        ((Lifecycle) loader).destroy();
    }

    Manager manager = getManager();
    if (manager instanceof Lifecycle) {
        ((Lifecycle) manager).destroy();
    }

    if (resources != null) {
        resources.destroy();
    }

    super.destroyInternal();
}

StandardContext 中的基础阀 -- StandardContextValve

public final void invoke(Request request, Response response)
        throws IOException, ServletException {

    // Disallow any direct access to resources under WEB-INF or META-INF
    //禁止直接方法  WEB-INF 或者 META-INF 下的资源
    MessageBytes requestPathMB = request.getRequestPathMB();
    if ((requestPathMB.startsWithIgnoreCase("/META-INF/", 0))
        || (requestPathMB.equalsIgnoreCase("/META-INF"))
        || (requestPathMB.startsWithIgnoreCase("/WEB-INF/", 0))
        || (requestPathMB.equalsIgnoreCase("/WEB-INF"))) {
        response.sendError(HttpServletResponse.SC_NOT_FOUND);
        return;
    }

    // Select the Wrapper to be used for this Request
    //从 request 对象获取 Wrapper 对象
    Wrapper wrapper = request.getWrapper();
    if (wrapper == null || wrapper.isUnavailable()) {
        response.sendError(HttpServletResponse.SC_NOT_FOUND);
        return;
    }

    // Acknowledge the request
    try {
        response.sendAcknowledgement();
    } catch (IOException ioe) {
        container.getLogger().error(sm.getString(
            "standardContextValve.acknowledgeException"), ioe);
        request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, ioe);
        response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        return;
    }

    if (request.isAsyncSupported()) {
        request.setAsyncSupported(wrapper.getPipeline().isAsyncSupported());
    }
    //调用 Wrapper 对象的pipeline
    wrapper.getPipeline().getFirst().invoke(request, response);
}

小结

  • Context 中有大量的 web 方面的方法,这里只看启动流程