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大体的工做。