Reputation: 176
I am looking into implementing a standard error handling on my application. I want errors that cant be dealt with(custom unchecked errors) but that also aren't catastrophic to be logged by a fault barrier without having to clutter my code with pesky try catches and logger calls.
Let me illustrate. I have an intake that receives a json string and set it into my object model, one of the fields in said model call a timeHelper function, this function can throw an exception if the arguments are invalid(null or empty). The yield of this function is not critical to the program, in fact this program should never crash( to the best of my abilities) as it should stay up 24/7.
Model
public class MyModel{
private string myField
public void setMyField(String myfield){
this.myField = Helper.DoStuff(myField)
}
}
Intake
public class Intake{
public MyModel receiveJson(){
return JacksonMagic(arguments,MyModel.class)
}
}
Helper
Public class Helper{
public String DoStuff(String myField){
//Check that can throw exception
//regular operation with return
}
}
Now, when life is beautiful DoStuff
returns a string, in fact the exception should never be thrown because it implies that the source of the json, which is external to my application, sent wrong/missing information. If it does happen I want it to be logged so I can investigate what happened. I also want to set a framework in place, probably with Spring AOP, to handle that logging. But as you can see through the example, I also want execution to continue as this is not some app breaking thing.
The execution flow I am looking for is something like Intake > Model > Helper(THROW EXCEPTION) > Logger > Whoever Called Intake
And again, I want to do that without the try catch logger call cluter
Is this something possible with AOP?
Post answer Edit Just want to leave some sources here.
To set up your IDE for AspectJ compilation, this article is really helpful. https://www.baeldung.com/aspectj
Upvotes: 2
Views: 1168
Reputation: 67297
Okay, here we go with a complete MCVE, assuming you know how to use the AspectJ compiler in order to compile your project. Sorry for repeating your classes with package names, imports etc., but I like you to see all details:
First we need our helper which randomly throws unchecked exceptions so we can see the aspect in action later:
package de.scrum_master.app;
import java.util.Random;
public class Helper {
private static final Random RANDOM = new Random();
public static String doStuff(String myField) {
if (RANDOM.nextBoolean())
throw new RuntimeException("uh-oh!");
return "processed " + myField;
}
}
package de.scrum_master.app;
public class MyModel {
private String myField;
public void setMyField(String myField) {
this.myField = Helper.doStuff(myField);
}
@Override
public String toString() {
return "MyModel(myField=" + myField + ")";
}
}
package de.scrum_master.app;
public class Intake {
public MyModel receiveJson(String... arguments) {
return jacksonMagic(arguments, MyModel.class);
}
public MyModel jacksonMagic(String[] arguments, Class<?> clazz) {
MyModel myModel = new MyModel();
myModel.setMyField(arguments[0]);
return myModel;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++)
System.out.println(new Intake().receiveJson("foo"));
}
}
Now when you run the little driver application via Intake.main
you will see unhandled exceptions on the console. Here is how to handle this using an aspect. I am limiting the aspect to matching all method executions with a String
return type, stupidly returning a dummy value whenever an exception occurs. You just add your more sophisticated logic in there as you see fit and also adjust the aspect's pointcut to match the methods you want to handle.
package de.scrum_master.aspect;
public aspect ErrorHandler {
String around() : execution(String *(..)) {
try {
return proceed();
} catch (Exception e) {
System.out.println("Exception handled: " + e);
return "dummy";
}
}
}
I love the expressive native AspectJ syntax, but I know that some people for whatever reason feel more comfortable with annotation-based syntax. Just look at the throws
declaration, the pointcuts in string constants, the explicit joinpoint declaration, the cast - yuck! Anyway, here we go:
package de.scrum_master.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class ErrorHandler {
@Around("execution(String *(..))")
public String handleErrors(ProceedingJoinPoint thisJoinPoint) throws Throwable {
try {
return (String) thisJoinPoint.proceed();
} catch (Exception e) {
System.out.println("Exception handled: " + e);
return "dummy";
}
}
}
The console log looks like this with the aspect in place:
MyModel(myField=processed foo)
MyModel(myField=processed foo)
Exception handled: java.lang.RuntimeException: uh-oh!
MyModel(myField=dummy)
MyModel(myField=processed foo)
Exception handled: java.lang.RuntimeException: uh-oh!
MyModel(myField=dummy)
Exception handled: java.lang.RuntimeException: uh-oh!
MyModel(myField=dummy)
Exception handled: java.lang.RuntimeException: uh-oh!
MyModel(myField=dummy)
MyModel(myField=processed foo)
Exception handled: java.lang.RuntimeException: uh-oh!
MyModel(myField=dummy)
MyModel(myField=processed foo)
Upvotes: 1
Reputation: 7267
This is not a good use case for exceptions.
An exception represents something that you're not able to handle, an "exceptional" occurrence that you're not able to deal with. The fact that you're saying this is a possible scenario changes this from an exception to a use-case, in which case logging a warning in your service tier is probably the best solution.
Exceptions have their place, however overusing them makes code harder to follow, since it breaks the "flow" of an application. Exceptions should not be used to control flow.
AOP
, in my option, offers little when it comes to exception handling. At best it can log the exception (which can also be achieved in a much clearer way using an ExceptionHandler
pattern), however it certainly can't trigger your code to continue as though it didn't happen.
If you haven't already, look into logging strategies, they can be really useful in this kind of scenario.
The bottom line is: if you want control flow to continue, don't throw an exception (checked or unchecked).
Upvotes: 2