Jetty源码剖析系列(2)-web.xml的解析与执行

当用Jetty来启动一个web项目的时候, Jetty会去读取war包的WEB-INFO目录里面的web.xml文件,然后解析这个web.xml。那这一步是什么时候进行和整个过程是怎样的?我们一起来分析一下。

以用Jetty Runner来启动一个war包为例, 在org.eclipse.jetty.runner.Runner类的configure方法里,我们看到它先构建出一个WebAppContext:

上面的代码中我们要注意webapp.setConfigurationClasses(_plusConfigurationClasses)这一句,我们先来看看_plusConfigurationClasses是什么东西: 我们可以看到就是一个String数组,里面包含了各种Configuration类的类名,这里我们要注意的是WebXmlConfiguration这个类,一看名字就知道是跟web.xml有关的:)。我们再来看setConfigurationClasses这个方法做了什么: 可以看到会把_plusConfigurationClasses里面的所有类都通过反射创建出一个实例对象来,其中包括了WebXmlConfiguration

接下来我们要跳去看WebAppContextdoStart方法————至于是怎样调用到这个方法,我要另起一篇文章来剖析,简单说一下,就是,WebAppContext是实现了Handler接口,同时也实现了LifeCycle接口,而每个Handler又是被当成org.eclipse.jetty.server.Server(Jetty HTTP Servlet Server)的内部管理的一个Bean,当Server start的时候,这些Bean也会被start,然后再调用每个bean的doStart方法。 WebAppContext.doStart() 我们可以看到这个调用了preConfigure方法,我们对叫这种名字的方法一定不能放过,我们再进入WebAppContextpreConfigure方法里,看到它先是做了一大堆工作,我们暂且不管,重要的是它执行以下代码: _configurations.preConfigure(this); 这个是调用了ConfigurationspreConfigure方法: 可以看到,这个方法其实是遍历了上文中的_plusConfigurationClasses所有Configuration实例对象,然后调用它的preConfigur方法,这里我们只关注WebXmlConfiguration的preConfigure方法: 看到上面调用的findWebXml方法没?到这里,我们终于找到了Jetty是在什么时候和哪里去加载web.xml这个文件! 这里我们要注意的是它调用了WebAppContextgetWebInf方法: 我们可以看到,它会去调用getBaseResource方法,得出一个路径,然后再拼接"WEB-INF"目录,那么这个BaseResource又是什么时候设置的呢?其实它是在另外一个Configuration类,WebInfConfigurationpreConfigure方法时设置的: WebInfConfiguration.preConfigure(): WebInfConfigurationpreConfigure方法里,它会在tmp创建一个目录,然后将war包解压出来,这个解压出来的目录就会被设置为BaseResource。 这样WebXmlConfiguration就会把找出来的web.xml设置给WebAppContextMetaData: 我们继续看WebAppContextdoStart方法,执行完preConfigure方法后,它就会调用super.doStart(),而实际上在super.doStart方法里,又会调用子类的startContext方法,我们看WebAppContextstartContext方法: 终于到了configure了: Configurations.configure(): 同样是遍历了所有Configuration,然后调用它的configure方法,这里我们同样只关注WebXmlConfigurationWebXmlConfiguration.configure(): 我们可以看到它给WebAppContextMetaData添加了一个叫StandardDescriptorProcessorDescriptorProcessor,那我们看看这个StandardDescriptorProcessor是个什么东西:

看到listener,filter-mapping,servlet,servlet-mapping这些是不是觉得似曾相识?这些不就是web.xml文件里我们常见的标签吗?!再看registerVisitor方法: 看到这里,我们大概可以猜出它里面实现的东西了,就是注册了当解析到web.xml里面的某个node时候,应该调用StandardDescriptorProcessor的方法,比如当解析到listener这个标签的时候,应该调用visitListener方法: 我们可以看到,它会从web.xml里面,解析出listener-class,然后通过反射创建出一个listener的实例,然后把它加到WebAppContextEventListener列表里。这些是在WebAppContextstartContext方法里执行完configure后,调用了MetaDataresolve方法来进行的,MetaData.resolve(): StandardDescriptorProcessor调用的是它的父类IterativeDescriptorProcessorprocess方法 嗯,看到这里,已经证实我们前文的猜测,解析到web.xml的某个tag时,就会通过反射调用前文注册的StandardDescriptorProcessor的相应方法!

在回到前文,把在web.xml中解析出来的Listener加到EventListener列表里做什么呢?我们回过头来继续看WebAppContextstartContext方法,在configure完和resolve完metadata后,它会调用super.startContext(),也就是它的父类ServletContextHandlerstartContext方法,而ServletContextHandler又会继续调用它自己的父类的startContext()方法,也就是ContextHandlerstartContext()方法: 我们这时可以看到它会遍历context里的EventListener列表里的Listener,然后调用它的contextInitialized方法: 。 如果我们在web.xml里配置了如下Listener 那我们看看Spring的这个常见的ContextLoaderListener的contextInitialized方法: 它调用了父类ContextLoaderinitWebApplicationContext方法, ContextLoader.initWebApplicationContext: 我们看到它尝试去从ServletContext(在本文是指WebAppContext这个子类)的initParameter中get到一个key为CONTEXT_CLASS_PARAM("contextClass")的值,这些initParameter都是在解析web.xml时设置进去的,如果我们在web.xml里面没有配置contextClass,那这个contextClassName就为null,接着它就会尝试去defaultStrategies里面找,

private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";

我们可以看到它是到classpath里查找一个叫ContextLoader.properties的文件,在它的classpath的spring-web.jar里面找到这个文件,它里面内容如下: org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext 找出这个default的XmlWebApplicationContext的全类名后,就会加载这个类,然后通过反射创建这个类的一个实例对象, 再回到ContextLoaderinitWebApplicationContext方法: 因为XmlWebApplicationContextConfigurableWebApplicationContext的一个实现类,所以我们会进入ContextLoaderconfigureAndRefreshWebApplicationContext方法, 我们可以看到它会到ServletContextInitParameter里面找一个key为contextConfigLocation的参数,它同样也是在解析web.xml时得到的,其实它就是设置了springcontext配置文件,比如: 找到这个配置文件的位置后,就会开始Springcontext的初始化!

到此,本文剖析了Jetty解析和执行web.xml的过程,主要是分析了listener标签和context-param标签,其他标签,如Servletservlet-mapping,filter这些标签,也有着类似的解析过程,我之后继续另起文章剖析!

Written on 13 August 2017