I've got java spring boot configuration like this:
#Bean
public SubscribableChannel channel(MessageHandler handler) {
PublishSubscribeChannel channel = new PublishSubscribeChannel(Executors.newFixedThreadPool(2));
channel.subscribe(handler);
return channel;
}
My handler code:
#Service
public class SomeDataHandler implements MessageHandler {
#Override
public void handleMessage(Message<?> message) throws MessagingException {
System.out.print(message.getPayload());
}
}
And some client code:
#Autowired
private SubscribableChannel channel
...
channel.send(...)
And it doesn't work. Just nothing happens.
But this configuration works fine for me:
#Bean
public SubscribableChannel channel(MessageHandler handler) {
PublishSubscribeChannel channel = new PublishSubscribeChannel();
channel.subscribe(handler);
return channel;
}
So looks like I need to do something more to apply task executor to my channel. Any ideas? Thanks.
Spring integration version - 4.2.5
"doesn't work" is not much to go on - show your executor and handler bean configurations.
Turn on DEBUG logging - it should help you figure out what's happening.
You should NOT be subscribing to the channel in the bean declaration; the handler will be subscribed automatically by the framework (if properly configured).
EDIT
You are not really using the framework as it was designed; when using an executor, the dispatcher is replaced during bean creation - overriding your subscription. You are subscribing too early in the bean lifecycle.
While not necessary, it's generally better to subclass AbstractMessageHandler and implement handleMessageInternal - then configure it like this...
#ServiceActivator(inputChannel="channel")
#Bean
public MyAMHSubclass handler() {
return new MyAMHSubclass();
}
and remove the subscribe from the channel bean declaration.
The #ServiceActivator wraps the handler in a consumer which subscribes to the channel when the context is initialized.
Related
I'm currently reading through Spring AMQP's official sample project along with it's corresponding explanations from Spring AMQP docs. The project involves an sync and async version, and the two only differs slightly. Here's the async version:
Producer config:
#Configuration
public class ProducerConfiguration {
protected final String helloWorldQueueName = "hello.world.queue";
#Bean
public RabbitTemplate rabbitTemplate() {
RabbitTemplate template = new RabbitTemplate(connectionFactory());
template.setRoutingKey(this.helloWorldQueueName);
return template;
}
#Bean
public ConnectionFactory connectionFactory() {
return new CachingConnectionFactory();
}
#Bean
public ScheduledProducer scheduledProducer() {
return new ScheduledProducer();
}
#Bean
public BeanPostProcessor postProcessor() {
return new ScheduledAnnotationBeanPostProcessor();
}
static class ScheduledProducer {
#Autowired
private volatile RabbitTemplate rabbitTemplate;
private final AtomicInteger counter = new AtomicInteger();
#Scheduled(fixedRate = 3000)
public void sendMessage() {
rabbitTemplate.convertAndSend("Hello World " + counter.incrementAndGet());
}
}
}
Consumer config:
#Configuration
public class ConsumerConfiguration {
protected final String helloWorldQueueName = "hello.world.queue";
#Bean
public ConnectionFactory connectionFactory() {
return new CachingConnectionFactory();
}
#Bean
public SimpleMessageListenerContainer listenerContainer() {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(connectionFactory());
container.setQueueNames(this.helloWorldQueueName);
container.setMessageListener(new MessageListenerAdapter(new HelloWorldHandler()));
return container;
}
#Bean
public RabbitTemplate rabbitTemplate() {
RabbitTemplate template = new RabbitTemplate(connectionFactory());
template.setRoutingKey(this.helloWorldQueueName);
template.setDefaultReceiveQueue(this.helloWorldQueueName);
return template;
}
#Bean
public Queue helloWorldQueue() {
return new Queue(this.helloWorldQueueName);
}
}
HelloWorldHandler:
public class HelloWorldHandler {
public void handleMessage(String text) {
System.out.println("Received: " + text);
}
}
As the docs explains:
Since this sample demonstrates asynchronous message reception, the producing side is designed to continuously send messages (if it were a message-per-execution model like the synchronous version, it would not be quite so obvious that it is, in fact, a message-driven consumer). The component responsible for continuously sending messages is defined as an inner class within the ProducerConfiguration. It is configured to run every three seconds.
I failed to understand what's "async" about this code, since, from my understanding, in a basic "synchronous fashion", operations like amqpTemplate.converAndSend() and amqpTemplate.receiveAndConvert() already peforms Rabbitmq's async actions, neither producer nor consumer are blocking when sending/receiving messages.
So, how's async in this example manifested? And how to understand async vs sync in the Spring AMQP context?
With async, the MessageListener is invoked by the framework; messages arrive whenever they are available.
With sync, the application calls a receive method which either returns immediately if no message is available, or blocks until a message arrives or a timeout expires.
In the sync case, the application controls when messages are received, with async, the framework is in control.
I'm using spring-integration-sftp and my goal is to push local file to SFTP (just that for now, without confirmation or anything else). My configuration is as follows:
#EnableIntegration
#IntegrationComponentScan
#Configuration
#Lazy
public class SftpConfiguration {
#Bean(name = "toSftpChannel")
public MessageChannel sftpMessageChannel() {
return new DirectChannel();
}
#Bean
public DefaultSftpSessionFactory sftpSessionFactory(
#Qualifier("sftpDestination") SftpPropertiesService sftpPropertiesService
) {
DefaultSftpSessionFactory factory = new DefaultSftpSessionFactory();
factory.setHost(sftpPropertiesService.getServiceHost());
factory.setPort(22);
factory.setUser(sftpPropertiesService.getUsername());
factory.setPassword(sftpPropertiesService.getPassword());
factory.setAllowUnknownKeys(true);
return factory;
}
#Bean
public SftpRemoteFileTemplate sftpRemoteFileTemplate(DefaultSftpSessionFactory dssf,
#Value("${sftp.output.directory}") String outputDirectory) {
SftpRemoteFileTemplate template = new SftpRemoteFileTemplate(dssf);
template.setRemoteDirectoryExpression(new LiteralExpression(outputDirectory));
return template;
}
#Bean
#ServiceActivator(inputChannel = "toSftpChannel")
public MessageHandler handler(SftpRemoteFileTemplate sftpRemoteFileTemplate) {
SftpOutboundGateway gateway =
new SftpOutboundGateway(sftpRemoteFileTemplate, Command.PUT.getCommand(), "payload");
gateway.setFileExistsMode(FileExistsMode.FAIL);
return gateway;
}
#MessagingGateway
public interface OutputSftpGateway {
#Gateway(requestChannel = "toSftpChannel")
void sendToSftp(File file);
}
}
and sending is just
private final OutputSftpGateway outputSftpGateway;
...
outputSftpGateway.sendToSftp(file);
When I'm running my code I at first get
A bean definition with name 'toSftpChannel' exists, but failed to be created; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'toSftpChannel': Requested bean is currently in creation: Is there an unresolvable circular reference?
which is kind of expected with lazy init (though still will have to fix it), but at the second and subsequent runs I'm getting stuck with
Exception occurred during request processing. org.springframework.messaging.MessageDeliveryException. Dispatcher has no subscribers for channel 'application.toSftpChannel'.; nested exception is org.springframework.integration.MessageDispatchingException: Dispatcher has no subscribers
I'll honestly admit that I'm new with Spring messaging magic, so the cause is probably very stupid but can someone give me a hint why this is happening and how can I fix it?
The #Lazy may have some effect on those beans initialization. Consider to divide your configuration logic to extract only those beans which cannot live with #Lazy. And don't apply it for those Spring Integration components.
I'm having to change from simply using #JmsListener to setting up my listeners dynamically in order to allow my user to configure the application choosing which queue to read to.
I have tried follow the Spring JMS Documentation for Programmatic Endpoint Registration but there is one aspect it does not cover: how to set the ListenerContainerFactory I want to use for my listener.
I have tried the following:
#Configuration
#EnableJms
public class JmsConfig implements JmsListenerConfigurer {
#Autowired
private JmsListenerEndpointRegistry registry;
#Overide
public void configureJmsListeners(JmsListenerEndpointRegistrar register) {
SimpleJmsListenerEndpoint endpoint = new SimpleJmsListenerEndpoint();
endpoint.setId("myJmsEndpoint");
endpoint.setDestination("TestQueue");
endpoint.setupListenerContainer(registry.getListenerContainer("myContainerFactory"))
endpoint.setMessageListener( message -> {
// handle
});
register.registerEndpoint(endpoint)
}
#Bean
public JmsListenerContainerFactory myContainerFactory(ConnectionFactory connectionFactory, DefaultJmsListenerContainerFactoryConfigurer configurer) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
configurer.configure(factory, connectionFactory);
return factory;
}
// Other Connection and Container factories
}
but I'm receiving:
java.lang.IllegalArgumentException: Could not configure endpoint with the specified container 'null' Only JMS (org.springframework.jms.listener.AbstractMessageListenerContainer subclass) or JCA (org.springframework.jms.listener.endpoint.JmsMessageEndpointManager) are supported.
at org.springframework.jms.config.AbstractJmsListenerEndpoint$JcaEndpointConfigurer.configureEndpoint(AbstractJmsListenerEndpoint.java:188)
I'm guessing the null is because, at this phase, Spring still doesn't have the beans created (right?).
What is the right approach here to make this work?
Thanks!
I am currently working on an event-based async AMQP message listener, like this:
#Configuration
public class ExampleAmqpConfiguration {
#Bean(name = "container")
public SimpleMessageListenerContainer messageListenerContainer() {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(rabbitConnectionFactory());
container.setQueueName("some.queue");
container.setMessageListener(exampleListener());
return container;
}
#Bean
public ConnectionFactory rabbitConnectionFactory() {
CachingConnectionFactory connectionFactory =
new CachingConnectionFactory("localhost");
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
return connectionFactory;
}
#Bean
public MessageListener exampleListener() {
return new MessageListener() {
public void onMessage(Message message) {
System.out.println("received: " + message);
}
};
}
}
I have changed the container bean's autostart property to false. And I have autowired this bean to an event lister that starts the container when StartEvent happens:
#EventListener
public void startContainer(StartEvent startEvent) {
this.container.start();
}
At the same time, I have also autowired the bean to another event that stops the container and shutdowns the container, hoping that the container will be stopped and that there will be no lingering connection:
#EventListener
public void endContainer(EndEvent endEvent) {
this.container.stop();
this.container.shutdown();
}
However, after the EndEvent, I find in my RabbitMQ admin console that all the channels are closed but there is still a lingering connection.
Does that mean that shutdown() is not the right method to use to remove the lingering connection? If that's the case, what is the right method to use?
Thanks.
You need to call resetConnection() on the CachingConnectionFactory to close the connection; as implied by the class name; the connection is cached.
I'm using Spring AMQP to set up remoting between different services, as described here. However, as I set a reply-timeout on my configuration, the first ever request always fails because the time taken to declare the queues, exchanges and binding exceeds the timeout:
The RabbitAdmin component can declare exchanges, queues and bindings
on startup. It does this lazily, through a ConnectionListener, so if
the broker is not present on startup it doesn't matter. The first time
a Connection is used (e.g. by sending a message) the listener will
fire and the admin features will be applied.
Is there any way the declaration can be made eagerly on startup instead of being made on the very first publish event to prevent the first request from always failing?
If you declare your queues with annotations:
#Configuration
public class QueuesConfiguration {
#Bean
public FanoutExchange exchange() {
return new FanoutExchange("exchange", true, false);
}
#Bean
public Binding binding() {
return BindingBuilder.bind(queue()).to(exchange());
}
#Bean
public Queue queue() {
return new Queue("queue");
}
#Bean
public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {
return new RabbitAdmin(connectionFactory);
}
}
then call the RabbitAdmin.initialize() manually on application startup with this:
#Component
public class ApplicationStartup implements ApplicationListener<ApplicationReadyEvent> {
#Autowired
private RabbitAdmin rabbitAdmin;
#Override
public void onApplicationEvent(final ApplicationReadyEvent event) {
rabbitAdmin.initialize();
}
}
As we see by that description and the code from the RabbitAdmin, the last one just populates the ConnectionListener to the provided ConnectionFactory.
That ConnectionListener.onCreate is invoked from the ConnectionFactory.createConnection().
So, you can handle, for example, ContextRefreshedEvent and just do void connectionFactory.createConnection() eagerly.
From other side RabbitAdmin has initialize() public method for the same purpose.
UPDATE
Actually ListenerContainer does that on its start() too. You must declare your queues, exchanges and binding in the app where you a have a listener and make it autoStartup = true. To be honest the listener app is responsible for the real Broker entities.
The sending app should get deal just only with exchangeName and routingKey.