Assuming that your external library classes/interfaces are in a certain package such as org.external.library
or in any of its subpackages, you can express those classes/interfaces in AspectJ syntax as org.external.library..*+
. The ..
means "include subpackages", the *
means "all classes", the +
means "include subclasses". Now append .*(..)
and you get all methods regardless of name and number of arguments for those classes.
Now for example the pointcut
execution(!static * org.external.library..*+.*(..))
intercepts all non-static methods in any library subclasses regardless of return type.
The bad news is that you cannot limit the pointcut to overriding methods because the @Override
annotation has retention type "source", i.e. it is unavailable for aspect matching. But maybe you want to narrow down the pointcut to public methods only in order to exclude errors thrown by internal methods. It depends on your situation and how you want to deal with it. Assuming that also non-overriding methods can break base components, the pointcut is good, though.
Here is a self-consistent sample:
Library classes/interfaces:
package org.external.library;
public abstract class AbstractBase {
public abstract String getText();
public abstract int getNumber();
public abstract void doSomething();
}
package org.external.library;
public interface Service {
void start();
void stop();
boolean isRunning();
}
A class not extending any library class:
As you can see, this class throws runtime exceptions randomly in ~50% of all cases for demo purposes. We expect them not to be handled by our aspect.
package de.scrum_master.app;
import java.util.Random;
public class MyOwnClass {
private static final Random RANDOM = new Random();
public String getGreeting(String recipient) {
if (RANDOM.nextBoolean())
throw new RuntimeException("cannot get greeting for '" + recipient + "'");
return "Hello " + recipient + "!";
}
}
Classes extending/implementing library classes/interfaces:
As you can see, these classes also throw runtime exceptions randomly in ~50% of all cases for demo purposes. We expect them to be handled by our aspect.
The main
method demonstrates the whole setup by calling each method a few times, not handling any errors in overridden library classes, but only in our own class.
package de.scrum_master.app;
import java.util.Random;
import org.external.library.Service;
public class FooService implements Service {
private static final Random RANDOM = new Random();
private boolean isRunning;
@Override
public void start() {
if (RANDOM.nextBoolean())
throw new RuntimeException("cannot start");
isRunning = true;
}
@Override
public void stop() {
if (RANDOM.nextBoolean())
throw new RuntimeException("cannot stop");
isRunning = false;
}
@Override
public boolean isRunning() {
return isRunning;
}
public void pause() {
if (RANDOM.nextBoolean())
throw new RuntimeException("cannot pause");
isRunning = false;
}
public void resume() {
if (RANDOM.nextBoolean())
throw new RuntimeException("cannot resume");
isRunning = true;
}
}
package de.scrum_master.app;
import java.util.Random;
import org.external.library.AbstractBase;
public class Application extends AbstractBase {
private static final Random RANDOM = new Random();
@Override
public String getText() {
if (RANDOM.nextBoolean())
throw new RuntimeException("cannot get text");
return "Hello world!";
}
@Override
public int getNumber() {
if (RANDOM.nextBoolean())
throw new RuntimeException("cannot get number");
return RANDOM.nextInt(10);
}
@Override
public void doSomething() {
if (RANDOM.nextBoolean())
throw new RuntimeException("cannot do something");
}
public void doSomethingElse() {
if (RANDOM.nextBoolean())
throw new RuntimeException("cannot do something else");
}
public static void main(String[] args) {
Application application = new Application();
FooService fooService = new FooService();
MyOwnClass myOwnClass = new MyOwnClass();
for (int i = 0; i < 5; i++) {
application.getText();
application.getNumber();
application.doSomething();
application.doSomethingElse();
fooService.start();
fooService.pause();
fooService.isRunning();
fooService.resume();
fooService.isRunning();
fooService.stop();
try {
myOwnClass.getGreeting("world");
myOwnClass.getGreeting("guys");
}
catch (Exception e) {
System.out.println("Uncaught by aspect: " + e);
}
}
}
}
Error handling aspect:
package de.scrum_master.aspect;
public aspect ErrorHandler {
Object around() : execution(!static * org.external.library..*+.*(..)) {
try {
return proceed();
}
catch (Exception e) {
System.out.println(thisJoinPoint + " -> " + e);
return null;
}
}
}
Console output:
The output looks a bit different each time you run the application due to the randomisation in the code:
execution(int de.scrum_master.app.Application.getNumber()) -> java.lang.RuntimeException: cannot get number
execution(void de.scrum_master.app.Application.doSomething()) -> java.lang.RuntimeException: cannot do something
execution(void de.scrum_master.app.Application.doSomethingElse()) -> java.lang.RuntimeException: cannot do something else
execution(void de.scrum_master.app.FooService.start()) -> java.lang.RuntimeException: cannot start
execution(void de.scrum_master.app.FooService.pause()) -> java.lang.RuntimeException: cannot pause
execution(void de.scrum_master.app.FooService.resume()) -> java.lang.RuntimeException: cannot resume
execution(void de.scrum_master.app.FooService.stop()) -> java.lang.RuntimeException: cannot stop
Uncaught by aspect: java.lang.RuntimeException: cannot get greeting for 'world'
execution(String de.scrum_master.app.Application.getText()) -> java.lang.RuntimeException: cannot get text
execution(int de.scrum_master.app.Application.getNumber()) -> java.lang.RuntimeException: cannot get number
execution(void de.scrum_master.app.Application.doSomething()) -> java.lang.RuntimeException: cannot do something
execution(void de.scrum_master.app.FooService.start()) -> java.lang.RuntimeException: cannot start
execution(void de.scrum_master.app.FooService.pause()) -> java.lang.RuntimeException: cannot pause
execution(void de.scrum_master.app.FooService.stop()) -> java.lang.RuntimeException: cannot stop
Uncaught by aspect: java.lang.RuntimeException: cannot get greeting for 'guys'
execution(void de.scrum_master.app.Application.doSomething()) -> java.lang.RuntimeException: cannot do something
execution(void de.scrum_master.app.FooService.start()) -> java.lang.RuntimeException: cannot start
execution(void de.scrum_master.app.FooService.pause()) -> java.lang.RuntimeException: cannot pause
Uncaught by aspect: java.lang.RuntimeException: cannot get greeting for 'guys'
execution(void de.scrum_master.app.Application.doSomethingElse()) -> java.lang.RuntimeException: cannot do something else
execution(void de.scrum_master.app.FooService.resume()) -> java.lang.RuntimeException: cannot resume
execution(void de.scrum_master.app.FooService.stop()) -> java.lang.RuntimeException: cannot stop
Uncaught by aspect: java.lang.RuntimeException: cannot get greeting for 'world'
execution(String de.scrum_master.app.Application.getText()) -> java.lang.RuntimeException: cannot get text
execution(int de.scrum_master.app.Application.getNumber()) -> java.lang.RuntimeException: cannot get number
execution(void de.scrum_master.app.Application.doSomething()) -> java.lang.RuntimeException: cannot do something
execution(void de.scrum_master.app.Application.doSomethingElse()) -> java.lang.RuntimeException: cannot do something else
execution(void de.scrum_master.app.FooService.start()) -> java.lang.RuntimeException: cannot start
execution(void de.scrum_master.app.FooService.pause()) -> java.lang.RuntimeException: cannot pause
execution(void de.scrum_master.app.FooService.stop()) -> java.lang.RuntimeException: cannot stop