Apache Camel源碼研究之啓動

Apache Camel發展十多年,代碼量和邏輯跳轉比較多,筆者接觸也不久,出於興趣粗淺閱讀了一下,因此本文只能算是拋磚引玉。不足之處還請一笑置之。
基於筆者的文筆,以及對於Apache Camel的瞭解程度,只靠這一篇文章將清楚Apache Camel的核心啓動邏輯是不可能的(哪怕講給自己聽),因此本文之後將有多篇補充性文檔來輔助理解這個過程。

1. 概述

Apache Camel作爲一個實現了大部分EIP的集成組件,發展至今已經擁有了三百多個擴展組件,如何管理這些組件的生命週期,以及如何在合適的時機調用它們的自定義邏輯勢必是一個非常具有挑戰的任務,而Camel的啓動屬於這個挑戰性任務中非常重要的一環。

2. 源碼解讀

2.1 樣例代碼

以下是我們本次的樣例代碼,我們儘量精簡了不必要的細節,減少關注點,集中注意力到啓動邏輯上。

DefaultCamelContext camelContext = new DefaultCamelContext();
camelContext.addRoutes(new RouteBuilder() {
		@Override
		public void configure() throws Exception {
			from("timer://myTimer?period=2000")//
					.setBody(simple("Current time is [ ${header.firedTime} ]"))//
					.to("stream:err");
		}
	});
camelContext.start();

代碼中路由的邏輯是啓動一個每兩秒支持一次的定時任務,該定時任務每次執行事後將當前時間輸出到異常和錯誤反饋的輸出流中(爲了醒目)。

因爲下面涉及的代碼量比較大,所以筆者把一些核心邏輯列舉在這裏,讓讀者有個初步的概念,看不懂沒關係,本來源碼的理解就是個慢工出細活的過程。

  1. 以上自定義配置最終在應用中構建出一個 TimerConsumer 實例, CamelContext的啓動邏輯會確保該實例只初始化一次,且在適當的時機被啓動,對應就是TimerConsumer.doStart()以及TimerConsumer.onCamelContextStarted()(因爲Camel要確保該定時任務是在CamelContext啓動完畢之後再執行)
  2. 上述TimerConsumer 實例被構造時候,作爲其構造函數參數processor傳入的實際類型爲CamelInternalProcessor,這個類看名字就知道是Camel內部使用的一個Processor(而這個processor對於Camel的重要性,從該類上的註釋內容就可見一斑)。 TimerConsumer 實例在執行定時任務時候其實就是調用的CamelInternalProcessor.process(Exchange exchange)方法。
  3. 至於上面第二步中的CamelInternalProcessor實例的構建邏輯,感興趣的讀者可以參考源碼 - DefaultRouteContext.commit() 方法。
  4. 限於篇幅等原因,接下來陳述一下在本例中,Camel中的幾個重要概念是如何組織的:
    a. CamelInternalProcessor。上面已經說過了,Consumer類內部的那個processor就是本類型 ; 而本類相較於基類,最大的改進是增加了Advice功能,我們以後要接觸的UoW功能就是使用其來實現的。
    b. PipelineCamelInternalProcessor的繼承鏈決定了其內部肯定包含一個processor,而這個processor在本例中正是 Pipeline類型。本例中,Camel會將from()之後的部分, 最終被Camel處理爲一個個Processor類型的節點(例如本例裏的SetBodyProcessorSendProcessor), 而Pipeline則是負責將它們封裝起來,將它們的順序執行封裝到Pipeline內部,減少客戶端的記憶負擔。
    c. Channel。上面在談到Pipeline的時候,我們提到Camel會將from()後面的部分解析爲一個個的Processor,但其實Camel並沒有直接將構建出來的Processor實例暴露出去,而是經過了一次Wrap,具體源碼位於ProcessorDefinition.wrapProcessor()。觀察Channel的繼承鏈就會發現,其唯一的實現類DefaultChannel是直接繼承自CamelInternalProcessor的,因此DefaultChannel天然就具有Advice能力。像什麼StreamCache,Delayer,MessageHistory,BacklogDebugger等功能實現,正是依託於Channel的這個能力。而且在DefaultChannel.initChannel()中,除了填充一系列既定的Advice之外,配置的InterceptStrategy也將在其中生效。
  5. 鑑於第四步裏的解釋,我們甚至可以大致猜測出一般情況下Route的執行邏輯:
    a. 執行由Consumer開始,因爲其直接繼承自Service,所以最終一定會被調用start()方法(或者是繼承自StartupListener接口的方法等),然後Consumer內部的CamelInternalProcessor實例的process()方法被執行。
    b. CamelInternalProcessor.process()執行的過程就是將控制權交給內部的processor去執行的過程,而這個processor一般情況下就是Pipeline。
    c. 而Pipeline.processor會依次調用內部的processor集合(Item類型爲Channel)去執行。
    d.Channel的執行過程就是將內部的Advice集合代表的邏輯,以及內部的processor代表的邏輯,按照設計的順序依次執行完畢。
  6. 總結一下本例中 CamelInternalProcessorPipelineChannel的關係。
    a. Consumer實例中有一個CamelInternalProcessor實例。
    b. CamelInternalProcessor實例中有一個 Pipeline實例。
    c. 而 Pipeline 實例中有兩個 Channel實例。
    d. 每個 Channel 實例分別代表 setBody(), to() 邏輯。(Advice邏輯省略)

2.2 源碼解讀

正如標題所言,本次我們的重心分爲兩個部分。

2.2.1 準備階段

也就是 CamelContext.addRoutes(final RoutesBuilder builder);方法了。

camelContext.addRoutes(new RouteBuilder(){
	@Override
	public void configure() throws Exception {
        from("timer://myTimer?period=2000")//	from()定義了一個Endoint, 將生成一個 RouteDefinition 實例, 並向其中注入一個FromDefinition實例(存儲在RouteDefinition類的inputs字段中, 注意這個inputs字段爲List類型, 也就是說多個from是可以的)	
            .setBody(simple("Current time is [ ${header.firedTime} ]"))// 生成一個 SetBodyDefinition 實例, 添加到RouteDefinition類的outputs字段中, 注意這個outputs字段爲List類型
            .to("stream:err") // 構造一個 ToDefinition  實例, 添加到RouteDefinition類的outputs字段中, 注意這個outputs字段爲List類型
            ;
        
        // 所以
        //	1. 以上這段, 最終生成了一個RouteDefinition實例
    }
});

// DefaultCamelContext.addRoutes()
public void addRoutes(final RoutesBuilder builder) throws Exception {
    log.debug("Adding routes from builder: {}", builder);
    // 指定執行時候線程上下文中的ClassLoader
    // 這種方式確保了執行時候ClassLoader的正確性, 以及執行完畢之後的線程上下文ClassLoader的復原
    // 另外注意, 這一步是中Camel將另起一條內部線程, 專門用於啓動邏輯, 讓出主線程, 即camelContext.start()將立即返回.
    doWithDefinedClassLoader(new Callable<Void>() {
        @Override
        public Void call() throws Exception {
        	// 詳解見下方
            builder.addRoutesToCamelContext(DefaultCamelContext.this);
            return null;
        }
    });
}

// RouteBuilder.addRoutesToCamelContext()
public void addRoutesToCamelContext(CamelContext context) throws Exception {
    // 先配置route,然後纔是rest
    // must configure routes before rests
    configureRoutes((ModelCamelContext) context);
    configureRests((ModelCamelContext) context);

    
    // but populate rests before routes, as we want to turn rests into routes
    // 接下來就正好相反了, 先Rest再Route ; 因爲這裏我們會將 rests轉換爲route, 保持概念一致
    // 在populateRests()方法中, 即會讀取rest相關配置, 例如 restConfiguration() 
    // 注意在camel-3.x中, Rest被作爲單獨的組件獨立出去了
    // 如何構造一個 RestDefinition?, 使用諸如: rest().get("/hello")//
    // RestDefinition有一個專門的方法 asRouteDefinition(CamelContext camelContext)
    // 這裏面有一個 from.getUri().startsWith("rest-api:"), 就是爲了防止多個RouteBuilder時候, 添加多次rest-api
    populateRests();
    // 將之前組裝完畢的RouteDefinition集合加入到DefaultCamelContext實例的相應routeDefinitions字段中
    populateRoutes();
}

// RouteBuilder.configureRoutes()
public RoutesDefinition configureRoutes(ModelCamelContext context) throws Exception {
    setContext(context);
    // 詳解見下方
    checkInitialized();
    routeCollection.setCamelContext(context);
    return routeCollection;
}


// RouteBuilder.checkInitialized()
protected void checkInitialized() throws Exception {
    if (initialized.compareAndSet(false, true)) {
        // Set the CamelContext ErrorHandler here
        ModelCamelContext camelContext = getContext();
        if (camelContext.getErrorHandlerBuilder() != null) {
            setErrorHandlerBuilder(camelContext.getErrorHandlerBuilder());
        }
        // 這個就是我們在上面覆寫的那個
        configure();
        // mark all route definitions as custom prepared because
        // a route builder prepares the route definitions correctly already
        for (RouteDefinition route : getRouteCollection().getRoutes()) {
            // 標識已經準備就緒了, 可以啓動了
            route.markPrepared();
        }
    }
}
2.2.2 啓動階段

也就是 CamelContext.start();方法了。

  1. DefaultCamelContext.start()方法
	// DefaultCamelContext.start()
	//		DefaultCamelContext.doStart()
	//			DefaultCamelContext.doStartCamel()
	// ===================== 爲了節省篇幅,前置的一些啓動狀態的變更,  指定線程相關聯的ClassLoader等操作這裏就不展開了
	//   DefaultCamelContext.doStartCamel()
	  private void doStartCamel() throws Exception {
	  
	      // 自定義屬性中可能使用到了屬性佔位符, 因此我們需要先將它們替換爲真實的值
	      // 這裏的properties可以通過 DefaultCamelContext 實例的 setProperties()來進行設置, 		
	      // custom properties may use property placeholders so resolve those early on
	      if (properties != null && !properties.isEmpty()) {
	          for (Map.Entry<String, String> entry : properties.entrySet()) {
	              String key = entry.getKey();
	              String value = entry.getValue();
	              if (value != null) {
	                  String replaced = resolvePropertyPlaceholders(value);
	                  if (!value.equals(replaced)) {
	                      if (log.isDebugEnabled()) {
	                          log.debug("Camel property with key {} replaced value from {} -> {}", new Object[]{key, value, replaced});
	                      }
	                      entry.setValue(replaced);
	                  }
	              }
	          }
	      }
	      
	      // 爲ClassResolver實現類注入必要的屬性....
	      // 用戶可以通過DefaultCamelContext實例的 setClassResolver() 進行自定義設置
	      if (classResolver instanceof CamelContextAware) {
	          ((CamelContextAware) classResolver).setCamelContext(this);
	      }
	
		  // 對於CamelContext實例中的屬性值進行監控:
		  // 輸出系統執行時候的內部狀態信息, 方便掌握系統運行時細節
		  //	1. classResolver (ClassLoader類型)
		  //	2. packageScanClassResolver (ClassLoader類型)
		  //	3. applicationContextClassLoader (ClassLoader類型)
		  //	4. streamCache (Boolean類型) , 默認爲false
		  //	5. trace (Boolean類型) , 默認爲false
		  //	6. useMDCLogging (Boolean類型) , 默認爲false
		  //	7. delay (Long類型) , 讓Camel的Pipeline執行速度放緩, 方便跟蹤問題; 相關的關鍵類有: CamelInternalProcessor.DelayerAdvice, DelayInterceptor (不再推薦), DelayProcessorSupport
	
		  // 默認爲false
	      if (isHandleFault()) {
	      	  // 確保 context.getInterceptStrategies() 中只存在一個HandleFault
	          // only add a new handle fault if not already configured
	          if (HandleFault.getHandleFault(this) == null) {
	              log.info("HandleFault is enabled on CamelContext: {}", getName());
	              addInterceptStrategy(new HandleFault());
	          }
	      }
			
		  // // 註冊Debugger ; 對應一個Debug 的 InterceptStrategy
	      // register debugger
	      if (getDebugger() != null) {
	          log.info("Debugger: {} is enabled on CamelContext: {}", getDebugger(), getName());
	          // register this camel context on the debugger
	          getDebugger().setCamelContext(this);
	          startService(getDebugger()); // Debugger接口繼承自Service, 因此使用之前需要啓動
	          // 放到InterceptStrategy中, Debug類直接實現了InterceptStrategy接口
	          addInterceptStrategy(new Debug(getDebugger()));
	      }
	
	      // 通過JMX來管理Camel內部組件, 擁有兩個實現: DefaultManagementStrategy(不進行管理), ManagedManagementStrategy(使用JMX)
	      //   managementStrategy實例由 DefaultCamelContext 的構造函數中實例化
	      //	 提前啓動 ManagementStrategy
	      // start management strategy before lifecycles are started
	      ManagementStrategy managementStrategy = getManagementStrategy();
	      // inject CamelContext if aware
	      if (managementStrategy instanceof CamelContextAware) {
	          ((CamelContextAware) managementStrategy).setCamelContext(this);
	      }
	      ServiceHelper.startService(managementStrategy);
	
		  // 默認情況下, lifecycleStrategies集合中只有一個元素, 是由ManagementStrategyFactory類會向其中添加的DefaultManagementLifecycleStrategy
		  // LifecycleStrategy接口定義了一系列的事件, 貫穿整個CamelContext生命週期. 
	      // start lifecycle strategies
	      ServiceHelper.startServices(lifecycleStrategies);
	      Iterator<LifecycleStrategy> it = lifecycleStrategies.iterator();
	      while (it.hasNext()) {
	          LifecycleStrategy strategy = it.next();
	          try {
	              strategy.onContextStart(this);
	          } catch (VetoCamelContextStartException e) { // Veto : 拒絕認可; 禁止
	              // okay we should not start Camel since it was vetoed
	              log.warn("Lifecycle strategy vetoed starting CamelContext ({}) due {}", getName(), e.getMessage());
	              throw e;
	          } catch (Exception e) {
	              log.warn("Lifecycle strategy " + strategy + " failed starting CamelContext ({}) due {}", getName(), e.getMessage());
	              throw e;
	          }
	      }
	
		  // 事件播發器; 默認爲空, 可以通過 getManagementStrategy().addEventNotifier() 進行添加
	      // 相關輔助類: EventNotifierSupport
	      // start notifiers as services
	      for (EventNotifier notifier : getManagementStrategy().getEventNotifiers()) {
	          if (notifier instanceof Service) {
	              Service service = (Service) notifier;
	              for (LifecycleStrategy strategy : lifecycleStrategies) {
	                  strategy.onServiceAdd(this, service, null);
	              }
	          }
	          if (notifier instanceof Service) {
	              startService((Service)notifier);
	          }
	      }
		
		  // 和Tomcat類似, 還有個staring狀態的事件通知
		  // 默認無操作, 相關輔助類: EventFactory.createCamelContextStartingEvent()
	      // must let some bootstrap service be started before we can notify the starting event
	      EventHelper.notifyCamelContextStarting(this);
	
	   // 對於一些必要的服務(例如Registry實現類(JndiRegistry, ApplicationContextRegistry), 等等), 其默認爲懶加載, 但因爲其他組件對它們有需要, 因此我們在這裏將其喚醒
	    // 在2.17.0版本下, 會強制加載如下內容(也就是爲CamelContext實例中相應的字段賦值): 
	    //		getRegistry(); 默認實現: PropertyPlaceholderDelegateRegistry , 其實際將功能委託給了內部的delegate, delegate類型默認爲JndiRegistry
	    //		getInjector();  默認實現: DefaultFactoryFinder
	    //		getLanguageResolver(); 默認實現: DefaultLanguageResolver
	    //		getTypeConverterRegistry();  默認實現: DefaultTypeConverter 這個類的基類BaseTypeConverterRegistry同時實現了TypeConverter, TypeConverterRegistry;;; 因此本方法中會同時爲字段typeConverterRegistry, typeConverter賦值 
	    //		getTypeConverter(); 默認實現: DefaultTypeConverter
	    //		getRuntimeEndpointRegistry(); 默認實現 DefaultRuntimeEndpointRegistry
	      forceLazyInitialization();
	
		  // 注意這裏的endpoints字段在DefaultCamelContext構造函數中已經初始化過了, 這裏是重新再生成一次實例。
	      // re-create endpoint registry as the cache size limit may be set after the constructor of this instance was called.
	      // and we needed to create endpoints up-front as it may be accessed before this context is started
	      endpoints = new DefaultEndpointRegistry(this, endpoints);
	      
	      // 以下的一系列 addService() 和 doAddService()  最終都會調用 doAddService(Object object, boolean stopOnShutdown) ,  這個方法裏會根據傳入對象的類型進行各類操作, 這也符合筆者在簡書裏的博客內容 —— 接口起到了標籤的作用
	      // 注意這個addService邏輯裏,  最終會startService, 也就是啓動當前Service      
	      addService(endpoints);
	      // 這裏顯式聲明executorServiceManager將被手動關閉, 而非由框架自動處理相關關閉邏輯
	      // special for executorServiceManager as want to stop it manually
	      doAddService(executorServiceManager, false);
	      addService(producerServicePool);
	      addService(pollingConsumerServicePool);
	      addService(inflightRepository);
	      addService(asyncProcessorAwaitManager);
	      addService(shutdownStrategy);
	      addService(packageScanClassResolver);
	      addService(restRegistry);
	      addService(messageHistoryFactory);
	
		  // 用於統計程序運行時候在Route時候Endpoint的使用情況
		  //	在默認實現中是藉助EventNotifier來完成的
		  //	相關類: DefaultRuntimeEndpointRegistry
	      if (runtimeEndpointRegistry != null) {
	          if (runtimeEndpointRegistry instanceof EventNotifier) {
	              getManagementStrategy().addEventNotifier((EventNotifier) runtimeEndpointRegistry);
	          }
	          addService(runtimeEndpointRegistry);
	      }
	
		  // 找出properties component, 避免之後的執行流程需要用到該組件, 造成隱性的性能消耗
	      // eager lookup any configured properties component to avoid subsequent lookup attempts which may impact performance
	      // due we use properties component for property placeholder resolution at runtime
	      Component existing = CamelContextHelper.lookupPropertiesComponent(this, false);
	      if (existing != null) {
	          // store reference to the existing properties component
	          if (existing instanceof PropertiesComponent) {
	              propertiesComponent = (PropertiesComponent) existing;
	          } else {
	              // properties component must be expected type
	              throw new IllegalArgumentException("Found properties component of type: " + existing.getClass() + " instead of expected: " + PropertiesComponent.class);
	          }
	      }
	
	      // components默認爲空。。。。
	      // 這裏主要是考慮到用戶會通過 CamelContext.addComponent() 來自定義組件, 例如:  camelContext.addComponent(componentName, component);
	      // start components
	      startServices(components.values());
	
	    // 在啓動route之前, 先啓動RouteDefinition
	    // 其實最終都會回調RouteDefinition.addRoutes()用來構造出相應的RouteContext集合, 
	    //	並將該 RouteContext集合使用RouteService(該類是RouteDefinition的運行時表現)封裝, 最終啓動該 RouteService實例。。。  
	      //   DefaultCamelContext.startRouteDefinitions()
	      //		DefaultCamelContext.startRoute(RouteDefinition route)
	      //			RouteDefinition.addRoutes(ModelCamelContext camelContext, Collection<Route> routes)
	      //				RouteDefinition.addRoutes(CamelContext camelContext, Collection<Route> routes, FromDefinition fromType)
	      //					ProcessorDefinition.addRoutes(RouteContext routeContext, Collection<Route> routes) // 其中會回調ProcessorDefinition實現類(例如這裏的SetBodyDefinition)的createProcessor(RouteContext routeContext)方法, 因爲這裏的邏輯比較複雜, 處於篇幅考慮, 這裏先省略, 注意這裏創建出來的Processor並不會直接返回, 而是會經過一系列的wrap, 附加上額外的邏輯.
	      //					DefaultRouteContext.commit()
	      // start the route definitions before the routes is started
	      startRouteDefinitions(routeDefinitions);
	
		  // 再次確認是否啓用了StreamCache
	      // is there any stream caching enabled then log an info about this and its limit of spooling to disk, so people is aware of this
	      boolean streamCachingInUse = isStreamCaching();
	      if (!streamCachingInUse) {
	          for (RouteDefinition route : routeDefinitions) {
	              Boolean routeCache = CamelContextHelper.parseBoolean(this, route.getStreamCache());
	              if (routeCache != null && routeCache) {
	                  streamCachingInUse = true;
	                  break;
	              }
	          }
	      }
	
		  // 默認開啓,如果確認不需要可以關閉以改善性能
	      if (isAllowUseOriginalMessage()) {
	          log.info("AllowUseOriginalMessage is enabled. If access to the original message is not needed,"
	                  + " then its recommended to turn this option off as it may improve performance.");
	      }
	
		  // 默認爲false
	      if (streamCachingInUse) {
	          // stream caching is in use so enable the strategy
	          getStreamCachingStrategy().setEnabled(true);
	          addService(getStreamCachingStrategy());
	      } else {
	          // log if stream caching is not in use as this can help people to enable it if they use streams
	          log.info("StreamCaching is not in use. If using streams then its recommended to enable stream caching."
	                  + " See more details at http://camel.apache.org/stream-caching.html");
	      }
	
	      // start routes
	      if (doNotStartRoutesOnFirstStart) {
	          log.debug("Skip starting of routes as CamelContext has been configured with autoStartup=false");
	      }
	
		  // 又一個核心邏輯, 預熱甚至直接啓動使用者所定義的route
	      // invoke this logic to warmup the routes and if possible also start the routes
	      doStartOrResumeRoutes(routeServices, true, !doNotStartRoutesOnFirstStart, false, true);
	
	      // starting will continue in the start method
	  }
  1. DefaultCamelContext.doStartOrResumeRoutes()方法
	// DefaultCamelContext.doStartOrResumeRoutes()
	protected void doStartOrResumeRoutes(Map<String, RouteService> routeServices, boolean checkClash,
	                                     boolean startConsumer, boolean resumeConsumer, boolean addingRoutes) throws Exception {
	    // 使用 ThreadLocal<Boolean> 來進行確保同一線程內只有一個啓動的操作在執行
	    isStartingRoutes.set(true);
	    try {
	        // 過濾掉已經啓動的route, 只記錄未啓動的route用作之後的操作.....
	        // filter out already started routes 
	        Map<String, RouteService> filtered = new LinkedHashMap<String, RouteService>();
	        for (Map.Entry<String, RouteService> entry : routeServices.entrySet()) {
	            boolean startable = false;
	
	            // 一般情況下這個consumer爲null
	            Consumer consumer = entry.getValue().getRoutes().iterator().next().getConsumer();
	            if (consumer instanceof SuspendableService) {
	                // consumer could be suspended, which is not reflected in the RouteService status
	                // consumer可以被掛起, 但這不會被反應到RouteService的狀態上
	                startable = ((SuspendableService) consumer).isSuspended();
	            }
	
	            if (!startable && consumer instanceof StatefulService) {
	                // consumer could be stopped, which is not reflected in the RouteService status
	                // consumer可以被停止, 但這也不會被反應到RouteService的狀態上
	                startable = ((StatefulService) consumer).getStatus().isStartable();
	            } else if (!startable) {
	                // no consumer so use state from route service
	                // 如果沒有consumer, 則直接取RouteService的狀態
	                startable = entry.getValue().getStatus().isStartable();
	            }
	
	            if (startable) {
	                filtered.put(entry.getKey(), entry.getValue());
	            }
	        }
	
	        if (!filtered.isEmpty()) {
	            // the context is now considered started (i.e. isStarted() == true))
	            // starting routes is done after, not during context startup
	            // 此時context已經啓動完畢, 現在要開始啓動RouteService ; 詳見下方說明
	            safelyStartRouteServices(checkClash, startConsumer, resumeConsumer, addingRoutes, filtered.values());
	        }
	
	        // 先移除狀態, 再作事件回調
	        // we are finished starting routes, so remove flag before we emit the startup listeners below
	        isStartingRoutes.remove();
	
	        // 默認兩個實現:
	        //		1. org.apache.camel.impl.DeferServiceStartupListener (確保Service啓動, 補漏)
	        //		2. org.apache.camel.management.DefaultManagementLifecycleStrategy.TimerListenerManagerStartupListener
	        // now notify any startup aware listeners as all the routes etc has been started,
	        // allowing the listeners to do custom work after routes has been started
	        for (StartupListener startup : startupListeners) {
	            startup.onCamelContextStarted(this, isStarted());
	        }
	    } finally {
	        // 確保移除 startingRoute 狀態
	        isStartingRoutes.remove();
	    }
	}
  1. DefaultCamelContext.safelyStartRouteServices()方法
	// DefaultCamelContext.safelyStartRouteServices()
	protected synchronized void safelyStartRouteServices(boolean checkClash, boolean startConsumer, boolean resumeConsumer,
	                                                     boolean addingRoutes, Collection<RouteService> routeServices) throws Exception {
	    // 將這些routeServices按照啓動順序進行排序, 值由RouteDefinition.getStartupOrder()設置
	    // 可以通過 from().startupOrder(order) 進行設置
	    // list of inputs to start when all the routes have been prepared for starting
	    // we use a tree map so the routes will be ordered according to startup order defined on the route
	    Map<Integer, DefaultRouteStartupOrder> inputs = new TreeMap<Integer, DefaultRouteStartupOrder>();
	
	    // figure out the order in which the routes should be started
	    for (RouteService routeService : routeServices) {
	        // 這裏的技巧是Camel定義了一個RouteStartupOrder接口, 利用其將這裏的排序再封裝一次...
	        DefaultRouteStartupOrder order = doPrepareRouteToBeStarted(routeService);
	        // check for clash before we add it as input
	        if (checkClash) {
	            doCheckStartupOrderClash(order, inputs);
	        }
	        inputs.put(order.getStartupOrder(), order);
	    }
		
	    // 預熱..... 這裏將調用每個RouteService.warmUp() , 詳見下方
	    // warm up routes before we start them
	    doWarmUpRoutes(inputs, startConsumer);
		
	    // 默認爲true
	    if (startConsumer) {
	        // 默認爲false
	        if (resumeConsumer) {
	            // and now resume the routes
	            doResumeRouteConsumers(inputs, addingRoutes);
	        } else {
	            // and now start the routes
	            // and check for clash with multiple consumers of the same endpoints which is not allowed
	            // 正式開始啓動Route-Consumer
	            doStartRouteConsumers(inputs, addingRoutes);
	        }
	    }
	
	    // inputs no longer needed
	    inputs.clear();
	}
  1. RouteService.warmUp()方法
	// RouteService.warmUp()
	public synchronized void warmUp() throws Exception {
	    // 技巧是使用AtomicBoolean類型確保多線程情況下此段邏輯只執行一次
	    if (endpointDone.compareAndSet(false, true)) {
	        // endpoints should only be started once as they can be reused on other routes
	        // and whatnot, thus their lifecycle is to start once, and only to stop when Camel shutdown
	        for (Route route : routes) {
	            // ensure endpoint is started first (before the route services, such as the consumer)
	            // 先預熱endpoint
	            ServiceHelper.startService(route.getEndpoint());
	        }
	    }
	    // 使用AtomicBoolean類型確保多線程情況下此段邏輯只執行一次
	    if (warmUpDone.compareAndSet(false, true)) {
	        for (Route route : routes) {
	            // warm up the route first
	            // 上面已經預熱了endpoint, 現在開始預熱Route ; 默認類型 EventDrivenConsumerRoute, 唯一的Route接口的實例類, 默認實現是清空其下所有的services
	            route.warmUp();
	
	            LOG.debug("Starting services on route: {}", route.getId());
	            List<Service> services = route.getServices();
	
	            // callback that we are staring these services
	            // 回調, 正是在這裏面會調用Endpoint.createConsumer() 來創建Consumer實例; 詳見 EventDrivenConsumerRoute.addServices(List<Service> services)
	            //	且會爲Consumer注入 Route實例(通過實現 RouteAware 接口)
	            route.onStartingServices(services);
	
	            // gather list of services to start as we need to start child services as well
	            Set<Service> list = new LinkedHashSet<Service>();
	            for (Service service : services) {
	                list.addAll(ServiceHelper.getChildServices(service));
	            }
	
	            // split into consumers and child services as we need to start the consumers
	            // afterwards to avoid them being active while the others start
	            List<Service> childServices = new ArrayList<Service>();
	            for (Service service : list) {
	
	                // inject the route
	                if (service instanceof RouteAware) {
	                    ((RouteAware) service).setRoute(route);
	                }
	
	                if (service instanceof Consumer) {
	                    inputs.put(route, (Consumer) service);
	                } else {
	                    childServices.add(service);
	                }
	            }
	            // 其中會觸發 LifecycleStrategy.onServiceAdd() 事件
	            startChildService(route, childServices);
	
	            // fire event
	            EventHelper.notifyRouteAdded(camelContext, route);
	        }
	
	        // ensure lifecycle strategy is invoked which among others enlist the route in JMX
	        for (LifecycleStrategy strategy : camelContext.getLifecycleStrategies()) {
	            strategy.onRoutesAdd(routes);
	        }
			
			// 將Route實例添加到CamelContext實例中
	        // add routes to camel context
	        camelContext.addRouteCollection(routes);
	    }
	}

3. 總結

  1. Apache Camel和Tomcat類似,都是採用了Lifecycle的概念來控制自身Context以及相關Plugin組件的啓動… 通過定義一系列生命週期事件,相關類有EventFactory, EventNotifier,EventHelper,LifecycleStrategy等。
  2. CamelContext作爲方法參數貫穿了整個初始化啓動過程,這樣在初始化過程中的任何環節和節點,輔助類都能獲取到自己需要的信息,這對於後期維護以及可能的自定義擴展都是非常契合的。
  3. 將啓動過程中的一些關鍵邏輯彙總如下:
	DefaultCamelContext.start()
		DefaultCamelContext.doStart()
			DefaultCamelContext.doStartCamel()
				addInterceptStrategy(new HandleFault());  // 需要設置camelContext.setHandleFault(true)
				addInterceptStrategy(new Debug(getDebugger())); // 需要設置camelContext.setDebugger(Debugger debugger)
				ServiceHelper.startService(managementStrategy); // 啓動JMX監控, managementStrategy默認類型爲ManagedManagementStrategy
				ServiceHelper.startServices(lifecycleStrategies); // 啓動所有註冊的 LifecycleStrategy
				lifecycleStrategy.onContextStart(this); // 回調
				lifecycleStrategy.onServiceAdd(this, eventNotifier, null); // start notifiers as services
				startService((Service)notifier); // start notifiers as services
				EventHelper.notifyCamelContextStarting(this); // 播發CamelContextStartingEvent
				forceLazyInitialization(); // 將一些基礎組件喚醒, 爲之後的操作預熱
				addService(xxx); // 啓動一系列內部的組件
				startServices(components.values()); // 啓動一系列用戶自定義組件
				startRouteDefinitions(routeDefinitions); // 啓動一系列自定義的RouteDefinition, 這裏面的核心邏輯是創建RouteService, 並添加到全局變量routeServices中, 方法體裏雖然有啓動RouteService的邏輯, 但初次啓動時候不會觸發
				doStartOrResumeRoutes(routeServices, true, !doNotStartRoutesOnFirstStart, false, true); // 預熱甚至啓動用戶定義的Route
					safelyStartRouteServices(checkClash, startConsumer, resumeConsumer, addingRoutes, filtered.values());
						doWarmUpRoutes(inputs, startConsumer);
							routeService.warmUp(); // 其中會回調Endpoint.createConsumer()
					startupListener.onCamelContextStarted(this, isStarted());

4. 參考鏈接:

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