Spring Boot啟動過程(六):內嵌Tomcat中StandardHost、StandardContext和StandardWrapper的啟動


  看代碼有助於線上出現預料之外的事的時候,不至於心慌。。。 

  StandardEngine[Tomcat].StandardHost[localhost]的啟動與StandardEngine不在同一個線程中,它的start:

        // Start our child containers, if any
        Container children[] = findChildren();
        List<Future<Void>> results = new ArrayList<>();
        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;
            }

        }
        if (fail) {
            throw new LifecycleException(
                    sm.getString("containerBase.threadedStartFailed"));
        }

 

    private static class StartChild implements Callable<Void> {

        private Container child; public StartChild(Container child) { this.child = child; } @Override public Void call() throws LifecycleException { child.start(); return null; } }

  這個start流程中,initInternal方法是ContainerBase的代碼,還是那個初始化startStopExecutor的,線程名例如Thread[localhost-startStop-1,5,main],這次是用來初始化host的子容器的,然后是StandardHost中的startInternal方法,主要是注冊了一個errorValue,如果現有的pipeline中沒有errorvalue,則反射創建org.apache.catalina.valves.ErrorReportValve實例,並加入pipeline中,容器pipeline加入Value時會發布一個Container.ADD_VALVE_EVENT事件,與engine一樣,之后進入ContainerBase的startInternal,但是這次Realm是null不需要啟動,然后findChildren出StandardEngine[Tomcat]. StandardHost [localhost].StandardContext[],然后同樣新開個線程new StartChild,start同樣是上面的代碼,需要特別說明的是,這次before_init的事件有監聽的了,FixContextListener,DisablePersistSessionListener,MemoryLeakTrackingListener;FixContextListener監聽的處理,會加入一個用於不做用戶身份認證的安全檢查的Value:

                Context context = (Context) event.getLifecycle();
                if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) { context.setConfigured(true); } // LoginConfig is required to process @ServletSecurity // annotations if (context.getLoginConfig() == null) { context.setLoginConfig( new LoginConfig("NONE", null, null, null)); context.getPipeline().addValve(new NonLoginAuthenticator()); }

   DisablePersistSessionListener監聽只處理start事件,所以這里只判斷了一下發現不是就出去了,其實這里可以思考下,有沒有更好的辦法,讓監聽不只是廣播方式,能不能用訂閱方式,先不細想了,接着看代碼,MemoryLeakTrackingListener只監聽了after_start事件,這步同樣什么都沒做。

  於是來到了StandardContext的initInternal,它的super.initInternal又是一個startStopExecutor,ContainerBase的super.initInternal就不再說了,發送j2ee.object.created消息:

            Notification notification = new Notification("j2ee.object.created",
                    this.getObjectName(), sequenceNumber.getAndIncrement()); broadcaster.sendNotification(notification);

   Notification是EventObject的子類,代表由MBean發出的通知,MBean server發出的通知會包含發出的MBean的引用,如果MBean注冊了監聽,可以通過object name或引用獲取消息發出者,官方建議使用object name;sendNotification方法:

    /**
     * Sends a notification.
     *
     * If an {@code Executor} was specified in the constructor, it will be given one
     * task per selected listener to deliver the notification to that listener.
     *
     * @param notification The notification to send.
     */
    public void sendNotification(Notification notification) {

        if (notification == null) { return; } boolean enabled; for (ListenerInfo li : listenerList) { try { enabled = li.filter == null || li.filter.isNotificationEnabled(notification); } catch (Exception e) { if (logger.debugOn()) { logger.debug("sendNotification", e); } continue; } if (enabled) { executor.execute(new SendNotifJob(notification, li)); } } }

  發完消息就轉變狀態為初始化完成,因為監聽器是注冊在context容器上的,於是after_init事件又觸發了那三個監聽器,這一階段監聽器什么都沒處理走了下過場而已;before_start同走過場;然后StandardContext的startInternal方法,發布了個j2ee.state.starting消息object name為Tomcat:j2eeType=WebModule,name=//localhost/,J2EEApplication=none, J2EEServer=none;setConfigured(false)還沒有正確的配置;設置WebResourceRoot,WebResourceRoot提供整個應用資源處理類的各種方法,內嵌用的實現類是StandardRoot,set的過程中加了寫鎖:

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

   StandardRoot的屬性allResources:

    private final List<List<WebResourceSet>> allResources =
            new ArrayList<>();
    {
        allResources.add(preResources);
        allResources.add(mainResources);
        allResources.add(classResources);
        allResources.add(jarResources);
        allResources.add(postResources);
    }

  http://tomcat.apache.org/tomcat-8.0-doc/api/org/apache/catalina/WebResourceRoot.html有相關說明,我就不翻譯了。

  set之后就是啟動resourcesStart,initInternal執行的是StandardRoot的initInternal方法,super.initInternal中依然是那兩行代碼,register(cache, getObjectNameKeyProperties() + ",name=Cache")會發送MBeanServerNotification. REGISTRATION_NOTIFICATION通知,生成ObjectName這里cacheJmxName是Tomcat:type=WebResourceRoot,host=localhost,context=/,name=Cache;registerURLStreamHandlerFactory里面的代碼是TomcatURLStreamHandlerFactory.register()這行代碼的注釋說這是為了支持war包內的jar資源的。之后是循環上面的allResources,init里面加入的webResourceSet,但是由於全都是空的,所以等於沒執行,就不說了,回頭再仔細看看什么情況下回不為空,還是內嵌的就是空的。createMainResourceSet主要是設置個主目錄,例如/tmp/tomcat-docbase.3031819619941848514.80,然后是各種資源該放在哪個子目錄的一些設置代碼;這次資源有一個了,所以可以有一個start了,DirResourceSet的;super.initInternal()的super是AbstractFileResourceSet:

    //-------------------------------------------------------- Lifecycle methods
    @Override
    protected void initInternal() throws LifecycleException { super.initInternal(); // Is this an exploded web application? if (getWebAppMount().equals("")) { // Look for a manifest File mf = file("META-INF/MANIFEST.MF", true); if (mf != null && mf.isFile()) { try (FileInputStream fis = new FileInputStream(mf)) { setManifest(new Manifest(fis)); } catch (IOException e) { log.warn(sm.getString("dirResourceSet.manifestFail", mf.getAbsolutePath()), e); } } } }

  super.initInternal主要是對base目錄進行了一些規范化處理,規范的方法主要是UnixFileSystem中的canonicalize其中還使用ExpiringCache對路徑做了緩存,另外還有在normalize方法中對路徑中類似"\.."的部分做了處理。WebAppMount是Web應用發布資源的位置,必須以‘/’開頭,這里應該是通過它來判斷不是war包部署的模式,然后由於manifest沒找到,所以方法返回初始化完成,這個資源一路狀態變化就啟動完了。

  回到StandardRoot,接下來是processWebInfLib方法,代碼很直觀,不解釋了:

    private void processWebInfLib() {
        WebResource[] possibleJars = listResources("/WEB-INF/lib", false); for (WebResource possibleJar : possibleJars) { if (possibleJar.isFile() && possibleJar.getName().endsWith(".jar")) { createWebResourceSet(ResourceSetType.CLASSES_JAR, "/WEB-INF/classes", possibleJar.getURL(), "/"); } } }

  接下來也不解釋:

        // Need to start the newly found resources
        for (WebResourceSet classResource : classResources) {
            classResource.start();
        }

  cache.enforceObjectMaxSizeLimit是計算緩存限制的,詳細的可以參考http://tomcat.apache.org/tomcat-8.0-doc/config/resources.html,至此StandardRoot的啟動完成就只剩下改狀態了。

  回到StandardContext,因為classloader已經有了不需要new了;接着創建Rfc6265CookieProcessor類型的cookieProcessor實例,關於Rfc6265標准參考http://www.rfc-editor.org/rfc/rfc6265.txt;character set mapper因為已經初始化好了只判斷了下;工作目錄處理,先根據host和engine名生成路徑如:work/Tomcat/localhost/ROOT,結合前面的base創建目錄例如/tmp/tomcat.3726907762383543267.80/work/Tomcat/localhost/ROOT,然后初始化StandardContext中的ApplicationContext類型可繼承的全局變量context構造用參數是this(context = new ApplicationContext(this)),返回new ApplicationContextFacade(this);將上面的全路徑設置給ServletContext.TEMPDIR屬性,並將這個屬性設置為只讀:

    /**
     * Set an attribute as read only.
     */
    void setAttributeReadOnly(String name) {

        if (attributes.containsKey(name)) readOnlyAttributes.put(name, name); }

  之后是對擴展進行驗證,這里說一下,StandardContext中不管是這里的獲取資源還是之后的讀取classloader都是加了讀鎖的:

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

  catalina.useNaming用於是否開啟命名服務支持,開啟了就會注冊NamingContextListener監聽器:

        if (!dependencyCheck) {
            // do not make application available if depency 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); } }

  ClassLoader oldCCL = bindThread()里有個ThreadBindingListener,不過因為webApplicationClassLoader是null,所以等於沒執行,返回的是null,里面的邏輯還不少,命名服務也沒開ContextBindings.bindThread於是也沒執行。

  old的沒有,但是loader還是有的,到了loader的start了,主要要說的是WebappLoader的startInternal方法,classloader創建:

            classLoader = createClassLoader();
            classLoader.setResources(context.getResources());
            classLoader.setDelegate(this.delegate);

  buildClassPath的主要功能是遍歷各個層次的classloader並將其中classpath的jar拼成一個字符串,例如:/usr/lib/jvm/java-8-oracle/jre/lib/charsets.jar:/usr/lib/jvm/java-8-oracle/jre/lib/deploy.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/cldrdata.jar...,是以':'作為分隔是因為我的開發環境是linux,在windows中應該是';':

        while (loader != null) {
            if (!buildClassPath(classpath, loader)) { break; } loader = loader.getParent(); } if (delegate) { // Delegation was enabled, go back and add the webapp paths loader = getClassLoader(); if (loader != null) { buildClassPath(classpath, loader); } }

  delegate之前提過了,是會向基loader類委托的;setClassPath的最后一句:servletContext.setAttribute(Globals.CLASS_PATH_ATTR, this.classpath)。

  setPermissions方法,由於我這第一個判斷就返回了,而且看上去代碼也很直觀,我就不說了:

    private void setPermissions() {

        if (!Globals.IS_SECURITY_ENABLED) return; if (context == null) return; // Tell the class loader the root of the context ServletContext servletContext = context.getServletContext(); // Assigning permissions for the work directory File workDir = (File) servletContext.getAttribute(ServletContext.TEMPDIR); if (workDir != null) { try { String workDirPath = workDir.getCanonicalPath(); classLoader.addPermission (new FilePermission(workDirPath, "read,write")); classLoader.addPermission (new FilePermission(workDirPath + File.separator + "-", "read,write,delete")); } catch (IOException e) { // Ignore  } } for (URL url : context.getResources().getBaseUrls()) { classLoader.addPermission(url); } }

  ((Lifecycle) classLoader).start(),這個classloader是TomcatEmbeddedWebappClassLoader走的是WebappClassLoaderBase中的start方法,這里因為是內嵌的版本(我沒確認,猜測)所以也並沒有加載到東西,所以也不細說了:

    public void start() throws LifecycleException {

        state = LifecycleState.STARTING_PREP; WebResource classes = resources.getResource("/WEB-INF/classes"); if (classes.isDirectory() && classes.canRead()) { localRepositories.add(classes.getURL()); } WebResource[] jars = resources.listResources("/WEB-INF/lib"); for (WebResource jar : jars) { if (jar.getName().endsWith(".jar") && jar.isFile() && jar.canRead()) { localRepositories.add(jar.getURL()); jarModificationTimes.put( jar.getName(), Long.valueOf(jar.getLastModified())); } } state = LifecycleState.STARTED; }

  然后生成ObjectName例如:Tomcat:context=/,host=localhost,type=TomcatEmbeddedWebappClassLoader,然后注冊MBean:getMBeanServer().registerMBean( mbean, oname);WebappLoader的start就沒什么了,started之后就是設置了幾個屬性:

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

  這里的unbindThread因為前面的bind幾乎沒做什么,所以什么也沒做;接着的bindThread主要講線程與classloader做了綁定: Thread.currentThread().setContextClassLoader (webApplicationClassLoader),至於threadBindingListener.bind()由於threadBindingListener用了個空實現,所以這里什么也沒做。

  接下來用讀鎖取到Realm並start它;接下來發布configure_start事件,FixContextListener中執行了context.setConfigured(true)。

  終於到了StandardWrapper(StandardEngine[Tomcat].StandardHost[localhost].StandardContext[].StandardWrapper[default])的start了,initInternal直接就是ContainerBase的初始化startStopExecutor,startInternal方法是發了個j2ee.state.starting的消息,ObjectName是Tomcat:j2eeType=Servlet, WebModule=//localhost/, name=default, J2EEApplication=none, J2EEServer=none,然后又到ContainerBase的startInternal,然而由於它沒有子容器了,所以這里並沒有StartChild的任務產生;於是開始執行它的Value,先start它的pipeline,startInternal方法依然是StandardPipeline的,按順序start,由於到這的時候一個都沒有,所以執行的是basic的,StandardWrapperValve的initInternal中只有一句注釋:Don't register this Valve in JMX;startInternal的最后是threadStart,但由於backgroundProcessorDelay是-1所以並沒有啟動背景線程;setAvailable(0L)設置可用,它的說明 The date and time at which this servlet will become available (in milliseconds since the epoch), or zero if the servlet is available;然后發送一個消息j2ee.state.running,ObjectName是Tomcat:j2eeType=Servlet,WebModule=//localhost/,name=default,J2EEApplication=none,J2EEServer=none;

  StandardWrapper就啟動完了,回到StandardContext,start它的pipeline;與StandardWrapper的pipeline不同,它之前被注冊過NonLoginAuthenticator,它的startInternal方法定義在AuthenticatorBase,方法中設置了jaspicAppContextID(例如:Tomcat/localhost ),然后獲取上級容器也就是host的pipeline中的所有Value,並找到其中SingleSignOn類型的Value,明顯是用於單點登錄的,我這里沒有,於是又去找了上一級容器engine當然還是沒有,於是就往下走了;實例化了一個StandardSessionIdGenerator,設置安全隨機數生成算法我這里是SHA1PRNG,生成器類名為null,生成器provider也是null,然后就是下一個Value對象StandardContextValve的start,只不過它的start是標准的什么額外事都沒干,於是回到了StandardContext中。下面一段主要是執行了TomcatEmbeddedContext中的setManager方法:

    @Override
    public void setManager(Manager manager) { if (manager instanceof ManagerBase) { ((ManagerBase) manager).setSessionIdGenerator(new LazySessionIdGenerator()); } super.setManager(manager); }

  這里判斷是true,LazySessionIdGenerator整個的代碼:

class LazySessionIdGenerator extends StandardSessionIdGenerator {

    @Override
    protected void startInternal() throws LifecycleException { setState(LifecycleState.STARTING); } }

  TomcatEmbeddedContext的super.setManager(manager)的super是StandardContext,在寫鎖中執行的,spring中多數的set都是交換的方式,先set個old保存下來,然后判斷新值和old是否相同,不相同用新的並將新值綁定容器,相同直接返回;getServletContext().setAttribute(Globals.RESOURCES_ATTR, getResources())沒什么好解釋的;setNamingResources(new NamingResourcesImpl());然后init這個namingResources,NamingResourcesImpl的initInternal,在設置當前已知命名資源前設置resourceRequireExplicitRegistration用於避免時序問題,重復注冊是正常的,后面一段我不想解釋:

        for (ContextResource cr : resources.values()) {
            try { MBeanUtils.createMBean(cr); } catch (Exception e) { log.warn(sm.getString( "namingResources.mbeanCreateFail", cr.getName()), e); } } for (ContextEnvironment ce : envs.values()) { try { MBeanUtils.createMBean(ce); } catch (Exception e) { log.warn(sm.getString( "namingResources.mbeanCreateFail", ce.getName()), e); } } for (ContextResourceLink crl : resourceLinks.values()) { try { MBeanUtils.createMBean(crl); } catch (Exception e) { log.warn(sm.getString( "namingResources.mbeanCreateFail", crl.getName()), e); } }

  init之后是start,start中只發布了個configure_start事件。

  setInstanceManager(new DefaultInstanceManager(context, injectionMap, this, this.getClass().getClassLoader())),InstanceManager主要是用於創建和回收實例,然后綁定:

                getServletContext().setAttribute(
                        InstanceManager.class.getName(), getInstanceManager()); InstanceManagerBindings.bind(getLoader().getClassLoader(), getInstanceManager());

  還有:

                getServletContext().setAttribute(
                        JarScanner.class.getName(), getJarScanner());

  合並參數mergeParameters由於我這里是空的,所以什么也沒做;然后遍歷initializers並onStartup:

  先是進入到TomcatStarter的onStartup,這里又是:

            for (ServletContextInitializer initializer : this.initializers) {
                initializer.onStartup(servletContext);
            }

  先是執行:

    private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
        return new ServletContextInitializer() { @Override public void onStartup(ServletContext servletContext) throws ServletException { selfInitialize(servletContext); } }; }

  EmbeddedWebApplicationContext中的selfInitialize ,prepareEmbeddedWebApplicationContext正常情況下先打一條日志Initializing Spring embedded WebApplicationContext然后servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this)然后將this綁定servletContext,如果啟動Info級別日志,會打印類似這樣的日志:Root WebApplicationContext: initialization completed in 3150193 ms;然后new ExistingWebApplicationScopes,這玩意的注釋說它允許與非嵌入式相同的方式注冊作用域到ApplicationContextInitializer,先執行了一個靜態代碼塊:

        static {
            Set<String> scopes = new LinkedHashSet<String>(); scopes.add(WebApplicationContext.SCOPE_REQUEST);//request scopes.add(WebApplicationContext.SCOPE_SESSION);//session scopes.add(WebApplicationContext.SCOPE_GLOBAL_SESSION);//global session SCOPES = Collections.unmodifiableSet(scopes); }

  但是似乎在我這add白做了,因為構造函數中從bean工廠並沒取到Scope實例:

            this.beanFactory = beanFactory;
            for (String scopeName : SCOPES) { Scope scope = beanFactory.getRegisteredScope(scopeName); if (scope != null) { this.scopes.put(scopeName, scope); } }

   真正注冊作用域是在下一句WebApplicationContextUtils.registerWebApplicationScopes(beanFactory, getServletContext()):

        beanFactory.registerScope(WebApplicationContext.SCOPE_REQUEST, new RequestScope());
        beanFactory.registerScope(WebApplicationContext.SCOPE_SESSION, new SessionScope(false)); beanFactory.registerScope(WebApplicationContext.SCOPE_GLOBAL_SESSION, new SessionScope(true)); if (sc != null) { ServletContextScope appScope = new ServletContextScope(sc); beanFactory.registerScope(WebApplicationContext.SCOPE_APPLICATION, appScope); // Register as ServletContext attribute, for ContextCleanupListener to detect it. sc.setAttribute(ServletContextScope.class.getName(), appScope); } beanFactory.registerResolvableDependency(ServletRequest.class, new RequestObjectFactory()); beanFactory.registerResolvableDependency(ServletResponse.class, new ResponseObjectFactory()); beanFactory.registerResolvableDependency(HttpSession.class, new SessionObjectFactory()); beanFactory.registerResolvableDependency(WebRequest.class, new WebRequestObjectFactory()); if (jsfPresent) { FacesDependencyRegistrar.registerFacesDependencies(beanFactory); }

  registerResolvableDependency將類型與對應的裝配對象注冊進bean工廠。existingScopes.restore里的代碼:

        public void restore() {
            for (Map.Entry<String, Scope> entry : this.scopes.entrySet()) { if (logger.isInfoEnabled()) { logger.info("Restoring user defined scope " + entry.getKey()); } this.beanFactory.registerScope(entry.getKey(), entry.getValue()); } }

  WebApplicationContextUtils.registerEnvironmentBeans(beanFactory, getServletContext())把相應的變量key與值注冊給bean工廠,如servletContext、contextParameters和contextAttributes;從bean工廠中獲取所有org.springframework.boot.web.servlet.ServletContextInitializer類型的bean,如filterRegistrationBean和dispatcherServletRegistration然后add給ServletContextInitializerBeans實例的initializers;addAdaptableBeans方法先從bean工廠中獲取javax.servlet.MultipartConfigElement類型的對象,然而javax.servlet.Servlet沒在bean工廠里找到,所以add什么也沒做;javax.servlet.Filter找到characterEncodingFilter、hiddenHttpMethodFilter、httpPutFormContentFilter、requestContextFilter;ServletListenerRegistrationBean.getSupportedTypes()取的是ServletListenerRegistrationBean的SUPPORTED_TYPES,不過全都沒找到,所以什么也沒做:

    static {
        Set<Class<?>> types = new HashSet<Class<?>>(); types.add(ServletContextAttributeListener.class); types.add(ServletRequestListener.class); types.add(ServletRequestAttributeListener.class); types.add(HttpSessionAttributeListener.class); types.add(HttpSessionListener.class); types.add(ServletContextListener.class); SUPPORTED_TYPES = Collections.unmodifiableSet(types); }

  然后是對找到的進行排序:

        List<ServletContextInitializer> sortedInitializers = new ArrayList<ServletContextInitializer>();
        for (Map.Entry<?, List<ServletContextInitializer>> entry : this.initializers .entrySet()) { AnnotationAwareOrderComparator.sort(entry.getValue()); sortedInitializers.addAll(entry.getValue()); } this.sortedList = Collections.unmodifiableList(sortedInitializers); public static void sort(Object[] array) { if (array.length > 1) { Arrays.sort(array, INSTANCE); } } private int doCompare(Object o1, Object o2, OrderSourceProvider sourceProvider) { boolean p1 = (o1 instanceof PriorityOrdered); boolean p2 = (o2 instanceof PriorityOrdered); if (p1 && !p2) { return -1; } else if (p2 && !p1) { return 1; } // Direct evaluation instead of Integer.compareTo to avoid unnecessary object creation. int i1 = getOrder(o1, sourceProvider); int i2 = getOrder(o2, sourceProvider); return (i1 < i2) ? -1 : (i1 > i2) ? 1 : 0; }

  然后對這些初始化器進行beans.onStartup(servletContext);filterRegistrationBean執行的AbstractFilterRegistrationBean的,主要執行了這兩句:

        FilterRegistration.Dynamic added = servletContext.addFilter(name, filter);
                ...
        configure(added);

  name:characterEncodingFilter,filter:OrderedCharacterEncodingFilter,它的配置中這里設定了過濾器轉發模式有FORWARD、INCLUDE、REQUEST、ASYNC,攔截路徑:"/*";然后是hiddenHttpMethodFilter和OrderedHiddenHttpMethodFilter,httpPutFormContentFilter和OrderedHttpPutFormContentFilter,requestContextFilter和OrderedRequestContextFilter,cipherFilter和CipherFilter(我這自定義的)。ServletRegistrationBean的:dispatcherServlet和DispatcherServlet,asyncSupported是true,url映射是‘/’,設置StandardWrapper的loadOnStartup、 multipartConfigElement。

  到了下一個初始化器SessionConfiguringInitializer:

        public void onStartup(ServletContext servletContext) throws ServletException {
            if (this.session.getTrackingModes() != null) { servletContext.setSessionTrackingModes(this.session.getTrackingModes()); } configureSessionCookie(servletContext.getSessionCookieConfig()); }

  將session中的cookie信息補充進ApplicationSessionCookieConfig的實例中,例如:

                config.setName(cookie.getName());
                config.setDomain(cookie.getDomain());
                config.setPath(cookie.getPath());
                config.setComment(cookie.getComment());
                config.setHttpOnly(cookie.getHttpOnly());
                config.setSecure(cookie.getSecure());
                config.setMaxAge(cookie.getMaxAge());

  實際中我這里一個都沒執行,因為我這的session中cookie信息都是null。

  下一個初始化器InitParameterConfiguringServletContextInitializer由於參數沒有,所以進去就出來了。

  回到listenerStart,listenerStart:org.apache.tomcat.websocket.server.WsContextListener,用前面的DefaultInstanceManager的newInstance創建,然后加到lifecycleListeners中,然后傳給applicationLifecycleListenersObjects,然后是newServletContextListenerAllowed=false:當listener發生調用后不允許添加,發布beforeContextInitialized事件,然后WsContextListener的contextInitialized:

        ServletContext sc = sce.getServletContext();
        if(sc.getAttribute("javax.websocket.server.ServerContainer") == null) { WsSci.init(sce.getServletContext(), false); }

  init中先是初始化WsServerContainer:

    static {
        GET_BYTES = "GET ".getBytes(StandardCharsets.ISO_8859_1); ROOT_URI_BYTES = "/".getBytes(StandardCharsets.ISO_8859_1); HTTP_VERSION_BYTES = " HTTP/1.1\r\n".getBytes(StandardCharsets.ISO_8859_1); } static { AUTHENTICATED_HTTP_SESSION_CLOSED = new CloseReason(CloseCodes.VIOLATED_POLICY, "This connection was established under an authenticated HTTP session that has ended."); } WsServerContainer(ServletContext servletContext) { this.enforceNoAddAfterHandshake = Constants.STRICT_SPEC_COMPLIANCE; //Boolean.getBoolean("org.apache.tomcat.websocket.STRICT_SPEC_COMPLIANCE") this.addAllowed = true; this.authenticatedSessions = new ConcurrentHashMap(); this.endpointsRegistered = false; this.servletContext = servletContext;   //我這里添加了org.apache.tomcat.websocket.server和本地語言en_US(我代碼是在英文版ubuntu上跑的) this.setInstanceManager((InstanceManager)servletContext.getAttribute(InstanceManager.class.getName())); String value = servletContext.getInitParameter("org.apache.tomcat.websocket.binaryBufferSize"); if(value != null) { this.setDefaultMaxBinaryMessageBufferSize(Integer.parseInt(value)); } value = servletContext.getInitParameter("org.apache.tomcat.websocket.textBufferSize"); if(value != null) { this.setDefaultMaxTextMessageBufferSize(Integer.parseInt(value)); } 
    //Java WebSocket 規范 1.0 並不允許第一個服務端點開始 WebSocket 握手之后進行程序性部署。默認情況下,Tomcat 繼續允許額外的程序性部署。 value = servletContext.getInitParameter("org.apache.tomcat.websocket.noAddAfterHandshake"); if(value != null) { this.setEnforceNoAddAfterHandshake(Boolean.parseBoolean(value)); } Dynamic fr = servletContext.addFilter("Tomcat WebSocket (JSR356) Filter", new WsFilter()); fr.setAsyncSupported(true); EnumSet types = EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD); fr.addMappingForUrlPatterns(types, true, new String[]{"/*"}); }

 

  init創建了 WsServerContainer之后,將它設置給servletContext的javax.websocket.server.ServerContainer屬性,然后servletContext.addListener(new WsSessionListener(sc))加進前面的applicationLifecycleListenersObjects中,init結束,回到StandardContext發布afterContextInitialized事件,我這到這里listenerStart結束。

  checkConstraintsForUncoveredMethods(findConstraints())因為我這里find出來的並沒有,所以pass;start StandardManager startInternal先是super(ManagerBase),一進方法先是將兩個雙端隊列sessionCreationTiming和sessionExpirationTiming根據常量TIMING_STATS_CACHE_SIZE用null填滿,設置jvmRoute(jvmRoute用於區分多tomcat節點,根據jvmRoute的值來確定當前會話屬於哪個節點 ),從engine上取得,之前設置過,getEngine:

    public Engine getEngine() {
        Engine e = null; for (Container c = getContext(); e == null && c != null ; c = c.getParent()) { if (c instanceof Engine) { e = (Engine)c; } } return e; }

  set給sessionIdGenerator,將之前初始化過的一些sessionIdGenerator值set給新new的SessionIdGeneratorBase,然后start之前的sessionIdGenerator,這個start沒做什么特別的,於是回到StandardManager,加載文件(例:/tmp/tomcat.7550276477249965168.80/work/Tomcat/localhost/ROOT/SESSIONS.ser),用於session持久化的,這時候找不到的。

  filterStart對filterConfigs同步鎖,filterConfigs.put(name, filterConfig):

  loadOnStartup(findChildren()),其實都一起start過了就不用了:

 

   該啟動StandardContext的后天線程了super.threadStart(),當然因為backgroundProcessorDelay所以也沒啟,unbindThread說是解綁,其實只是把classloader還原了,別的沒做什么,對應着之前的bind。

  設置StandardContext的startTime=System.currentTimeMillis(),發j2ee.state.running的通知,ObjectName是Tomcat:J2EEApplication=none, J2EEServer=none, j2eeType=WebModule, name=//localhost/;getResources().gc()因為WebResources引用了一些jar,有些平台可能會對jar加鎖,這里先清理,但實際上這里的實現是空的。

   DisablePersistSessionListener由於並沒有配置session持久化,所以會觸發這個監聽器,實際只執行了((StandardManager) manager).setPathname(null)。MemoryLeakTrackingListener只走了個過場。

   發布after_start事件,這回終於執行了MemoryLeakTrackingListener:

            if (event.getType().equals(Lifecycle.AFTER_START_EVENT)) {
                if (event.getSource() instanceof Context) { Context context = ((Context) event.getSource()); childClassLoaders.put(context.getLoader().getClassLoader(), context.getServletContext().getContextPath()); } }

  子容器就啟動完成了。

==========================================================

咱最近用的github:https://github.com/saaavsaaa

微信公眾號:

                      

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM