Jetty源碼分析之WebAppContext

WebAppContext即Web Application ContextHandler,表示一個Web應用的上下文,是上篇文章介紹的ContextHandler的一個子類,也是實際中應用的ContextHandler。先來看下類圖:
這裏寫圖片描述javascript

能夠看到在ContextHandler和WebAppContext中間還有一個ServletContxtHandler,下面就先從這個類開始分析。java

1.ServletContxtHandler

ServletContextHandler是ContextHandler的直接子類,具備ContextHandler的大部分特徵,不一樣的地方是ServletContextHandler中管理了三個Handler:ServletHandler、SessionHandler、SecurityHandler。web

protected SessionHandler _sessionHandler;
    protected SecurityHandler _securityHandler;
    protected ServletHandler _servletHandler;

ServletHandler對ContextHandler的擴展也主要集中在對三個Handler特別是ServletHandler的管理上。SessionHandler和SecurityHandler都是可選的,ServletHandler中定義了幾個常量用來表示是否須要相應的Handler。apache

public final static int SESSIONS=1; //須要SessionHandler
    public final static int SECURITY=2; //須要SecurityHandler
    public final static int NO_SESSIONS=0;//不須要SessionHandler
    public final static int NO_SECURITY=0;//不須要SecurityHandler

經過 new ServletContext("/context",Context.SESSIONS|Context.NO_SECURITY); 能夠配置SessionHandler和SecurityHandler。對這幾個的Handler的組織是放在startContext()方法裏進行的:websocket

protected void startContext() throws Exception
    {
        //防止默寫Handler沒有正確配置,強行初始化Handler
        getSessionHandler();
        getSecurityHandler();
        getServletHandler();

        //下面是Handler鏈的組織過程
        Handler handler = _servletHandler;
        if (_securityHandler!=null)
        {
            _securityHandler.setHandler(handler);
            handler=_securityHandler;
        }

        if (_sessionHandler!=null)
        {
            _sessionHandler.setHandler(handler);
            handler=_sessionHandler;
        }

        // 跳過全部Wrapper類型的Handler
        _wrapper=this;
        while (_wrapper!=handler && _wrapper.getHandler() instanceof HandlerWrapper)
            _wrapper=(HandlerWrapper)_wrapper.getHandler();

        // if we are not already linked
        if (_wrapper!=handler)
        {
            if (_wrapper.getHandler()!=null )
                throw new IllegalStateException("!ScopedHandler");
            _wrapper.setHandler(handler);
        }

        super.startContext();

        //下面是ServletHandler初始化的過程
        if (_servletHandler != null && _servletHandler.isStarted())
        {
            for (int i=_decorators.size()-1;i>=0; i--)
            {
                Decorator decorator = _decorators.get(i);
                if (_servletHandler.getFilters()!=null)
                    for (FilterHolder holder:_servletHandler.getFilters())
                        decorator.decorateFilterHolder(holder);
                if(_servletHandler.getServlets()!=null)
                    for (ServletHolder holder:_servletHandler.getServlets())
                        decorator.decorateServletHolder(holder);
            }   

            _servletHandler.initialize();
        }
    }

上面最重要的工做就是造成了ServletContextHandler—>SessionHandler—>SecurityHandler—>ServletHandler的Handler鏈,經過這個Handler鏈,ServletContextHandler拿到請求以後就能依次傳遞給各個Handler處理,最後會經過ServletHandler轉交給具體的Servlet處理。最後再這個方法裏還對ServeltHandler進行了初始化。ServletHandler的initialize()方法中主要是對Filter和Servlet調用其start()方法進行啓動。
ServletContextHandler中還有一些添加Servlet和Filter的方法,但實際上都是調用ServletHandler的相應方法:session

/** conveniance method to add a servlet. */
    public ServletHolder addServlet(Class<? extends Servlet> servlet,String pathSpec)
    {
        return getServletHandler().addServletWithMapping(servlet.getName(), pathSpec);
    }

    /** conveniance method to add a servlet. */
    public void addServlet(ServletHolder servlet,String pathSpec)
    {
        getServletHandler().addServletWithMapping(servlet, pathSpec);
    }

    /** conveniance method to add a filter */
    public void addFilter(FilterHolder holder,String pathSpec,EnumSet<DispatcherType> dispatches)
    {
        getServletHandler().addFilterWithMapping(holder,pathSpec,dispatches);
    }

    /** convenience method to add a filter */
    public FilterHolder addFilter(Class<? extends Filter> filterClass,String pathSpec,EnumSet<DispatcherType> dispatches)
    {
        return getServletHandler().addFilterWithMapping(filterClass,pathSpec,dispatches);
    }

主要的來講,ServletContextHandler就是重寫了父類的startContext()方法,將SessionHandler、SecurityHandler、ServletHandler組成一個Handler鏈來對請求進行處理,而且在startContext()方法中進行了ServletHandler的初始化。另外ServletContextHandler中還提供了一個內部類Context,它繼承了ContextHandler的內部類,也是ServletContext的一個具體實現。app

2.WebAppContext

上篇文章分析ContextHandler的時候就提到過,做爲Web應用的上下文ContextHandler沒有實現對資源文件的處理,而WebAppContext做爲ContextHandler的子類正是對這一部分進行了擴展。
主要的啓動邏輯和資源加載邏輯都放在doStart()方法裏,下面來重點看下這個方法:eclipse

@Override
    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;
        }
    }

super.start()會調用startContext()方法,而WebAppContext也重寫了這個方法:webapp

@Override
    protected void startContext()
        throws Exception
    {
        configure();

        //resolve the metadata
        _metadata.resolve(this);

        super.startContext();
    }

這裏面主要是調用configure()方法,結合上面doStart()方法中流程,能夠看到大概就是preConfigure()—>configure()—>postConfigure()這麼三個步驟。
先來看下preConfigure()方法:socket

public void preConfigure() throws Exception
    {
        // 設置配置類集合:configurations
        loadConfigurations();

        // 設置系統類class:system classes
        loadSystemClasses();

        // 設置應用服務本身須要的一些class:server classes
        loadServerClasses();

        // 爲應用建立一個ClassLoader,每一個web應用都須要一個本身的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();
            }
        }

        // 調用每一個配置類的preConfigure()方法
        for (int i=0;i<_configurations.length;i++)
        {
            LOG.debug("preConfigure {} with {}",this,_configurations[i]);
            _configurations[i].preConfigure(this);
        }
    }

一開始就是三個load()方法,先看下loadConfigurations():

protected void loadConfigurations()
        throws Exception
    {
         //若是_configurations屬性不爲空,說明已經初始化過了
        if (_configurations!=null)
            return;

        //若是沒有設置過配置類,則使用默認提供的配置類,不然使用設置的
        if (!_configurationClassesSet)
            _configurationClasses=__dftConfigurationClasses;

//下面其實就是經過ClassLoader將_configurationClasses中指定的配置類加載到內存中並新建一個實例
        _configurations = new Configuration[_configurationClasses.length];
        for (int i = 0; i < _configurationClasses.length; i++)
        {
            _configurations[i]=(Configuration)Loader.loadClass(this.getClass(), _configurationClasses[i]).newInstance();
        }
    }

下面是jetty默認的配置類集合:

//每個配置類負責一部分配置文件的解析或class文件/jar包的導入
 private static String[] __dftConfigurationClasses =
    {
        "org.eclipse.jetty.webapp.WebInfConfiguration",//對webinfo的處理,主要用於載入class文件以及jar包 
        "org.eclipse.jetty.webapp.WebXmlConfiguration",//負責web.xml的解析
        "org.eclipse.jetty.webapp.MetaInfConfiguration",
        "org.eclipse.jetty.webapp.FragmentConfiguration",
        "org.eclipse.jetty.webapp.JettyWebXmlConfiguration"//,
        //"org.eclipse.jetty.webapp.TagLibConfiguration"
    } ;

上面的_configurations中就是存放了這些配置類的實例。
接下來的loadSystemClasses()、loadServerClasses()分別用來設置系統須要的classes 和應用服務本身須要的classes,這兩個類型的類在jetty中都有默認值:

// System classes are classes that cannot be replaced by
    // the web application, and they are *always* loaded via
    // system classloader.
    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.servlet.DefaultServlet" // webapp cannot change default servlets
    } ;
// Server classes are classes that are hidden from being
    // loaded by the web application using system classloader,
    // so if web application needs to load any of such classes,
    // it has to include them in its distribution.
    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.servlet.DefaultServlet", // don't hide default servlet
        "-org.eclipse.jetty.servlet.listener.", // don't hide useful listeners
        "org.eclipse.jetty."                // hide other jetty classes
    } ;

上面的load操做,其實就是從Server裏面拿到對應屬性的設置,而後爲其建立ClasspathPattern對象,若是從Server中沒有對其進行設置,則使用上面的默認值建立ClasspathPattern對象,下面是loadSystemClasses()方法:

protected void loadSystemClasses()
    {
        if (_systemClasses != null)
            return;

        //look for a Server attribute with the list of System classes
        //to apply to every web application. If not present, use our defaults.
        Server server = getServer();
        if (server != null)
        {
            Object systemClasses = server.getAttribute(SERVER_SYS_CLASSES);
            if (systemClasses != null && systemClasses instanceof String[])
                _systemClasses = new ClasspathPattern((String[])systemClasses);
        }

        if (_systemClasses == null)
            _systemClasses = new ClasspathPattern(__dftSystemClasses);
    }

完成load操做後就是給當前應用建立一個ClassLoader而且對上面得到的每一個配置對象Configuration都調用一次PreConfigure()方法。每一個配置對象在這個階段作的事情都不太相同,下面是WebInfConfiguration的preConfigure方法,方法中一些代碼的具體做用見下面的註釋:

@Override
    public void preConfigure(final WebAppContext context) throws Exception
    {
        // 查找工做目錄,若是不存在則建立一個
        File work = findWorkDirectory(context);
        if (work != null)
            makeTempDirectory(work, context, false);

        //Make a temp directory for the webapp if one is not already set
        resolveTempDirectory(context);

        //若是有必要解壓war包
        unpack (context);


       //肯定jar包的載入順序 
        String tmp = (String)context.getAttribute(WEBINF_JAR_PATTERN);
        Pattern webInfPattern = (tmp==null?null:Pattern.compile(tmp));
        tmp = (String)context.getAttribute(CONTAINER_JAR_PATTERN);
        Pattern containerPattern = (tmp==null?null:Pattern.compile(tmp));

        //Apply ordering to container jars - if no pattern is specified, we won't
        //match any of the container jars
        PatternMatcher containerJarNameMatcher = new PatternMatcher ()
        {
            public void matched(URI uri) throws Exception
            {
                context.getMetaData().addContainerJar(Resource.newResource(uri));
            }      
        };
        ClassLoader loader = context.getClassLoader();
        while (loader != null && (loader instanceof URLClassLoader))
        {
            URL[] urls = ((URLClassLoader)loader).getURLs();
            if (urls != null)
            {
                URI[] containerUris = new URI[urls.length];
                int i=0;
                for (URL u : urls)
                {
                    try 
                    {
                        containerUris[i] = u.toURI();
                    }
                    catch (URISyntaxException e)
                    {
                        containerUris[i] = new URI(u.toString().replaceAll(" ", "%20"));
                    }  
                    i++;
                }
                containerJarNameMatcher.match(containerPattern, containerUris, false);
            }
            loader = loader.getParent();
        }

        //Apply ordering to WEB-INF/lib jars
        PatternMatcher webInfJarNameMatcher = new PatternMatcher ()
        {
            @Override
            public void matched(URI uri) throws Exception
            {
                context.getMetaData().addWebInfJar(Resource.newResource(uri));
            }      
        };
        List<Resource> jars = findJars(context);

        //Convert to uris for matching
        URI[] uris = null;
        if (jars != null)
        {
            uris = new URI[jars.size()];
            int i=0;
            for (Resource r: jars)
            {
                uris[i++] = r.getURI();
            }
        }
        webInfJarNameMatcher.match(webInfPattern, uris, true); //null is inclusive, no pattern == all jars match 
    }

總的來講就是對當前的web程序的war包進行處理,進行解壓,放到相應的文件夾下面,並處理WEB-INF下放置的依賴jar包。

preConfigure以後就是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);
        }
    }

能夠看到主要是調用各個配置對象的configure()方法。
下面仍是來看下WebInfConfiguration的configure()方法:

@Override
    public void configure(WebAppContext context) throws Exception
    {
        //cannot configure if the context is already started
        if (context.isStarted())
        {
            if (LOG.isDebugEnabled())
                LOG.debug("Cannot configure webapp "+context+" after it is started");
            return;
        }

        Resource web_inf = context.getWebInf();

        // Add WEB-INF classes and lib classpaths
        if (web_inf != null && web_inf.isDirectory() && context.getClassLoader() instanceof WebAppClassLoader)
        {
            // Look for classes directory
            Resource classes= web_inf.addPath("classes/");
            if (classes.exists())
                ((WebAppClassLoader)context.getClassLoader()).addClassPath(classes);

            // Look for jars
            Resource lib= web_inf.addPath("lib/");
            if (lib.exists() || lib.isDirectory())
                ((WebAppClassLoader)context.getClassLoader()).addJars(lib);
        }

        // Look for extra resource
        @SuppressWarnings("unchecked")
        List<Resource> resources = (List<Resource>)context.getAttribute(RESOURCE_URLS);
        if (resources!=null)
        {
            Resource[] collection=new Resource[resources.size()+1];
            int i=0;
            collection[i++]=context.getBaseResource();
            for (Resource resource : resources)
                collection[i++]=resource;
            context.setBaseResource(new ResourceCollection(collection));
        }
    }

這裏的邏輯是很簡單明瞭的,主要就是將WEB-INF目錄下的classe文件和依賴jar包加入到到classpath中去。至於最後的postConfigure,就是作些清除工做,這裏就不展開講了。

至此對於WebAppContext啓動時候所作的工做應該有了大體的瞭解。下面再總結下,其實主要的初始化工做是兩個方面:1是war包的解壓爲其建立工做目錄並將其中WEB-INF下的classes文件和依賴jar包加入到classpath中,還須要爲應用建立一個ClassLoader。2是將管理的SessionHandler、SecurityHandler、ServletHandler和WebAppContext構建成一個Handler鏈,用來處理請求,再將請求交給其它鏈中的其它節點處理以前,還須要對請求進行url和目標主機的校驗,若是校驗不經過則直接返回。這就是WebAppContext大體的工做。