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