在前面的文章中就已经提到过,在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方法。。。