Spring AmqpTemplate nested autowiring - java

I have a service that is responsible for placing a message on a RabbitMQ queue. I have set up the AMQP config in such a way that I can autowire the AmqpTemplate class into the service. This config works when I move the logic into the body of a JUnit test.
However, When I create a test with the service autowired in and call the method to trigger the AmqpTemplates convertAndSend method nothing happens. Using wireshark I have seen that it still handshakes with the RabbitMQ server but no exchange is created and no messages appear in any queue even when I am making use of RabbitMQ's firehose trace options.
The code is as follows:
<!-- AMQP messaging configurations starts here -->
<!-- Spring AMQP connection factory -->
<rabbit:connection-factory id="connectionFactory"
host="localhost"
port="5672"
username="guest"
password="guest"
channel-cache-size="25"/>
<!-- Queues -->
<rabbit:queue name="test.queue"/>
<!-- Exchanges with their queue bindings -->
<rabbit:topic-exchange name="test.exchange">
<rabbit:bindings>
<rabbit:binding queue="test.queue" pattern="test.*"/>
</rabbit:bindings>
</rabbit:topic-exchange>
<!-- Spring AMQP template - Creates a bean which can send a message to the topicExchange-->
<rabbit:template id="testTemplate"
connection-factory="connectionFactory"
exchange="test.exchange"/>
<!-- Spring AMQP Admin -->
<rabbit:admin connection-factory="connectionFactory"/>
The above code segment appears in my application context that is used for the JUnit tests.
#Service
public class AsyncQueueServiceImplementation implements AsyncQueueService
{
#Autowired
private AmqpTemplate template;
#Override
#Async
public void publish()
{
template.convertAndSend("test.debug", "test payload");
}
}
The above code segment is the service that is responsible for actually sending an object to the AmqpTemplate. Please not that the AmqpTemplate is autowired in here.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "classpath:application-context-unitTests.xml" })
public class AsyncTraceServiceImplementationTest
{
#Autowired
AsyncTraceService traceService;
#Test
public void testPublishAtDebugLevel()
{
traceService.publish();
}
}
The above segment is the JUnit test. It uses the application context that contains all the rabbit mq configuration. It then autowires in the service and calls the message.
When I place simple System.out.println's around, I can see that in both the service the AmqpTemplate is instantiated but doesn't seem to do what is expected.
Could this perhaps be an issue with the context not being passed on to the service for some reason.
I have tried using ReflectionTestUtils to set the template field in the service from the Junit test however I was unable to do so.

I managed to fix this issue.
The issue was in the RabbitMQ topics that I was using. The exchange received an unknown topic and as a result did nothing with it.
Correcting this resulted in the messages appearing in the correct queue as the exchange was then able to route the messages correctly.

Related

How To Override Default SQS Configurations For Spring Cloud AWS Messaging

Specifically looking to override the default AmazonSQSAsync client in order to ensure that the client is compatible with FIFO queues as mentioned in the version 2.4.2 documentation here . Defining a bean in my application in a #Configuration class similar to the documentation (as shown below) still results in the warning AmazonSQSBufferedAsyncClient that Spring Cloud AWS uses by default to communicate with SQS is not compatible with FIFO queues. Consider registering non-buffered AmazonSQSAsyncClient bean. Although, requests do seem to work I have not yet been able to determine if the correct AmazonSQSAsync client is being used. I'm looking for either a way to adjust my configuration that removes this warning (because my
AmazonSQSAsync bean is being used) or way to confirm that the message is actually a red herring. The dependency I'm using is spring-cloud-aws-messaging version 2.4.2
#Configuration
public class SQSConfig {
#Bean
public AmazonSQSAsync amazonSQS(#Value("${aws.region}") String awsRegion) {
return AmazonSQSAsyncClientBuilder.standard()
.withCredentials(DefaultAWSCredentialsProviderChain.getInstance())
.withRegion(awsRegion)
.build();
}
}
Looks like it was an error with configuration. I had mistakenly left the xml configuration <aws-messaging:annotation-driven-queue-listener /> active which was the source of the erroneous SQS client. Removing that xml configuration and including an override of the amazonSQS bean (bean name must match exactly) with an instance of AmazonSQSAsync solved the issue.

How to #autowired spring beans in JPOS QServer listener?

We are running spring boot application. And within there we try to run JPOS QServer, after Spring is booted. It starts the Q2 and we can recive ISOmsgs from simulator clinet and we can response beck as well.
To start the Q2 we used xml configuration, which looks like this.
<server class="org.jpos.q2.iso.QServer" name="Server-A-Server" logger="Q2">
<attr name="port" type="java.lang.Integer">8118</attr>
<attr name="maxSessions" type="java.lang.Integer">100</attr>
<attr name="minSessions" type="java.lang.Integer">0</attr>
<!-- Format server name: "Project Name" + "-" + "Channel" -->
<channel name="Server-A-Channel" class="org.jpos.iso.channel.XMLChannel" logger="Q2"
packager="org.jpos.iso.packager.XMLPackager">
</channel>
<request-listener class="fi.neocard.jpos.ISOMessageListener">
<property name="timeout" value="10000"/>
</request-listener>
as you see from xml above it listens ISOMessangerListener class, which naturally not spring related class, Moreover I think that spring and QServer are running on different Servlets. So here it comes the problem that we do not have any Spring related beans in here and we cant #Autowired any. But our all business logic is already written in spring.
What we tried so far
1) Keep the application context static and use it whenever needed in ISOMessageListener (failed the context was null after receiving message to the listener).
2) To do something like this. SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
And readed a lot of articals how it is possible to get context from outside of spring.
So far I checked with jvisualvm that both Q2 and Springboot are running as a single application.
Hope to get at least some suggestions what to do.
If someone is interested here how I got it work, Instead of running ISOServer through Q2 (xml file) I just did it manually when my spring is already booted and then took the already existing bean of ISORequestListener and pass it to the server.
#Component
public class ISOserver implements ISORequestListener {
#Autowired
ISOserver isOserver;
#EventListener(ContextRefreshedEvent.class)
public void contextRefreshedEvent() throws Exception{
Logger logger = new Logger();
logger.addListener(new SimpleLogListener(System.out));
ServerChannel channel = new XMLChannel(new XMLPackager());
((LogSource) channel).setLogger(logger, "channel");
ISOServer isoServer = new ISOServer(8118, channel, null);
isoServer.setLogger(logger, "server");
isoServer.addISORequestListener(isOserver);
new Thread(isoServer).start();
}
#Override
public boolean process(ISOSource isoSource, ISOMsg receivedISOmsg) {
return false;
}
}

Using #JmsListener with logging

I'm following a guide on using Spring JMS using the JmsListener annotation at the method level. I think it is working but since I can't debug my breakpoint set in that method or log4j logging doesn't work, or not even a simple System.out.println(), I'm not 100% sure that destination is hitting.
#Component
public class JmsEmailServiceConsumer {
private final Logger log = LoggerFactory.getLogger(this.getClass());
private final JmsEmailService jmsEmailService;
#Autowired
public JmsEmailServiceConsumer(JmsEmailService jmsEmailService){
this.jmsEmailService = jmsEmailService;
}
#JmsListener(destination = "simple.queue")
public void receiveEmailData(EmailData emailData) {
jmsEmailService.sendEmail(emailData);
}
}
Pretty simple task. All I'm trying to do is create a JMS queue to handle the generating of emails. This process makes a call to the Service, jmsEmailService, which determines via calls to DAOs to select a list of email addresses to send emails. If none are found, no email is sent. Now I am testing locally and I don't have an email server up and running but I want to verify if the calls to the DAOs are working. If they are then I can proceed with committing in the code and get QA to test the email process.
I did it this way because of a blog I found that really removes the bulk of dealing with JMS. As you can see, all I needed to do was annotate the receiveEmailData method with JmsListener and provide a destination which has already been setup in the Producer class as:
private static final String SIMPLE_QUEUE = "simple.queue";
#Autowired
public JmsEmailProducerImpl(JmsTemplate jmsTemplate) {
this.jmsTemplate = jmsTemplate;
}
#Override
public void sendEmail(EmailData emailData) {
//EmailData emailData = new EmailData(userId, person, company, roleKind, isRemoved);
jmsTemplate.convertAndSend(SIMPLE_QUEUE, emailData);
}
Pretty easy right? That's what I thought. For reference, here's the website I am looking at:
http://xpadro.blogspot.com/2015/04/configure-spring-jms-application-with.html
Any thoughts? I can place a breakpoint at the line in the producer class which works, but once the jmsTemplate fires off the convertAndSend method, no breakpoint in the consumer class, System.out.println() or log4j logging works. I do see this in my broker logging:
2015-10-26 00:02:34,804 DEBUG org.apache.activemq.broker.region.Queue::expireMessages:905 queue://simple.queue expiring messages ..
2015-10-26 00:02:34,804 DEBUG org.apache.activemq.broker.region.Queue::expireMessages:911 queue://simple.queue expiring messages done.
2015-10-26 00:02:34,804 DEBUG org.apache.activemq.broker.region.Queue::doPageInForDispatch:1874 queue://simple.queue, subscriptions=0, memory=0%, size=2, pending=0 toPageIn: 0, Inflight: 0, pagedInMessages.size 2, pagedInPendingDispatch.size 2, enqueueCount: 2, dequeueCount: 0, memUsage:48394
Thanks for the nudge Gary! I had a block of settings for org.springframework in my log4j properties but the jms logging didn't appear until I added it for org.springframework.jms. I did a bit of analyzing with and without my code and saw that the console and file output remained the same.
So in the end, what I was missing that the author of that blog didn't explain is I needed to add the #EnableJms annotation to my JMSConfiguration class and I needed to add the following to the same class:
#Bean
public DefaultJmsListenerContainerFactory jmsListenerContainerFactory() {
DefaultJmsListenerContainerFactory factory =
new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory());
return factory;
}
I'm assuming that Spring Boot adds the necessary plumbing to your configuration class automagically and that is one thing I didn't do. Once I did this, the breakpoints worked fine.
It's interesting how there are so many ways to skin a cat in Spring and I could have easily stuck with using MessageListeners and overriding the onMessage method, but I wanted to try out using the JmsListener annotation since it's cleaner code. If I want to add a new JMS queue, all I need to do is create a POJO and add the #JmsListener annotation to the method that will receive the message from the producer.

What is the java config equivalent to tcp-outbound-gateway?

I have the following spring-integration XML config
<ip:tcp-outbound-gateway id="outboundClient"
request-channel="requestChannel"
reply-channel="string2ObjectChannel"
connection-factory="clientConnectionFactory"
request-timeout="10000"
reply-timeout="10000"/>
How can I write the Java config equivalent of the above?
I thought the equivalent would be
#Bean
public TcpOutboundGateway outboundClient() {
TcpOutboundGateway tcpOutboundGateway = new TcpOutboundGateway();
tcpOutboundGateway.setConnectionFactory(clientConnectionFactory());
tcpOutboundGateway.setRequiresReply(true);
tcpOutboundGateway.setReplyChannel(string2ObjectChannel());
tcpOutboundGateway.setRequestTimeout(10000);
tcpOutboundGateway.setSendTimeout(10000);
return tcpOutboundGateway;
}
But I couldn't find a way to set the request channel.
Any help would be appreciated.
Thank you
Your config looks good, but you should know in addition that any Spring Integration Consumer component consists of two main objects: MessageHandler (TcpOutboundGateway in your case) and EventDrivenConsumer for subscriable input-channel or PollingConsumer if input-channel is Pollable.
So, since you already have the first, handling, part you need another consuming. For this purpose Spring Integration suggests to mark your #Bean with endpoint annotations:
#Bean
#ServiceActivator(inputChannel = "requestChannel")
public TcpOutboundGateway outboundClient() {
See more in the Spring Integration Reference Manual.
However to allow such a annotation process (or any other Spring Integration infrastructure) you have to mark your #Configuration with #EnableIntegration.
Also consider to use Spring Integration Java DSL to have more gain from JavaConfig.

Spring 4 + Websockets: how to close the session?

I am using Spring 4 + Websockets + Stomp JS library.
I could not find any way to setup websocket ping/pong mechanism (heartbeat).
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans xmlns="http://www.springframework.org/schema/beans" ...">
<websocket:message-broker>
<websocket:stomp-endpoint path="/cors/auth/clientEndpoint">
<websocket:handshake-handler ref="myHandshakeHandler" />
</websocket:stomp-endpoint>
<websocket:simple-broker prefix="/queue, /topic" />
<websocket:client-inbound-channel>
<websocket:interceptors>
<bean class="com.mycompany.myproject.utils.messaging.MyInboundChannelInterception"></bean>
</websocket:interceptors>
</websocket:client-inbound-channel>
</websocket:message-broker>
<bean id="myHandshakeHandler" class="com.mycompany.myproject.utils.security.MyHandshakeHandler" />
<bean class="org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean">
<property name="maxSessionIdleTimeout" value="120000" />
</bean>
As result, I am implementing my own mechanism of ping/pong messages.
One of the tasks here - to implement server side closure of the websocket in case if no ping message during more than 10s from client.
And no way to do this using Spring Websockets!
Maybe somebody can tell me how to access Session object of the user or to close those Session via Spring Websockets?
Seems Spring is very limited here.
I'm surprised spring doc doesn't mention how to config server ping...It seems that spring expects us to read code instead of read doc..
after some time searching on net and reading source code, I realize it's already supported, but not documented at a noticeable place like spring websocket doc.
I'm using spring 4.3.3, and here is how to config server ping without using sockJS:
#Configuration
#EnableWebSocketMessageBroker
public class StompOverWebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws").setAllowedOrigins("*");
}
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.setApplicationDestinationPrefixes("/app");
ThreadPoolTaskScheduler pingScheduler = new ThreadPoolTaskScheduler();
pingScheduler.initialize();
registry.enableSimpleBroker("/topic")
.setHeartbeatValue(new long[]{20000, 0}).setTaskScheduler(pingScheduler);
}
....
}
and should make sure that you correctly set web socket session timeout, it should be greater than ping interval, like this:
<bean id="servletServerContainerFactoryBean" class="org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean">
<property name="maxSessionIdleTimeout" value="30000"/>
</bean>
In this case, configuring SockJS in your app could go a long way:
<websocket:stomp-endpoint path="/cors/auth/clientEndpoint">
<websocket:handshake-handler ref="myHandshakeHandler" />
<websocket:sockjs/>
</websocket:stomp-endpoint>
This will give you:
better HTTP clients support
heartbeat management
If you want to actually close a session from STOMP endpoints, then I suggest you to vote/follow the SPR-12288 JIRA issue.
To access websocket session you can use the following approach:
https://stackoverflow.com/a/32270216/2982835

Categories

Resources