Applying Java Dynamic Proxy to Simplify MVP Null Checks and Module Decoupling
This article explains the fundamentals of Java dynamic proxy, demonstrates how to use Proxy and InvocationHandler to eliminate repetitive null‑checks in MVP presenters and to create flexible, decoupled module interactions, providing complete code examples and practical insights.
This article introduces the Java dynamic proxy pattern, highlighting its ability to increase code flexibility, and presents two practical examples: reducing repetitive null‑checks in MVP presenters and achieving more flexible, pure decoupling between modules.
Dynamic proxy relies on two core classes, Proxy and InvocationHandler. The proxy object can only implement Interface, not a concrete Class or Abstract Class, because all generated proxy classes inherit from Proxy, and Java supports only single inheritance.
Below is a minimal demonstration where an interface A is implemented by AIpml, a proxy handler AProxy intercepts method calls, and a proxy instance is created via Proxy.newProxyInstance. Running the program prints the invocation log followed by the original method output.
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class DemonstrationProxy {
interface A {
void method();
}
static class AIpml implements A {
@Override
public void method() {
System.out.println("method in AIpml");
}
}
// Dynamic proxy handler
static class AProxy implements InvocationHandler {
final Object origin;
AProxy(Object origin) { this.origin = origin; }
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("start to invoke method " + method.getName() + " proxy is " + proxy.getClass().getCanonicalName());
return method.invoke(origin, args);
}
}
public static void main(String[] args) {
final A a = new AIpml();
final InvocationHandler handler = new AProxy(a);
final A proxyA = (A) Proxy.newProxyInstance(a.getClass().getClassLoader(), a.getClass().getInterfaces(), handler);
proxyA.method();
}
}Output:
start to invoke method proxy is $Proxy0
method in AIpmlIn MVP architecture, the Presenter holds a reference to a View and frequently checks if (getView() != null) to avoid NullPointerExceptions after the View is destroyed. By returning a dynamic proxy view when the real view is unavailable, the Presenter can call getView() without null checks, keeping the code clean and safe.
public class AbsPresenter<View extends IView> implements IPresenter {
private View mView;
private Class<? extends IView> mViewClass;
public AbsPresenter(@NonNull View iView) {
this.mView = iView;
this.mViewClass = iView.getClass();
if (this.mViewClass.getInterfaces().length == 0) {
throw new IllegalArgumentException("iView must implement IView interface");
}
}
public void detach() { this.mView = null; }
public @NonNull View getView() {
if (mView == null) {
return ViewProxy.newInstance(mViewClass);
}
return mView;
}
private static final class ViewProxy implements InvocationHandler {
public static <View> View newInstance(Class<? extends IView> clazz) {
return (View) Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), new ViewProxy());
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Class type = method.getReturnType();
if (type == boolean.class) return false;
else if (type == int.class) return 0;
else if (type == short.class) return (short)0;
else if (type == char.class) return (char)0;
else if (type == byte.class) return (byte)0;
else if (type == long.class) return 0L;
else if (type == float.class) return 0f;
else if (type == double.class) return 0D;
else return null;
}
}
}For module decoupling, a shared ModuleProvider supplies interfaces of various functional modules. Previously, each missing module required an explicit empty implementation, leading to duplicated code. By using a dynamic proxy that returns default values, the provider can generate a proxy instance on demand, eliminating the need for separate empty classes.
public class EmptyModuleProxy implements InvocationHandler {
public static <T> T newInstance(Class<T> clazz) {
return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, new EmptyModuleProxy());
}
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
// Check for custom annotations that specify return values
SpecifyClassValue classAnn = method.getAnnotation(SpecifyClassValue.class);
if (classAnn != null) return classAnn.returnValue();
SpecifyIntegerValue intAnn = method.getAnnotation(SpecifyIntegerValue.class);
if (intAnn != null) return intAnn.returnValue();
SpecifyStringValue strAnn = method.getAnnotation(SpecifyStringValue.class);
if (strAnn != null) return strAnn.returnValue();
SpecifyBooleanValue boolAnn = method.getAnnotation(SpecifyBooleanValue.class);
if (boolAnn != null) return boolAnn.returnValue();
return defaultValueByType(method.getReturnType());
}
private Object defaultValueByType(Class type) {
if (type == boolean.class) return false;
else if (type == int.class) return 0;
else if (type == short.class) return (short)0;
else if (type == char.class) return (char)0;
else if (type == byte.class) return (byte)0;
else if (type == long.class) return 0L;
else if (type == float.class) return 0f;
else if (type == double.class) return 0D;
else return null;
}
}Consequently, ModuleProvider.getB() can be implemented as:
public static B getB() {
if (b == null) {
b = EmptyModuleProxy.newInstance(B.class);
}
return b;
}Custom annotations such as @SpecifyBooleanValue(returnValue = true) allow developers to define specific return values for methods when a module is absent, facilitating testing without altering production code.
In summary, the two real‑world examples demonstrate how Java dynamic proxy can dramatically improve code flexibility, reduce boilerplate, and enable clean decoupling; the technique is also employed by libraries like Retrofit to turn service interfaces into executable network calls.
The article concludes with a hiring notice for mobile developers at the Fluently Speaking technology team.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
