How To Access and Handle Java Exceptions Using Spring Aspect-Oriented Programming
August 13, 2022
- Why Aspect-Oriented Programming?
- Aspect-Oriented Programming Basics
- How Spring AOP Works
- Exception Handling Implementations Using Spring AOP
Why Aspect-Oriented Programming (AOP)?
In software engineering often times certain logic needs to be applied in multiple modules of an application, e.g. making client requests to a server, database access etc. In object-oriented programming classes are usually used to encapsulate such logic. For example, an ApiClient
class which multiple modules of an application would use in order to make requests to some API service.
That being said some application logic may be used so extensively that it becomes extremely repetetive to use such logic each time. Suppose we want to log with debug level the name of each method, its arguments and the return value. We could manually write such code for each and every method but there’re a few disadvantages with this approach:
- A lot of manual, tedious work.
- One can forget to log some method.
- Code becomes harder to read.
- Time-consuming refactors: say message format needs to be changed then the change needs to be performed across all occurrences.
Such ubiquitous logic which is scattered all over the application is an example of a cross-cutting concern: it literally cuts across almost every part of the application. Examples of cross-cutting concerns include logging, exception handling, recording application metrics, tracing etc. Aspect-oriented programming aims to tackle these issues.
Several implementations of aspect-oriented programming are available for Java where AspectJ is the de-facto standard. In addition Java Spring framework provides a subset of AspectJ features. In this tutorial I will describe several strategies to access and handle Java exceptions using aspect-oriented prpgramming. This can be handy if exception handling involves repetetive tasks like sending an error metric on each exception or executing other custom exception related logic. If you just want to get error metrics on each exception you may also be interested in Spring actuator Logback metrics. This metric will not include the actual type of exception though.
Aspect-Oriented Programming Basics
AOP and AspectJ deserve comprehensive study in their own right hence I will give a very brief introduction into the main concepts:
- Aspect: logic which cuts across multiple parts of an application (exception handling in our case). Fun fact: transaction management (
@Transactional
) is implemented using AOP in Spring. - Join point: a point during application execution when a cross-cutting concern occurs e.g. method execution, accessing a class field. Spring AOP only allows method execution join points.
- Advice: dictates when aspect logic is invoked e.g. before method execution, after etc.
- Pointcut: a logical condition which needs to be matched so that an aspect is executed. For example, we may want to execute certain logic only for methods of Java package
services
.
How Spring AOP Works
To summarize the above Spring AOP can intercept method execution before/after etc. the actual execution. You can also tell it which methods to intercept and what to do following the intercept. How does Spring AOP actually intercept method execution? It does it at runtime using two options:
- Native JDK dynamic proxies if the intercepted method belongs to a class which implements an interface.
- CGLIB in all other cases.
It’s important to understand the implications of proxies usage (this should already be familiar to anyone using @Transactional
annotation in Spring):
- Only public methods can be intercepted.
- Method which is called by another method of the same class will not be intercepted.
You can read more about Spring AOP proxies here. In addition Spring AOP components are regular Spring beans and are annotated with @Aspect
+ @Component
, @Service
etc. This means that aspects can’t be applied to classes which are not Spring beans.
To enable @AspectJ
support with Java @Configuration
add the @EnableAspectJAutoProxy
annotation. Lastly, make sure that org.aspectj:aspectjweaver
is on the class path (Maven central link).
Exception Handling Implementations Using Spring AOP
The simplest way to access exceptions is to use after throwing advice. Each time a method exits because an exception was thrown the aspect will be invoked. An important caveat to this advice is that if try/catch
is used the aspect will not be invoked (because the method didn’t exit):
package com.myapp.mypackage;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class ExceptionHandlingAspect {
@Pointcut("within(com.myapp..*) && execution(* *(..))")
public void matchAllMyMethods() {}
@AfterThrowing(value = "matchAllMyMethods()", throwing = "exception")
public void doSomethingWithException(JoinPoint joinPoint, Throwable exception) {
// get access to the actual exception thrown
System.out.println(exception);
// get access to the class name of the method which threw exception
String className = joinPoint.getSignature().getDeclaringType().getSimpleName();
// get access to the method name of the method which threw exception
String methodName = joinPoint.getSignature().getName();
// get access to the arguments of the method which threw exception
Object[] methodArgs = joinPoint.getArgs();
}
}
Let’s break down the pointcut expression:
within(com.myapp..*)
will match all methods belonging to all the Java packages insidecom.myapp
essentially all methods of an application. Ifwithin
condition wasn’t used the aspect would be invoked on Spring beans of other dependencies (e.g. metrics registry beans) so it’s a good idea to always limit execution to the methods within our actual application at the very least.execution(* *(..))
will match method execution join points (the only join point type supported by Spring AOP). Here* *(..)
refers to a method signature: first*
matches any method return type, second*
matches any method name,..
matches any method arguments. If we wanted to match only a specific methodpublic int getRandomNumber()
we could use the following pointcut expression:execution(int getRandomNumber())
. It’s worth noting that pointcut syntax is the same as that of AspectJ, please see AspectJ docs for more examples of pointcut expressions.
As can be seen we can get access to the exception thrown itself and the metadata of the method which threw the exception. We could do something useful like perhaps send a metric with exception/method data.
Consider the following classes in an application with the above aspect:
public class UserService {
public User getUser() {
// simulate exception
throw new RuntimeException();
}
}
public class AuthService {
public void handleAuth() {
User user = userService.getUser(); // no try/catch
// do something
}
}
getUser
throws an exception so the aspect will be invoked on it. handleAuth
will also exit when getUser
is called so the aspect will be invoked again on the same exception which may or may not be desired. If certain exception related logic needs to be performed exactly once the state has to be tracked somewhere, perhaps using ThreadLocal
variable (make sure performance doesn’t degrade when using ThreadLocal
variables). Lastly, aspect logic is executed synchronously in the above example: adding time-consuming calculations in the aspect will negatively affect performance.
Note that the problem with the above approach is that we don’t get access to exceptions which were caught inside try/catch
. This is actually impossible to do in Spring AOP however it is possible to achieve this if plain AspectJ is used. AspectJ uses complilation or load-time weaving in order to provide AOP functionality which will definitely increase build complexity. If AspectJ is used then "handler(*) && args(e)"
pointcut can be used to access exceptions in catch
blocks as described here.
Another problem with the above approach is that we get access to the exception thrown and its metadata however we can’t actually handle or catch the exception: by the time the aspect is invoked the method had already exited. In order to actually catch the exception around advice can be used:
package com.myapp.mypackage;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class ExceptionHandlingAspect {
@Pointcut("within(com.myapp..*) && execution(* *(..))")
public void matchAllMyMethods() {}
@Around(value = "matchAllMyMethods()")
public Object doSomethingWithException(ProceedingJoinPoint joinPoint) throws Throwable {
String className = joinPoint.getSignature().getDeclaringType().getSimpleName();
String methodName = joinPoint.getSignature().getName();
Object[] methodArgs = joinPoint.getArgs();
try {
// continue the original method execution
return joinPoint.proceed();
} catch (Exception exception) {
// custom aspect logic
throw exception;
} finally {
// custom aspect logic
}
}
}
The above approach uses ProceedingJoinPoint
which allows to intercept the actual method which throws the exception, catch the exception and do something about it. Then the exception is re-thrown in order to let the original method also deal with the exception. All this makes around advice probably the most powerful advice available in AOP. You can see the full list of advice supported by Spring AOP here.
Enjoy aspect-oriented programming!