Easy Java delegates with Lombok
Ever wanted to override a method of an object instance in Java?
Let’s say you want to implement a custom Scheduler
hook for Project Reactor. You start with Schedulers.addExecutorServiceDecorator, implement a BiFunction
accepting Scheduler
’s instance and ScheduledExecutorService
and you need to return another ScheduledExecutorService
instance where schedule*
methods wrapped with your code.
A straightforward approach would be to create a new class, implement all methods of ScheduledExecutorService interface, and delegate most of them to the instance of ScheduledExecutorService
we received as an argument. This pattern is called “Delegation”.
But if you check the interface, you will see that you have to implement 17 methods, while you want to override only 4 of them. Is it not only error-prone, but a lot of boilerplate!
Well, 17 is fairly okay, imagine you have to delegate TransferQueue (36 methods)? No, I’m too lazy and not getting paid for the number of lines I wrote!
Project Lombok to the rescue!
When I hear “Java” and “boilerplate” in one sentence, I usually think of Project Lombok. What I like about it is that you don’t have to change your language/tooling/IDE/buildsystem to remove a lot of boilerplate from your Java code. Definitelly check it if you never did before!
There are many features in Lombok, but we will focus on @Delegate annotation. It looks somewhat like this:
class ExcludesDelegateExample {
long counter = 0L;
private interface Add { // (2)
boolean add(String x);
boolean addAll(Collection<? extends String> x);
}
@Delegate(excludes=Add.class) // (1)
private final Collection<String> collection = new ArrayList<String>();
public boolean add(String item) { // (3)
counter++;
return collection.add(item);
}
public boolean addAll(Collection<? extends String> col) { (4)
counter += col.size();
return collection.addAll(col);
}
}
No, wait! This is not the end of the article yet!:)
What we have here?
(1): Here we define @Delegate
annotation on top of the field. All methods of the Collection
interface will be delegated to this field, except the excludes. It will also add Collection
interface to the ExcludesDelegateExample
class’ signature.
(2): This interface defines which methods Lombok should not delegate. You will have to implement them, otherwise you get a compilation error.
(3): Our custom add
method increments a counter before calling the original method.
Not bad, huh? I could end the article here, but there is one trick I use to avoid writing interfaces for the excludes
property.
Override it!
While excludes
work for simple examples, I feel uncomfortable having to create another interface, look up for the methods, copy them into it, etc…
Maybe one day Lombok will automatically detect implemented methods. Until that happens, there is (IMO) a much cleaner approach (unless you feel bad about creating more classes).
First, simply create a dummy delegate, without any exclusions:
@RequiredArgsConstructor
class DelegatingScheduledExecutorService implements ScheduledExecutorService {
@Delegate
private final ScheduledExecutorService delegate;
}
Now we can use this class to override any method we want, with Java’s standard method overriding:
class WrappingScheduledExecutorService extends DelegatingScheduledExecutorService {
public WrappingScheduledExecutorService(ScheduledExecutorService delegate) {
super(delegate);
}
@Override
public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
command = wrap(command);
return super.schedule(command, delay, unit);
}
}
Another good thing about this approach is that you can reuse DelegatingScheduledExecutorService
in case you have multiple versions of such delegate.