Apache Camel - 9 - Camel路由条件

Routing with Camel(Camel的路由)

Apache Camel相关代码已经上传GitHub,需要的自取:GitHub - Apache Camel 完整Demo

如果觉得还行,麻烦点个Star

 

Oneof the most important features of Camel is routing; without it, Camel wouldessentially be a library of transport connectors. In this chapter, we’ll diveinto routing with Camel.

Camel最重要的特点之一是路由;没有它,Camel本质上是一个运输连接器库。 在这一章中,我们将深入Camel路由。

Routinghappens in many aspects of everyday life. When you mail a letter, for instance,it may be routed through several cities before reaching its final address.

路由发生在日常生活的许多方面。例如,当你邮寄一封信时,它可能会在达到最终地址之前通过多个城市。

Anemail you send will be routed through many different computer network systemsbefore reaching its final destination. In all cases, the router’s function isto selectively move the message forward.

您发送的电子邮件将通过许多不同的计算机网络系统路由到达最终目的地之前。在所有情况下,路由器的功能是有选择地将消息向前移动

Amessage router consumes messages from an input channel and, depending on a setof conditions, sends the message to one of a set of output channels.

消息路由器消耗来自输入通道的消息,并根据一组条件将消息发送到一组输出通道之一。

Inthe context of enterprise messaging systems, routing is the process by which amessage is taken from an input queue and, based on a set of conditions, sent toone of several output queues, as shown in figure 2.1.

在企业消息传递系统中,路由是指从输入队列中获取消息的过程,并根据一组条件发送到多个输出队列之一,如图2.1所示。

Thiseffectively means that the input and output queues are unaware of theconditions in between them.

这实际上意味着输入和输出队列不知道它们之间的条件。

Theconditional logic is decoupled from the message consumer and producer.

条件逻辑与消息使用者和生产者分离

InApache Camel, routing is a more general concept. It’s defined as a step-by-stepmovement of the message, which originates from an endpoint in the role of aconsumer. The consumer could be receiving the message from an external service,polling for the message on some system, or even creating the message itself.This message then flows through a processing component, which could be anenterprise integration pattern (EIP), a processor, an interceptor, or someother custom creation. The message is finally sent to a target endpoint that’sin the role of a producer. A route may have many processing components thatmodify the message or send it to another location, or it may have none, inwhich case it would be a simple pipeline.

在ApacheCamel中,路由是一个更一般的概念。它被定义为一步一步的消息的移动,消息的起源于消费者角色的端点。消费者可以从外部服务接收消息,在某个系统上轮询消息,甚至创建消息本身。然后这个消息流经一个处理组件,这个组件可能是一个企业集成模式(EIP),一个处理器,一个拦截器或者其他一些自定义的创建。该消息最终被发送到作为生产者角色的目标端点。一个路由可能有许多处理组件修改消息或发送到另一个位置,或者它可能没有,在这种情况下,它将是一个简单的管道

 

Routing路由条件

控制端点和处理器之间处理器与处理器之间,Camel允许开发人员进行路由条件的设置。

例如开发人员可以拥有当ExchangeIn Message的内容为A的情况下将消息送入下一个处理器A,当Exchange In Message的内容为B时将消息送入下一个处理器B的处理能力。

又例如,无论编排的路由中上一个元素的处理消息如何,都将携带消息的Exchange对象复制多份,分别送入下一处理器X、Y、Z。

开发人员甚至还可以通过路由规则完成Exchange到多个Endpoint的负载传输。

Camel中支持的路由规则非常丰富,包括:MessageFilter、Based Router、Dynamic Router、Splitter、Aggregator、Resequencer等等。

在Camel的官方文档中使用了非常形象化的图形来表示这些路由功能

http://camel.apache.org/enterprise-integration-patterns.html)

这里我们选取两个比较有代表性的路由规则进行讲解:Content Based RouterRecipientList

 

希望对各位读者理解Camel中的路由规则有所帮助:

 

 

Content Based Router 基于内容的路由

把ContentBased Router译为基于内容的路由,笔者觉得更为贴切(并不能译作基本路由,实际上你无法定义什么是基本路由)。

它并不是一种单一的路由方式,而是多种基于条件和判断表达式的路由方式。

其中可能包括choice语句/方法、when语句/方法、otherwise语句/方法。请看以下示例:

代码一:

package com.camel.choicecamel;

import org.apache.camel.impl.DefaultCamelContext;
import org.apache.camel.model.ModelCamelContext;
import org.apache.log4j.PropertyConfigurator;


/**
 *  * 基于内容的路由
 * <p>
 *  *
 * <p>
 *  * @author CYX
 * <p>
 *  * @time 2017年12月15日下午3:23:11
 * <p>
 *  
 */

public class ChoiceCamel {
    
    public static void main(String[] args) throws Exception {

        PropertyConfigurator.configure("./conf/log4j.properties");

        PropertyConfigurator.configureAndWatch("./conf/log4j.properties", 1000);


        // 这是camel上下文对象,整个路由的驱动全靠它了。
        ModelCamelContext camelContext = new DefaultCamelContext();
        
        // 启动route
        camelContext.start();
        
        // 将我们编排的一个完整消息路由过程,加入到上下文中
        camelContext.addRoutes(new ChoiceCamelRouteBuilder());


        // 通用没有具体业务意义的代码,只是为了保证主线程不退出
        synchronized (ChoiceCamel.class) {
            ChoiceCamel.class.wait();
        }
    }
}

 

代码二:

package com.camel.choicecamel;

import java.io.InputStream;

import org.apache.camel.Exchange;
import org.apache.camel.ExchangePattern;
import org.apache.camel.Message;
import org.apache.camel.Processor;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.http.common.HttpMessage;
import org.apache.camel.model.language.JsonPathExpression;
import org.apache.commons.io.IOUtils;

public class ChoiceCamelRouteBuilder extends RouteBuilder {


    @Override
    public void configure() throws Exception {
        // 这是一个JsonPath表达式,用于从http携带的json信息中,提取orgId属性的值
        JsonPathExpression jsonPathExpression = new JsonPathExpression("$.data.orgId");
        jsonPathExpression.setResultType(String.class);
        System.out.println("jsonPathExpression : " + jsonPathExpression);
        // 通用使用http协议接受消息
        from("jetty:http://0.0.0.0:8282/choiceCamel")
                // 首先送入HttpProcessor,
                // 负责将exchange in Message Body之中的stream转成字符串                
                // 当然,不转的话,下面主要的choice操作也可以运行
                // HttpProcessor中的实现和上文代码片段中的一致,这里就不再重复贴出                
                .process(new Processor() {
                    @Override
                    public void process(Exchange exchange) throws Exception {
                        // 因为很明确消息格式是http的,所以才使用这个类
                        // 否则还是建议使用org.apache.camel.Message这个抽象接口
                        HttpMessage message = (HttpMessage) exchange.getIn();
                        InputStream bodyStream = (InputStream) message.getBody();
                        String inputContext = IOUtils.toString(bodyStream, "UTF-8");
                        System.out.println("inputContext -- : " + inputContext);
                        bodyStream.close();
                        // 存入到exchange的out区域
                        if (exchange.getPattern() == ExchangePattern.InOut) {
                            Message outMessage = exchange.getOut();
                            outMessage.setBody(inputContext + " || out");
                        }
                    }
                })
                // 将orgId属性的值存储 exchange in Message的header中,以便后续进行判断
                .setHeader("orgId", jsonPathExpression).choice()
                // 当orgId == yuanbao,执行OtherProcessor
                // 当orgId == yinwenjie,执行OtherProcessor2
                // 其它情况执行OtherProcessor3
                .when(header("orgId").isEqualTo("yuanbao")).process(new Processor() {
            @Override
            public void process(Exchange exchange) throws Exception {
                // 因为很明确消息格式是http的,所以才使用这个类
                // 否则还是建议使用org.apache.camel.Message这个抽象接口
                HttpMessage message = (HttpMessage) exchange.getIn();
                String body = message.getBody().toString();
                System.out.println("OtherProcessor body -- : " + body);
                // 存入到exchange的out区域
                if (exchange.getPattern() == ExchangePattern.InOut) {
                    Message outMessage = exchange.getOut();
                    outMessage.setBody(body + " || 被OtherProcessor处理");
                }
            }
        }).when(header("orgId").isEqualTo("yinwenjie")).process(new Processor() {
            @Override
            public void process(Exchange exchange) throws Exception {
                // 因为很明确消息格式是http的,所以才使用这个类
                // 否则还是建议使用org.apache.camel.Message这个抽象接口
                HttpMessage message = (HttpMessage) exchange.getIn();
                String body = message.getBody().toString();
                System.out.println("OtherProcessor2 body -- : " + body);
                // 存入到exchange的out区域
                if (exchange.getPattern() == ExchangePattern.InOut) {
                    Message outMessage = exchange.getOut();
                    outMessage.setBody(body + " || 被OtherProcessor2处理");
                }
            }
        }).otherwise().process(new Processor() {
            @Override
            public void process(Exchange exchange) throws Exception {
                // 因为很明确消息格式是http的,所以才使用这个类
                // 否则还是建议使用org.apache.camel.Message这个抽象接口
                HttpMessage message = (HttpMessage) exchange.getIn();
                String body = message.getBody().toString();
                System.out.println("OtherProcessor3 body -- : " + body);
                // 存入到exchange的out区域
                if (exchange.getPattern() == ExchangePattern.InOut) {
                    Message outMessage = exchange.getOut();
                    outMessage.setBody(body + " || 被OtherProcessor3处理");
                }
            }
        }).endChoice();// 结束
    }
}

 

 

测试、代码三:

package com.test.client.choicecamel;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.nio.charset.Charset;

public class HttpClient {

	public static final String CODEFORMAT = "UTF-8";
	public static String doPost(String requestBody, int timeout, HttpURLConnection http) throws Exception {
		String retResult = "";
		try {
			// 设置是否从HttpURLConnection读入 ,默认是true
			http.setDoInput(true);
			// post请求的时候需要将DoOutput 设置成true 参数要放在http正文内,因此需要设为true ,默认是false
			http.setDoOutput(true);
			// post请求UseCaches 设置成false 不能设置缓存
			http.setUseCaches(false);
			// 连接主机超时时间
			http.setConnectTimeout(timeout);
			// 从主机读取数据超时 (都是毫秒)
			http.setReadTimeout(timeout);
			// 默认是get
			http.setRequestMethod("POST");
			http.setRequestProperty("accept", "*/*");
			http.setRequestProperty("connection", "Keep-Alive");
			http.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
			// 参数设置完成之后进行连接
			http.connect();
			OutputStreamWriter osw = new OutputStreamWriter(http.getOutputStream(), Charset.forName("UTF-8"));
			osw.write(requestBody);
			osw.flush();
			osw.close();
			if (http.getResponseCode() == 200) {
				BufferedReader in = new BufferedReader(new InputStreamReader(http.getInputStream(), Charset.forName("UTF-8")));
				String inputLine;
				while ((inputLine = in.readLine()) != null) {
					retResult += inputLine;
				}
				in.close();
			} else {
				throw new Exception("the http.getResponseCode() is " + http.getResponseCode());
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		finally {
			if (http != null) {
				http.disconnect();
				http = null;
			}
		}
		return retResult;
	}
}

测试、代码四:

 

package com.test.client.choicecamel;

import java.net.HttpURLConnection;
import java.net.URL;
import org.json.JSONObject;

public class TestClient {

	public static void main(String[] args) {
		URL url = null;
		HttpURLConnection http = null;
		try {
			url = new URL("http://0.0.0.0:8282/choiceCamel");
			for (int i = 0; i < 1; i++) {
				System.out.println("http post start !!!");
				Long startTime = System.currentTimeMillis();
				http = (HttpURLConnection) url.openConnection();
				// ************************************************************
				JSONObject authorityJson = new JSONObject();
				authorityJson.put("orgId", "yuanbao");
				JSONObject requestJson = new JSONObject();
				requestJson.put("data", authorityJson);
				requestJson.put("token", "asdaopsd89as0d8as7dasdas-=8a90sd7as6dasd");
				requestJson.put("desc", "");
				// ************************************************************
				StringBuffer sb = new StringBuffer();
				sb.append(requestJson.toString());
				System.out.println(sb.toString());
				String result = HttpClient.doPost(sb.toString(), 30000000, http);
				System.out.println("http post end cost :" + (System.currentTimeMillis() - startTime) + "ms");
				System.out.println(result);
				Thread.sleep(500);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

以上代码中,我们首先使用JsonPath表达式,从Http区域中携带的JSON信息中寻找到orgId这个属性的值;

 

然后将这个值存储在Exhcange的header(这样做只是为了后续方便判断,你也可以存储在Exchange的properties区域,还可以直接使用JsonPath表达式进行判断)。

接下来,通过判断存储在header区域的值,让消息路由进入不同的Processor处理器。

由于我们设置的from-jetty-endpoint中默认的Exchange Pattern值为InOut,所以在各个Processor处理器中完成处理后,Out Message的Body内容会以HTTP响应的结果形式返回到from-jetty-endPoint中。

测试输出结果:

inputContext -- : {"data":{"orgId":"yuanbao"},"token":"asdaopsd89as0d8as7dasdas-=8a90sd7as6dasd","desc":""}
[2017-12-15 16:24:06,777] [qtp1273958371-19] [DEBUG] [com.jayway.jsonpath.internal.path.CompiledPath.evaluate(CompiledPath.java:47)] - Evaluating path: $['data']['orgId']
[2017-12-15 16:24:06,780] [qtp1273958371-19] [DEBUG] [org.eclipse.jetty.jndi.local.localContextRoot.lookup(localContextRoot.java:411)] - Looking up name="header"
[2017-12-15 16:24:06,780] [qtp1273958371-19] [DEBUG] [org.apache.camel.util.ResolverHelper.lookupInRegistry(ResolverHelper.java:124)] - Lookup Language with name header in registry. Found: null
[2017-12-15 16:24:06,780] [qtp1273958371-19] [DEBUG] [org.eclipse.jetty.jndi.local.localContextRoot.lookup(localContextRoot.java:411)] - Looking up name="header-language"
[2017-12-15 16:24:06,780] [qtp1273958371-19] [DEBUG] [org.apache.camel.util.ResolverHelper.lookupInRegistry(ResolverHelper.java:124)] - Lookup Language with name header-language in registry. Found: null
[2017-12-15 16:24:06,780] [qtp1273958371-19] [DEBUG] [org.apache.camel.processor.FilterProcessor.matches(FilterProcessor.java:67)] - Filter matches: true for exchange: Exchange[ID-CYX-49613-1513326239738-0-1]
OtherProcessor body -- : {"data":{"orgId":"yuanbao"},"token":"asdaopsd89as0d8as7dasdas-=8a90sd7as6dasd","desc":""} || out
[2017-12-15 16:24:06,780] [qtp1273958371-19] [DEBUG] [org.apache.camel.http.common.DefaultHttpBinding.doWriteDirectResponse(DefaultHttpBinding.java:489)] - Streaming response in chunked mode with buffer size 32768

我们将测试数据中的orgId修改成"yinwenjie"

 

inputContext -- : {"data":{"orgId":"yinwenjie"},"token":"asdaopsd89as0d8as7dasdas-=8a90sd7as6dasd","desc":""}
[2017-12-15 16:25:42,480] [qtp1273958371-22] [DEBUG] [com.jayway.jsonpath.internal.path.CompiledPath.evaluate(CompiledPath.java:47)] - Evaluating path: $['data']['orgId']
[2017-12-15 16:25:42,480] [qtp1273958371-22] [DEBUG] [org.apache.camel.processor.FilterProcessor.matches(FilterProcessor.java:67)] - Filter matches: false for exchange: Exchange[ID-CYX-49613-1513326239738-0-3]
[2017-12-15 16:25:42,480] [qtp1273958371-22] [DEBUG] [org.apache.camel.processor.FilterProcessor.matches(FilterProcessor.java:67)] - Filter matches: true for exchange: Exchange[ID-CYX-49613-1513326239738-0-3]
OtherProcessor2 body -- : {"data":{"orgId":"yinwenjie"},"token":"asdaopsd89as0d8as7dasdas-=8a90sd7as6dasd","desc":""} || out
[2017-12-15 16:25:42,480] [qtp1273958371-22] [DEBUG] [org.apache.camel.http.common.DefaultHttpBinding.doWriteDirectResponse(DefaultHttpBinding.java:489)] - Streaming response in chunked mode with buffer size 32768

将orgId修改为其他的...

 

inputContext -- : {"data":{"orgId":"cyx"},"token":"asdaopsd89as0d8as7dasdas-=8a90sd7as6dasd","desc":""}\
[2017-12-15 16:27:15,264] [qtp1273958371-22] [DEBUG] [com.jayway.jsonpath.internal.path.CompiledPath.evaluate(CompiledPath.java:47)] - Evaluating path: $['data']['orgId']
[2017-12-15 16:27:15,264] [qtp1273958371-22] [DEBUG] [org.apache.camel.processor.FilterProcessor.matches(FilterProcessor.java:67)] - Filter matches: false for exchange: Exchange[ID-CYX-49613-1513326239738-0-5]
[2017-12-15 16:27:15,264] [qtp1273958371-22] [DEBUG] [org.apache.camel.processor.FilterProcessor.matches(FilterProcessor.java:67)] - Filter matches: false for exchange: Exchange[ID-CYX-49613-1513326239738-0-5]
OtherProcessor3 body -- : {"data":{"orgId":"cyx"},"token":"asdaopsd89as0d8as7dasdas-=8a90sd7as6dasd","desc":""} || out
[2017-12-15 16:27:15,264] [qtp1273958371-22] [DEBUG] [org.apache.camel.http.common.DefaultHttpBinding.doWriteDirectResponse(DefaultHttpBinding.java:489)] - Streaming response in chunked mode with buffer size 32768

 

 

Recipient List接收者列表

在本小节上部分的介绍中,我们说明了怎么使用条件判断向若干可能的路由路径中的某一条路径传送消息。

那么如何做到根据判断条件,向若干可能的路径中的其中多条路径传送同一条消息呢?又或者向若干条可能的路径全部传输同一条消息呢?

在Camel中可能被选择的消息路由路径称为接收者,Camel提供了多种方式向路由中可能成为下一处理元素的多个接收者发送消息:静态接收者列表(StaticRecipient List)、动态接收者列表(Dynamic Recipient List)和 循环动态路由(Dynamic Router)。

下面我们对这几种接收者列表形式进行逐一讲解。

 

使用multicast处理StaticRecipient List(静态路由)

使用multicast方式时,Camel将会把上一元素输出的Exchange复制多份发送给这个列表中的所有接收者,并且按照顺序逐一执行这些接收者(可设置为并行处理)。

这些接收者可能是通过Direct连接的另一个路由,也可能是Processor或者某个单一的Endpoint。

需要注意的是,Exchange是在Endpoint控制端点和Processor处理器间或者两个Processor处理器间唯一能够有效携带Message的元素,所以将一条消息复制多份并且让其执行互不干扰受到影响,那么必然就会对Exhchange对象进行复制。

(复制!复制!虽然主要属性内容相同,但是这些Excahnge使用的内存区域都是不一样的,ExchangeId也不一样)。

以下是multicast使用的简单示例代码:

代码一:

package com.camel.multicastcamel;

import org.apache.camel.impl.DefaultCamelContext;
import org.apache.camel.model.ModelCamelContext;
import org.apache.log4j.PropertyConfigurator;

public class MulticastCamel {

	public static void main(String[] args) throws Exception {
		// 日志
		PropertyConfigurator.configure("./conf/log4j.properties");
		PropertyConfigurator.configureAndWatch("./conf/log4j.properties", 1000);
		// 这是camel上下文对象,整个路由的驱动全靠它了。
		ModelCamelContext camelContext = new DefaultCamelContext();
		// 启动route
		camelContext.start();
		// 将我们编排的一个完整消息路由过程,加入到上下文中
		camelContext.addRoutes(new MulticastCamelRouteBuilder());
		// 通用没有具体业务意义的代码,只是为了保证主线程不退出
		synchronized (MulticastCamel.class) {
			MulticastCamel.class.wait();
		}
	}
}

代码二:

 

package com.camel.multicastcamel;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.model.MulticastDefinition;

public class MulticastCamelRouteBuilder extends RouteBuilder {

	@Override
	public void configure() throws Exception {
		// 这个线程池用来进行multicast中各个路由线路的并发执行
		ExecutorService executorService = Executors.newFixedThreadPool(10);
		MulticastDefinition multicastDefinition = from("jetty:http://0.0.0.0:8282/multicastCamel").multicast();
		// multicast 中的消息路由可以顺序执行也可以并发执行
		// 这里我们演示并发执行
		multicastDefinition.setParallelProcessing(true);
		// 为并发执行设置一个独立的线程池
		multicastDefinition.setExecutorService(executorService);
		// 注意,multicast中各路由路径的Excahnge都是基于上一路由元素的excahnge复制而来
		// 无论前者Excahnge中的Pattern如何设置,其处理结果都不会反映在最初的Excahnge对象中
		multicastDefinition.to("log:helloworld1?showExchangeId=true", "log:helloworld2?showExchangeId=true")
		// 一定要使用end,否则OtherProcessor会被做为multicast中的一个分支路由
		.end()
		// 所以您在OtherProcessor中看到的Excahnge中的Body、Header等属性内容
		// 不会有“复制的Exchange”设置的任何值的痕迹
		.process(new OtherProcessor());
	}
}

代码三:

 

package com.camel.multicastcamel;

import java.io.InputStream;
import org.apache.camel.Exchange;
import org.apache.camel.ExchangePattern;
import org.apache.camel.Message;
import org.apache.camel.Processor;
import org.apache.commons.io.IOUtils;

public class OtherProcessor implements Processor {

	@Override
	public void process(Exchange exchange) throws Exception {
		Message message = exchange.getIn();
		System.out.println("OtherProcessor中的exchange" + exchange);
		InputStream body = (InputStream) message.getBody();
		String str = IOUtils.toString(body, "UTF-8");
//                System.out.println("OtherProcessor str : " + str);
		// 存入到exchange的out区域
		if (exchange.getPattern() == ExchangePattern.InOut) {
			Message outMessage = exchange.getOut();
			outMessage.setBody(str + " || 被OtherProcessor处理");
		}
	}
}

以上代码中,我们使用multicast()将原始的Exchange复制了多份,分别传送给multicast中的两个接收者;

 

"log:helloworld1?showExchangeId=true"、"log:helloworld2?showExchangeId=true";

为了保证两个接收者的处理过程是并行的,我们还专门为multicast设置了一个线程池(不设置的话,Camel将自行设置)。

上面代码中,在multicast路由定义之后我们还设置了一个OtherProcessor处理器,它主要的作用就是查看OtherProcessor中的Exchange对象状态。

[2017-12-14 14:49:21,043] [qtp1463022229-19] [DEBUG]  - Waiting for on-the-fly aggregation to complete aggregating 2 responses for exchangeId: ID-CYX-49594-1513234148448-0-1
[2017-12-14 14:49:21,066] [pool-1-thread-2] [DEBUG]  - >>>> log://helloworld2?showExchangeId=true Exchange[ID-CYX-49594-1513234148448-0-4]
[2017-12-14 14:49:21,066] [pool-1-thread-1] [DEBUG]  - >>>> log://helloworld1?showExchangeId=true Exchange[ID-CYX-49594-1513234148448-0-3]
[2017-12-14 14:49:21,067] [pool-1-thread-2] [INFO]  - Exchange[Id: ID-CYX-49594-1513234148448-0-4, ExchangePattern: InOut, BodyType: org.apache.camel.converter.stream.InputStreamCache, Body: [Body is instance of org.apache.camel.StreamCache]]
[2017-12-14 14:49:21,067] [pool-1-thread-1] [INFO]  - Exchange[Id: ID-CYX-49594-1513234148448-0-3, ExchangePattern: InOut, BodyType: org.apache.camel.converter.stream.InputStreamCache, Body: [Body is instance of org.apache.camel.StreamCache]]
[2017-12-14 14:49:21,069] [Camel (camel-1) thread #0 - MulticastProcessor-AggregateTask] [DEBUG]  - Done aggregating 2 exchanges on the fly.
[2017-12-14 14:49:21,069] [Camel (camel-1) thread #0 - MulticastProcessor-AggregateTask] [DEBUG]  - Signaling we are done aggregating on the fly for exchangeId: ID-CYX-49594-1513234148448-0-1
[2017-12-14 14:49:21,069] [qtp1463022229-19] [DEBUG]  - Done parallel processing 2 exchanges
OtherProcessor中的exchangeExchange[ID-CYX-49594-1513234148448-0-1]
[2017-12-14 14:49:21,075] [qtp1463022229-19] [DEBUG]  - Streaming response in chunked mode with buffer size 32768

通过上面执行结果可以看出,在multicast中的两个接收者(两个路由分支的设定)分别在我们设置的线程池中运行,线程ID分别是'pool-1-thread-2'、'pool-1-thread-1'

 

在multicast中的所有路由分支都运行完后,OtherProcessor处理器的实例在[qtp1463022229-19]线程中继续运行。

(jetty:http-endpint对于本次请求的处理原本就在这个线程上运行)。

请各位读者特别注意以上三句日志所输出的ExchangeId,它们是完全不同的三个Exchange实例!其中在multicast的两个路由分支中承载Message的Excahnge对象;

它们的Exchange-ID分别是:[ID-CYX-49594-1513234148448-0-1]、[ID-CYX-49594-1513234148448-0-4]、[ID-CYX-49594-1513234148448-0-3],来源则是multicast对原始Exchange对象的复制,原始Exchagne对象的Exchange-ID为[ID-CYX-49594-1513234148448-0-1]。

 

 

处理Dynamic Recipient List(动态路由)

在编排路由,很多情况下开发人员不能确定有哪些接收者会成为下一个处理元素:

因为它们需要由Exchange中所携带的消息内容,动态的决定下一个处理元素。

在这种情况下,开发人员就需要用到recipient方法对下一路由进行动态判断。

以下代码示例中,我们将三个已经编排好的路由注册到Camel服务中,并通过打印在控制台上的结果观察其执行:

代码一:

package com.camel.direct;

import org.apache.camel.impl.DefaultCamelContext;
import org.apache.camel.model.ModelCamelContext;
import org.apache.log4j.PropertyConfigurator;

/**
 * 处理Dynamic Recipient List
 *
 * @author CYX
 * @time 2017年12月18日上午11:11:28
 */
public class DirectMain {

	public static void main(String[] args) throws Exception {
		PropertyConfigurator.configure("./conf/log4j.properties");
		PropertyConfigurator.configureAndWatch("./conf/log4j.properties", 1000);
		// 这是camel上下文对象,整个路由的驱动全靠它了。
		ModelCamelContext camelContext = new DefaultCamelContext();
		// 启动route
		camelContext.start();
		// 将我们编排的一个完整消息路由过程,加入到上下文中
		camelContext.addRoutes(new DirectRouteA());
		camelContext.addRoutes(new DirectRouteB());
		camelContext.addRoutes(new DirectRouteC());
		// 通用没有具体业务意义的代码,只是为了保证主线程不退出
		synchronized (DirectMain.class) {
			DirectMain.class.wait();
		}
	}
}

代码二:

 

package com.camel.direct;

import java.io.InputStream;
import org.apache.camel.Exchange;
import org.apache.camel.ExchangePattern;
import org.apache.camel.Message;
import org.apache.camel.Processor;
import org.apache.camel.builder.RouteBuilder;
import org.apache.commons.io.IOUtils;

public class DirectRouteA extends RouteBuilder {

	@Override
	public void configure() throws Exception {
		from("jetty:http://0.0.0.0:8282/directMain")
		.setExchangePattern(ExchangePattern.InOnly).recipientList()
		.jsonpath("$.data.routeName").delimiter(",").end().process(new Processor() {
			@Override
			public void process(Exchange exchange) throws Exception {
				Message message = exchange.getIn();
				System.out.println("OtherProcessor中的exchange" + exchange);
				InputStream body = (InputStream) message.getBody();
				String str = IOUtils.toString(body, "UTF-8");
				System.out.println("OtherProcessor str : " + str);
				// 存入到exchange的out区域
				if (exchange.getPattern() == ExchangePattern.InOut) {
					Message outMessage = exchange.getOut();
					outMessage.setBody(str + " || 被OtherProcessor处理");
				}
			}
		});
	}
}

代码三:

 

package com.camel.direct;

import org.apache.camel.builder.RouteBuilder;

public class DirectRouteB extends RouteBuilder {

	@Override
	public void configure() throws Exception {
		// 第二个路由和第三个路由的代码都相似
		// 唯一不同的是类型
		from("direct:directRouteB").to("log:DirectRouteB?showExchangeId=true");
	}
}

代码四:

 

package com.camel.direct;

import org.apache.camel.builder.RouteBuilder;

public class DirectRouteC extends RouteBuilder {

	@Override
	public void configure() throws Exception {
		// 第二个路由和第三个路由的代码都相似
		// 唯一不同的是类型
		from("direct:directRouteC").to("log:DirectRouteC?showExchangeId=true");
	}
}

代码五:

 

package com.test.client.direct;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URLDecoder;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.json.JSONObject;

public class TestHttpClient {

	public static void main(String[] args) throws Exception {

		CloseableHttpClient httpClient = HttpClients.createDefault();
		HttpPost httpPost = new HttpPost("http://0.0.0.0:8282/directMain");
		// 添加参数
		JSONObject authorityJson = new JSONObject();
		authorityJson.put("routeName", "direct:directRouteB,direct:directRouteC");
		JSONObject requestJson = new JSONObject();
		requestJson.put("data", authorityJson);
		requestJson.put("token", "d9c33c8f-ae59-4edf-b37f-290ff208de2e");
		requestJson.put("desc", "oasdjosjdsidjfisodjf");
		StringBuffer sbb = new StringBuffer();
		sbb.append(requestJson.toString());
		httpPost.setHeader("ContentType", "UTF-8");
		StringEntity urlEntity = new StringEntity(sbb.toString(), "UTF-8");
		httpPost.setEntity(urlEntity);
		HttpResponse response = httpClient.execute(httpPost);
		HttpEntity entity = response.getEntity();
		InputStream inputStream = entity.getContent();
		BufferedReader br = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
		// 读取HTTP请求内容
		String buffer = null;
		StringBuffer sb = new StringBuffer();
		while ((buffer = br.readLine()) != null) {
		// 在页面中显示读取到的请求参数
			sb.append(buffer + "\n");
		}
		System.out.println("接收返回数据:\n" + URLDecoder.decode(sb.toString().trim(), "UTF-8"));
		System.out.println("Login form get: " + response.getStatusLine() + entity.getContent());
	}
}

DirectRouteB路由和DirectRouteC路由中的代码非常简单,就是从direct连接到本路由的上一个路由实例中获取并打印Exchange对象的信息。

 

recipientList方法,该方法可以像multicast方法那样进行并发执行或者运行线程池的设置,但是在DirectRouteA的代码中我们并没有那么做,

这是为了让我们看清recipientList方法和multicast方法的顺序执行效果。

下面我们通过测试方法,传入JSON格式的信息:

{"data":{"routeName":"direct:directRouteB,direct:directRouteC"},"token":"d9c33c8f-ae59-4edf-b37f-290ff208de2e","desc":""}

recipientList方法将以data.routeName中指定的路由信息动态决定下一个或者多个消息接收者,以上JSON片段我们指定了两个"direct:directRouteB,direct:directRouteC"。

于是recilientList会使用delimiter方法中设置的","作为分隔符来分别确定这两个接收者。

观察下运行结果:

[com.jayway.jsonpath.internal.path.CompiledPath.evaluate(CompiledPath.java:47)] - Evaluating path: $['data']['routeName']
[org.apache.camel.impl.DefaultProducer.doStart(DefaultProducer.java:77)] - Starting producer: Producer[direct://directRouteB]
[org.apache.camel.impl.ProducerCache.doGetProducer(ProducerCache.java:587)] - Adding to producer cache with key: direct://directRouteB for producer: Producer[direct://directRouteB]
[org.apache.camel.impl.DefaultProducer.doStart(DefaultProducer.java:77)] - Starting producer: Producer[direct://directRouteC]
[org.apache.camel.impl.ProducerCache.doGetProducer(ProducerCache.java:587)] - Adding to producer cache with key: direct://directRouteC for producer: Producer[direct://directRouteC]
[org.apache.camel.processor.SendProcessor.process(SendProcessor.java:141)] - >>>> log://DirectRouteB?showExchangeId=true Exchange[ID-CYX-49692-1513585693776-0-3]
[org.apache.camel.util.CamelLogger.log(CamelLogger.java:159)] - Exchange[Id: ID-CYX-49692-1513585693776-0-3, ExchangePattern: InOnly, BodyType: org.apache.camel.converter.stream.InputStreamCache, Body: [Body is instance of org.apache.camel.StreamCache]]
[org.apache.camel.processor.SendProcessor.process(SendProcessor.java:141)] - >>>> log://DirectRouteC?showExchangeId=true Exchange[ID-CYX-49692-1513585693776-0-4]
[org.apache.camel.util.CamelLogger.log(CamelLogger.java:159)] - Exchange[Id: ID-CYX-49692-1513585693776-0-4, ExchangePattern: InOnly, BodyType: org.apache.camel.converter.stream.InputStreamCache, Body: [Body is instance of org.apache.camel.StreamCache]]
[org.apache.camel.processor.MulticastProcessor.doProcessSequential(MulticastProcessor.java:650)] - Done sequential processing 2 exchanges
OtherProcessor中的exchangeExchange[ID-CYX-49692-1513585693776-0-1]
OtherProcessor str : {"data":{"routeName":"direct:directRouteB,direct:directRouteC"},"token":"d9c33c8f-ae59-4edf-b37f-290ff208de2e","desc":"oasdjosjdsidjfisodjf"}
[org.apache.camel.http.common.DefaultHttpBinding.doWriteDirectResponse(DefaultHttpBinding.java:489)] - Streaming response in chunked mode with buffer size 32768

静态路由和动态路由在执行结果上有很多相似之处。

例如在两种路径方式中,路由分支上的接收者使用的Exchange对象的来源都是上一执行元素所输出的Exchange对象的复制;

这些Exchange对象除了其中携带的业务内容相同之外,ExchangeID是不一样的,也就是说每个路由分支的Exchange对象都不相同;

所以各个路由分支的消息都不受彼此的影响。

另外,动态路由和静态路由都支持对路由分支的顺序执行和并发执行,都可以为并发执行设置独立的线程池

从以上执行结果中我们可以看出,由于我们没有设置动态路由是并发执行,所以各个需要执行的路由分支都是由名为[qtp534753234-19]的Camel服务线程依次执行;

并且每个路由分支的Exchange对象都不受彼此影响。

另外,请注意以上执行结果的最后一条日志信息,它是在路由分支以外对OtherProcessor处理器的执行;

由此可见路由分支不论如何执行,都不会影响分支以外的元素执行时所使用的Exchange对象

 

 

循环动态路由 Dynamic Router

动态循环路由的特点是开发人员可以通过条件表达式等方式,动态决定下一个路由位置。

在下一路由位置处理完成后Exchange将被重新返回到路由判断点,并由动态循环路由再次做出新路径的判断。

如此循环执行直到动态循环路由不能再找到任何一条新的路由路径为止。

下图来源于官网

这里可以看出动态循环路由(dynamicRouter)和之前介绍的动态路由(recipientList)在工作方式上的差异。

 

dynamicRouter一次选择只能确定一条路由路径,而recipientList只进行一次判断并确定多条路由分支路径;

dynamicRouter确定的下一路由在执行完成后,Exchange对象还会被返回到dynamicRouter中以便开始第二次循环判断,

而recipientList会为各个分支路由复制一个独立的Exchange对象,并且各个分支路由执行完成后Exchange对象也不会返回到recipientList;

下面我们还是通过源代码片段,向各位读者展示dynamicRouter的使用方式。

在代码中,我们编排了三个路由DirectRouteA主要负责通过Http协议接收处理请求,并执行dynamicRouter。

DirectRouteB和DirectRouteC两个路由是可能被dynamicRouter选择的分支路径:

package com.camel.dynamic;

import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.camel.Exchange;
import org.apache.camel.Processor;
import org.apache.camel.Properties;
import org.apache.camel.builder.RouteBuilder;

public class DirectRouteA extends RouteBuilder {

	@Override
	public void configure() throws Exception {
		from("jetty:http://0.0.0.0:8282/dynamicRouterCamel")
		// 使用dynamicRouter,进行“动态路由”循环,
		// 直到指定的下一个元素为null为止
		.dynamicRouter().method(this, "doDirect").process(new Processor() {
			@Override
			public void process(Exchange exchange) throws Exception {
				System.out.println(exchange.getIn());
			}
		});
	}

	/**
	 * 该方法用于根据“动态循环”的次数,确定下一个执行的Endpoint
	 *
	 * @param properties
	 *            通过注解能够获得的Exchange中properties属性,可以进行操作,并反映在整个路由过程中
	 * @return
	 */
	public String doDirect(@Properties Map<String, Object> properties) {
		System.out.println("properties : " + properties);
		// 在Exchange的properties属性中,取出Dynamic Router的循环次数
		AtomicInteger time = (AtomicInteger) properties.get("time");
		if (time == null) {
			time = new AtomicInteger(0);
			properties.put("time", time);
		} else {
			time = (AtomicInteger) time;
		}
		System.out.println("这是Dynamic Router循环第:【" + time.incrementAndGet() + "】次执行!执行线程:" + Thread.currentThread().getName());
		// 第一次选择DirectRouteB
		if (time.get() == 1) {
			return "direct:directRouteB";
		}
		// 第二次选择DirectRouteC
		else if (time.get() == 2) {
			return "direct:directRouteC";
		}
		// 第三次选择一个Log4j-Endpoint执行
		else if (time.get() == 3) {
			return "log:DirectRouteA?showExchangeId=true&showProperties=ture&showBody=false";
		}
		// 其它情况返回null,终止 dynamicRouter的执行
		return null;
	}
}

在DirectRouteA中,我们使用"通过一个method方法返回信息"的方式确定dynamicRouter"动态循环路由"的下一个Endpoint。

 

当然在实际使用中,开发人员还有很多方式向dynamicRouter"动态循环路由"返回指定的下一个Endpoint。

例如:使用jsonpath指定JSON格式数据中的某个属性值,或者使用Xpath指定XML中的某个属性值,又或者使用header方法指定Exchange中Header部分的某个属性。

但是无论如何请确定一件事向dynamicRouter指定下一个Endpoint的方法中是会有返回null进行循环终止的,都则整个dynamicRouter会无限的执行下去

以上doDirect方法中,我们将一个计数器存储在了Exchange对象的properties区域,以便在同一个Exchange对象执行doDirect方法时进行计数操作。

当同一个Exchange对象第一次执行动态循环路由判断时,选择directRouteB最为一下路由路径;

当Exchange对象第二次执行动态循环路由判断时,选择DirectRouteC作为下一路由路径;当Exchange对象第三次执行时,选择一个Log4j-Endpoint作为下一个路由路径;

当Exchange对象第四次执行时,作为路由路径判断的方法doDirect返回null,以便终止dynamicRouter的执行。

不能在DirectRouteA类中定义一个全局变量作为循环路由的计数器,

因为由Jetty-HttpConsumer生成的线程池中,线程数量和线程对象是固定的,并且Camel也不是为每一个Exchange对象的运行创建新的DirectRouteA对象实例。

所以如果在DirectRouteA类中定义全局变量作为循环路由的计数器,各位读者自己想想会发生什么样的结果吧。

package com.camel.dynamic;

import org.apache.camel.builder.RouteBuilder;

public class DirectRouteB extends RouteBuilder {

	@Override
	public void configure() throws Exception {
		from("direct:directRouteB")
		.to("log:DirectRouteB?showExchangeId=true&showProperties=ture&showBody=false");
	}
}
package com.camel.dynamic;

import org.apache.camel.builder.RouteBuilder;

public class DirectRouteC extends RouteBuilder {
	@Override
	public void configure() throws Exception {
		from("direct:directRouteC")
		.to("log:DirectRouteC?showExchangeId=true&showProperties=ture&showBody=false");
	}
}
package com.camel.dynamic;

import org.apache.camel.impl.DefaultCamelContext;
import org.apache.camel.model.ModelCamelContext;
import org.apache.log4j.PropertyConfigurator;

public class DynamicMain {

	public static void main(String[] args) throws Exception {

		// 加载日志
		PropertyConfigurator.configure("./conf/log4j.properties");
		PropertyConfigurator.configureAndWatch("./conf/log4j.properties", 1000);
		// 这是camel上下文对象,整个路由的驱动全靠它了。
		ModelCamelContext camelContext = new DefaultCamelContext();
		// 启动route
		camelContext.start();
		// 将我们编排的一个完整消息路由过程,加入到上下文中
		camelContext.addRoutes(new DirectRouteA());
		camelContext.addRoutes(new DirectRouteB());
		camelContext.addRoutes(new DirectRouteC());
		// 通用没有具体业务意义的代码,只是为了保证主线程不退出
		synchronized (DynamicMain.class) {
			DynamicMain.class.wait();
		}
	}
}

测试代码:

 

package com.test.client.dynamic;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.nio.charset.Charset;

public class HttpClient {

    public static final String CODEFORMAT = "UTF-8";

    public static String doPost(String requestBody, int timeout, HttpURLConnection http) throws Exception {
        String retResult = "";
        try {
            // 设置是否从HttpURLConnection读入 ,默认是true
            http.setDoInput(true);
            // post请求的时候需要将DoOutput 设置成true 参数要放在http正文内,因此需要设为true ,默认是false
            http.setDoOutput(true);
            // post请求UseCaches 设置成false 不能设置缓存
            http.setUseCaches(false);
            // 连接主机超时时间
            http.setConnectTimeout(timeout);
            // 从主机读取数据超时 (都是毫秒)
            http.setReadTimeout(timeout);
            // 默认是get
            http.setRequestMethod("POST");
            http.setRequestProperty("accept", "*/*");
            http.setRequestProperty("connection", "Keep-Alive");
            http.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
            // 参数设置完成之后进行连接
            http.connect();
            OutputStreamWriter osw = new OutputStreamWriter(http.getOutputStream(), Charset.forName("UTF-8"));
            osw.write(requestBody);
            osw.flush();
            osw.close();
            if (http.getResponseCode() == 200) {
                BufferedReader in = new BufferedReader(new InputStreamReader(http.getInputStream(), Charset.forName("UTF-8")));
                String inputLine;
                while ((inputLine = in.readLine()) != null) {
                    retResult += inputLine;
                }
                in.close();
            } else {
                throw new Exception("the http.getResponseCode() is " + http.getResponseCode());
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (http != null) {
                http.disconnect();
                http = null;
            }
        }
        return retResult;
    }
}

 

package com.test.client.dynamic;

import java.net.HttpURLConnection;
import java.net.URL;
import org.json.JSONObject;

public class TestClient {

	public static void main(String[] args) {
		URL url = null;
		HttpURLConnection http = null;

		try {
			url = new URL("http://0.0.0.0:8282/dynamicRouterCamel");
			for (int i = 0; i < 1; i++) {
				System.out.println("http post start !!!");
				Long startTime = System.currentTimeMillis();
				http = (HttpURLConnection) url.openConnection();
				// ************************************************************
				JSONObject authorityJson = new JSONObject();
				authorityJson.put("userid", "222222222222222"); // 用户身份证号码
				authorityJson.put("username", "VIP_USER");// 用户姓名
				authorityJson.put("userdept", "VIP");// 用户单位

				JSONObject queryInfoJson = new JSONObject();
				queryInfoJson.put("source", "60155");// 测试用
				queryInfoJson.put("condition", "FIRSTKEY = '320103671118051'");
				queryInfoJson.put("starttime", "");
				queryInfoJson.put("endtime", "");

				JSONObject requestJson = new JSONObject();
				requestJson.put("authority", authorityJson);
				requestJson.put("queryInfo", queryInfoJson);
				// ************************************************************

				StringBuffer sb = new StringBuffer();
				sb.append(requestJson.toString());
				System.out.println(sb.toString());

				String result = HttpClient.doPost(sb.toString(), 30000000, http);
				System.out.println("http post end cost :" + (System.currentTimeMillis() - startTime) + "ms");
				System.out.println(result);
				Thread.sleep(500);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
properties : {CamelMessageHistory=[DefaultMessageHistory[routeId=route1, node=dynamicRouter1]], CamelCreatedTimestamp=Mon Dec 18 17:30:29 CST 2017}
这是Dynamic Router循环第:【1】次执行!执行线程:qtp1447353473-22
[2017-12-18 17:30:29,179] [qtp1447353473-22] [DEBUG] [org.apache.camel.processor.SendProcessor.process(SendProcessor.java:141)] - >>>> log://DirectRouteB?showBody=false&showExchangeId=true&showProperties=ture Exchange[ID-CYX-49710-1513587558614-0-7]
[2017-12-18 17:30:29,179] [qtp1447353473-22] [INFO] [org.apache.camel.util.CamelLogger.log(CamelLogger.java:159)] - Exchange[Id: ID-CYX-49710-1513587558614-0-7, ExchangePattern: InOut, BodyType: org.apache.camel.converter.stream.InputStreamCache]
properties : {CamelSlipEndpoint=direct://directRouteB, CamelMessageHistory=[DefaultMessageHistory[routeId=route1, node=dynamicRouter1], DefaultMessageHistory[routeId=route2, node=to1]], time=1, CamelToEndpoint=log://DirectRouteB?showBody=false&showExchangeId=true&showProperties=ture, CamelCreatedTimestamp=Mon Dec 18 17:30:29 CST 2017}
这是Dynamic Router循环第:【2】次执行!执行线程:qtp1447353473-22
[2017-12-18 17:30:29,180] [qtp1447353473-22] [DEBUG] [org.apache.camel.processor.SendProcessor.process(SendProcessor.java:141)] - >>>> log://DirectRouteC?showBody=false&showExchangeId=true&showProperties=ture Exchange[ID-CYX-49710-1513587558614-0-7]
[2017-12-18 17:30:29,181] [qtp1447353473-22] [INFO] [org.apache.camel.util.CamelLogger.log(CamelLogger.java:159)] - Exchange[Id: ID-CYX-49710-1513587558614-0-7, ExchangePattern: InOut, BodyType: org.apache.camel.converter.stream.InputStreamCache]
properties : {CamelSlipEndpoint=direct://directRouteC, CamelMessageHistory=[DefaultMessageHistory[routeId=route1, node=dynamicRouter1], DefaultMessageHistory[routeId=route2, node=to1], DefaultMessageHistory[routeId=route3, node=to2]], time=2, CamelToEndpoint=log://DirectRouteC?showBody=false&showExchangeId=true&showProperties=ture, CamelCreatedTimestamp=Mon Dec 18 17:30:29 CST 2017}
这是Dynamic Router循环第:【3】次执行!执行线程:qtp1447353473-22
[2017-12-18 17:30:29,182] [qtp1447353473-22] [INFO] [org.apache.camel.util.CamelLogger.log(CamelLogger.java:159)] - Exchange[Id: ID-CYX-49710-1513587558614-0-7, ExchangePattern: InOut, BodyType: org.apache.camel.converter.stream.InputStreamCache]
properties : {CamelSlipEndpoint=log://DirectRouteA?showBody=false&showExchangeId=true&showProperties=ture, CamelMessageHistory=[DefaultMessageHistory[routeId=route1, node=dynamicRouter1], DefaultMessageHistory[routeId=route2, node=to1], DefaultMessageHistory[routeId=route3, node=to2]], time=3, CamelToEndpoint=log://DirectRouteA?showBody=false&showExchangeId=true&showProperties=ture, CamelCreatedTimestamp=Mon Dec 18 17:30:29 CST 2017}
这是Dynamic Router循环第:【4】次执行!执行线程:qtp1447353473-22
HttpMessage@0x6a2388c6
[2017-12-18 17:30:29,184] [qtp1447353473-22] [DEBUG] [org.apache.camel.http.common.DefaultHttpBinding.doWriteDirectResponse(DefaultHttpBinding.java:489)] - Streaming response in chunked mode with buffer size 32768

 

从以上执行效果看,无论dynamicRouter执行的是第几次循环判断,Exchange都是同一个

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值