Camel:无法动态添加路由

时间:2016-02-05 22:26:00

标签: java apache-camel tomcat7 java-7

我正在使用Apache-Camel 2.15.2。

我正在尝试动态地将路由添加到CamelContext,但我遇到了一个困扰我的问题。

据我所知,我确实将路由添加到正确的CamelContext,看起来他们的configure()被调用而没有抛出异常。但是当我尝试执行主路由时,我得到一个运行时异常告诉我,我动态添加的路由不存在。

以下是我的代码的简化版本:

public class MainRouteBuilder extends RouteBuilder
{
    public static CamelContext camelContext;

    public static boolean returnLabel = true;

    public static RouteBuilder nestedRouteBuilder;

    @Override
    public void configure() throws Exception
    {
        System.out.println("Building main route!");
        System.out.println("Context: " + getContext());

        camelContext = getContext();

        from("direct:mainRoute")
        //3. I do not actually want to instantiate RouteContainer like this each time I call this route.
        //I would simply want to reuse a reference to an instance I created outside of configure()...
        .to(new RouteContainer().getMyRoute(2))
        ;

        returnLabel = false;

        //configure direct:myRoute.2

        includeRoutes(nestedRouteBuilder);      
    }

}

public class RouteContainer extends RouteBuilder
{
    public Route route;

    RouteContainer() {
        super(MainRouteBuilder.camelContext);
    }

    String getMyRoute(final int n) {

        if (MainRouteBuilder.returnLabel && route == null) {
            route = new Route() {

                @Override
                public void configure()
                {

                    System.out.println("Building nested route!");
                    System.out.println("Context: " + getContext());

                    from("direct:myRoute." + n)
                    .transform()
                    .simple("number: " + n)
                    .to("stream:out")
                    .process(new Processor() {
                        @Override
                        public void process(Exchange exchange) throws Exception {
                            Response response = new Response();
                            response.setStatus(Status.SUCCESS);

                            exchange.getOut().setBody(response);
                        }
                    });
                }               
            };
        }

//1. works:
        MainRouteBuilder.nestedRouteBuilder = this;

//2. does not work:
//      RouteContainer routeContainer = new RouteContainer();
//      routeContainer.route = this.route;
//      MainRouteBuilder.nestedRouteBuilder = routeContainer;

        return "direct:myRoute." + n;
    }


    @Override
    public void configure() throws Exception {
        if (route != null) {
            route.configure();
        }
    }

    public abstract static class Route {        
        abstract public void configure();
    }

}

发送到direct:mainRoute的请求有效。 在Camel启动期间,我在控制台中看到:

Building main route!
Context: SpringCamelContext(camel-1) with spring id org.springframework.web.context.WebApplicationContext:/sample-route
Building nested route!
Context: SpringCamelContext(camel-1) with spring id org.springframework.web.context.WebApplicationContext:/sample-route

当我向direct:mainRoute发送请求时,输出为:

{"status":"SUCCESS"}

但是,如果我在上面注释掉(1)并取消注释(2),Camel会以相同的输出启动到控制台,但是当我向direct:mainRoute发送请求时,路由的执行失败除了:

org.apache.camel.component.direct.DirectConsumerNotAvailableException: No consumers available on endpoint: Endpoint[direct://myRoute.2].

澄清:我的问题是因为我实际上不希望每次调用它的路径时都不会实例化RouteContainer,就像我在(3)中所做的那样。这就是我在第(2)点实例化它们并将Route实例插入其中的原因......

所以我希望MainRouteBuilder看起来像这样:

public class MainRouteBuilder extends RouteBuilder
{
    public static CamelContext camelContext;

    public static boolean returnLabel = true;

    public static RouteBuilder nestedRouteBuilder;

    RouteContainer routeContainer = new RouteContainer();

    @Override
    public void configure() throws Exception
    {
        System.out.println("Building main route!");
        System.out.println("Context: " + getContext());

        camelContext = getContext();

        from("direct:mainRoute")
        .to(routeContainer.getMyRoute(2))
        //I may want to call it again like this:
        //.to(routeContainer.getMyRoute(3))
        ;

        returnLabel = false;

        //configure direct:myRoute.2

        includeRoutes(nestedRouteBuilder);      
    }

}

我的假设是,嵌套路由direct:myRoute.2可能是在错误的CamelContext中创建的,但控制台输出告诉我它不是这样。

知道我在这里做错了吗?

1 个答案:

答案 0 :(得分:8)

路线配置!=路线执行

您似乎将路由配置与路由执行混淆。我们都去过那里; - )

MainRouteBuilder#configure()中配置RouteBuilder时,该方法仅在Camel应用程序引导时执行一次,以便设置路由逻辑。 DSL为路径(处理器,拦截器等)创建管道,并为创建路由运行时。

要点回家:每个Exchange都反复执行DSL

换句话说,Camel没有按照你在(3)中指出的那样做。它不会为每个Exchange执行new RouteContainer().getMyRoute(2)。考虑一下:configure()的字节码仅在配置Camel时执行,字节码实例化类RouteContainer的对象,并使用参数getMyRoute调用2。生成的对象将被输入to() DSL生成的SendProcessor

您的代码分析

现在,关于为什么你的代码没有产生你期望的结果。

RouteContainer的状态保存存在问题。每次调用getMyRoute时,都会覆盖实例变量route。因此,您当前的代码无法多次调用getMyRoute(使用不同的n s),然后在最后调用includeRoutes一次,因为只会添加最近生成的路由

我也不喜欢用你自己的类来掩盖Camel Route类,只是作为占位符,但这会带来你不需要的不同讨论。

更简单的解决方案

这是一个RouteContainer类,而不是你的RouteGenerator类,它创建路由并将direct:端点返回给调用者。它跟踪内部Set中的所有路径。

public class RouteGenerator {

    private Set<RouteBuilder> routeBuilders = new HashSet<>();

    public String generateRoute(final int n) {

        routeBuilders.add(new RouteBuilder() {
            @Override public void configure() throws Exception {
                System.out.println("Building nested route!");
                System.out.println("Context: " + getContext());

                from("direct:myRoute." + n)
                    .transform() .simple("number: " + n)
                    .to("stream:out")
                    .process(new Processor() {
                        @Override
                        public void process(Exchange exchange) throws Exception {
                            Response response = new Response();
                            response.setStatus(Status.SUCCESS);
                            exchange.getOut().setBody(response);
                        }
                    });
            }
        });

        return "direct:myRoute." + n;

    }

    public Set<RouteBuilder> getRouteBuilders() {
        return routeBuilders;
    }

}

这是你的MainRouteBuilder,它只会实例化RouteGenerator一次,并且可以生成任意数量的路径。

完成路线配置后,您只需迭代累积的RouteBuilders并将其包含在内:

public class MainRouteBuilder extends RouteBuilder {

    public static CamelContext camelContext;

    public static RouteGenerator routeGenerator = new RouteGenerator();

    @Override
    public void configure() throws Exception {
        System.out.println("Building main route!");
        System.out.println("Context: " + getContext());

        camelContext = getContext();

        from("direct:mainRoute")
            .to(routeGenerator.generateRoute(2));


        for (RouteBuilder routeBuilder : routeGenerator.getRouteBuilders()) {
            includeRoutes(routeBuilder);
        }
    }

}

编辑:为什么你的选项(2)不起作用?

调试一段时间后,我意识到为什么你会看到这种效果。

摘自the Java Tutorial

  

与实例方法和变量一样,内部类是关联的   使用其封闭类的实例并可直接访问它   对象的方法和领域。

在(2)中,您在初始Route对象的范围内创建RouteContainer的实例,充当外部对象。 Route对象将外部RouteContainer保留为其外部对象。因此,在初始from()RouteContainer)对象上调用RouteBuilder及后续方法,而不是在您稍后创建的新RouteContainer上调用,这是您提供给上部RouteBuilder的一个(与CamelContext关联)。

这就是为什么你的direct:myRoute.2没有被添加到Camel Context中,因为它是在不同的路由构建器中创建的。

另请注意(2)的控制台输出:

Building main route!
Context: CamelContext(camel-1)
Building nested route!
Context: CamelContext(camel-2)
Added!

第二条路线被添加到不同的上下文camel-2。这个新的上下文是由Camel懒惰地添加路由到旧的RouteBuilder而创建的,而旧的RouteBuilder尚未与任何Camel Context相关联。

请注意,初始RouteContainer的Camel Context(在实例变量初始化中创建)为null,因为您稍后会分配MainRouteBuilder.camelContext属性。

您可以通过添加以下println语句来查看如何使用两个不同的路由构建器:

内部路线#configure:

System.out.println("RouteContainer to which route is added: " + RouteContainer.this.hashCode());

在MainRouteBuilder #confin中,就在includeRoutes之前:

System.out.println("RouteContainer loaded into Camel: " + nestedRouteBuilder.hashCode());

使用(1),哈希码是相同的。使用(2),哈希码是不同的,清楚地表明存在两个不同的RouteBuilder(一个包含路由,一个加载到Context中,不包括路由)

来源:我是Apache Camel PMC成员和提交者。