tomcat請求處理分析(一) 啓動container實例

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
中的lifecycleEvent START_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) &&(cluster instanceof Lifecycle))
        ((Lifecycle) cluster).start()
;
   
Realm realm =getRealmInternal();
    if
((realm != null) &&(realm instanceof Lifecycle))
        ((Lifecycle) realm).start()
;

   
// 獲取子容器獲取HOST
   
Container children[] =findChildren();
   
List<Future<Void>>results = new ArrayList<>();
    for
(int i = 0; i <children.length; i++) {
        results.add(
startStopExecutor.submit(new StartChild(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 (pipeline instanceof 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 = new ArrayList<>();
    for
(int i = 0; i <children.length; i++) {
        results.add(
startStopExecutor.submit(new StartChild(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 StartChild implements Callable<Void>{

   
private Container child;

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

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


1.1.1.2    向standHost管道里面加入閥門

其添加的方式是獲取管道並調用其addValve方法進行添加,管道是在其父類ContainerBase中,其是一個成員變量,並且將this即standHost注入當前管道,

 

public Pipeline getPipeline() {

   
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 (current instanceof 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
(host instanceof 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 = new ArrayList<>();

    for
(int i = 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 =
new LinkedBlockingQueue<>();
   
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 DeployDescriptor implements Runnable {

   
private HostConfig config;
    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 = new File(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&& context instanceof 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(
new MultipartConfigElement(
                   multipartdef.getLocation()
,
                   
Long.parseLong(multipartdef.getMaxFileSize()),
                   
Long.parseLong(multipartdef.getMaxRequestSize()),
                   
Integer.parseInt(
                           multipartdef.getFileSizeThreshold())))
;
       
} else {
           wrapper.setMultipartConfigElement(
new MultipartConfigElement(
                   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(new ContainerBackgroundProcessor(), threadName);
   
thread.setDaemon(true);
   
thread.start();

}

 

   由上不難看出其會構建ContainerBackgroundProcessor實例,並調用其run方法,ContainerBackgroundProcessor類的結構如下

/**
 *
run方法中它會先暫停一段時間之後調用processChildren方法
 * */
protected class ContainerBackgroundProcessor implements 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 (container instanceof 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
(int i = 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 (container instanceof 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
(host instanceof 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(new DeployedApplication[0]);
        for
(int i = 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文件重新加載的一部分,另一部分則是實例化)

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章