用户工具

站点工具


about_jetty_3

Jetty的那些事儿 第三章 webapp初始化

作者:陈科

联系方式:chenke1818@gmail.com

转载请说明出处:http://www.dumpcache.com/wiki/doku.php?id=about_jetty_3

经过前面的初始化之后,所有的启动入口都是从org.eclipse.jetty.server.Server的doStart方法开始的。

我们先来看下组件整体的调用时序图:

1.启动DeploymentManager

在jetty-deploy.xml中配置了:

<Configure id="Server" class="org.eclipse.jetty.server.Server">
 16 
 17     <Call name="addBean">
 18       <Arg>
 19         <New id="DeploymentManager" class="org.eclipse.jetty.deploy.DeploymentManager">
 20           <Set name="contexts">
 21             <Ref id="Contexts" />
 22           </Set>
 23           <Call name="setContextAttribute">
 24             <Arg>org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern</Arg>
 25             <Arg>.*/servlet-api-[^/]*\.jar$</Arg>
 26           </Call>

Server的父类为:AggregateLifeCycle,其doStart方法为:

protected void doStart() throws Exception
    {
        for (Bean b:_beans)
        {
            if (b._managed && b._bean instanceof LifeCycle)
            {
                LifeCycle l=(LifeCycle)b._bean;
                if (!l.isRunning())
                    l.start();
            }
        }

org.eclipse.jetty.deploy.DeploymentManager作为其中一个bean启动了:

protected void doStart() throws Exception
    {
        if (_useStandardBindings)
        {
            LOG.debug("DeploymentManager using standard bindings");
            addLifeCycleBinding(new StandardDeployer());
            addLifeCycleBinding(new StandardStarter());
            addLifeCycleBinding(new StandardStopper());
            addLifeCycleBinding(new StandardUndeployer());
        }

        // Start all of the AppProviders
        for (AppProvider provider : _providers)
        {
            startAppProvider(provider);
        }
        super.doStart();
    }

又因为在jetty-webapp.xml中配置了:

<Configure id="Server" class="org.eclipse.jetty.server.Server">
 13     <Ref id="DeploymentManager">
 14           <Call id="webappprovider" name="addAppProvider">
 15             <Arg>
 16               <New class="org.eclipse.jetty.deploy.providers.WebAppProvider">
 17                 <Set name="monitoredDirName"><Property name="jetty.home" default="." />/webapps</Set>
 18                 <Set name="defaultsDescriptor"><Property name="jetty.home" default="."/>/etc/webdefault.xml</Set>
 19                 <Set name="scanInterval">1</Set>
 20                 <Set name="contextXmlDir"><Property name="jetty.home" default="." />/contexts</Set>
 21         <Set name="extractWars">true</Set>
 22               </New>
 23             </Arg>
 24           </Call>
 25     </Ref>
 26 </Configure>

2.扫描war包

WebappProvider启动了Scanner针对webapp目录进行war包的扫描工作:

protected void doStart() throws Exception
    {
        if (LOG.isDebugEnabled()) 
            LOG.debug(this.getClass().getSimpleName() + ".doStart()");
        if (_monitoredDir == null)
        {
            throw new IllegalStateException("No configuration dir specified");
        }

        File scandir = _monitoredDir.getFile();
        LOG.info("Deployment monitor " + scandir + " at interval " + _scanInterval);
        _scanner = new Scanner();
        _scanner.setScanDirs(Collections.singletonList(scandir));
        _scanner.setScanInterval(_scanInterval);
        _scanner.setRecursive(_recursive);
        _scanner.setFilenameFilter(_filenameFilter);
        _scanner.setReportDirs(true);
        _scanner.addListener(_scannerListener);
        _scanner.start();
    }

上面的_scannerListenner很重要,扫描完后根据文件的状态将会调用:

private final Scanner.DiscreteListener _scannerListener = new Scanner.DiscreteListener()
    {
        public void fileAdded(String filename) throws Exception
        {
            ScanningAppProvider.this.fileAdded(filename);
        }

        public void fileChanged(String filename) throws Exception
        {
            ScanningAppProvider.this.fileChanged(filename);
        }

        public void fileRemoved(String filename) throws Exception
        {
            ScanningAppProvider.this.fileRemoved(filename);
        }
    };

扫描进行了2次:

public synchronized void doStart()
    {
        if (_running)
            return;

        _running = true;

        if (_reportExisting)
        {
            // if files exist at startup, report them
            scan();
            scan(); // scan twice so files reported as stable
        }
        else
        {
            //just register the list of existing files and only report changes
            scanFiles();
            _prevScan.putAll(_currentScan);
        }
        schedule();
    }


public synchronized void scan ()
    {
        reportScanStart(++_scanCount);
        scanFiles();
        reportDifferences(_currentScan, _prevScan);
        _prevScan.clear();
        _prevScan.putAll(_currentScan);
        reportScanEnd(_scanCount);
        
        for (Listener l : _listeners)
        {
            try
            {
                if (l instanceof ScanListener)
                    ((ScanListener)l).scan();
            }
            catch (Exception e)
            {
                LOG.warn(e);
            }
            catch (Error e)
            {
                LOG.warn(e);
            }
        }
    }

扫描完后,调用了上面的listener方法: 比如add:

protected void fileAdded(String filename) throws Exception
    {
        if (LOG.isDebugEnabled()) 
            LOG.debug("added {}",filename);
        App app = ScanningAppProvider.this.createApp(filename);
        if (app != null)
        {
            _appMap.put(filename,app);
            _deploymentManager.addApp(app);
        }
    }

3.创建app

这里创建了app:

public void addApp(App app)
    {
        LOG.info("Deployable added: " + app.getOriginId());
        AppEntry entry = new AppEntry();
        entry.app = app;
        entry.setLifeCycleNode(_lifecycle.getNodeByName("undeployed"));
        _apps.add(entry);

        if (isRunning() && _defaultLifeCycleGoal != null)
        {
            // Immediately attempt to go to default lifecycle state
            this.requestAppGoal(entry,_defaultLifeCycleGoal);
        }
    }
    

3.1状态转换

接着进行状态迁移变化(状态转换图):

public AppLifeCycle()
    {
        // Define Default Graph

        // undeployed -> deployed
        addEdge(UNDEPLOYED,DEPLOYING);
        addEdge(DEPLOYING,DEPLOYED);

        // deployed -> started
        addEdge(DEPLOYED,STARTING);
        addEdge(STARTING,STARTED);

        // started -> deployed
        addEdge(STARTED,STOPPING);
        addEdge(STOPPING,DEPLOYED);

        // deployed -> undeployed
        addEdge(DEPLOYED,UNDEPLOYING);
        addEdge(UNDEPLOYING,UNDEPLOYED);
    }):

requestAppGoal根据目标节点获取转换路径,并进行状态转换:

private void requestAppGoal(AppEntry appentry, String nodeName)
    {
        Node destinationNode = _lifecycle.getNodeByName(nodeName);
        if (destinationNode == null)
        {
            throw new IllegalStateException("Node not present in Deployment Manager: " + nodeName);
        }
        // Compute lifecycle steps
        Path path = _lifecycle.getPath(appentry.lifecyleNode,destinationNode);
        if (path.isEmpty())
        {
            // nothing to do. already there.
            return;
        }

        // Execute each Node binding.  Stopping at any thrown exception.
        try
        {
            Iterator<Node> it = path.getNodes().iterator();
            if (it.hasNext()) // Any entries?
            {
                // The first entry in the path is always the start node
                // We don't want to run bindings on that entry (again)
                it.next(); // skip first entry
                while (it.hasNext())
                {
                    Node node = it.next();
                    LOG.debug("Executing Node {}",node);
                    _lifecycle.runBindings(node,appentry.app,this);
                    appentry.setLifeCycleNode(node);
                }
            }
        }
        catch (Throwable t)
        {
            LOG.warn("Unable to reach node goal: " + nodeName,t);
        }
    }

3.2进行部署

接下来webapp分阶段开始执行动作:

我们先来看下binding接口:

public static interface Binding
    {
        /**
         * Get a list of targets that this implementation should bind to.
         * 
         * @return the array of String node names to bind to. (use <code>"*"

to bind to all known node names)

  • /

String[] getBindingTargets();

      /**
       * Event called to process a {@link AppLifeCycle} binding.
       * 
       * @param node
       *            the node being processed
       * @param app
       *            the app being processed
       * @throws Exception
       *             if any problem severe enough to halt the AppLifeCycle processing
       */
      void processBinding(Node node, App app) throws Exception;
  }

</code>

每个阶段都会执行processBinding方法。

3.2.1.进行deploying

StandardDeployer的执行逻辑为:

public void processBinding(Node node, App app) throws Exception
    {
        ContextHandler handler = app.getContextHandler();
        if (handler == null)
        {
            throw new NullPointerException("No Handler created for App: " + app);
        }
        app.getDeploymentManager().getContexts().addHandler(handler);
    }

app.getContextHandler()方法创建了contextHandler:

public ContextHandler getContextHandler() throws Exception
    {
        if (_context == null)
        {
            _context = getAppProvider().createContextHandler(this);
            
            AttributesMap attributes = _manager.getContextAttributes();
            if (attributes!=null && attributes.size()>0)
            {
                // Merge the manager attributes under the existing attributes
                attributes = new AttributesMap(attributes);
                attributes.addAll(_context.getAttributes());
                _context.setAttributes(attributes);
            }
        }
        return _context;
    }



public ContextHandler createContextHandler(final App app) throws Exception
    {
        Resource resource = Resource.newResource(app.getOriginId());
        File file = resource.getFile();
        if (!resource.exists())
            throw new IllegalStateException("App resouce does not exist "+resource);

        String context = file.getName();
        
        if (file.isDirectory())
        {
            // must be a directory
        }
        else if (FileID.isWebArchiveFile(file))
        {
            // Context Path is the same as the archive.
            context = context.substring(0,context.length() - 4);
        }
        else 
        {
            throw new IllegalStateException("unable to create ContextHandler for "+app);
        }

        // Ensure "/" is Not Trailing in context paths.
        if (context.endsWith("/") && context.length() > 0) 
        {
            context = context.substring(0,context.length() - 1);
        }
        
        // Start building the webapplication
        WebAppContext wah = new WebAppContext();
        wah.setDisplayName(context);
        
        // special case of archive (or dir) named "root" is / context
        if (context.equalsIgnoreCase("root"))
        {
            context = URIUtil.SLASH;
        }
        else if (context.toLowerCase(Locale.ENGLISH).startsWith("root-"))
        {
            int dash=context.toLowerCase(Locale.ENGLISH).indexOf('-');
            String virtual = context.substring(dash+1);
            wah.setVirtualHosts(new String[]{virtual});
            context = URIUtil.SLASH;
        }

        // Ensure "/" is Prepended to all context paths.
        if (context.charAt(0) != '/') 
        {
            context = "/" + context;
        }


        wah.setContextPath(context);
        wah.setWar(file.getAbsolutePath());
        if (_defaultsDescriptor != null) 
        {
            wah.setDefaultsDescriptor(_defaultsDescriptor);
        }
        wah.setExtractWAR(_extractWars);
        wah.setParentLoaderPriority(_parentLoaderPriority);
        if (_configurationClasses != null) 
        {
            wah.setConfigurationClasses(_configurationClasses);
        }

        if (_tempDirectory != null)
        {
            /* Since the Temp Dir is really a context base temp directory,
             * Lets set the Temp Directory in a way similar to how WebInfConfiguration does it,
             * instead of setting the
             * WebAppContext.setTempDirectory(File).  
             * If we used .setTempDirectory(File) all webapps will wind up in the
             * same temp / work directory, overwriting each others work.
             */
            wah.setAttribute(WebAppContext.BASETEMPDIR,_tempDirectory);
        }
        return wah; 
    }

以上代码创建了WebAppContext→ ServletContextHandler

3.2.2.进行starting

StandardStarter主要进行handler的,上个阶段初始化的webappcontext的start工作:

public void processBinding(Node node, App app) throws Exception
    {
        ContextHandler handler = app.getContextHandler();
        if (!handler.isStarted())
        {
            handler.start();
        }
    }

我们来看下WebappContext的doStart方法:

protected void doStart() throws Exception
    {
        try
        {
            _metadata.setAllowDuplicateFragmentNames(isAllowDuplicateFragmentNames());
            preConfigure();
            super.doStart();
            postConfigure();

            if (isLogUrlOnStart())
                dumpUrl();
        }
        catch (Exception e)
        {
            //start up of the webapp context failed, make sure it is not started
            LOG.warn("Failed startup of context "+this, e);
            _unavailableException=e;
            setAvailable(false);
            if (isThrowUnavailableOnStartupException())
                throw e;
        }
    }

主要分为三个阶段:

  preConfigure();
  
  super.doStart();
  
  postConfigure();
  

3.2.2.1 preConfigure

public void preConfigure() throws Exception
    {
        // Setup configurations
        loadConfigurations();

        // Setup system classes
        loadSystemClasses();

        // Setup server classes
        loadServerClasses();

        // Configure classloader
        _ownClassLoader=false;
        if (getClassLoader()==null)
        {
            WebAppClassLoader classLoader = new WebAppClassLoader(this);
            setClassLoader(classLoader);
            _ownClassLoader=true;
        }

        if (LOG.isDebugEnabled())
        {
            ClassLoader loader = getClassLoader();
            LOG.debug("Thread Context classloader {}",loader);
            loader=loader.getParent();
            while(loader!=null)
            {
                LOG.debug("Parent class loader: {} ",loader);
                loader=loader.getParent();
            }
        }

        // Prepare for configuration
        for (int i=0;i<_configurations.length;i++)
        {
            LOG.debug("preConfigure {} with {}",this,_configurations[i]);
            _configurations[i].preConfigure(this);
        }
    }

a)首先装载配置文件:

private static String[] __dftConfigurationClasses =
    {
        "org.eclipse.jetty.webapp.WebInfConfiguration",
        "org.eclipse.jetty.webapp.WebXmlConfiguration",
        "org.eclipse.jetty.webapp.MetaInfConfiguration",
        "org.eclipse.jetty.webapp.FragmentConfiguration",
        "org.eclipse.jetty.webapp.JettyWebXmlConfiguration"//,
        //"org.eclipse.jetty.webapp.TagLibConfiguration"
    }

b)接着装载jetty定义的系统类:

public final static String[] __dftSystemClasses =
    {
        "java.",                            // Java SE classes (per servlet spec v2.5 / SRV.9.7.2)
        "javax.",                           // Java SE classes (per servlet spec v2.5 / SRV.9.7.2)
        "org.xml.",                         // needed by javax.xml
        "org.w3c.",                         // needed by javax.xml
        "org.apache.commons.logging.",      // TODO: review if special case still needed
        "org.eclipse.jetty.continuation.",  // webapp cannot change continuation classes
        "org.eclipse.jetty.jndi.",          // webapp cannot change naming classes
        "org.eclipse.jetty.plus.jaas.",     // webapp cannot change jaas classes
        "org.eclipse.jetty.websocket.WebSocket", // WebSocket is a jetty extension
        "org.eclipse.jetty.websocket.WebSocketFactory", // WebSocket is a jetty extension
        "org.eclipse.jetty.websocket.WebSocketServlet", // webapp cannot change WebSocketServlet
        "org.eclipse.jetty.servlet.DefaultServlet" // webapp cannot change default servlets
    } ;

...

_systemClasses = new ClasspathPattern(__dftSystemClasses);

c)接着装载jetty服务定义的类:

public final static String[] __dftServerClasses =
    {
        "-org.eclipse.jetty.continuation.", // don't hide continuation classes
        "-org.eclipse.jetty.jndi.",         // don't hide naming classes
        "-org.eclipse.jetty.plus.jaas.",    // don't hide jaas classes
        "-org.eclipse.jetty.websocket.WebSocket", // WebSocket is a jetty extension
        "-org.eclipse.jetty.websocket.WebSocketFactory", // WebSocket is a jetty extension
        "-org.eclipse.jetty.websocket.WebSocketServlet", // don't hide WebSocketServlet
        "-org.eclipse.jetty.servlet.DefaultServlet", // don't hide default servlet
        "-org.eclipse.jetty.servlet.listener.", // don't hide useful listeners
        "org.eclipse.jetty."                // hide other jetty classes
    } ;


_serverClasses = new ClasspathPattern(__dftServerClasses);

d)初始化webappclassloader:

if (getClassLoader()==null)
        {
            WebAppClassLoader classLoader = new WebAppClassLoader(this);
            setClassLoader(classLoader);
            _ownClassLoader=true;
        }

e)最后对configure数组进行preconfigure动作:

for (int i=0;i<_configurations.length;i++)
        {
            LOG.debug("preConfigure {} with {}",this,_configurations[i]);
            _configurations[i].preConfigure(this);
        }

我们一个一个来看:

1)WebInfConfiguration.preConfigure

代码很长,就不在黏贴,这个步骤主要在临时目录生成webapp目录,解压war包,装载container的jar包和web-inf/lib下的jar包。

2). WebXmlConfiguration.preConfigure

这个步骤主要寻找jetty-home/etc目录下的webdefault.xml和web工程web-inf目录下的web.xml,并加入到metadata中.

3). MetaInfConfiguration.preconfigure

这个步骤主要扫描每个jar包种的meta-inf目录,把META-INF/web-fragment.xml,META-INF/resources/目录以及.tld结尾的文件添加到resource中。

4). FragmentConfiguration.preConfigure

这个步骤主要寻找 jar包中的/META-INF/web-fragment.xml文件,并添加到metedata中。

f)接下来进行super.doStart()

该步骤首先设置:setContextClassLoader

   current_thread = Thread.currentThread();
   old_classloader = current_thread.getContextClassLoader();
   current_thread.setContextClassLoader(_classLoader);

3.2.2.2 configure

然后进入了configure阶段:

public void configure() throws Exception
    {
        // Configure webapp
        for (int i=0;i<_configurations.length;i++)
        {
            LOG.debug("configure {} with {}",this,_configurations[i]);
            _configurations[i].configure(this);
        }
    }

我们一个一个来看:

1)WebInfConfiguration.configure

这一步主要是把web-inf目录下的classes和lib目录下的jar包添加到webappClassloader的urls中去。

…
if (file != null)
                {
                    URL url= resource.getURL();
                    addURL(url);
                }
…

2). WebXmlConfiguration.configure

这一步初始化了一个web.xml文件的解析处理器

context.getMetaData().addDescriptorProcessor(new StandardDescriptorProcessor());

3). FragmentConfiguration.configure

这一步对jar包进行排序:

//order the fragments
        context.getMetaData().orderFragments();

4). AnnotationConfiguration.configure

context.addDecorator(new AnnotationDecorator(context));

if (context.getServletContext().getEffectiveMajorVersion() >= 3 || context.isConfigurationDiscovered())
           {
               _discoverableAnnotationHandlers.add(new WebServletAnnotationHandler(context));
               _discoverableAnnotationHandlers.add(new WebFilterAnnotationHandler(context));
               _discoverableAnnotationHandlers.add(new WebListenerAnnotationHandler(context));
           }



createServletContainerInitializerAnnotationHandlers(context, getNonExcludedInitializers(context));

这一步扫描classpath下的 META-INF/services/javax.servlet.ServletContainerInitializer文件,并读取每行中的ServletContainerInitializer实现,并根据HandlesTypes标注初始化:

_containerInitializerAnnotationHandlers.add(new ContainerInitializerAnnotationHandler(initializer, c));

接下来开始解析classes和lib目录下的class的annotation:

if (!_discoverableAnnotationHandlers.isEmpty() || _classInheritanceHandler != null || !_containerInitializerAnnotationHandlers.isEmpty())
       {           
           parser = createAnnotationParser();
           if (LOG.isDebugEnabled()) LOG.debug("Scanning all classses for annotations: webxmlVersion="+context.getServletContext().getEffectiveMajorVersion()+" configurationDiscovered="+context.isConfigurationDiscovered());
           parseContainerPath(context, parser);
           //email from Rajiv Mordani jsrs 315 7 April 2010
           //    If there is a <others/> then the ordering should be 
           //          WEB-INF/classes the order of the declared elements + others.
           //    In case there is no others then it is 
           //          WEB-INF/classes + order of the elements.
           parseWebInfClasses(context, parser);
           parseWebInfLib (context, parser);
           
           for (DiscoverableAnnotationHandler h:_discoverableAnnotationHandlers)
               context.getMetaData().addDiscoveredAnnotations(((AbstractDiscoverableAnnotationHandler)h).getAnnotationList());      
       }

扫描containerJar中class类中的annotation并且执行相关handler逻辑

扫描classes中的class类中的annotation并且执行相关handler逻辑

扫描web-inf/lib中jar包里的class类中的annotation并且执行相关handler逻辑

该过程是通过asm字节码增强技术类执行的:

protected void scanClass (InputStream is)
    throws IOException
    {
        ClassReader reader = new ClassReader(is);
        reader.accept(new MyClassVisitor(), ClassReader.SKIP_CODE|ClassReader.SKIP_DEBUG|ClassReader.SKIP_FRAMES);
    }

5) JettyWebXmlConfiguration.configure

这一步寻找web-jetty.xml的配置文件并进行配置。

接下来进行web.xml解析,并初始化servlet,filter,listener等工作:

ServletHolder holder = context.getServletHandler().getServlet(servlet_name);

addServletMapping:

protected ServletMapping addServletMapping (String servletName, XmlParser.Node node, WebAppContext context, Descriptor descriptor)
    {
        ServletMapping mapping = new ServletMapping();
        mapping.setServletName(servletName);
        
        List<String> paths = new ArrayList<String>();
        Iterator<XmlParser.Node> iter = node.iterator("url-pattern");
        while (iter.hasNext())
        {
            String p = iter.next().toString(false, true);
            p = normalizePattern(p);
            paths.add(p);
            context.getMetaData().setOrigin(servletName+".servlet.mapping."+p, descriptor);
        }
        mapping.setPathSpecs((String[]) paths.toArray(new String[paths.size()]));
        context.getServletHandler().addServletMapping(mapping);
        return mapping;
    }

然后进行ServletContextHandler的startContext:

getSessionHandler();
        getSecurityHandler();
        getServletHandler();
        
        Handler handler = _servletHandler;
        if (_securityHandler!=null)
        {
            _securityHandler.setHandler(handler);
            handler=_securityHandler;
        }
        
        if (_sessionHandler!=null)
        {
            _sessionHandler.setHandler(handler);
            handler=_sessionHandler;
        }

先把3个handler设置为责任链,并启动。

protected void doStart() throws Exception
    {
        if (_handler!=null)
            _handler.start();
        super.doStart();
    }

sessionHandler初始化了hashSession的随机数和销毁session的timetask

securityHandler初始化loginService和IdentityService鉴权服务。

servletHandler初始化了updateNameMappings()和updateMappings();

然后启动ServletContainerInitializerListener

最后:

if (_contextListeners != null)
        {
            ServletContextEvent event = new ServletContextEvent(_scontext);
            for (int i = 0; i < LazyList.size(_contextListeners); i++)
            {
                callContextInitialized(((ServletContextListener)LazyList.get(_contextListeners, i)), event);
            }
        }

在web.xml中配置的contextListener都在这里初始化了。包括spring容器。我们的webapp工程也就此启动完毕了。

在servletContextHandler中还有最后一步:

servletHandler.initialize();

进行filter和servlet的初始化

public void initialize()
        throws Exception
    {
        MultiException mx = new MultiException();

        // Start filters
        if (_filters!=null)
        {
            for (int i=0;i<_filters.length; i++)
                _filters[i].start();
        }
        
        if (_servlets!=null)
        {
            // Sort and Initialize servlets
            ServletHolder[] servlets = (ServletHolder[])_servlets.clone();
            Arrays.sort(servlets);
            for (int i=0; i<servlets.length; i++)
            {
                try
                {
                    if (servlets[i].getClassName()==null && servlets[i].getForcedPath()!=null)
                    {
                        ServletHolder forced_holder = (ServletHolder)_servletPathMap.match(servlets[i].getForcedPath());
                        if (forced_holder==null || forced_holder.getClassName()==null)
                        {    
                            mx.add(new IllegalStateException("No forced path servlet for "+servlets[i].getForcedPath()));
                            continue;
                        }
                        servlets[i].setClassName(forced_holder.getClassName());
                    }
                    
                    servlets[i].start();
                }
                catch(Throwable e)
                {
                    LOG.debug(Log.EXCEPTION,e);
                    mx.add(e);
                }
            } 
            mx.ifExceptionThrow();  
        }
    }

g)最后进行postConfigure工作,这一步主要进行一些之前配置的清理工作。

到这里为止,你的webapp工程算是彻底被容器加载起来了。

about_jetty_3.txt · 最后更改: 2018/10/14 15:31 (外部编辑)