用户工具

站点工具


about_jetty_4

Jetty的那些事儿 第四章 web工程动态检测&Classloader体系结构

作者:陈科

联系方式:chenke1818@gmail.com

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

上一节我们讲到Jetty在新增一个war工程的时候,如何加载和初始化的webapp。现在我们再来分析下,如果我们修改了war包重新部署的话,jetty会如何处理。

先来一张webapp工程的状态流转图:

然后我们从AppLifeCycle的代码上看下webapp的状态如何流转的:

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);
    }

1.通知监听器

touch webapps/xxx.war

然后监听器将会执行以下逻辑:

private final Scanner.DiscreteListener _scannerListener = new Scanner.DiscreteListener()
    {
      ...

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

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

2.移除旧的app

我们来看deploymentManager的removeApp方法:

public void removeApp(App app)
    {
        Iterator<AppEntry> it = _apps.iterator();
        while (it.hasNext())
        {
            AppEntry entry = it.next();
            if (entry.app.equals(app))
            {
                if (! AppLifeCycle.UNDEPLOYED.equals(entry.lifecyleNode.getName()))
                    requestAppGoal(entry.app,AppLifeCycle.UNDEPLOYED);
                it.remove();
                LOG.info("Deployable removed: " + entry.app);
            }
        }
    }

其调用的方法还是deploymentManager的requestAppGoal方法,其实和addApp的原理是一样的,状态的流转就是通过图的节点来转变的。

首先调用StandardStopper:

public class StandardStopper implements AppLifeCycle.Binding
{
    public String[] getBindingTargets()
    {
        return new String[]
        { "stopping" };
    }

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

其调用了webappContext的doStop方法:

protected void doStop() throws Exception
    {
        super.doStop();

        try
        {
            for (int i=_configurations.length;i-->0;)
                _configurations[i].deconfigure(this);

            if (_metadata != null)
                _metadata.clear();
            _metadata=new MetaData();

        }
        finally
        {
            if (_ownClassLoader)
                setClassLoader(null);

            setAvailable(true);
            _unavailableException=null;
        }
    }

上面一共做了几件事情:

2.1进行handler和filter/servlet等的扫尾工作

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

2.1.1 HashSessionManager结束task,销毁session

 synchronized(this)
        {
            if (_saveTask!=null)
                _saveTask.cancel();
            _saveTask=null;
            if (_task!=null)
                _task.cancel();
            _task=null;
            if (_timer!=null && _timerStop)
                _timer.cancel();
            _timer=null;
        }
        

protected void invalidateSessions() throws Exception
    {
        // Invalidate all sessions to cause unbind events
        ArrayList<HashedSession> sessions=new ArrayList<HashedSession>(_sessions.values());
        int loop=100;
        while (sessions.size()>0 && loop-->0)
        {
            // If we are called from doStop
            if (isStopping() && _storeDir != null && _storeDir.exists() && _storeDir.canWrite())
            {
                // Then we only save and remove the session - it is not invalidated.
                for (HashedSession session : sessions)
                {
                    session.save(false);
                    removeSession(session,false);
                }
            }
            else
            {
                for (HashedSession session : sessions)
                    session.invalidate();
            }

            // check that no new sessions were created while we were iterating
            sessions=new ArrayList<HashedSession>(_sessions.values());
        }
    }

2.1.2 调用filter/servlet的stop方法

protected synchronized void doStop()
        throws Exception
    {
        super.doStop();

        // Stop filters
        List<FilterHolder> filterHolders = new ArrayList<FilterHolder>();
        List<FilterMapping> filterMappings = LazyList.array2List(_filterMappings);
        if (_filters!=null)
        {
            for (int i=_filters.length; i-->0;)
            {
                try { _filters[i].stop(); }catch(Exception e){LOG.warn(Log.EXCEPTION,e);}
                if (_filters[i].getSource() != Source.EMBEDDED)
                {
                    //remove all of the mappings that were for non-embedded filters
                    _filterNameMap.remove(_filters[i].getName());
                    //remove any mappings associated with this filter
                    ListIterator<FilterMapping> fmitor = filterMappings.listIterator();
                    while (fmitor.hasNext())
                    {
                        FilterMapping fm = fmitor.next();
                        if (fm.getFilterName().equals(_filters[i].getName()))
                            fmitor.remove();
                    }
                }
                else
                    filterHolders.add(_filters[i]); //only retain embedded
            }
        }
        _filters = (FilterHolder[]) LazyList.toArray(filterHolders, FilterHolder.class);
        _filterMappings = (FilterMapping[]) LazyList.toArray(filterMappings, FilterMapping.class);
        _matchAfterIndex = (_filterMappings == null || _filterMappings.length == 0 ? -1 : _filterMappings.length-1);
        _matchBeforeIndex = -1;


        // Stop servlets
        List<ServletHolder> servletHolders = new ArrayList<ServletHolder>();  //will be remaining servlets
        List<ServletMapping> servletMappings = LazyList.array2List(_servletMappings); //will be remaining mappings
        if (_servlets!=null)
        {
            for (int i=_servlets.length; i-->0;)
            {
                try { _servlets[i].stop(); }catch(Exception e){LOG.warn(Log.EXCEPTION,e);}
                if (_servlets[i].getSource() != Source.EMBEDDED)
                {
                    //remove from servlet name map
                    _servletNameMap.remove(_servlets[i].getName());
                    //remove any mappings associated with this servlet
                    ListIterator<ServletMapping> smitor = servletMappings.listIterator();
                    while (smitor.hasNext())
                    {
                        ServletMapping sm = smitor.next();
                        if (sm.getServletName().equals(_servlets[i].getName()))
                            smitor.remove();
                    }
                }
                else
                    servletHolders.add(_servlets[i]); //only retain embedded 
            }
        }
        _servlets = (ServletHolder[]) LazyList.toArray(servletHolders, ServletHolder.class);
        _servletMappings = (ServletMapping[])LazyList.toArray(servletMappings, ServletMapping.class);


        //will be regenerated on next start
        _filterPathMappings=null;
        _filterNameMappings=null;       
        _servletPathMap=null;
    }

2.2调用_configurations.deconfigure方法

这里主要是进行清理和扫尾的工作,例如AnnotationConfiguration的:

public void deconfigure(WebAppContext context) throws Exception
    {
        context.removeAttribute(CLASS_INHERITANCE_MAP);
        context.removeAttribute(CONTAINER_INITIALIZERS);
        ServletContainerInitializerListener listener = (ServletContainerInitializerListener)context.getAttribute(CONTAINER_INITIALIZER_LISTENER);
        if (listener != null)
        {
            context.removeBean(listener);
            context.removeAttribute(CONTAINER_INITIALIZER_LISTENER);
        }
    }

webxmlConfiguration的:

 public void deconfigure (WebAppContext context) throws Exception
    {
        ServletHandler _servletHandler = context.getServletHandler();
       
        context.setWelcomeFiles(null);

        if (context.getErrorHandler() instanceof ErrorPageErrorHandler)
            ((ErrorPageErrorHandler) 
                    context.getErrorHandler()).setErrorPages(null);


        // TODO remove classpaths from classloader

    }

webinfConfigure的deconfigure删除之前建的临时文件夹:

public void deconfigure(WebAppContext context) throws Exception
    {
        // delete temp directory if we had to create it or if it isn't called work
        Boolean tmpdirConfigured = (Boolean)context.getAttribute(TEMPDIR_CONFIGURED);
        
        if (context.getTempDirectory()!=null && (tmpdirConfigured == null || !tmpdirConfigured.booleanValue()) && !isTempWorkDirectory(context.getTempDirectory()))
        {
            IO.delete(context.getTempDirectory());
            context.setTempDirectory(null);
            
            //clear out the context attributes for the tmp dir only if we had to
            //create the tmp dir
            context.setAttribute(TEMPDIR_CONFIGURED, null);
            context.setAttribute(WebAppContext.TEMPDIR, null);
        }

        
        //reset the base resource back to what it was before we did any unpacking of resources
        context.setBaseResource(_preUnpackBaseResource);
    }
 

2.3metadata进行清理工作

 public void clear ()
    {
        _webDefaultsRoot = null;
        _origins.clear();
        _webXmlRoot = null;
        _webOverrideRoots.clear();
        _metaDataComplete = false;
        _annotations.clear();
        _descriptorProcessors.clear();
        _webFragmentRoots.clear();
        _webFragmentNameMap.clear();
        _webFragmentResourceMap.clear();
        _webFragmentAnnotations.clear();
        _webInfJars.clear();
        _orderedWebInfJars.clear();
        _orderedContainerJars.clear();
        _ordering = null;
        allowDuplicateFragmentNames = false;
    }

接着调用了StandardUndeployer:

 public void processBinding(Node node, App app) throws Exception
    {
        ContextHandler handler = app.getContextHandler();
        ContextHandlerCollection chcoll = app.getDeploymentManager().getContexts();

        recursiveRemoveContext(chcoll,handler);
    }

3.创建新的app

这一步的动作和前一章webapp初始化是一模一样的,所以,不在这里再次重复了。

4.关于工程的classloader体系结构

工程的classloader是由2级结构组成的

第一级是顶级classloader装载了jettyhome/lib下的jar包中的class 第二级别是WebAppClassLoader装载了工程的class

而每次创建新的工程(包括war包modify之后的部署)都是新创建一个WebAppClassLoader,这就必然造成了热部署之后,jvm的perm区的暴涨,导致最终的oom.

所以,真正要做到热部署,还得通过其他途径来实现。

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