Reputation: 2036
From my application, I need to configure multiple client connections that needs to connect to a single server. To do this, I create a variable amount of beans with the ApplicationContext Beanfactory, based on how many clients I have configured. Here is the code for 2 clients:
//setup beans;
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.scan("pkg");
ConnectionFactory factory = new ConnectionFactory();
int clients = 2; //TODO read this value from file
ConfigurableListableBeanFactory beanFactory = ctx.getBeanFactory();
for (int count = 1; count <= clients; count++) {
TcpNetClientConnectionFactory connectionFactory = factory.createClientConnectionFactory("127.0.0.1", 6680);
//connection factory
beanFactory.registerSingleton("connectionFactory_" + String.valueOf(count), connectionFactory);
//inbound gateway
MessageChannel input = new DirectChannel();
MessageChannel output = new DirectChannel();
TcpInboundGateway gateway = factory.createInboundGateway(connectionFactory, beanFactory, input, output, 10000, 20000);
beanFactory.registerSingleton("gateway_" + String.valueOf(count), gateway);
//message transformation and handling
IntegrationFlow flow = factory.createFlow(input);
beanFactory.registerSingleton("flow_" + String.valueOf(count), flow);
}
ctx.refresh();
//open connections
for(int count = 1; count <= clients; count++) {
TcpInboundGateway gateway = ctx.getBean("gateway_" + count, TcpInboundGateway.class);
//necessary for the client to connect
gateway.retryConnection();
}
Here is my factory methods:
@EnableIntegration
@IntegrationComponentScan
@Configuration
public class ConnectionFactory {
public TcpNetClientConnectionFactory createClientConnectionFactory(String ip, int port) {
TcpNetClientConnectionFactory factory = new TcpNetClientConnectionFactory(ip, port);
factory.setSingleUse(false);
factory.setSoTimeout(10000);
factory.setSerializer(new ByteArrayLfSerializer());
factory.setDeserializer(new ByteArrayLfSerializer());
return factory;
}
public TcpInboundGateway createInboundGateway(
AbstractConnectionFactory factory,
BeanFactory beanFactory,
MessageChannel input,
int replyTimeout,
int retryInterval) {
TcpInboundGateway gateway = new TcpInboundGateway();
gateway.setRequestChannel(input);
gateway.setConnectionFactory(factory);
gateway.setClientMode(true);
gateway.setReplyTimeout(replyTimeout);
gateway.setRetryInterval(retryInterval);
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.initialize();
gateway.setTaskScheduler(scheduler);
gateway.setBeanFactory(beanFactory);
return gateway;
}
public IntegrationFlow createFlow(MessageChannel input) {
IntegrationFlowBuilder builder = IntegrationFlows.from(input);
builder.transform(Transformers.objectToString()).handle(System.out::println);
return builder.get();
}
}
When I run my program, both clients connects to my server. However, as soon as the server sends its first payload to each client I get the following exception (one for each client):
Exception sending message: GenericMessage [payload=byte[5], headers={ip_tcp_remotePort=6680, ip_connectionId=localhost:6680:33372:e26b9973-a32e-4c28-b808-1f2556576d01, ip_localInetAddress=/127.0.0.1, ip_address=127.0.0.1, id=4443ca34-fb53-a753-7603-53f6d7d82e11, ip_hostname=localhost, timestamp=1464098102462}]
org.springframework.messaging.MessageDeliveryException: Dispatcher has no subscribers for channel 'unknown.channel.name'.; nested exception is org.springframework.integration.MessageDispatchingException: Dispatcher has no subscribers
at org.springframework.integration.channel.AbstractSubscribableChannel.doSend(AbstractSubscribableChannel.java:81) ~[spring-integration-core-4.2.5.RELEASE.jar:na]
at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:442) ~[spring-integration-core-4.2.5.RELEASE.jar:na]
at org.springframework.messaging.core.GenericMessagingTemplate.doSend(GenericMessagingTemplate.java:115) ~[spring-messaging-4.2.4.RELEASE.jar:4.2.4.RELEASE]
at org.springframework.messaging.core.GenericMessagingTemplate.doSendAndReceive(GenericMessagingTemplate.java:150) ~[spring-messaging-4.2.4.RELEASE.jar:4.2.4.RELEASE]
at org.springframework.messaging.core.GenericMessagingTemplate.doSendAndReceive(GenericMessagingTemplate.java:45) ~[spring-messaging-4.2.4.RELEASE.jar:4.2.4.RELEASE]
at org.springframework.messaging.core.AbstractMessagingTemplate.sendAndReceive(AbstractMessagingTemplate.java:42) ~[spring-messaging-4.2.4.RELEASE.jar:4.2.4.RELEASE]
at org.springframework.integration.core.MessagingTemplate.sendAndReceive(MessagingTemplate.java:97) ~[spring-integration-core-4.2.5.RELEASE.jar:na]
at org.springframework.integration.gateway.MessagingGatewaySupport.doSendAndReceive(MessagingGatewaySupport.java:422) ~[spring-integration-core-4.2.5.RELEASE.jar:na]
at org.springframework.integration.gateway.MessagingGatewaySupport.sendAndReceiveMessage(MessagingGatewaySupport.java:390) ~[spring-integration-core-4.2.5.RELEASE.jar:na]
at org.springframework.integration.ip.tcp.TcpInboundGateway.doOnMessage(TcpInboundGateway.java:119) ~[spring-integration-ip-4.2.5.RELEASE.jar:na]
at org.springframework.integration.ip.tcp.TcpInboundGateway.onMessage(TcpInboundGateway.java:97) ~[spring-integration-ip-4.2.5.RELEASE.jar:na]
at org.springframework.integration.ip.tcp.connection.TcpNetConnection.run(TcpNetConnection.java:182) ~[spring-integration-ip-4.2.5.RELEASE.jar:na]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [na:1.8.0_31]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [na:1.8.0_31]
at java.lang.Thread.run(Thread.java:745) [na:1.8.0_31]
Caused by: org.springframework.integration.MessageDispatchingException: Dispatcher has no subscribers
at org.springframework.integration.dispatcher.UnicastingDispatcher.doDispatch(UnicastingDispatcher.java:153) ~[spring-integration-core-4.2.5.RELEASE.jar:na]
at org.springframework.integration.dispatcher.UnicastingDispatcher.dispatch(UnicastingDispatcher.java:120) ~[spring-integration-core-4.2.5.RELEASE.jar:na]
at org.springframework.integration.channel.AbstractSubscribableChannel.doSend(AbstractSubscribableChannel.java:77) ~[spring-integration-core-4.2.5.RELEASE.jar:na]
... 14 common frames omitted
The idea was that the data would be read, sent through the channels I configured for my InboundGateway to the transformer, which will then transform the data to a String after which it will be printed out.
Why does the framework not know which channel to put the data? As far as I can see, I did create a unique channel for each client in the inbound gateway factory method. Can someone please have a look at my configuration and let me know what I missed, as I am absolutely stumped by this one.
Upvotes: 2
Views: 3185
Reputation: 2036
Here is the working simplified solution:
Beans.java
package beanconfig;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.config.EnableIntegration;
@Configuration
@EnableIntegration
public class Beans {
//Beans can be configured here
}
IntegrationTest.java
import org.junit.Test;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.dsl.IntegrationFlows;
import org.springframework.integration.ip.tcp.TcpInboundGateway;
import org.springframework.integration.ip.tcp.connection.TcpNetClientConnectionFactory;
import org.springframework.integration.ip.tcp.serializer.ByteArrayLfSerializer;
import org.springframework.integration.transformer.ObjectToStringTransformer;
import org.springframework.messaging.MessageChannel;
public class IntegrationTest {
private String generateComponentName(String baseName, int instanceCount) {
return baseName + "_" + instanceCount;
}
@Test
public void integrationTest1() throws Exception {
try(AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext()) {
ctx.scan("beanconfig");
ctx.refresh();
ConfigurableListableBeanFactory beanFactory = ctx.getBeanFactory();
int numberOfClients = 2; //TODO configure from file
for (int count = 0; count < numberOfClients; count++) {
//connection factory
TcpNetClientConnectionFactory connectionFactory = new TcpNetClientConnectionFactory("127.0.0.1", 6680);
connectionFactory.setSingleUse(false);
connectionFactory.setSoTimeout(10000);
connectionFactory.setSerializer(new ByteArrayLfSerializer());
connectionFactory.setDeserializer(new ByteArrayLfSerializer());
//inbound gateway
TcpInboundGateway inboundGateway = new TcpInboundGateway();
inboundGateway.setRequestChannel(new DirectChannel());
inboundGateway.setConnectionFactory(connectionFactory);
inboundGateway.setClientMode(true);
inboundGateway.setReplyTimeout(10000);
inboundGateway.setRetryInterval(20000);
//message transformation and flow
String flowName = generateComponentName("flow", count);
IntegrationFlow flow = IntegrationFlows.from(inboundGateway)
.transform(new ObjectToStringTransformer())
.handle(h -> System.out.println("Message received: " + h.getPayload()))
.get();
beanFactory.registerSingleton(flowName, flow);
beanFactory.initializeBean(flow, flowName);
}
ctx.start();
//TODO do proper validation here
Thread.sleep(10000);
}
}
}
Basically there were a couple of things wrong with my initial attempt. Here is what I changed to make it work:
1) When creating the AnnotationConfigApplicationContext, it must be created with a configuration class as parameter that is marked with the @EnableIntegration annotation. If not, then a component must be scanned by the context that contains this annotation. I did do this in my first attempt but called refresh too late, it should be called directly after ctx.scan. Because my ctx.refresh() was after my beanfactory registrations, @EnableIntegration was actually not set when the integration beans were created. Moving ctx.refresh() directly below ctx.scan() solves the problem.
2) Each bean registered into the context must also be initialized by the beanfactory. This is to ensure that the BeanPostProcessors are run (this is not done automatically by registerSingleton).
3) ctx.start() then needs to be called to enable the beans that were created after ctx.refresh().
Upvotes: 0
Reputation: 121177
There is no one who is going to consume message from your gateway.setReplyChannel(output);
.
At least we don't see anything like:
after which it will be printed out.
In most cases we have Dispatcher has no subscribers
if some your SubscribableChannel
is without any subscribers: not configured or stopped.
EDIT
Forget my previous expression. It is for the outbound
case.
Your TcpInboundGateway
is good. Although you don't need setReplyChannel()
because you always can rely on the default built-in TemporaryReplyChannel
to wait for some result from downstream flow.
Your IntegrationFlow
also looks good. And that's correct that the .transform()
doesn't send anything to any other channel. It just relies on the TemporaryReplyChannel
in headers.
I think your problem is that you don't specify @EnableIntegraiton
for any of your @Configuration
class: http://docs.spring.io/spring-integration/reference/html/overview.html#_configuration
EDIT 2
See the GH issue on the matter.
So, what you need in addition to your code is:
beanFactory.initializeBean();
for each your manual registerSingleton()
. Because see JavaDocs of the last one:
* <p>The given instance is supposed to be fully initialized; the registry
* will not perform any initialization callbacks (in particular, it won't
* call InitializingBean's {@code afterPropertiesSet} method).
Do that already after ctx.refresh()
to let to be registered all necessary BeanPostProcessor
s including one for Spring Integration Java DSL parsing.
Invoke ctx.start()
to start all the Lifecycle
s. Because these new manually added haven't been visible by the regular ctx.refresh()
process.
Upvotes: 1