I don't know about elegant, but here is a working implementation using Java's built-in java.lang.reflect.Proxy
that enforces that all method invocations on Foo
begin by checking the enabled
state.
main
method:
public static void main(String[] args) {
Foo foo = Foo.newFoo();
foo.setEnabled(false);
foo.bar(); // won't print anything.
foo.setEnabled(true);
foo.bar(); // prints "Executing method bar"
}
Foo
interface:
public interface Foo {
boolean getEnabled();
void setEnabled(boolean enable);
void bar();
void baz();
void bat();
// Needs Java 8 to have this convenience method here.
static Foo newFoo() {
FooFactory fooFactory = new FooFactory();
return fooFactory.makeFoo();
}
}
FooFactory
class:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class FooFactory {
public Foo makeFoo() {
return (Foo) Proxy.newProxyInstance(
this.getClass().getClassLoader(),
new Class[]{Foo.class},
new FooInvocationHandler(new FooImpl()));
}
private static class FooImpl implements Foo {
private boolean enabled = false;
@Override
public boolean getEnabled() {
return this.enabled;
}
@Override
public void setEnabled(boolean enable) {
this.enabled = enable;
}
@Override
public void bar() {
System.out.println("Executing method bar");
}
@Override
public void baz() {
System.out.println("Executing method baz");
}
@Override
public void bat() {
System.out.println("Executing method bat");
}
}
private static class FooInvocationHandler implements InvocationHandler {
private FooImpl fooImpl;
public FooInvocationHandler(FooImpl fooImpl) {
this.fooImpl = fooImpl;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getDeclaringClass() == Foo.class &&
!method.getName().equals("getEnabled") &&
!method.getName().equals("setEnabled")) {
if (!this.fooImpl.getEnabled()) {
return null;
}
}
return method.invoke(this.fooImpl, args);
}
}
}
As others have pointed out, it does seem like overkill for what you need if you only have a handful of methods to worry about.
That said, there certainly are benefits:
- A certain separation of concerns is achieved, because
Foo
's method implementations don't have to worry about the enabled
check cross-cutting concern. Instead, the method's code only needs to worry about what the method's primary purpose is, nothing more.
- There is no way for an innocent developer to add a new method to the
Foo
class and mistakenly "forget" to add the enabled
check. The enabled
check behavior is automatically inherited by any newly added method.
- If you need to add another cross-cutting concern, or if you need to enhance the
enabled
check, it's very easy to do so safely and in one place.
- It is kind of nice that you can get this AOP-like behavior with built-in Java functionality. You are not forced into having to integrate some other framework like
Spring
, though they can definitely be good options too.
To be fair, some of the downsides are:
- Some of the implementation code that handles the proxy invocations is ugly. Some would also say that having inner classes to prevent instantiation of the
FooImpl
class is ugly.
- If you want to add a new method to
Foo
, you have to make a change in 2 spots: the implementation class and the interface. Not a big deal, but it's still a bit more work.
- Proxy invocations are not free. There is a certain performance overhead. For general use though, it won't be noticeable. See here for more information.
EDIT:
Fabian Streitel's comment got me thinking about 2 annoyances with my above solution that, I'll admit, I'm not happy about myself:
- The invocation handler uses magic strings to skip the "enabled-check" on the "getEnabled" and "setEnabled" methods. This can easily break if the method names are refactored.
- If there was a case where new methods need to be added that should not inherit the "enabled-check" behavior, then it can be pretty easy for the developer to get this wrong, and at the very least, it would mean adding more magic strings.
To resolve point #1, and to at least ease the problem with point #2, I would create an annotation BypassCheck
(or something similar) that I could use to mark the methods in the Foo
interface for which I don't want to perform the "enabled check". This way, I don't need magic strings at all, and it becomes a lot easier for a developer to correctly add a new method in this special case.
Using the annotation solution, the code would look like this:
main
method:
public static void main(String[] args) {
Foo foo = Foo.newFoo();
foo.setEnabled(false);
foo.bar(); // won't print anything.
foo.setEnabled(true);
foo.bar(); // prints "Executing method bar"
}
BypassCheck
annotation:
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BypassCheck {
}
Foo
interface:
public interface Foo {
@BypassCheck boolean getEnabled();
@BypassCheck void setEnabled(boolean enable);
void bar();
void baz();
void bat();
// Needs Java 8 to have this convenience method here.
static Foo newFoo() {
FooFactory fooFactory = new FooFactory();
return fooFactory.makeFoo();
}
}
FooFactory
class:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class FooFactory {
public Foo makeFoo() {
return (Foo) Proxy.newProxyInstance(
this.getClass().getClassLoader(),
new Class[]{Foo.class},
new FooInvocationHandler(new FooImpl()));
}
private static class FooImpl implements Foo {
private boolean enabled = false;
@Override
public boolean getEnabled() {
return this.enabled;
}
@Override
public void setEnabled(boolean enable) {
this.enabled = enable;
}
@Override
public void bar() {
System.out.println("Executing method bar");
}
@Override
public void baz() {
System.out.println("Executing method baz");
}
@Override
public void bat() {
System.out.println("Executing method bat");
}
}
private static class FooInvocationHandler implements InvocationHandler {
private FooImpl fooImpl;
public FooInvocationHandler(FooImpl fooImpl) {
this.fooImpl = fooImpl;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getDeclaringClass() == Foo.class
&& !method.isAnnotationPresent(BypassCheck.class) // no magic strings
&& !this.fooImpl.getEnabled()) {
return null;
}
return method.invoke(this.fooImpl, args);
}
}
}