JobServiceImpl.java:
#CircuitBreaker(name = "jobsApiServiceGetAllJobs", fallbackMethod = "getAllJobsFallback")
public ResponseEntity<JobsResponse> getAllJobs() {
...
throw new ApiException();
}
public ResponseEntity<JobsResponse> getAllJobsFallback(Throwable throwable) {
log.error("Fallback method called");
}
application.properties:
resilience4j.circuitbreaker.instances.jobsApiServiceGetAllJobs.ignoreExceptions=ccp.shared.platform.exception.ApiException,ccp.shared.platform.exception.BadRequestApiException
Whenever ccp.shared.platform.exception.ApiException is thrown, the fallback method is called even though I have added it in the ignoreExceptions list in the application.properties file. I want it to not trigger the fallback method when ApiException is thrown. I have tried similar questions on stack overflow and those does not work for me. Am I doing anything wrong?
Seems that the property is defined in the docs but actually, it does not work. There is a pull request for that -> CircuitBreakerConfigurationProperties has no ignoreException property.
You can use the common Spring+java configuration to achieve that in the meantime:
#Bean
CircuitBreaker reportingApiCircuitBreaker(CircuitBreakerRegistry registry) {
CircuitBreakerConfig config = CircuitBreakerConfig.custom()
.ignoreExceptions(ApiException.class, BadRequestApiException.class)
.build();
return registry.circuitBreaker("yourCircuitBreakername", config);
}
Related
I'm calling a service class method using the transformDeferred method:
public Mono<SPathResponse> getPath(SPathRequest request) {
return pathService.getPath(request)
.transformDeferred(RetryOperator.of(retry));
}
My retry configuration:
Retry retry = Retry.of("es", RetryConfig.custom()
.maxAttempts(3)
.waitDuration(Duration.of(30, ChronoUnit.MILLIS))
.writableStackTraceEnabled(true)
.failAfterMaxAttempts(true)
.retryOnException(throwable -> throwable instanceof RuntimeException)
.build());
and the test method:
#Test
void shouldRetry() {
BDDMockito.given(pathService.getPath(any(SPathRequest.class)))
.willReturn(Mono.error(RuntimeException::new))
.willReturn(Mono.just(SPathResponse.builder().build()));
cachedPathService.getPath(SPathRequest.builder()
.sourceNodeId("2")
.sourceCategoryId("1234")
.destinationNodeId("123")
.build())
.as(StepVerifier::create)
.expectError(RuntimeException.class)
.verify();
var captor = ArgumentCaptor.forClass(SPathRequest.class);
BDDMockito.then(pathService).should(times(2)).getPath(captor.capture());
Running it, I do get the expected exception, but 'getPath' is invoked only once.
I probably miss something cause the retry mechanism should have retried and return the stubbed result on the 2nd invocation which should fail the test since no exception occurred and the actual result should have been expected.
What is wrong with my configuration?
Edit:
I want the equivalent of this snippet (from resilience4j-reactor examples) for directly invoking on my Mono rather then wrapping a function with Mono.fromCallable):
#Test
public void returnOnErrorUsingMono() {
RetryConfig config = retryConfig();
Retry retry = Retry.of("testName", config);
RetryOperator<String> retryOperator = RetryOperator.of(retry);
given(helloWorldService.returnHelloWorld())
.willThrow(new HelloWorldException());
StepVerifier.create(Mono.fromCallable(helloWorldService::returnHelloWorld)
.transformDeferred(retryOperator))
.expectSubscription()
.expectError(HelloWorldException.class)
.verify(Duration.ofSeconds(1));
StepVerifier.create(Mono.fromCallable(helloWorldService::returnHelloWorld)
.transformDeferred(retryOperator))
.expectSubscription()
.expectError(HelloWorldException.class)
.verify(Duration.ofSeconds(1));
then(helloWorldService).should(times(6)).returnHelloWorld();
Retry.Metrics metrics = retry.getMetrics();
assertThat(metrics.getNumberOfFailedCallsWithRetryAttempt()).isEqualTo(2);
assertThat(metrics.getNumberOfFailedCallsWithoutRetryAttempt()).isEqualTo(0);
}
Where retryConfig is defined like this:
private RetryConfig retryConfig() {
return RetryConfig.custom()
.waitDuration(Duration.ofMillis(10))
.build();
}
Thanks.
I've a SpringBatch Job where I skip all duplicate items write to a Flat file.
However the FlatFileItemWriter throws the below error whenever there's a duplicate:
Writer must be open before it can be written to
Below is the Writer & SkipListener configuration -
#Bean(name = "duplicateItemWriter")
public FlatFileItemWriter<InventoryFileItem> dupItemWriter(){
return new FlatFileItemWriterBuilder<InventoryFileItem>()
.name("duplicateItemWriter")
.resource(new FileSystemResource("duplicateItem.txt"))
.lineAggregator(new PassThroughLineAggregator<>())
.append(true)
.shouldDeleteIfExists(true)
.build();
}
public class StepSkipListener implements SkipListener<InventoryFileItem, InventoryItem> {
private FlatFileItemWriter<InventoryFileItem> skippedItemsWriter;
public StepSkipListener(FlatFileItemWriter<InventoryFileItem> skippedItemsWriter) {
this.skippedItemsWriter = skippedItemsWriter;
}
#Override
public void onSkipInProcess(InventoryFileItem item, Throwable t) {
System.out.println(item.getBibNum() + " Process - " + t.getMessage());
try {
skippedItemsWriter.write(Collections.singletonList(item));
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
The overall Job is defined as below and I'm using the duplicateItemWriter from the SkipListener.
#Bean(name = "fileLoadJob")
#Autowired
public Job fileLoadJob(JobBuilderFactory jobs, StepBuilderFactory steps,
FlatFileItemReader<inventoryFileItem> fileItemReader,
CompositeItemProcessor compositeItemProcessor,
#Qualifier(value = "itemWriter") ItemWriter<InventoryItem> itemWriter,
StepSkipListener skipListener) {
return jobs.get("libraryFileLoadJob")
.start(steps.get("step").<InventoryFileItem, InventoryItem>chunk(chunkSize)
.reader(FileItemReader)
.processor(compositeItemProcessor)
.writer(itemWriter)
.faultTolerant()
.skip(Exception.class)
.skipLimit(Integer.parseInt(skipLimit))
.listener(skipListener)
.build())
.build();
}
I've also tried to write all data to FlatFileItemWriter - that doesn't work as well. However if write to a DB, then there's no issue with it.
The Spring-Batch version I'm using is - 4.3.3
I've referred to the below threads as well:
unit testing a FlatFileItemWriter outside of Spring - "Writer must be open before it can be written to" exception
Spring Batch WriterNotOpenException
FlatfileItemWriter with Compositewriter example
This was just gross oversight, I missed that the FlatFileItemWriter needs a stream.
I'm somewhat disappointed to put up this question, but I'm posting the answer just in case it helps someone.
The solution was as simple as adding a stream(dupItemWriter) to the Job Definition.
FlatfileItemWriter with Compositewriter example
#Bean(name = "fileLoadJob")
#Autowired
public Job fileLoadJob(JobBuilderFactory jobs, StepBuilderFactory steps,
FlatFileItemReader<inventoryFileItem> fileItemReader,
CompositeItemProcessor compositeItemProcessor,
#Qualifier(value = "itemWriter") ItemWriter<InventoryItem> itemWriter,
#Qualifier(value = "duplicateItemWriter")FlatFileItemWriter<InventoryFileItem> dupItemWriter,
StepSkipListener skipListener) {
return jobs.get("libraryFileLoadJob")
.start(steps.get("step").<InventoryFileItem, InventoryItem>chunk(chunkSize)
.reader(FileItemReader)
.processor(compositeItemProcessor)
.writer(itemWriter)
.faultTolerant()
.skip(Exception.class)
.skipLimit(Integer.parseInt(skipLimit))
.listener(skipListener)
.stream(dupItemWriter)
.build())
.build();
}
Its not absolutely necessary to include the .stream(dupItemWriter)
you can also call the writer's .open() method instead.
In my case I was creating dynamic/programmatic ItemWriters so adding them to the streams was not feasible.
.stream(writer-1)
.stream(writer-2)
.stream(writer-N)
Instead I called .open() method myself:
FlatFileItemWriter<OutputContact> itemWriter = new FlatFileItemWriter<>();
itemWriter.setResource(outPutResource);
itemWriter.setAppendAllowed(true);
itemWriter.setLineAggregator(lineAggregator);
itemWriter.setHeaderCallback(writer -> writer.write("ACCT,MEMBER,SOURCE"));
itemWriter.open(new ExecutionContext());
I had the same issue,
I created two writers by inheritance of FlatFileItemWriter.
That was working before I added #StepScope annotation. But after that, the first one throws an Exception with "Writer must be open before it can be written to" error message. But the second one worked without any problem.
I solved that by calling the method open(new ExecutionContext()); but i still not understand why the second one works but not the first one.
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 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
In my project, when I add a exception handler method in my controller, it doesn't work. But if I move this code to a demo project which have the same Spring version, missingParamterHandler method works well. Can anyone help me handle this problem?
#Controller
#RequestMapping("/order")
public class OrderControlle{
#ExceptionHandler(Exception.class)
public #ResponseBody ClientResult missingParamterHandler(Exception exception) {
/*inspect and exception and obtain meaningful message*/
ClientResult clientResult = new ClientResult();
clientResult.getBstatus().setCode(BstatusCode.PARAM_ERR);
return clientResult;
}
}
I try debug, and find in Spring's DispatcherServlet.java, matchingBeans.isEmpty() returns true, is it the reason #ExceptionHandler(Exception.class) not work in my project?
private void initHandlerExceptionResolvers(ApplicationContext context) {
this.handlerExceptionResolvers = null;
if (this.detectAllHandlerExceptionResolvers) {
// Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.
Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils
.beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerExceptionResolvers = new ArrayList<HandlerExceptionResolver>(matchingBeans.values());
// We keep HandlerExceptionResolvers in sorted order.
OrderComparator.sort(this.handlerExceptionResolvers);
}
}
.....
explicit add <bean class="org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver" /> or <mvc:annotation-driven /> to app-context.xml, solve my problem.
without this above line, spring add ExceptionResolvers as below in my project.
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver