Apache Camel - 在启动时触发任务仅运行一次

19

我正在使用Camel和Spring进行Java项目开发。我们希望在Spring完成所有操作并且Camel构建完所有路由后,触发单例bean的初始化方法。

我们不能在类创建时调用该方法,因为它与其他类具有动态链接,这些类是从@Component Spring注释中获取的,我们不知道这些类何时/是否已经加载,以便将init方法作为构造函数的一部分实际运行。

我该如何在Camel启动完成后仅调用一次方法或多个方法?

谢谢!

7个回答

35

另一种简单的选择是使用camel-timer,它可以让你更加灵活。设置repeatCount为1,延迟时间足够长以便让所有内容初始化完成。你还可以添加基本的异常处理来进行延迟/重试等操作。

from("timer://runOnce?repeatCount=1&delay=5000").to("bean:runOnceBean");

请问您能提供一些示例代码吗?我在尝试在我的类中使用from() API时遇到了困难。 - PAA
2
即使这样也应该可以工作: from("timer:// runOnce?repeatCount = 1").to("bean:runOnceBean");我认为我们在这里不需要延迟,因为如果我们将其保留在路由中,它只会在上下文准备就绪时被调用。如果我哪里想错了,请告诉我。 - Siddharth Sachdeva
正确,除非您正在等待某些异步内容启动上下文或希望事先准备好某些内容,否则延迟是不必要的。 - Ben ODay

11

2
'页面未找到'...另一个EventNotifier的示例:https://people.apache.org/~dkulp/camel/eventnotifier-to-log-details-about-all-sent-exchanges.html - Marek Manduch
http://web.archive.org/web/20190126135728/https://camel.apache.org/eventnotifier-to-log-details-about-all-sent-exchanges.html 在2019年仍然存在。 - Eljah

3

在你的bean的一个方法中添加逻辑,并使用@PostConstruct进行注释 - spring将在此bean完全初始化并设置其所有依赖项后调用此方法。

@Component
class SomeClass {

 @PostConstruct
 void init() {
 }

}

如果需要在整个 Spring 应用上下文完全初始化之后调用逻辑,则可以通过实现 LifeCycle 接口来实现。


谢谢。@PostConstruct 在全局意义上是什么意思?如果我正在查找某种类型的所有 @Component,例如使用 ApplicationContent.getBeansOfType(SomeType.class),它是否总是会选择这些组件的实例,即使 Spring 加载它们的顺序发生变化? - NightWolf
有没有不使用LifeCycle接口的方法来解决这个问题?例如,对于一个bean,你可以使用<bean class="MyClass" init-method="start" destroy-method="stop"/>来分别调用start()和stop()...我希望可能有类似于Camel Context的东西...你有使用LifeCycle接口的示例吗? - NightWolf
@gkamal 这样做不起作用,因为camelContext初始化并不一定在spring应用程序上下文完成后就完成了。因此,在post construct上调用它并不能保证camel context已经启动并且所有路由都可用。 - Fritz Duchardt

3
一种解决方案是修补几个文件(请参见PR https://github.com/apache/camel/pull/684):CamelContextConfiguration.java和RoutesCollector.java。
在CamelContextConfiguration中,添加以下方法:
void afterApplicationStart(CamelContext camelContext);

RoutesCollectoronApplicationEvent中添加如下内容:

        if (camelContextConfigurations != null) {
            for (CamelContextConfiguration camelContextConfiguration : camelContextConfigurations) {
                camelContextConfiguration.afterApplicationStart(camelContext);
            }
        }

如果您使用的是最新版本,则可以省略 if (camelContextConfigurations != null)

然后按照以下方式创建一个Spring bean以添加您的代码:

@Bean
CamelContextConfiguration contextConfiguration() {
    return new CamelContextConfiguration() {

        @Override
        public void beforeApplicationStart(CamelContext camelContext) {
        }

        @Override
        public void afterApplicationStart(CamelContext camelContext) {
            // Put your code here
        }
    };
}

更新:这个拉取请求已经合并。

2
您可以使用Camel文档中记录的启动顺序功能:http://camel.apache.org/configuring-route-startup-ordering-and-autostartup.html
<route startupOrder="1" id="thisOneGoesFirst">    
  <from uri="seda:foo"/>
  <to uri="mock:result"/>
</route>
<route startupOrder="2" id="thisOneGoesSecond">
  <from uri="direct:start"/>
  <to uri="seda:foo"/>
</route> 
<route id="thisOneGoesLast">
  <from uri="direct:bar"/>
  <to uri="seda:bar"/>
</route>

在具有startupOrder属性的路由之前,会按顺序执行这些路由,并且在所有没有startupOrder的路由之前执行。因此,您可以在任何时候都使用带有定时器consumer的路由,在启动您的路由之前或之后。


假设您使用startupOrder="1"定义了“thisOneGoesFirst”路由,其中包含一个“from”元素,该元素调用计时器以无延迟开始(例如“time:startup?repeatCount=1&delay=0”)。此时,计时器内部的逻辑是否会在“thisOneGoesSecond”路由内部的逻辑之前被调用?我们通过“startupOrder”知道计时器将首先被调用,但是我认为计时器只会调用另一个线程来运行“thisOneGoesFirst”路由内部的逻辑,并且该线程是否在“thisOneGoesSecond”内部的逻辑之前被调用是随机的。 - JohnC

1
你可以尝试将驼峰上下文注入到你的单例 Bean 中。注入将不会发生,直到上下文完全初始化…包括所有路由的构建。缺点是你可能实际上并不需要在你的 Bean 中使用上下文。我正在思考一个将单例 Bean 依赖链接到 spring 配置文件中的 camelContext 初始化的想法,但不确定那是否真的可行。

1
这样做不起作用,因为camelContext初始化完成的时间不一定是在spring应用程序上下文完成之后。 - Fritz Duchardt

1

正如之前的答案所暗示的那样,这更像是一个Spring而不是Camel的问题。在Spring中,您可以简单地实现InitializingBean并实现afterPropertiesSet方法。当装配完成时,将调用此方法。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接