tomcat请求处理分析(一) 启动container实例

来源:互联网 发布:淘宝联盟转链工具 编辑:程序博客网 时间:2024/06/10 23:06

1.1.1  启动container实例

其主要是进行了生命周期中一系列的操作之后调用StandardEngine中的 startInternal方法,不难看出其调用其父类的startInternal方法, 其父类是ContainerBase.java

 

protected synchronized void startInternal()throws LifecycleException{
   
if(log.isInfoEnabled())
       
log.info("StartingServlet Engine: " + ServerInfo.getServerInfo());
    super
.startInternal();
}

     父类ContainerBase.java中的startInternal

/** @author 郑小康
 *
 * 1.
如果配置了集群组件Cluster则启动
 *
 * 2.
如果配置了安全组件,则启动
 *
 * 3
启动子节点,默认为StandContext
 *
 * 4.
启动Host所持有的Pipeline组件
 *
 * 5.
设置Host状态为STARTING此时会触发START_EVENT生命周期事件
 *  HostConfig
中的lifecycleEventSTART_EVENT时会调用其start方法
 *  HostConfig
监听该事件扫描web部署对于部署文件、WAR包、会自动创建StandardContext实例,添加到Host并启动
 *
 * 6.
启动Host层级的后台任务处理包括部署变更
 *
 * */
@Override
protectedsynchronized void startInternal()throws LifecycleException{

   
// Start our subordinatecomponents, if any
   
logger =null;
   
getLogger();
   
//集群
   
Cluster cluster =getClusterInternal();
    if
((cluster != null) &&(clusterinstanceof Lifecycle))
        ((Lifecycle) cluster).start()
;
   
Realm realm =getRealmInternal();
    if
((realm != null) &&(realminstanceof Lifecycle))
        ((Lifecycle) realm).start()
;

   
// 获取子容器获取HOST
   
Container children[] =findChildren();
   
List<Future<Void>>results = newArrayList<>();
    for
(inti = 0;i <children.length;i++) {
        results.add(
startStopExecutor.submit(newStartChild(children[i])));
   
}
   
boolean fail =false;
    for
(Future<Void>result : results) {
       
try {
            result.get()
;
       
} catch(Exceptione) {
           
log.error(sm.getString("containerBase.threadedStartFailed"),e);
           
fail = true;
       
}

    }
   
if (fail) {
       
throw new LifecycleException(
               
sm.getString("containerBase.threadedStartFailed"));
   
}

   
// Start the Valves in ourpipeline (including the basic), if any
   
if (pipelineinstanceof Lifecycle)
        ((Lifecycle)
pipeline).start();

   
//当前方法加载web应用
   
setState(LifecycleState.STARTING);

   
// Start our thread
   
threadStart();
}

 

1.1.1.1  启动StandHost

    获取engine下所有的host的实例,这个是在server.xml文件中定义的,其默认实现类是StandHost,在这里通过future模式进行处理,将所有StandHost给启动,默认server.xml中只有一个实例,所以在这里只是启动了一个标准的host虚拟主机

    Container children[] =findChildren();
   
List<Future<Void>>results = newArrayList<>();
    for
(inti = 0;i <children.length;i++) {
        results.add(
startStopExecutor.submit(newStartChild(children[i])));
   
}
   
boolean fail =false;
    for
(Future<Void>result : results) {
       
try {
            result.get()
;
       
} catch(Exceptione) {
           
log.error(sm.getString("containerBase.threadedStartFailed"),e);
           
fail = true;
       
}

    }

 

StartChild类的内部结构,通过future模式进行get的时候会调用其call方法,将standHost给启动

 

private static class StartChildimplements Callable<Void>{

   
private Containerchild;

    public
StartChild(Containerchild) {
       
this.child= child;
   
}

   
@Override
   
public Voidcall() throws LifecycleException{
       
child.start();
        return null;
   
}
}


1.1.1.2    向standHost管道里面加入阀门

其添加的方式是获取管道并调用其addValve方法进行添加,管道是在其父类ContainerBase中,其是一个成员变量,并且将this即standHost注入当前管道,

 

public PipelinegetPipeline() {

   
return (this.pipeline);

}

 

protected final Pipeline pipeline = newStandardPipeline(this);

 

public StandardPipeline(Container container) {
   
super();
   
setContainer(container);

}

   上面描述了获取管道的过程,下面是具体向管道中添加对应的阀门

 

protectedsynchronized void startInternal()throws LifecycleException{

   
// 获取错误报告阀门,该类的作用主要是在服务器处理异常的时输出错误界面
   
String errorValve =getErrorReportValveClass();
    if
((errorValve != null) &&(!errorValve.equals(""))) {
       
try {
           
boolean found =false;
           
//org.apache.catalina.core.StandardHostValve[localhost]
           //org.apache.catalina.valves.AccessLogValve[localhost]
           //errorValve==org.apache.catalina.valves.ErrorReportValve
给添加进去
           
Valve[] valves =getPipeline().getValves();
            for
(Valve valve : valves){
               
if (errorValve.equals(valve.getClass().getName())){
                    found =
true;
                    break;
               
}
            }
           
if(!found) {
                Valve valve =
                    (Valve) Class.forName(errorValve).newInstance()
;
               
getPipeline().addValve(valve);
           
}
        }
catch (Throwablet) {
            ExceptionUtils.handleThrowable(t)
;
       
}
    }
   
super.startInternal();
}

 

 

1.1.1.3  启动管道

  该方法是遍历管道里面所有的阀门,然后将他们依次给启动

protected synchronized void startInternal()throws LifecycleException{

   
Valve current = first;
    if
(current == null) {
        current =
basic;
   
}
   
while (current !=null) {
       
if (currentinstanceof Lifecycle)
            ((Lifecycle) current).start()
;
       
current =current.getNext();
   
}
    setState(LifecycleState.
STARTING);
}

 

org.apache.catalina.valves.AccessLogValve[localhost]   日志记录类

 

org.apache.catalina.valves.ErrorReportValve[localhost]  异常状态返回报告页

参考链接:http://www.10tiao.com/html/308/201702/2650076436/1.html

 

org.apache.catalina.core.StandardHostValve[localhost]

 

HostConfig

 

org.apache.catalina.LifecycleEvent[source=StandardEngine[Catalina].StandardHost[localhost]]

 

StandardEngine[Catalina].StandardHost[localhost]

 

1.1.1.4  加载web应用

setState(LifecycleState.STARTING);
  

加载web应用是在改变当前HostConfig类的状态为start的时候,调用其对应的监听时间,从而调用了该类的start方法,有下面该类的lifecycleEvent方法可以看出

public void lifecycleEvent(LifecycleEvent event){
    try {
       
host = (Host)event.getLifecycle();
        if
(hostinstanceof StandardHost){
            setCopyXML(((StandardHost)
host).isCopyXML());
           
setDeployXML(((StandardHost)host).isDeployXML())//liveDeploy属性指明host是否要周期性的检查是否有新的应用部署
           
setUnpackWARs(((StandardHost)host).isUnpackWARs());
           
setContextClass(((StandardHost)host).getContextClass());
       
}
    }
catch (ClassCastException e){
       
log.error(sm.getString("hostConfig.cce",event.getLifecycle()),e);
        return;
   
}

   
// Process the event thathas occurred
   
if (event.getType().equals(Lifecycle.PERIODIC_EVENT)) {
        check()
;
   
} else if(event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {
        beforeStart()
;
   
} else if(event.getType().equals(Lifecycle.START_EVENT)) {
        start()
;
   
} else if(event.getType().equals(Lifecycle.STOP_EVENT)) {
        stop()
;
    
}
}

 

具体的start方法代码如下:

public void start() {
   
if (log.isDebugEnabled())
       
log.debug(sm.getString("hostConfig.start"));
    try
{
        ObjectName hostON =
host.getObjectName();
       
oname =new ObjectName
            (hostON.getDomain() +
":type=Deployer,host="+host.getName());
       
Registry.getRegistry(null, null).registerComponent
            (
this, oname, this.getClass().getName());
   
} catch(Exceptione) {
       
log.error(sm.getString("hostConfig.jmx.register",oname),e);
   
}

   
if (!host.getAppBaseFile().isDirectory()){
       
log.error(sm.getString("hostConfig.appBase",host.getName(),
               
host.getAppBaseFile().getPath()));
       
host.setDeployOnStartup(false);
       
host.setAutoDeploy(false);
   
}

   
if (host.getDeployOnStartup())
        deployApps()
;

}
      根据代码不难发现共做了三件事,第一件是注册MBServer,第二件是监测其时候是文件,第三件是进行部署

 

1.1.1.5  部署web应用

   该方法是上面方法的具体实现,其先获取应用文件夹的路径,再获取配置文件的路径,然后进行三种应用加载方式,第一种,加载配置文件中所有web应用,第二种加载WARS形式所有应用,第三中加载webapps下所有的应用

protected void deployApps() {
   
//获取基本文件夹路径/project/eclipseWS/tomcatMac/output/build/webapps
   
File appBase = host.getAppBaseFile();
   
//获取配置文件路径/project/eclipseWS/tomcatMac/output/build/conf/Catalina/localhost
   
File configBase = host.getConfigBaseFile();
   
//webapps下的文件路径以字符串存放
   
String[]filteredAppPaths = filterAppPaths(appBase.list());
   
//根据配置文件部署web应用
   
deployDescriptors(configBase,configBase.list());
   
// 部署WARs
   
deployWARs(appBase,filteredAppPaths);
   
// 部署应用在webapps
   
deployDirectories(appBase,filteredAppPaths);
}

 

1.1.1.6  根据配置文件加载web应用

    根据配置文件,顾名思义就是通过指向的方式即config/catalina/localhost下的配置文件进行部署

protected void deployDescriptors(File configBase,String[]files) {
   
if (files ==null)
       
return;
   
ExecutorService es = host.getStartStopExecutor();
   
List<Future<?>>results = newArrayList<>();

    for
(inti = 0;i <files.length;i++) {

        //
        File contextXml =
new File(configBase,files[i]);

        if
(files[i].toLowerCase(Locale.ENGLISH).endsWith(".xml")) {
            ContextName cn =
new ContextName(files[i], true);

            if
(isServiced(cn.getName())|| deploymentExists(cn.getName()))
               
continue;

           
results.add(
                    es.submit(
new DeployDescriptor(this,cn, contextXml)));
       
}
    }

   
for (Future<?>result : results) {
       
try {
            result.get()
;
       
} catch(Exceptione) {
           
log.error(sm.getString(
                   
"hostConfig.deployDescriptor.threaded.error"),e);
        
}
    }
}

 

 

 

第一步:检测文件夹是否为空,为空则返回

第二步:获取线程池,该线程是在初始化HostConfig的时候实例化的

protected void initInternal()throws LifecycleException{
    BlockingQueue<Runnable>startStopQueue =
newLinkedBlockingQueue<>();
   
startStopExecutor =new ThreadPoolExecutor(
            getStartStopThreadsInternal()
,
           
getStartStopThreadsInternal(),10, TimeUnit.SECONDS,
           
startStopQueue,
            new
StartStopThreadFactory(getName()+"-startStop-"));
   
startStopExecutor.allowCoreThreadTimeOut(true);
    super
.initInternal();

}

第三步:遍历所有后缀名为.xml构建其DeployDescriptor实例添加到future集合中去,进行异步执行,其中DeployDescriptor类结构如下

private static class DeployDescriptorimplements Runnable {

   
private HostConfigconfig;
    private
ContextName cn;
    private
File descriptor;

    public
DeployDescriptor(HostConfigconfig,ContextName cn,
           
File descriptor) {
       
this.config= config;
        this
.cn= cn;
        this
.descriptor= descriptor;
   
}

   
@Override
   
public void run() {
       
config.deployDescriptor(cn,descriptor);
   
}

}

 

 

由上可见根据配置文件最终执行的还是deployDescriptor方法,该方法的操做有构建DeployedApplication实例,获取配置文件名,以及将文件流标签解析成对应的ContextConfig对象,创建监听器,获取docBase的值,通过host.addChild(context)将其加到文件监听中,这样修改该文件夹中的文件,就会通过监听线程进行获取,下文会分析监听线程的具体操作,这里不讲述,在这之后finally里面的操作是解析配置文件,找到docBase,把应用存放到deployed里面,这样做的目的是一个虚拟主机可能能存在多个web应用,在deployed这个map里面存放的key是web应用,v是对应的deployedApp,这里面的存放了web.xml等文件的位置如下例子:

成员变量:redeployResources

0 = {LinkedHashMap$Entry@3050} "/project/eclipseWS/tomcatMac/output/build/conf/Catalina/localhost/test.xml"-> "1502378759000"

1 = {LinkedHashMap$Entry@3051}"/project/eclipseWS/tomcatMac/output" -> "1501518597000"

2 = {LinkedHashMap$Entry@3052}"/project/eclipseWS/tomcatMac/output/build/conf/context.xml" ->"1501478677000"

成员变量:reloadResources

 

   0 = {HashMap$Node@2944}"/project/eclipseWS/tomcatMac/output/WEB-INF/web.xml" ->"0"

1= {HashMap$Node@3062}"/project/eclipseWS/tomcatMac/output/build/conf/web.xml" ->"1502089964000"

    代码具体执行过程如下:

 

protected void deployDescriptor(ContextName cn,FilecontextXml) {

   
// 构建DeployedApplication实例主要是将web应用名赋值
   
DeployedApplicationdeployedApp =
           
new DeployedApplication(cn.getName(), true);

    long
startTime = 0;
   
// Assume this is aconfiguration descriptor and deploy it
   
if(log.isInfoEnabled()){
       startTime = System.currentTimeMillis()
;
      
log.info(sm.getString("hostConfig.deployDescriptor",
               
contextXml.getAbsolutePath()));
   
}

    Context context =
null;
    boolean
isExternalWar = false;
    boolean
isExternal = false;
   
File expandedDocBase = null;
   
//获取文件流解析成对应的context实例,在生成之后,它会清空digester为下次解析流做准备
   
try (FileInputStreamfis =new FileInputStream(contextXml)) {
       
synchronized (digesterLock) {
           
try {
                context = (Context)
digester.parse(fis);
           
} catch(Exceptione) {
               
log.error(sm.getString(
                       
"hostConfig.deployDescriptor.error",
                       
contextXml.getAbsolutePath()),e);
           
} finally{
               
digester.reset();
                if
(context == null) {
                    context =
new FailedContext();
               
}
            }
        }
        
//class org.apache.catalina.startup.ContextConfig
       
Class<?> clazz =Class.forName(host.getConfigClass());
       
LifecycleListenerlistener =
            (LifecycleListener)clazz.newInstance()
;
       
//给当前StandContext添加ContextConfig这个监听器
        
context.addLifecycleListener(listener);
       
//设置配置文件的路径
       
context.setConfigFile(contextXml.toURI().toURL());
       
//给当前容器设置名字
       
context.setName(cn.getName());
       
//设置web用用的上下文路径
       
context.setPath(cn.getPath());
       
//设置web应用的版本
       
context.setWebappVersion(cn.getVersion());
       
// Add the associateddocBase to the redeployed list if it's a WAR
       
if (context.getDocBase()!=null) {
            File docBase =
new File(context.getDocBase());
            if
(!docBase.isAbsolute()){
                docBase =
new File(host.getAppBaseFile(),context.getDocBase());
           
}
           
/**
             *
给部署的deployedApp实例的redeployResources这个Map集合添加两条记录
             *
第一条是以配置文件路径为key时间为v
             *
第二提是配置文件的odcBase指向的路径
             *
如果docBase所指向的是一个war包,将isExternalWar标记为true
             */
           
if (!docBase.getCanonicalPath().startsWith(
                   
host.getAppBaseFile().getAbsolutePath()+ File.separator)) {
                isExternal =
true;
               
deployedApp.redeployResources.put(contextXml.getAbsolutePath(),
                       
Long.valueOf(contextXml.lastModified()));

               
deployedApp.redeployResources.put(docBase.getAbsolutePath(),
                       
Long.valueOf(docBase.lastModified()));
                if
(docBase.getAbsolutePath().toLowerCase(Locale.ENGLISH).endsWith(".war")) {
                    isExternalWar =
true;
               
}
            }
else {
               
log.warn(sm.getString("hostConfig.deployDescriptor.localDocBaseSpecified",
                        
docBase));
               
// Ignore specifieddocBase
               
context.setDocBase(null);
           
}
        }

      
 host.addChild(context);
   
} catch(Throwable t){
        ExceptionUtils.handleThrowable(t)
;
       
log.error(sm.getString("hostConfig.deployDescriptor.error",
                              
contextXml.getAbsolutePath()),t);
   
} finally{
       
//检查是否存在默认的appBase即在webapps下面有没有对应该项目名的文件夹,简而言之就是后面路径存在就覆盖,不存在就是用默认的
       
expandedDocBase = newFile(host.getAppBaseFile(),cn.getBaseName());
      
//如果docBase不为空,并且不是以后缀名.war结束的,获取当前文件路径
       
if (context.getDocBase()!=null
               
&&!context.getDocBase().toLowerCase(Locale.ENGLISH).endsWith(".war")) {
            expandedDocBase =
new File(context.getDocBase());
           
//如果不是绝对路径,则获取的基本应用路径+docBase的路径
           
if (!expandedDocBase.isAbsolute()){
                expandedDocBase =
new File(host.getAppBaseFile(),context.getDocBase());
           
}
        }

       
boolean unpackWAR =unpackWARs;//true才会每次重新解压war
       
if (unpackWAR&& contextinstanceof StandardContext) {
            unpackWAR =((StandardContext) context).getUnpackWAR()
;
       
}

       
// Add the eventualunpacked WAR and all the resources which will be
        // watched inside it
        //
如果是war包,并且unpackWARtrue则将其加到redeployResources集合,并且添加监听
       
if (isExternalWar){
           
if (unpackWAR){
                deployedApp.
redeployResources.put(expandedDocBase.getAbsolutePath(),
                       
Long.valueOf(expandedDocBase.lastModified()));
               
addWatchedResources(deployedApp,expandedDocBase.getAbsolutePath(),context);
           
} else{
               addWatchedResources(deployedApp
, null,context);
           
}
        }
else {
           
// Find an existingmatching war and expanded folder
           
if (!isExternal){
                File warDocBase =
new File(expandedDocBase.getAbsolutePath()+".war");
                if
(warDocBase.exists()){
                    deployedApp.
redeployResources.put(warDocBase.getAbsolutePath(),
                           
Long.valueOf(warDocBase.lastModified()));
               
} else{
                    
// Trigger a redeploy if aWAR is added
                   
deployedApp.redeployResources.put(
                           warDocBase.getAbsolutePath()
,
                           
Long.valueOf(0));
               
}
            }
           
if (unpackWAR){
                deployedApp.
redeployResources.put(expandedDocBase.getAbsolutePath(),
                       
Long.valueOf(expandedDocBase.lastModified()));
               
//将其下所有web.xml文件加到deployedAppreloadResources里面
               
addWatchedResources(deployedApp,
                       
expandedDocBase.getAbsolutePath(),context);
           
} else{
               addWatchedResources(deployedApp
, null,context);
           
}
           
if (!isExternal){
               
// For external docBases,the context.xml will have been
                // added above.
               
deployedApp.redeployResources.put(
                       contextXml.getAbsolutePath()
,
                       
Long.valueOf(contextXml.lastModified()));
            
}
        }
       
// Add the global redeployresources (which are never deleted) at
        // the end so they don'tinterfere with the deletion process
       
addGlobalRedeployResources(deployedApp);//添加全局的资源文件
   
}

   
//将其以应用名为key  deployedApp实例为key存放到当前HostConfig实例里面
   
if (host.findChild(context.getName())!=null) {
       
deployed.put(context.getName(),deployedApp);
   
}

   
if (log.isInfoEnabled()){
       
log.info(sm.getString("hostConfig.deployDescriptor.finished",
           
contextXml.getAbsolutePath(),Long.valueOf(System.currentTimeMillis()- startTime)));
   
}
}

在上面讲述了配置文件的方式,默认文件夹和war包部署大同小异,不做解释。

1.1.1.7  加载wraper

这个是在部署web应用的一个具体操作,每部署一个Web引用

 

host.addChild(context);

public void addChild(Container child) {
   
if (Globals.IS_SECURITY_ENABLED) {
        PrivilegedAction<Void> dp =
           
new PrivilegedAddChild(child);
       
AccessController.doPrivileged(dp);
   
} else {
        addChildInternal(child)
;
   
}
}

  根据代码可以看出调用的是addChildInternal方法,其中child是StandardEngine[Catalina].StandardHost[localhost].StandardContext[/test]这个实例,代码如下:

 

private void addChildInternal(Container child) {

   
if( log.isDebugEnabled())
       
log.debug("Addchild "+ child + " "+ this);
    synchronized
(children) {
       
if (children.get(child.getName())!=null)
           
throw new IllegalArgumentException("addChild:  Child name '"+
                                              child.getName() +
                                              
"' is not unique");
        
child.setParent(this)// May throw IAE
       
children.put(child.getName(),child);
   
}
    try {
       
if ((getState().isAvailable()||
                LifecycleState.
STARTING_PREP.equals(getState()))&&
               
startChildren) {
            
child.start();
       
}
    }
catch (LifecycleExceptione) {
       
log.error("ContainerBase.addChild:start: ",e);
        throw new
IllegalStateException("ContainerBase.addChild:start: "+ e);
   
} finally{
        fireContainerEvent(
ADD_CHILD_EVENT,child);
   
}
}

主要是会调用start方法,根据上文知道其是一个StandardContext实例,所以调用的是StandardContext.start()方法,start都是LifecycleBase里面,最终调用的还是StandardContext中的startInternal方法

这个方法做了很多操作,如设置上下文参数,启动监听器过滤器,但是这些不是我主要要描述的内容,我要描述的如何将web应用的具体servlet给封装

 

protected synchronized void startInternal()throws LifecycleException{

           
//触发CONFIGURE_START_EVENT事件
           
fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);
  }

 

而后会执行其监听器ContextConfig,的configureStart方法,这个方法的核心是执行里面的webConfig

执行顺序configureStart==》webConfig==》  configureContext(webXml)

public void lifecycleEvent(LifecycleEvent event){

   
// Identify the context weare associated with
   
try {
       
context = (Context)event.getLifecycle();
   
} catch(ClassCastExceptione) {
       
log.error(sm.getString("contextConfig.cce",event.getLifecycle()),e);
        return;
   
}
  
 if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)){
        configureStart();
    }
else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {
        beforeStart()
;
   
} else if(event.getType().equals(Lifecycle.AFTER_START_EVENT)) {
        if (originalDocBase!=null) {
           
context.setDocBase(originalDocBase);
       
}
    }
else if (event.getType().equals(Lifecycle.CONFIGURE_STOP_EVENT)) {
        configureStop()
;
   
} else if(event.getType().equals(Lifecycle.AFTER_INIT_EVENT)) {
        init()
;
   
} else if(event.getType().equals(Lifecycle.AFTER_DESTROY_EVENT)) {
        destroy()
;
   
}

}

   下面这个方法就是configureContext中具体构造wrapper对象并添加到StandWrapper,在这里只需要明确的事实例化对象是在这个过程,至于具体的使用在后面会进行讲解

for (ServletDef servlet : webxml.getServlets().values()) {
    Wrapper wrapper =
context.createWrapper();
   
// Description is ignored
    // Display name is ignored
    // Icons are ignored

    // jsp-file gets passed to the JSPServlet as an init-param

   
if (servlet.getLoadOnStartup()!=null) {
       wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue())
;
   
}
   
if (servlet.getEnabled()!=null) {
        wrapper.setEnabled(servlet.getEnabled().booleanValue())
;
   
}
   wrapper.setName(servlet.getServletName())
;
   
Map<String,String>params = servlet.getParameterMap();
    for
(Entry<String,String>entry : params.entrySet()) {
       wrapper.addInitParameter(entry.getKey()
,entry.getValue());
   
}
    wrapper.setRunAs(servlet.getRunAs())
;
   
Set<SecurityRoleRef>roleRefs = servlet.getSecurityRoleRefs();
    for
(SecurityRoleRefroleRef : roleRefs) {
        wrapper.addSecurityReference(
                roleRef.getName()
, roleRef.getLink());
   
}
   wrapper.setServletClass(servlet.getServletClass())
;
   
MultipartDefmultipartdef = servlet.getMultipartDef();
    if
(multipartdef != null) {
       
if (multipartdef.getMaxFileSize()!=null&&
               multipartdef.getMaxRequestSize()!=
null&&
               multipartdef.getFileSizeThreshold() !=
null) {
           wrapper.setMultipartConfigElement(
newMultipartConfigElement(
                   multipartdef.getLocation()
,
                   
Long.parseLong(multipartdef.getMaxFileSize()),
                   
Long.parseLong(multipartdef.getMaxRequestSize()),
                   
Integer.parseInt(
                           multipartdef.getFileSizeThreshold())))
;
       
} else{
           wrapper.setMultipartConfigElement(
newMultipartConfigElement(
                   multipartdef.getLocation()))
;
       
}
    }
   
if (servlet.getAsyncSupported()!=null) {
        wrapper.setAsyncSupported(
                servlet.getAsyncSupported().booleanValue())
;
   
}
   wrapper.setOverridable(servlet.isOverridable())
;
   
context.addChild(wrapper);

}

 

1.1.1.8  监听文件修改重部署

这个是开启了一个线程进行处理的,在上文加载了web应用之后,可能存在修改里面的文件,这样在重新访问的时候应该访问到的新界面,这个就是threadStart();这个方法所做的事情,下面我们看一下它究竟是怎么处理

 

 

/**

 *@author 郑小康
 *
校验条件通过的话将会启动一个线程
 *
 *
并且线程的名字即以"ContainerBackgroundProcessor[ "开头,线程名字后面取的是对象的toString方法
 *
 *
其中backgroundProcessorDelay的作用是:
 *
 *   StandardEngine
StandardHost都继承了当前类,是否都启动了这个线程
 *
 *   
其实不然,这就是backgroundProcessorDelay的作用,在StandardEngine实例化的时候其赋值为10
 *   
StandardHost却并没有,所以只有在StandardEngine调用startInternal的时候才会启动线程
 */
protected void threadStart() {

   
//检验线程是否存在,存在直接返回,这样做的目的是避免该类的线程二次启动
   
if (thread!= null)
       
return;

    if
(backgroundProcessorDelay<=0)
       
return;

   
threadDone =false;
   
String threadName = "ContainerBackgroundProcessor["+toString() +"]";
   
thread =new Thread(newContainerBackgroundProcessor(),threadName);
   
thread.setDaemon(true);
   
thread.start();

}

 

   由上不难看出其会构建ContainerBackgroundProcessor实例,并调用其run方法,ContainerBackgroundProcessor类的结构如下

/**
 *
run方法中它会先暂停一段时间之后调用processChildren方法
 * */
protected class ContainerBackgroundProcessorimplements Runnable {

   
@Override
   
public void run() {
        Throwable t =
null;
       
StringunexpectedDeathMessage = sm.getString(
               
"containerBase.backgroundProcess.unexpectedThreadDeath",
               
Thread.currentThread().getName());
        try
{
           
while (!threadDone) {
               
try {
                    Thread.sleep(
backgroundProcessorDelay*1000L);
               
} catch(InterruptedExceptione) {
               
}
               
if (!threadDone) {
                   processChildren(ContainerBase.
this);
               
}
            }
        }
catch (RuntimeException|Errore) {
            t = e
;
            throw
e;
       
} finally{
           
if (!threadDone) {
               
log.error(unexpectedDeathMessage,t);
           
}
        }

}

看一下processChildren方法的实现,如下:

 

protected void processChildren(Container container) {
        ClassLoader originalClassLoader =
null;

        try
{
           
if (containerinstanceof Context) {
                Loader loader =((Context) container).getLoader()
;
               
// Loader will be null forFailedContext instances
               
if (loader ==null) {
                   
return;
               
}
                originalClassLoader =((Context) container).bind(false, null);
           
}
            container.backgroundProcess()
;
           
Container[] children =container.findChildren();
            for
(inti = 0;i <children.length;i++) {
               
if (children[i].getBackgroundProcessorDelay()<=0) {
                   processChildren(children[i])
;
               
}
            }
        }
catch (Throwablet) {
            ExceptionUtils.handleThrowable(t)
;
           
log.error("Exceptioninvoking periodic operation: ",t);
       
} finally{
            
if (containerinstanceof Context) {
                ((Context)container).unbind(
false,originalClassLoader);
          
}
        }
    }

}

processChildren方法做了两件事,一是调用容器组件自身的backgroundProcess方法,二是取出该容器组件的所有子容器组件并调用它们的processChildren方法。归结起来这个线程的实现就是定期通过递归的方式调用当前容器及其所有子容器的backgroundProcess方法。而这个backgroundProcess方法在ContainerBase内部已经给出了实现

 

/**
 * 1.
获取所有集群 (不是我研究的重点)
 *
 * 2.
获取用户管理(不是我研究的重点)
 *
 * 3.
执行其阀门下面所有的backgroundProcess方法,可以看出这是一个递归执行backgroundProcess
 *
 *  engine  -->   host ---->  context 
这是一个大致过程
 *
 * 4.
调用生命周期的监听方法,修改状态为PERIODIC_EVENT
 * */
@Override
publicvoid backgroundProcess() {

   
if (!getState().isAvailable())
       
return;

   
Cluster cluster =getClusterInternal();
    if
(cluster != null) {
       
try {
            cluster.backgroundProcess()
;
       
} catch(Exceptione) {
           
log.warn(sm.getString("containerBase.backgroundProcess.cluster",
                   
cluster),e);
       
}
    }
    Realm realm = getRealmInternal()
;
    if
(realm != null) {
       
try {
            realm.backgroundProcess()
;
       
} catch(Exceptione) {
           
log.warn(sm.getString("containerBase.backgroundProcess.realm",realm), e);
       
}
    }
    Valve current =
pipeline.getFirst();
    while
(current != null) {
       
try {
            current.backgroundProcess()
;
       
} catch(Exceptione) {
           
log.warn(sm.getString("containerBase.backgroundProcess.valve",current), e);
       
}
        current = current.getNext()
;
   
}
  
 fireLifecycleEvent(Lifecycle.PERIODIC_EVENT,null);
}

   这样做的目的,我觉得主要是找到所有HostConfig,然后修改其状态为PERIODIC_EVENT这样就可以执行对应的方法

 

public void lifecycleEvent(LifecycleEvent event){

   
// Identify the host weare associated with
   
try {
       
host = (Host)event.getLifecycle();
        if
(hostinstanceof StandardHost){
            setCopyXML(((StandardHost)
host).isCopyXML());
           
setDeployXML(((StandardHost)host).isDeployXML())//liveDeploy属性指明host是否要周期性的检查是否有新的应用部署
           
setUnpackWARs(((StandardHost)host).isUnpackWARs());
           
setContextClass(((StandardHost)host).getContextClass());
       
}
    }
catch (ClassCastExceptione) {
       
log.error(sm.getString("hostConfig.cce",event.getLifecycle()),e);
        return;
   
}

    if(event.getType().equals(Lifecycle.PERIODIC_EVENT)) {
        check();
    }
else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {
        beforeStart()
;
   
} else if(event.getType().equals(Lifecycle.START_EVENT)) {
        start()
;
   
} else if(event.getType().equals(Lifecycle.STOP_EVENT)) {
        stop()
;
   
}

}

check方法代码如下:

protected void check() {
   
if (host.getAutoDeploy()){
      
        DeployedApplication[]apps =
           
deployed.values().toArray(newDeployedApplication[0]);
        for
(inti = 0;i < apps.length;i++) {
           
if (!isServiced(apps[i].name))
                checkResources(apps[i]
, false);
       
}

       
// Check for old versionsof applications that can now be undeployed
       
if (host.getUndeployOldVersions()){
            checkUndeploy()
;
       
}
       
deployApps();
   
}

}

现在我们值得讨论的是,为什么在启动之后,修改配置文件会加载新的内容,这是因为ContainerBackgroundProcessor这个新开的线程实例里面的run方法是一个死循环,每隔10秒都会执行一下,调用HostConfig的声明周期事件,并传入状态为PERIODIC_EVENT,这样的话就会不断的调用check方法,不断的进行重部署(注意:这并不是热部署,热部署是修改了java文件,只收class文件发生了修改,但是java文件在修改之后,编辑器能够自动编译成class文件,但是这需要涉及到class文件的重加载,因为它不像静态文件直接读取,所以这只是java文件重新加载的一部分,另一部分则是实例化)

 

原创粉丝点击