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包,并且unpackWAR为true则将其加到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文件加到deployedApp的reloadResources里面 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文件重新加载的一部分,另一部分则是实例化)

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏微服务生态

Flume-NG源码分析-整体结构及配置载入分析

终于开始Flume源码的分析研究工作了,我也是边学边和大家分享,内容上难免有不足之处,望大家见谅。

794
来自专栏菩提树下的杨过

redis 学习笔记(5)-Spring与Jedis的集成

首先不得不服Spring这个宇宙无敌的开源框架,几乎整合了所有流行的其它框架,http://projects.spring.io/spring-data/ 从这...

2117
来自专栏牛肉圆粉不加葱

[源码剖析]Spark读取配置Spark读取配置

我们知道,有一些配置可以在多个地方配置。以配置executor的memory为例,有以下三种方式:

733
来自专栏Ryan Miao

SpringCloud学习6-如何创建一个服务消费者consumer

2034
来自专栏IT杂记

Mapreduce 任务提交源码分析1

提交过程 一般我们mapreduce任务是通过如下命令进行提交的 $HADOOP_HOME/bin/hadoop jar $MR_JAR $MAIN_CLASS...

2226
来自专栏Create Sun

基础拾遗-----mongoDB操作

前言   nosq互联网中运用极广的技术,mongo应该算是必不可少的技术之一,虽说我在项目中redis用的较多,mongo在项目中算是用的比较少的技术,但是也...

32511
来自专栏潇涧技术专栏

Lint Tool Analysis (2)

本系列的几篇源码分析文档意义不大,如果你正好也在研究lint源码,或者你想知道前面自定义lint规则中提出的那几个问题,抑或你只是想大致了解下lint的源码都有...

661
来自专栏皮皮之路

【Spring】Spring boot多数据源历险记

2056
来自专栏IT进修之路

原 JAVA懒开发:对象虚拟化,全程无实体

2162
来自专栏xingoo, 一个梦想做发明家的程序员

Java程序员的日常—— Properties文件的读写

在日常的Java程序开发中,Properties文件的读写是很常用的。经常有开发系统通过properties文件来当做配置文件,方便用户对系统参数进行调整。 ...

1757

扫码关注云+社区