I am implementing an AOP interceptor which process the logic in RetryTemplate. And problem is that ProceedingJoinPoint#execute is declared to throw Throwable, but RetryTemplate#doWithRetry allows to re-throw an Exception only. So I have to wrap Throwable and then unwrap it back.
My best try looks like this:
#Aspect
#Component
public class RetryableOperationFailureInterceptor {
#Around("#annotation(retryable)")
public Object performOperation(final ProceedingJoinPoint pjp, Retryable retryable)
throws Throwable {
RetryTemplate retryTemplate = getRetryTemplate(retryable);
try {
return retryTemplate.execute(new RetryCallback<Object>() {
#Override
public Object doWithRetry(RetryContext context)
throws Exception {
try {
return pjp.proceed(); // throws Throwable
} catch (Exception e) {
throw e;
} catch (Error e) {
throw e;
} catch (Throwable e) {
throw new RetryWrappedException(e);
}
}
});
} catch (RetryWrappedException e) {
throw e.getCause();
}
}
}
Is there any way to avoid wrapping or/and catching Throwable? The latest is more important for me, as catching Throwable trigger a code style violation.
M. Deinum is right, but you do not need to use your own RetryWrappedException because AspectJ already offers a SoftException (extends RuntimeException) for softening/wrapping checked exceptions, but it also works for other Throwables.
Because I found your question interesting, but am not a Spring user, I replicated some of your sample classes as dummies or minimal implementations in order to create a fully working POJO + AspectJ example without any Spring dependency. I also extended the around advice so as to do the retry loop by itself, reading a maxRetries value from the Retryable annotation. I know it is not 100% your use case, but it illustrates the problem and a few other things:
Dummy classes/interfaces:
package de.scrum_master.app;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
#Retention(RetentionPolicy.RUNTIME)
public #interface Retryable {
int maxRetries();
}
--
package de.scrum_master.app;
public class RetryContext {}
--
package de.scrum_master.app;
public interface RetryCallback<T> {
Object doWithRetry(RetryContext context) throws Exception;
}
--
package de.scrum_master.app;
public class RetryTemplate {
public Object execute(RetryCallback<Object> retryCallback) throws Exception {
return retryCallback.doWithRetry(new RetryContext());
}
}
Driver application:
package de.scrum_master.app;
import java.util.Random;
public class Application {
private static final Random RANDOM = new Random();
public static void main(String[] args) throws Exception {
new Application().performOperation();
}
#Retryable(maxRetries = 2)
public void performOperation() throws Exception {
System.out.println("Performing some operation");
if (RANDOM.nextBoolean())
throw new Exception("Operation failed");
System.out.println("Operation succeeded");
}
}
Without an aspect, there will be no retries and performOperation() will randomly either succeed
Performing some operation
Operation succeeded
or fail
Performing some operationException in thread "main"
java.lang.Exception: Operation failed
at de.scrum_master.app.Application.performOperation(Application.java:16)
at de.scrum_master.app.Application.main(Application.java:9)
Intercept/retry aspect:
Now with the following aspect we can
intercept methods annotated by Retryable,
get the number of maximum retries and
loop and try joinpoint execution via proceed() as many times as specified (maxRetries + 1 because even if zero retries are requested we call the method at least once).
As you can also see, we use different error handling depending on error type:
An Exception will either be just printed if the maximum number of retries has not been reached yet or re-thrown otherwise, i.e. if the final retry also fails, its the exception will be thrown.
An Error will always be re-thrown because an error is usually thrown by the JVM and non-recoverable, i.e. in this case there will be no more retries.
A Throwable (should never occur) will be wrapped into a SoftException and the latter re-thrown.
package de.scrum_master.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.SoftException;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import de.scrum_master.app.RetryCallback;
import de.scrum_master.app.RetryContext;
import de.scrum_master.app.RetryTemplate;
import de.scrum_master.app.Retryable;
#Aspect
public class RetryableOperationFailureInterceptor {
#Around("#annotation(retryable) && execution(* *(..))")
public Object performOperation(final ProceedingJoinPoint pjp, final Retryable retryable) throws Throwable {
final int maxRetries = retryable.maxRetries();
System.out.println(pjp + " - maxRetries = " + maxRetries);
RetryTemplate retryTemplate = getRetryTemplate(retryable);
return retryTemplate.execute(
new RetryCallback<Object>() {
#Override
public Object doWithRetry(RetryContext context) throws Exception {
for (int i = 0; i <= maxRetries; i++) {
System.out.println("Retry #" + i);
try {
return pjp.proceed();
} catch (Exception e) {
if (i == maxRetries)
throw e;
System.out.println(e);
} catch (Error e) {
throw e;
} catch (Throwable t) {
throw new SoftException(t);
}
}
return null;
}
}
);
}
private RetryTemplate getRetryTemplate(Retryable retryable) {
return new RetryTemplate();
}
}
Resulting output with active Aspect:
Immediate success (no retries necessary):
execution(void de.scrum_master.app.Application.performOperation()) - maxRetries = 2
Retry #0
Performing some operation
Operation succeeded
Success after retries:
execution(void de.scrum_master.app.Application.performOperation()) - maxRetries = 2
Retry #0
Performing some operation
java.lang.Exception: Operation failed
Retry #1
Performing some operation
java.lang.Exception: Operation failed
Retry #2
Performing some operation
Operation succeeded
Failure after final retry:
execution(void de.scrum_master.app.Application.performOperation()) - maxRetries = 2
Retry #0
Performing some operation
java.lang.Exception: Operation failed
Retry #1
Performing some operation
java.lang.Exception: Operation failed
Retry #2
Performing some operation
Exception in thread "main" java.lang.Exception: Operation failed
at de.scrum_master.app.Application.performOperation_aroundBody0(Application.java:16)
at de.scrum_master.app.Application$AjcClosure1.run(Application.java:1)
at org.aspectj.runtime.reflect.JoinPointImpl.proceed(JoinPointImpl.java:149)
at de.scrum_master.aspect.RetryableOperationFailureInterceptor$1.doWithRetry(RetryableOperationFailureInterceptor.java:27)
at de.scrum_master.app.RetryTemplate.execute(RetryTemplate.java:5)
at de.scrum_master.aspect.RetryableOperationFailureInterceptor.performOperation(RetryableOperationFailureInterceptor.java:20)
at de.scrum_master.app.Application.performOperation(Application.java:13)
at de.scrum_master.app.Application.main(Application.java:9)
Related
I have a notification service(I have control of this class).
If there is any unchecked exception, then I do not want to throw it. But instead want to log it in a specific manner.
I can directly have try catch block in each method's implementation but I am in search of some magic here 😄
Is there a common way that this can be handled through Spring?
Update:
AOP is also a way to do it. For example: https://dzone.com/articles/handling-exceptions-using-springs-aop
Any other direct implementation for this?
This was my requirement but I was not able to find anything with respect to this.
I faced similar issues when dealing with calling multiple apis from rest service, where i was suppose to provide a fallback implementation when error occured. My Aspect was more than what i am giving example here.
Service
#Service
public class SuspiciousService {
final Random random = new Random();
public String beSuspicious() {
final boolean value = random.nextBoolean();
if (value) {
throw new IllegalStateException("Exception occured for: " + value);
}
return "I am not suspicious";
}
}
Sample service which randomly throws an error.
Controller
#RestController
#RequestMapping("/is-suspicious")
#AllArgsConstructor
public class SampleController {
private final SuspiciousService suspiciousService;
#GetMapping
public Map<String, String> get() {
return Map.of("isSuspicious", suspiciousService.beSuspicious());
}
}
Controller which invokes this service.
Finally, Around Aspect catches this exception and provides the sample response.
#Aspect
#Component
#Order(2)
public class AspectAroundSuspiciousService {
#Around("execution(* in.silentsudo.sprintbootresttemplate.SuspiciousService.beSuspicious(..))")
public Object parallelExecuteBeforeAndAfterCompose(ProceedingJoinPoint point) throws Throwable {
try {
return point.proceed();
} catch (RuntimeException re) {
return "Yes, I am suspicious";
}
}
}
The other approach is if you are using ByteBuddy, you can add annotation to the method throwing exception
#Advice.OnMethodExit(onThrowable = RuntimeException.class)
and have an ExceptionHandler to cath this
#ExceptionHandler
private String suspiciousRuntimeException(RuntimeException exception) {
return "Yes, I am suspicious from ex handler, error: " + exception.getMessage();
}
I choose aspect over bytebuddy for simple reason as i was handling ladder of api exception, where as this implementation will catch in general RuntimeException happenning from service#method
I have a scenario where I call three #Transactional #Async methods. Everything works fine except all three methods have their own transaction context. I want to execute them in calling method's transaction context.
My Calling method is like this:
#Transactional
public void execute(BillingRequestDto requestDto) {
try {
LOGGER.info("Start Processing Request : {}", requestDto.getId());
List<Future<?>> futures = new ArrayList<>();
futures.add(inboundProcessingService.execute(requestDto));
futures.add(orderProcessingService.execute(requestDto));
futures.add(waybillProcessingService.execute(requestDto));
futures.stream().parallel().forEach(future -> {
try {
future.get();
} catch (Exception e) {
futures.forEach(future1 -> future1.cancel(true));
throw new FBMException(e);
}
});
requestDto.setStatus(RequestStatus.SUCCESS.name());
requestDto.setCompletedAt(new Date());
LOGGER.info("Done Processing Request : {}", requestDto.getId());
} catch (Exception e) {
requestDto.setStatus(RequestStatus.FAIL.name());
requestDto.setCompletedAt(new Date());
throw new FBMException(e);
}
}
And all called methods are annotated with #Async and #Transactional.
#Transactional
#Async
public Future<Void> execute(BillingRequestDto requestDto) {
LOGGER.info("Start Waybill Processing {}", requestDto.getId());
long count = waybillRepository.deleteByClientNameAndMonth(requestDto.getClientName(), requestDto.getMonth());
LOGGER.info("Deleted {} Records for Request {} ", count, requestDto.getId());
try (InputStream inputStream = loadCsvAsInputStream(requestDto)) {
startBilling(requestDto, inputStream);
} catch (IOException e) {
LOGGER.error("Error while processing");
throw new FBMException(e);
}
LOGGER.info("Done Waybill Processing {}", requestDto.getId());
return null;
}
Implementation for all three methods is more or less same.
Now if there is a failure in any of these methods then transaction in rolled-back for that method only.
My requirement is to run all three methods in calling methods transaction context so any exception in one method will rollback all three methods.
This scenario works well if I disable #Async. There are time taking methods so I want them to run in parallel.
Please suggest any solution for this.
I guess you should use spring TransactionTemplate for programmatic control.
The main thread should perform control, if any thread throws exception you should notify the others thread that they should be rollbacked.
Say, each "transactional" thread after execution should wait(), in case of no exception just perform notifyAll() and in your thread do transaction commit, in case of exception you should call Thread.interrupt() and do rollback.
I think that yours #Async method can throw checked exception
#Transactional
#Async
public Future<Void> execute(BillingRequestDto requestDto) throw RollBackParentTransactionException {
...
}
and the caller can be annotated with:
#Transactional(rollbackFor = RollBackParentTransactionException.class)
public void execute(BillingRequestDto requestDto) { ... }
that should work.
I have some troubles with aspectj and its syntax. I have something written on a java file and I want to translate it to an .aj file because I think that is easier, but I can't find a tutorial to follow.
This is my code:
#Aspect
public class Aspect{
#Pointcut("#annotation(annotationVariableName)")
public void annotationPointCutDefinition(Annotation annotationVariableName){
}
#Pointcut("execution(* *(..))")
public void atExecution(){}
#Around("annotationPointCutDefinition(withTransactionVariableName) && atExecution()")
public Object aroundAdvice(ProceedingJoinPoint joinPoint, Annotation annotationVariableName) throws Throwable {
boolean parameter= annotationVariableName.parameter();
Object returnObject = null;
try {
returnObject = joinPoint.proceed();
} catch (Throwable throwable) {
throw throwable;
}
return returnObject;
}
}
Can anyone help me with this? Thank you!
I have made up a little example MCVE relating to your comment about transaction management, so as to make the code and its log output a little clearer:
Annotation:
package de.scrum_master.app;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface Transaction {
boolean myFlag();
}
Driver application:
Please note that two methods bear the annotation, one does not.
package de.scrum_master.app;
public class Application {
public static void main(String[] args) {
Application application = new Application();
application.doSomething();
application.doSomethingElse();
application.doSomethingWeird();
}
#Transaction(myFlag = true)
public void doSomething() {
System.out.println("Doing something");
}
public void doSomethingElse() {
System.out.println("Doing something else\n");
}
#Transaction(myFlag = false)
public void doSomethingWeird() {
System.out.println("Doing something weird");
throw new RuntimeException("oops");
}
}
Aspect:
package de.scrum_master.aspect;
import org.aspectj.lang.SoftException;
import de.scrum_master.app.Transaction;
public aspect TransactionAspect {
pointcut hasAnnotation(Transaction myAnnotation) : #annotation(myAnnotation);
pointcut methodExecution() : execution(* *(..));
Object around(Transaction myAnnotation) : methodExecution() && hasAnnotation(myAnnotation) {
System.out.println(thisJoinPoint + " -> " + myAnnotation);
boolean parameter = myAnnotation.myFlag();
System.out.println("Transaction start");
try {
Object result = proceed(myAnnotation);
System.out.println("Transaction commit\n");
return result;
} catch (Exception e) {
System.out.println("Transaction roll-back\n");
// Native AspectJ advices must not return checked exceptions, only runtime exceptions.
// So we soften the caught exception, just in case.
throw new SoftException(e);
}
}
}
Console log:
execution(void de.scrum_master.app.Application.doSomething()) -> #de.scrum_master.app.Transaction(myFlag=true)
Transaction start
Doing something
Transaction commit
Doing something else
execution(void de.scrum_master.app.Application.doSomethingWeird()) -> #de.scrum_master.app.Transaction(myFlag=false)
Transaction start
Doing something weird
Transaction roll-back
Exception in thread "main" org.aspectj.lang.SoftException
at de.scrum_master.app.Application.doSomethingWeird_aroundBody3$advice(Application.java:22)
at de.scrum_master.app.Application.doSomethingWeird(Application.java:1)
at de.scrum_master.app.Application.main(Application.java:8)
Caused by: java.lang.RuntimeException: oops
at de.scrum_master.app.Application.doSomethingWeird_aroundBody2(Application.java:23)
at de.scrum_master.app.Application.doSomethingWeird_aroundBody3$advice(Application.java:17)
... 2 more
By the way, if you are fine with anonymous pointcuts, there is no need to declare them separately. You can just do it this way:
Aspect variant with anonymous pointcut:
package de.scrum_master.aspect;
import org.aspectj.lang.SoftException;
import de.scrum_master.app.Transaction;
public aspect TransactionAspect {
Object around(Transaction myAnnotation) : execution(* *(..)) && #annotation(myAnnotation) {
System.out.println(thisJoinPoint + " -> " + myAnnotation);
boolean parameter = myAnnotation.myFlag();
System.out.println("Transaction start");
try {
Object result = proceed(myAnnotation);
System.out.println("Transaction commit\n");
return result;
} catch (Exception e) {
System.out.println("Transaction roll-back\n");
// Native AspectJ advices must not return checked exceptions, only runtime exceptions.
// So we soften the caught exception, just in case.
throw new SoftException(e);
}
}
}
Personally I found the following programmer guide useful myself although it's not really a tutorial : https://eclipse.org/aspectj/doc/next/progguide/index.html. Click on Pointcuts to get the basics of converting your pointcuts, advice is also covered on that page although it doesn't detail "around" advice but there is an example of that under production aspects
A quick search for a tutorial throws up the following (I haven't used this) :
http://o7planning.org/en/10257/java-aspect-oriented-programming-tutorial-with-aspectj
Could you tell me please, is it normal practice to write a method (example: JUnit Test) that throws an Exception, for example:
class A {
public String f(int param) throws Exception {
if (param == 100500)
throw new Exception();
return "";
}
}
private A object = new A();
#Test
public void testSomething() throws Exception {
String expected = "";
assertEquals(object.f(5), expected);
}
In fact, method f() won't throw an exception for that parameter(5) but nevertheless I must declare that exception.
Yes it is completely fine, and if it does throw the exception the test will be considered as failed.
You need to specify that the method throws an Exception even if you know that the specific case does not (this check is done by the compiler).
In this case, what you expect is object.f(5) returns an empty string. Any other outcome (non-empty string or throwing an exception) would result in a failed test case.
A JUnit-Test is meant to test a given method for correct behavior. It is a perfectly valid scenario that the tested method throws an error (e.g. on wrong parameters). If it is a checked exception, you either have to add it to your test method declaration or catch it in the method and Assert to false (if the exception should not occur).
You can use the expected field in the #Test annotation, to tell JUnit that this test should pass if the exception occurs.
#Test(expected = Exception.class)
public void testSomething() throws Exception {
String expected = "";
assertEquals(object.f(5), expected);
}
In this case, the tested method should throw an exception, so the test will pass. If you remove the expected = Exception.class from the annotation, the test will fail if an exception occurs.
If the method you're calling throws a checked exception yes, you'll either need a try catch or to rethrow. It's fine to do this from the test itself. There are a variety of ways to test Exception using JUnit. I've tried to provide a brief summary below:
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
/**
* Example uses Kent Beck - Test Driven Development style test naming
* conventions
*/
public class StackOverflowExample {
#Rule
public ExpectedException expectedException = ExpectedException.none();
#Test
// Note the checked exception makes us re-throw or try / catch (we're
// re-throwing in this case)
public void calling_a_method_which_throws_a_checked_exception_which_wont_be_thrown() throws Exception {
throwCheckedException(false);
}
/*
* Put the class of the specific Exception you're looking to trigger in the
* annotation below. Note the test would fail if it weren't for the expected
* annotation.
*/
#Test(expected = Exception.class)
public void calling_a_method_which_throws_a_checked_exception_which_will_be_thrown_and_asserting_the_type()
throws Exception {
throwCheckedException(true);
}
/*
* Using ExpectedException we can also test for the message. This is my
* preferred method.
*/
#Test
public void calling_a_method_which_throws_a_checked_exception_which_will_be_thrown_and_asserting_the_type_and_message()
throws Exception {
expectedException.expect(Exception.class);
expectedException.expectMessage("Stack overflow example: checkedExceptionThrower");
throwCheckedException(true);
}
// Note we don't need to rethrow, or try / catch as the Exception is
// unchecked.
#Test
public void calling_a_method_which_throws_an_unchecked_exception() {
expectedException.expect(Exception.class);
expectedException.expectMessage("Stack overflow example: uncheckedExceptionThrower");
throwUncheckedException();
}
private void throwCheckedException(boolean willThrow) throws Exception {
// Exception is a checked Exception
if (willThrow) {
throw new Exception("Stack overflow example: checkedExceptionThrower");
}
}
private void throwUncheckedException() throws NullPointerException {
// NullPointerException is an unchecked Exception
throw new NullPointerException("Stack overflow example: uncheckedExceptionThrower");
}
}
You can test that the exception is launched with this:
#Test(expected = ValidationException.class)
public void testGreaterEqual() throws ValidationException {
Validate.greaterEqual(new Float(-5), 0f, "error7");
}
Is it possible to catch an exception in a CMT(Container Managed Transaction) stateless bean?
The code below wont catch any exeption when I tried it. If I use BMT(Bean Managed Transaction), I can catch the exception. But I want to remain with CMT.
#Path("books")
public class BookResource
{
#EJB
private BooksFacade book_facade;
private Books local_book;
#POST
#Consumes({"application/xml", "application/json"})
public Response create(Books entity)
{
try
{
book_facade.create(entity);
} catch (RuntimeException ex)
{
System.out.println("Caught database exception");
}
return Response.status(Response.Status.CREATED).build();
}
public class TXCatcher
{
//#Resource
//UserTransaction tx;
private final static Logger LOG = Logger.getLogger(TXCatcher.class.getName());
#AroundInvoke
public Object beginAndCommit(InvocationContext ic) throws Exception
{
//ic.proceed();
System.out.println("Invoking method: " + ic.getMethod());
try
{
//tx.begin();
Object retVal = ic.proceed();
//tx.commit();
return retVal;
}catch (RollbackException e)
{
LOG.log(Level.SEVERE, "-----------------Caught roolback(in interceptor): {0}", e.getCause());
System.out.println("Invoking method: " + ic.getMethod());
throw new CustomEx("Database error");
}catch (RuntimeException e)
{
LOG.log(Level.SEVERE, "-----------------Caught runtime (in interceptor): {0}", e.getCause());
System.out.println("Invoking method: " + ic.getMethod());
//tx.rollback();
throw new CustomEx("Database error",e.getCause());
//throw new CustomEx("Database error");
}
//return ic.proceed();
}
}
It depends what kind of problem you're trying to catch. You could try an explicit EntiyManager.flush, but depending on your data source isolation level, some errors cannot be caught until transaction commit, and there is no mechanism for catching transaction commit errors for a CMT. If that's the case, your only option is to use BMT (even though you said you don't want to). The only suggestion that might make that more palatable would be to write an EJB interceptor that behaves similarly to CMT (that is, inject UserTransaction into the interceptor, and begin/commit/rollback in the #AroundInvoke).
By placing the following above my function in my BooksFacade class create function, the CMT created a 2nd transaction within the first transaction. When the exception was thrown from the 2nd transaction, my BookResource class create method could catch it. No need for BMT.
#Overide
#TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void create(Books entity)
{
super.create(entity);
}
I noted that the annotation only works when placed on the individual methods, by placing it on the class itself wont make a difference.