在前面的文章中就已经提到过,在jetty中,我们部署的每一个web应用程序都对应着一个webAppContext。。。
因此在jetty中,web应用程序的创建与启动说白了就是WebAppContext的创建于启动。。。
在前面我们还分析过ContextHandlerCollection这个类型,它可以看成是WebAppContext的容器,我们常见的部署方法就是在jetty中部署多个web程序,那么这个collection就是用于维护这些context,并且会对http请求进行路由,交给相应的webAppContext来处理。。。
这里用一张图来做表现一下他们之间的关系:
首先请求会先到server,然后server交给内部的contextHandlerCollection来处理。。然后其进行一下路由。。交给对应的WebAppContext来处理。。
上面就将整个http请求的大的处理流程就描述的差不多了。。。当然交给WebAppContext之后,其实还涉及到再一次的路由,将其交给对应的servlet来处理。。。当然这个就是后话了。。。
这里分析WebAppContext的创建一般情况下都是先来看看一个工具类的实现:WebAppDeployer,他就是专门用于建立WebAppContext的,并为其设置一些必须的参数:
那么我们来看看他的doStart方法吧:
public void doStart() throws Exception {
_deployed=new ArrayList(); //创建app的数组,也就是WebAppContext的数组
scan();
}
这里可以看到,先是创建了一个数组,这个数组就适用于存放待会将会创建的所有WebAppContext。。
接着调用scan方法,这个方法的作用其实从名字就看出来了,很明显,扫描。。。扫描要部署的app所在的目录,然后依次为这些app创建相应的webAppContext。。。
public void scan() throws Exception {
if (_contexts==null) //这个是在创建WebAppDployer的时候会传进来额 ,webcontextHandler的容器
throw new IllegalArgumentException("No HandlerContainer");
Resource r=Resource.newResource(_webAppDir); //用于存放app的文件夹,获取它的resource引用
if (!r.exists())
throw new IllegalArgumentException("No such webapps resource "+r);
if (!r.isDirectory())
throw new IllegalArgumentException("Not directory webapps resource "+r);
String[] files=r.list(); //当前文件夹下面的所有文件
//遍历当前文件夹下面的所有文件
files: for (int f=0; files!=null&&f<files.length; f++)
{
String context=files[f]; //文件的名字,这里其实也就可以知道默认是按照app的文件名字来命名其的context的名字,也就是用这个来进行路由
if (context.equalsIgnoreCase("CVS/")||context.equalsIgnoreCase("CVS")||context.startsWith("."))
continue;
//获取当前app的资源的resource引用
Resource app=r.addPath(r.encode(context));
//判断当前app的类型,是war或者jar
if (context.toLowerCase().endsWith(".war")||context.toLowerCase().endsWith(".jar")) {
context=context.substring(0,context.length()-4); //将后缀去掉
Resource unpacked=r.addPath(context);
if (unpacked!=null&&unpacked.exists()&&unpacked.isDirectory())
continue;
} else if (!app.isDirectory()) { //连文件夹都不是。。什么扯淡的玩意。。直接跳过
continue;
}
if (context.equalsIgnoreCase("root")||context.equalsIgnoreCase("root/")) {
context=URIUtil.SLASH;
} else {
context="/"+context; //相当于为context添加成从根目录开始
}
if (context.endsWith("/")&&context.length()>0) { //如果是文件夹路径,那么需要将最后那个/去掉
context=context.substring(0,context.length()-1);
}
// Check the context path has not already been added or the webapp itself is not already deployed
//检查当前的的app是否已经部署了,或者有重名的context
if (!_allowDuplicates) //检查是否有冲突的
{
Handler[] installed=_contexts.getChildHandlersByClass(ContextHandler.class);
for (int i=0; i<installed.length; i++)
{
//这里一般都进不来
ContextHandler c=(ContextHandler)installed[i];
if (context.equals(c.getContextPath()))
continue files;
String path;
if (c instanceof WebAppContext)
path = ((WebAppContext)c).getWar();
else
path = (c.getBaseResource()==null?"":c.getBaseResource().getFile().getAbsolutePath());
if (path.equals(app.getFile().getAbsolutePath()))
continue files;
}
}
// create a webapp
WebAppContext wah=null; //创建一个webappcontext
if (_contexts instanceof ContextHandlerCollection &&
WebAppContext.class.isAssignableFrom(((ContextHandlerCollection)_contexts).getContextClass())) {
try {
wah=(WebAppContext)((ContextHandlerCollection)_contexts).getContextClass().newInstance();
} catch (Exception e) {
throw new Error(e);
}
} else {
wah=new WebAppContext(); //创建一个webapplicationcontext
}
// configure it
wah.setContextPath(context); //设置context的路径例如:/manager
if (_configurationClasses!=null) {
wah.setConfigurationClasses(_configurationClasses); //设置默认的配置类
}
if (_defaultsDescriptor!=null)
wah.setDefaultsDescriptor(_defaultsDescriptor);
wah.setExtractWAR(_extract);
wah.setWar(app.toString()); //设置war包的路径
wah.setParentLoaderPriority(_parentLoaderPriority); //class的加载是否是从父类加载器优先
// add it
_contexts.addHandler(wah); //为当前的contexts添加一个contextHandler
_deployed.add(wah); //添加已经部署过的webAppContext
//刚开始的话这里其实是不会启动的,因为会交给server来启动所有的context
if (_contexts.isStarted()) { //ContextHandlerCollection如果已经启动,那么这里再启动一次。。
_contexts.start(); // TODO Multi exception
}
}
}
这个方法的定义还挺长的,不过其实代码还是比较容易理解的。。。
(1)遍历要部署的app所在的目录,这里一般情况下都是webapps目录。。。
(2)对于webapps目录下的所有文件,首先获取它的名字,然后判断当前文件的类型,如果是war,jar或者文件夹啥的,那就没问题了。。。如果是单个的文件的话,那就直接忽略掉了。。
(3)对名字进行预处理,为其首部加上“/”,处理之后的名字将会成为这个web程序的contextPath,ContextHandlerCollection将会用这个名字来进行路由。。
(4)判断是否有重名的app部署。。。
(5)创建WebAppContext,设置它的contextPath属性,app文件所在的路径,然后再将其加入到ContextHandlerCollection里面去。。。
好了。。到这里WebAppContext的创建过程就差不多了。。。。那么接下来就可以来看看WebAppContext的启动过程了。。。
//这里可以理解为启动这个app,app都需要创建自己的classLoader
protected void doStart() throws Exception {
try {
loadConfigurations(); //加载需要用到的加载类对象,这些对象会被保存到configurations数组里面去
//设置他们的context
for (int i=0;i<_configurations.length;i++) {
_configurations[i].setWebAppContext(this);
}
// Configure classloader
_ownClassLoader=false;
if (getClassLoader()==null) {
WebAppClassLoader classLoader = new WebAppClassLoader(this); //创建classLoader。这个classLoader将会专属于这个web应用,每个webApp都有一个自己专属的classLoader
setClassLoader(classLoader); //设置当前的classLoader
_ownClassLoader=true; //表示使用自己的classLoader
}
if (Log.isDebugEnabled()) {
ClassLoader loader = getClassLoader();
Log.debug("Thread Context class loader is: " + loader);
loader=loader.getParent();
while(loader!=null)
{
Log.debug("Parent class loader is: " + loader);
loader=loader.getParent();
}
}
//这个里面的操作会加压war文件
for (int i=0;i<_configurations.length;i++) {
_configurations[i].configureClassLoader(); //这些configure对象的classLoader的配置
}
getTempDirectory();
super.doStart(); //父类的doStart方法
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;
_unavailable = true;
}
}
这里代码还是相对比较简单的吧,起码比较容易理解的
(1)首先调用了loadConfigurations方法,这个方法用创建一些默认的配置类的对象。然后将他们保存在configurations数组中。。。这些配置类有如下:
"org.mortbay.jetty.webapp.WebInfConfiguration", //对webinfo的处理,主要用于载入class文件以及jar包
"org.mortbay.jetty.webapp.WebXmlConfiguration", //这个主要是对web.xml的处理
"org.mortbay.jetty.webapp.JettyWebXmlConfiguration",
"org.mortbay.jetty.webapp.TagLibConfiguration"
(2)创建WebAppClassLoader,这个classLoader用于加载当前web程序class文件。。。每一个web程序都会有一个自己的classLoader。。。他的作用就是为了实现各个程序之间的隔离,以及web程序与服务器资源的隔离。。
(3)调用每一个配置类对象的configureClassLoader方法,这个方法用于利用当前的classLoader进行一些预处理。。例如load当前app的jar包,class文件之类的。。。
(4)调用父类的doStart方法。。。
好了,在接着看父类的doStart方法之前。。。我们先来看看其中WebInfConfiguration的configureClassLoader方法做了什么事情吧。。WebInfConfiguration这个类型是干嘛的。看名字应该还挺清楚的。。。webinf相关的配置。。好了。。我们来看看它都干了什么时候吧。。。
public void configureClassLoader() throws Exception {
//cannot configure if the context is already started
if (_context.isStarted())
{
if (Log.isDebugEnabled()){Log.debug("Cannot configure webapp after it is started");}
return;
}
//这里用于获取web的基本信息,将会促使去解压当前的war包等,然后这里其实就相当于获取部署的那个文件夹下的WEB-INF文件夹
Resource web_inf=_context.getWebInf();
// Add WEB-INF classes and lib classpaths
if (web_inf != null && web_inf.isDirectory() && _context.getClassLoader() instanceof WebAppClassLoader)
{
//获取webinf目录下的classes文件夹的resource引用,这个里面就是用户自己定义的源代码的class文件。。
Resource classes= web_inf.addPath("classes/");
//添加class文件的loader
if (classes.exists()) { //用当前的classLoader来加载classes下面的class文件
((WebAppClassLoader)_context.getClassLoader()).addClassPath(classes.toString());
}
//这里当然还需要将那些jar包导进来了
Resource lib= web_inf.addPath("lib/");
if (lib.exists() || lib.isDirectory())
((WebAppClassLoader)_context.getClassLoader()).addJars(lib);
}
}
这个方法是干嘛用的看代码应该很清楚了吧。。。而且它的重要性应该也很明白了。。用于加载当前应用程序的webinf目录下面的资源。。例如classes文件夹下的class文件,以及lib文件夹下面的jar。。。。
好了。。那么接着回到webAppContext的启动。。。那么接下来应该看父类(ContextHandler)的doStart方法了:
//用于组件的启动
protected void doStart() throws Exception {
if (_contextPath==null)
throw new IllegalStateException("Null contextPath");
_logger=Log.getLogger(getDisplayName()==null?getContextPath():getDisplayName());
ClassLoader old_classloader=null; //classloader
Thread current_thread=null;
SContext old_context=null;
_contextAttributes=new AttributesMap(); //属性map
try {
//设置classLoader
if (_classLoader!=null) {
current_thread=Thread.currentThread();
old_classloader=current_thread.getContextClassLoader(); //存储以前 的classLoader
current_thread.setContextClassLoader(_classLoader); //将当前这个webContext的classLoader保存到线程变量里面
}
if (_mimeTypes==null)
_mimeTypes=new MimeTypes(); //mimetype
old_context=(SContext)__context.get(); //获取当前的线程servletcontext
__context.set(_scontext); //设置线程变量,保存当前的servletContext
if (_errorHandler==null)
setErrorHandler(new ErrorHandler());
//这里会中子类中的startContext一层一层的向上调用
startContext(); //启动当前的context
}
finally {
__context.set(old_context);
// reset the classloader
if (_classLoader!=null) {
current_thread.setContextClassLoader(old_classloader);
}
}
}
父类的启动也还蛮简单的吧。。首先获取当前的线程classLoader,然后将其设置为当前WebAppContext的classloader,而且还要设置当前的线程变量context,将其设置为当前webAppContext的servletContext。。。
好饿了,最后就是执行startContext方法了。。。
那么接下来来看看WebAppContext的startContext方法的定义吧:
protected void startContext()
throws Exception
{
// 一些默认的configure
for (int i=0;i<_configurations.length;i++)
_configurations[i].configureDefaults();
//获取webinf文件夹的resource引用
Resource web_inf=getWebInf();
if (web_inf!=null)
{
Resource work= web_inf.addPath("work");
if (work.exists()
&& work.isDirectory()
&& work.getFile() != null
&& work.getFile().canWrite()
&& getAttribute(ServletHandler.__J_S_CONTEXT_TEMPDIR) == null)
setAttribute(ServletHandler.__J_S_CONTEXT_TEMPDIR, work.getFile());
}
// Configure webapp 这个里面会读取默认的jetty的xml定义以及当前的web的xml的定义,创建servlet等
for (int i=0;i<_configurations.length;i++)
_configurations[i].configureWebApp();
super.startContext();
}
这里其实无非就是执行默认的配置类对象的一些方法,首先是configureDefaults方法,
这里就来看看WebXmlConfiguration的configureDefaults方法做了什么事情吧:
//这里是读取默认的jetty的webdefault.xml,一些default的servlet啥的
public void configureDefaults() throws Exception {
if (_context.isStarted()) { //如果都已经启动了,那么需要报错
if (Log.isDebugEnabled()){Log.debug("Cannot configure webapp after it is started");}
return;
}
String defaultsDescriptor=getWebAppContext().getDefaultsDescriptor(); //获取webdefault.xml文件的地址
if(defaultsDescriptor!=null&&defaultsDescriptor.length()>0)
{
Resource dftResource=Resource.newSystemResource(defaultsDescriptor);
if(dftResource==null) {
dftResource=Resource.newResource(defaultsDescriptor); //webdefault.xml的引用; 定义了一些基本的servlet,filter一类的
}
configure(dftResource.getURL().toString()); //这个里面会创建里面定义的filter,servlet一类的
_defaultWelcomeFileList=_welcomeFiles!=null;
}
}
代码其实也看不出来什么东西,不过注释已经说的很清楚了,对于webdefault.xml这个文件,应该搞过jetty的都知道它是干嘛的吧。。那么这个方法具体做了啥应该就很清楚了吧。。。。
接下来来看看WebXmlConfiguration的configureWebApp干了什么事情吧:
//处理当前的webapp的web.xml
public void configureWebApp() throws Exception {
//cannot configure if the context is already started
if (_context.isStarted())
{
if (Log.isDebugEnabled())
Log.debug("Cannot configure webapp after it is started");
return;
}
URL webxml=findWebXml(); //获取webinf目录下的web.xml
if (webxml!=null) {
configure(webxml.toString()); //处理这个xml
}
String overrideDescriptor=getWebAppContext().getOverrideDescriptor(); //覆盖的web描述在,这里一个web程序可能会定义多个web描述文件吧。。
if(overrideDescriptor!=null&&overrideDescriptor.length()>0)
{
Resource orideResource=Resource.newSystemResource(overrideDescriptor);
if(orideResource==null)
orideResource=Resource.newResource(overrideDescriptor);
_xmlParser.setValidating(false);
configure(orideResource.getURL().toString());
}
}
这里注释应该也说的很清楚吧。。。用于处理webinf目录下的web.xml文件。。这里主要是调用configure方法来处理这个文件。。这个方法做的事情很重要。。我觉得有必要以后来具体的说明。。。
不过可以粗略的说一下这个方法做了什么事情。。。它用于处理xml文件。。例如当处理到servlet的申明的时候,会根据申明的参数创建相应的servletholder,并将其保存到servlethandler里面去。。。。
好了。。WebAppContext的的startContext方法就差不多了。。那么来看看他父类定义的此方法的定义:
//这里主要是初始化servletHandler
protected void startContext() throws Exception
{
super.startContext();
// OK to Initialize servlet handler now
if (_servletHandler != null && _servletHandler.isStarted()) { //servlethandler的初始化
_servletHandler.initialize(); //这里会启动里面的所有servlet,包括获取servlet的class,如果需要startup的话还要新建servlet的对对象然后init
}
}
好吧。没啥意思。。。首先调用了父类的相应的方法,然后再调用了servletHanlder的initlize方法,它将会启动当前定义的servlet,包括从classLoader中获取class文件。。如果需要的话实例化servlet等。。
接下来看看父类中的这个方法做了什么事情吧:
//启动当前的context,其实这里最重要的就是通知contextListener,然后对属性进行设置
protected void startContext()
throws Exception {
super.doStart(); //这个其实是用于启动内部的handler,这里一把都是_sessionHandler
if (_errorHandler!=null)
_errorHandler.start();
// 调用当前的contextListener的contextInitialized方法
if (_contextListeners != null ) {
ServletContextEvent event= new ServletContextEvent(_scontext);
for (int i= 0; i < LazyList.size(_contextListeners); i++) {
((ServletContextListener)LazyList.get(_contextListeners, i)).contextInitialized(event); //相当于向这些contextListener发送事件
}
}
String managedAttributes = (String)_initParams.get(MANAGED_ATTRIBUTES);
if (managedAttributes!=null)
{
_managedAttributes=new HashSet();
String[] attributes = managedAttributes.toString().split(",");
for (int i=0;i<attributes.length;i++)
_managedAttributes.add(attributes[i]);
Enumeration e = _scontext.getAttributeNames();
while(e.hasMoreElements())
{
String name = (String)e.nextElement();
Object value = _scontext.getAttribute(name);
setManagedAttribute(name,value);
}
}
}
其实蛮简单的,最主要的就是调用了contextListener的contextInitialized 方法。。。
好了。。到这里整个WebAppContext的创建和启动就差不多啦。。。。
总结一下整个过程:
(1)创建WebAppContext对象,设置它的contextPath,资源文件路径啥的。
(2)创建配置类对象。。。以及当前context的classLoader。。。
(3)解压当前的程序war包什么的,并用classLoader来载入里面的源文件jar包什么的。。
(4)处理webdefault.xml,例如里面声明的默认servlet,filter啥的。。
(5)处理用户的webinf里面的web.xml(默认是它),根据里面的servlet,listener啥的定义,创建相应的servletholder啥的。。。。
(6)调用contextListener的contextInitialized方法,接着还要获取用户定义的initParams,将他们保存在当前servletContext里面去。。
(7)初始化servletHandler,例如从classLoader里面获取servlet的class文件,如果配置了startup的话,还需要立即创建servlet对象,而且调用init方法。。。
本文详细解析了Jetty中WebAppContext的创建与启动过程,从WebAppDeployer工具类出发,深入探讨了如何通过扫描指定目录部署Web应用,创建WebAppContext并配置其classLoader,最终启动应用的全过程。
1291

被折叠的 条评论
为什么被折叠?



