I am using a #Retryable annotation on a method in a #Serviceclass
#Service
#EnableRetry
public class PushService {
#Retryable(maxAttempts=5)
public Result pushIt(myMessage messageIn) {
...
}
}
and it works like a charme: I am getting a message directly from RabbitMQ, it is not acknowledged until either there is no error, or the number of attempts reach 5, and at that time the messages goes straight to the DLQ, right as I wanted.
My only problem is that I need to set the maxAttempts dynamically, from a property file. The solution should be setting an interceptor, but the only fact of having one causes an error, for example when I have :
#Service
#EnableRetry
public class PushService {
#Retryable(interceptor="myInterceptor")
public Result pushIt(myMessage messageIn) {
...
}
}
where myInterceptor is defined as :
#Bean
public StatefulRetryOperationsInterceptor myInterceptor() {
return RetryInterceptorBuilder.stateful().maxAttempts(5).build();
}
I get an infinite loop with the following exception:
2015-04-08 07:12:10,970 GMT [SimpleAsyncTaskExecutor-1] (ConditionalRejectingErrorHandler.java:67) WARN listener.ConditionalRejectingErrorHandler: Execution of Rabbit message listener failed.
org.springframework.amqp.rabbit.listener.exception.ListenerExecutionFailedException: Listener threw exception
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.wrapToListenerExecutionFailedExceptionIfNeeded(AbstractMessageListenerContainer.java:864)
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.doInvokeListener(AbstractMessageListenerContainer.java:802)
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.invokeListener(AbstractMessageListenerContainer.java:690)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.access$001(SimpleMessageListenerContainer.java:82)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$1.invokeListener(SimpleMessageListenerContainer.java:167)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.invokeListener(SimpleMessageListenerContainer.java:1241)
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.executeListener(AbstractMessageListenerContainer.java:660)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.doReceiveAndExecute(SimpleMessageListenerContainer.java:1005)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.receiveAndExecute(SimpleMessageListenerContainer.java:989)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.access$700(SimpleMessageListenerContainer.java:82)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$AsyncMessageProcessingConsumer.run(SimpleMessageListenerContainer.java:1103)
at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.ArrayIndexOutOfBoundsException: 1
at org.springframework.amqp.rabbit.config.StatefulRetryOperationsInterceptorFactoryBean$3.getKey(StatefulRetryOperationsInterceptorFactoryBean.java:103)
at org.springframework.retry.interceptor.StatefulRetryOperationsInterceptor.invoke(StatefulRetryOperationsInterceptor.java:132)
at org.springframework.retry.annotation.AnnotationAwareRetryOperationsInterceptor.invoke(AnnotationAwareRetryOperationsInterceptor.java:118)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:653)
at com.acme.push.service.PushService$$EnhancerBySpringCGLIB$$9d503bc1.pushMessage(<generated>)
at com.acme.push.receiver.PushListener.onMessage(PushListener.java:42)
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.doInvokeListener(AbstractMessageListenerContainer.java:799)
... 10 more
I am pretty sure I am keeping it too simple, but I just have no clues on what could cause this error and how to solve it, anybody has an idea of what is going on ?
The purpose of org.springframework.amqp.rabbit.config.RetryInterceptorBuilder is not to be used using the annotation #Retryable.
Its purpose is to be used with the advice chain of the SimpleRabbitListenerContainerFactory class.
See the reference documentation receiving-messages and he SimpleMessageListenerContainer#invokeListener signature:
#Override
protected void invokeListener(Channel channel, Message message) throws Exception {
proxy.invokeListener(channel, message);
}
You annotation will be useless as soon as you configure the advice chain with the appropriate RetryInterceptor.
I finally managed to obtain the needed flexibility without using the #Retrayable annotation.
I created a RetryAdvice with my parameters for delay and maximum number of attempts:
#Bean
public MethodInterceptor retryAdvice() {
FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
backOffPolicy.setBackOffPeriod(delay);
return RetryInterceptorBuilder.stateless().backOffPolicy(backOffPolicy)
.maxAttempts(maxAttempts).build();
}
and I inserted the Advice in the adviceChain of the ListenerContainer
#Bean
public SimpleMessageListenerContainer replyListenerContainer() {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(pushConnectionFactory());
container.setQueues(pushQueue());
container.setMessageListener(pushListener());
Advice[] adviceChain = new Advice[] { retryAdvice() };
container.setAdviceChain(adviceChain);
return container;
}
In this way, whenever my Listener will be throwing
throw new AmqpRejectAndDontRequeueException(cause);
this will cause the container to retry the indicated number of times with the desired delay, after which the exception will be propagated and the message will be delivered in the DLQ
Related
Hi I am trying to add Spring IntegrationFlow but dont know what is the error for following scenario.
My IntegrationConfig is as below
#Configuration
#EnableIntegration
#IntegrationComponentScan
public class IntegrationConfig {
#Bean
public IntegrationFlow sayHelloFlow(){
String uri = "http://localhost:8081/hellos";
return IntegrationFlows.from("integration.example.gateway.channel")
.filter("headers['operation'] == 'OPERATION_A'")
.<SearchRequest>handle((request) -> {
Map<String, String> header = new HashMap<String, String>();
header.put("a_header", request.getHeaders().get("initial_val", String.class));
SearchRequestB obj = new SearchRequestB(
request.getPayload()+"Modified",
header);
})
.handle(Http.outboundGateway(uri).httpMethod(HttpMethod.POST))
.get();
}
}
My IntegrationGateway class is
import org.springframework.integration.annotation.Gateway;
import org.springframework.integration.annotation.MessagingGateway;
#MessagingGateway
public interface IntegrationGateway {
#Gateway(requestChannel = "integration.example.gateway.channel")
public String canSearch(String message);
}
In the above problem is once I remove handle(Http.outboundGateway(uri).httpMethod(HttpMethod.POST)) line it works properly. and by keeping same line I am getting following error
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.integration.dsl.IntegrationFlow]: Factory method 'sayHelloFlow' threw exception; nested exception is org.springframework.beans.factory.BeanCreationException: The 'currentComponent' (com.integration.config.IntegrationConfig$$Lambda$793/0x0000000800567440#340cb97f) is a one-way 'MessageHandler' and it isn't appropriate to configure 'outputChannel'. This is the end of the integration flow.
I am wanted to call the another REST end point within this handle method.
What is wrong I am doing here
Thanks in advance !!
Your problem that handle(Message<?>) is really a one-way endpoint with a void return type and can be used only in the end of flow. That’s what that error about : since this endpoint cannot produce reply, there is no way to call the next endpoint in the flow. It is more suspicious that your code introduces that obj variable and does nothing with it.
See more in docs : https://docs.spring.io/spring-integration/docs/current/reference/html/dsl.html#java-dsl-class-cast.
To fix your solution we need to know if you want to call a rest with that obj or in parallel with this custom lambda.
I am having one config file as below,
#Slf4j
#Configuration
#EnableJms
public class MyQpidConfig {
#Bean("myQpidConn")
public AMQConnectionFactory amqConnectionFactory() throws URLSyntaxException {
AMQConnectionFactory amqConnectionFactory = new AMQConnectionFactory("URL");
return amqConnectionFactory;
}
#Bean("myJmsContainer")
public DefaultMessageListenerContainer defaultMessageListenerContainer(#Qualifier("myQpidAdaptor") MessageListenerAdapter messageListenerAdapter ,#Qualifier("myQpidConn")AMQConnectionFactory connectionFactory) {
DefaultMessageListenerContainer messageListenerContainer = new DefaultMessageListenerContainer();
messageListenerContainer.setConnectionFactory(connectionFactory);
messageListenerContainer.setDestinationName(destinationName);
messageListenerContainer.setMessageListener(messageListenerAdapter);
messageListenerContainer.setPubSubDomain(false);
messageListenerContainer.setSessionTransacted(true);
messageListenerContainer.setDurableSubscriptionName("MYSUBSCRIBER");
messageListenerContainer.setSubscriptionDurable(true);
messageListenerContainer.setAutoStartup(true);
messageListenerContainer.setDestination((Queue) () -> "MYQUEUE");
return messageListenerContainer;
}
#Bean("myQpidAdaptor")
public MessageListenerAdapter messageListenerAdapter() {
Preconditions.checkNotNull(qpidMessageListner, "Consumer listener not set");
MessageListenerAdapter messageListenerAdapter = new MessageListenerAdapter(new MyQpidMessageListner());
messageListenerAdapter.setDefaultListenerMethod("onMessage");
messageListenerAdapter.setDefaultResponseQueueName("MYQUEUE");
return messageListenerAdapter;
}
}
where destinationName is equal to
ADDR:MYSUBSCRIBER:MYQUEUE; {"create":"receiver",
"node":{"durable":true,"x-declare":
{"exclusive":true,"auto-delete":false,
"x-bindings":[{"exchange":"amq.topic","key":"MYQUEUE"}]}}}
and my Listener class is as below,
#Slf4j
#Component
public class MyQpidMessageListner implements MessageListener{
#Override
public void onMessage(Message jmsBytesMessage) {
log.info("Control Inside");
//to do
}
}
as a async consumer when i am running the my application at that time getting exception related to destination,
DefaultMessageListenerContainer -
Setup of JMS message listener invoker failed for destination 'com.my.MyQpidConfig$$Lambda$637/429515253#3aa1975' - trying to recover. Cause: com.my.MyQpidConfig$$Lambda$637/429515253 cannot be cast to org.apache.qpid.client.AMQDestination
If its related to AMQDestination then how can i create the AMQDestination in java code.
I am not getting any resource for this QPID issue.
Any suggestion help must be appreciated.
For some reason you're passing a lambda to org.springframework.jms.listener.DefaultMessageListenerContainer.setDestination(Destination), i.e.:
messageListenerContainer.setDestination((Queue) () -> "MYQUEUE");
This method needs a javax.jms.Destination implementation (e.g. org.apache.qpid.client.AMQDestination), not a lambda.
You're already setting the destination name, i.e.:
messageListenerContainer.setDestinationName(destinationName);
So it's not clear why you're also trying to set the destination as well. These are two ways to configure the same thing.
I recommend you simply remove the code attempting to set the destination and leave the code setting the destination name.
Is there a way to catch DestinationResolutionException and MessageDispatchingException when using DSL? These exceptions usually indicate misconfiguration but I am not sure how I could configure my flow to catch these exceptions and apply some custom logic?
#SpringBootApplication
public class IntegrationMisconfigurationExampleApplication {
public static void main(final String[] args) {
SpringApplication.run(IntegrationMisconfigurationExampleApplication.class, args);
}
#Bean
public IntegrationFlow loggingFlow() {
return IntegrationFlows.from("input")
.<String, String>transform(String::toUpperCase)
// .nullChannel();
.get();
}
#Bean
public CommandLineRunner demo() {
return args -> {
final MessagingTemplate template = messagingTemplate();
template.convertAndSend("input", "abc");
};
}
#Bean
public MessagingTemplate messagingTemplate() {
return new MessagingTemplate();
}
}
The example above throws a DestinationResolutionException because loggingFlow.transformer#0 is not properly initialized. Is there a way to catch this exception?
Those exceptions are runtime errors. We really can't determine a misconfiguration at startup.
The way to catch runtime exception like that and do some analyzing work is with the ExpressionEvaluatingRequestHandlerAdvice, which you can add to your transform(String::toUpperCase) configuration in the second argument for endpoint configuration:
.<String, String>transform(String::toUpperCase, e-> e.advice(myExpressionEvaluatingRequestHandlerAdvice()))
See more info about this advice in the Reference Manual: https://docs.spring.io/spring-integration/docs/current/reference/html/#message-handler-advice-chain
Also you need to keep in mind that transformer is really a request-reply component with required non-null return value. Therefore you really can't configure a transform() just for one-way flow. It is going to throw an exception when there is no next channel in the flow or no replyChannel header in the message.
I have used spring declarative retry in my project like
#Service
class Service {
#Async #Retryable(maxAttempts=12, backoff=#Backoff(delay=100, maxDelay=500))
public service() {
// ... do something
}
}
Now, I have two questions.
Is it fine to use retry with async, I don't have any issue, just
want to be sure.
The second requirement is, if the process fails I want to log it to log file including the number of remaining retries. So, is there a way to pass, or obtain the number of remaining retries from inside the method?
There is no way around using the annotations, #Recover annotated method executes only after the last failed retry, not after each one failing.
Refer to this github documentation
An excerpt from the link above- "Call the "service" method and if it fails with a RemoteAccessException then it will retry (up to three times by default), and then execute the "recover" method if unsuccessful."
Even with using RetryTemplate the Retry callback is called only after all retries are exhausted.
Another excerpt form the same link- "When a retry is exhausted the RetryOperations can pass control to a different callback, the RecoveryCallback. To use this feature clients just pass in the callbacks together to the same method"
You should use the #Recover annotation to perform an action on each fail and keep a count inside your object outside of the methods. Make sure no other methods interact with this counter. Here is the basic premise:
#Service
class Service {
private int attemptsLeft=12;
#Retryable(maxAttempts=12, backoff=#Backoff(delay=100, maxDelay=500))
public service() {
// ... do something that throws a KnownException you create to catch later on.
}
#Recover
public void connectionException(KnownException e) {
this.attemptsLeft = this.attemptsLeft-1; //decrease your failure counter
Logger.warn("Retry attempts left:{}",attemptsLeft);
}
}
If you don't want a member variable tracking count, you might need to ditch the annotations and declare the RetryTemplate to get access to the context, with the getRetryCount() method.
public String serviceWithRetry() {
RetryTemplate retryTemplate = new RetryTemplate();
final SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
retryPolicy.setMaxAttempts(12);
retryTemplate.setRetryPolicy(retryPolicy);
FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
backOffPolicy.setInterval(100L);
retryTemplate.setBackOffPolicy(backOffPolicy);
retryTemplate.execute(new RetryCallback<Void, RuntimeException>()
return retryTemplate.execute(new RetryCallback<Void, RuntimeException>() {
#Override
public void doWithRetry(RetryContext context) {
LOG.info("Retry of connection count: {}", context.getRetryCount());
return //something with your connection logic
}
});
}
I'm just starting to learn Spring Cloud Streams and Dataflow and I want to know one of important use cases for me. I created example processor Multiplier which takes message and resends it 5 times to output.
#EnableBinding(Processor.class)
public class MultiplierProcessor {
#Autowired
private Source source;
private int repeats = 5;
#Transactional
#StreamListener(Processor.INPUT)
public void handle(String payload) {
for (int i = 0; i < repeats; i++) {
if(i == 4) {
throw new RuntimeException("EXCEPTION");
}
source.output().send(new GenericMessage<>(payload));
}
}
}
What you can see is that before 5th sending this processor crashes. Why? Because it can (programs throw exceptions). In this case I wanted to practice fault prevention on Spring Cloud Stream.
What I would like to achieve is to have input message backed in DLQ and 4 messages that were send before to be reverted and not consumed by next operand (just like in normal JMS transaction). I tried already to define following properties in my processor project but without success.
spring.cloud.stream.bindings.output.producer.autoBindDlq=true
spring.cloud.stream.bindings.output.producer.republishToDlq=true
spring.cloud.stream.bindings.output.producer.transacted=true
spring.cloud.stream.bindings.input.consumer.autoBindDlq=true
Could you tell me if it possible and also what am I doing wrong? I would be overwhelmingly thankful for some examples.
You have several issues with your configuration:
missing .rabbit in the rabbit-specific properties)
you need a group name and durable subscription to use autoBindDlq
autoBindDlq doesn't apply on the output side
The consumer has to be transacted so that the producer sends are performed in the same transaction.
I just tested this with 1.0.2.RELEASE:
spring.cloud.stream.bindings.output.destination=so8400out
spring.cloud.stream.rabbit.bindings.output.producer.transacted=true
spring.cloud.stream.bindings.input.destination=so8400in
spring.cloud.stream.bindings.input.group=so8400
spring.cloud.stream.rabbit.bindings.input.consumer.durableSubscription=true
spring.cloud.stream.rabbit.bindings.input.consumer.autoBindDlq=true
spring.cloud.stream.rabbit.bindings.input.consumer.transacted=true
and it worked as expected.
EDIT
Actually, no, the published messages were not rolled back. Investigating...
EDIT2
OK; it does work, but you can't use republishToDlq - because when that is enabled, the binder publishes the failed message to the DLQ and the transaction is committed.
When that is false, the exception is thrown to the container, the transaction is rolled back, and RabbitMQ moves the failed message to the DLQ.
Note, however, that retry is enabled by default (3 attempts) so, if your processor succeeds during retry, you will get duplicates in your output.
For this to work as you want, you need to disable retry by setting the max attempts to 1 (and don't use republishToDlq).
EDIT3
OK, if you want more control over the publishing of the errors, this will work, when the fix for this JIRA is applied to Spring AMQP...
#SpringBootApplication
#EnableBinding({ Processor.class, So39018400Application.Errors.class })
public class So39018400Application {
public static void main(String[] args) {
SpringApplication.run(So39018400Application.class, args);
}
#Bean
public Foo foo() {
return new Foo();
}
public interface Errors {
#Output("errors")
MessageChannel errorChannel();
}
private static class Foo {
#Autowired
Source source;
#Autowired
Errors errors;
#StreamListener(Processor.INPUT)
public void handle (Message<byte[]> in) {
try {
source.output().send(new GenericMessage<>("foo"));
source.output().send(new GenericMessage<>("foo"));
throw new RuntimeException("foo");
}
catch (RuntimeException e) {
errors.errorChannel().send(MessageBuilder.fromMessage(in)
.setHeader("foo", "bar") // add whatever you want, stack trace etc.
.build());
throw e;
}
}
}
}
with properties:
spring.cloud.stream.bindings.output.destination=so8400out
spring.cloud.stream.bindings.errors.destination=so8400errors
spring.cloud.stream.rabbit.bindings.errors.producer.transacted=false
spring.cloud.stream.rabbit.bindings.output.producer.transacted=true
spring.cloud.stream.bindings.input.destination=so8400in
spring.cloud.stream.bindings.input.group=so8400
spring.cloud.stream.rabbit.bindings.input.consumer.transacted=true
spring.cloud.stream.rabbit.bindings.input.consumer.requeue-rejected=false
spring.cloud.stream.bindings.input.consumer.max-attempts=1