现在的位置: 首页 > 综合 > 正文

jetty中web程序的创建与启动

2014年07月04日 ⁄ 综合 ⁄ 共 12737字 ⁄ 字号 评论关闭

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



抱歉!评论已关闭.