2

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.

mdewit
  • 2,016
  • 13
  • 31
  • i am referring this example but I am getting error that can not instantiate ConnectionFactory. what's the problem ? – Shailesh Sep 01 '16 at 09:41
  • Hi Shailesh. Please post a new question with a copy-paste of the exact error that you are getting and I will have a look for you. – mdewit Sep 01 '16 at 10:37
  • Actually My question is here [link]http://stackoverflow.com/questions/39265684/how-to-create-and-hold-multiple-connections-in-spring-integration[link] – Shailesh Sep 01 '16 at 10:44

2 Answers2

1

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:

  1. 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).
    
  2. Do that already after ctx.refresh() to let to be registered all necessary BeanPostProcessors including one for Spring Integration Java DSL parsing.

  3. Invoke ctx.start() to start all the Lifecycles. Because these new manually added haven't been visible by the regular ctx.refresh() process.

Artem Bilan
  • 113,505
  • 11
  • 91
  • 118
  • Thank you for your answer. So if I understand correctly, it is the replyChannel that will be populated with data received from the server, even if the inbound handler is operating in client mode. I think I've been confused by this but fixed that now in my code by swapping input and output around in the createInboundGateway method (please see edited version). However, I still get the same exception. Will the data received on the reply channel not be sent to the IntegrationFlow bean and then transformed by the transformer? I'm not writing any data back yet. – mdewit May 24 '16 at 14:47
  • I actually do have \@EnableIntegration, \@IntegrationComponentScan and \@Configuration annotated at the top of my factory class, and this class is scanned by the ApplicationContext. I agree that there might still be a problem with this as I had to configur a taskScheduler and factory manually to prevent exceptions from occurring (as I understand, normally this taskScheduler will automatically be created if the above annotations are set). Is there anything else that I have to set extra due to me creating the beans myself through the ApplicationContext beanfactory (vs using \@Bean annotate)? – mdewit May 24 '16 at 15:23
  • H-m. I don't see anything obvious yet. The manual `gateway.retryConnection();` call looks suspicious, since everything should be done via `TcpInboundGateway.start()` from the container. Plus I think your `.handle(System.out::println);` is bad there. Because `TcpInboundGateway` should receive a reply anyway. Otherwise ti will stuck. But, yeah.. That isn't a root of `Dispatcher has no subscribers` cause... I'll play with something similar locally and come back to your over some time. – Artem Bilan May 24 '16 at 15:43
  • Please, see an `EDIT 2` in my answer. – Artem Bilan May 24 '16 at 19:10
  • Thank you very much for your help. I managed to solve it using your response here together with the github testcase. – mdewit May 25 '16 at 10:06
0

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().

mdewit
  • 2,016
  • 13
  • 31