Java Reflection and Dynamic Proxy Mechanisms Explained
Java Reflection System
Java reflection is defined as the ability to examine and modify the behavior of classes, methods, fields, and constructors at runtime. This mechanism enables programs to discover class information dynamically and invoke methods within those classes during execution.
Three primary approaches exist for obtaining a Class object:
- Using
instance.getClass()- inherited from the Object class - Accessing
Type.Classproperty - available for all data types including primitives - Employing
Class.forName("fully qualified class name")- a method provided by the Class class
Manipulating Class Fields Through Reflection
package com.example.reflection;
import java.lang.reflect.Field;
class FieldExample {
private String attribute = null;
public static void main(String[] args) throws Exception {
Class<?> targetClass = Class.forName("com.example.reflection.FieldExample");
Object instance = targetClass.newInstance();
Field fieldRef = targetClass.getDeclaredField("attribute");
fieldRef.setAccessible(true);
fieldRef.set(instance, "Reflection in Action");
System.out.println(fieldRef.get(instance));
}
}
Invoking Class Methods Through Reflection
package com.example.reflection;
import java.lang.reflect.Method;
class MethodExample {
public static void main(String[] args) throws Exception {
Class<?> targetClass = Class.forName("com.example.reflection.MethodExample");
Method methodOne = targetClass.getMethod("executeFirst");
methodOne.invoke(targetClass.newInstance());
Method methodTwo = targetClass.getMethod("executeSecond", int.class, String.class);
methodTwo.invoke(targetClass.newInstance(), 25, "John Doe");
}
public void executeFirst() {
System.out.println("Executing first method via reflection");
}
public void executeSecond(int age, String name) {
System.out.println("Executing second method via reflection");
System.out.println("Age: " + age + ", Name: " + name);
}
}
Dynamic Proxy Implementation
Understanding Dynamic Proxy AOP
The proxy pattern involves using one object to control access to another object. The controlling object serves as the proxy, while the controlled object represents the target or real subject. Proxy objects indirectly access corresponding methods in target objects through method invocation.
Key differences between static and dynamic proxies: Static proxies require defining separate proxy classes for each target class, whereas dynamic proxies eliminate the need for predefined proxy classes by utilizing the Proxy class from the reflection package to generate proxy instances dynamically.
In Java's dynamic proxy system, two components play crucial roles:
- Proxy class: Creates proxy classes and objects dynamically
- InvocationHandler interface: Handles method invocations on proxy instances
Every dynamic proxy instance must associate with an InvocationHandler implementation. When proxy methods are called, they automatically delegate to the associated handler's invoke method, which leverages reflection to call the target object's methods.
Classic Dynamic Proxy Example
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
interface ServiceInterface {
void performService();
void greet(String message);
}
class RealService implements ServiceInterface {
@Override
public void performService() {
System.out.println("Providing service functionality");
}
@Override
public void greet(String message) {
System.out.println("Greeting: " + message);
}
}
class ProxyHandler implements InvocationHandler {
private Object targetObject;
public ProxyHandler(Object target) {
this.targetObject = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] arguments) throws Throwable {
System.out.println("Before method execution");
System.out.println("Invoking method: " + method.getName());
Object result = method.invoke(targetObject, arguments);
System.out.println("After method execution");
return result;
}
}
class Application {
public static void main(String[] args) {
ServiceInterface target = new RealService();
ProxyHandler handler = new ProxyHandler(target);
ServiceInterface proxyInstance = (ServiceInterface) Proxy.newProxyInstance(
handler.getClass().getClassLoader(),
target.getClass().getInterfaces(),
handler
);
System.out.println(proxyInstance.getClass().getName());
proxyInstance.performService();
proxyInstance.greet("Dynamic Proxy World");
}
}
Output:
com.sun.proxy.$Proxy0
Before method execution
Invoking method: performService
Providing service functionality
After method execution
Before method execution
Invoking method: greet
Greeting: Dynamic Proxy World
After method execution
Key Points:
- Dynamic proxies eliminate the need for predefined proxy classes; Proxy generates them at runtime
- All method calls on proxy objects redirect to the associated handler's invoke method
- The invoke method uses reflection internally to call corresponding methods on the target object
- Spring's AOP (Aspect-Oriented Programming) utilizes similar dynamic proxy concepts, where proxy objects contain all methods of the target object
AOP Application Example
Consider scenarios where common functionality needs to be applied across multiple methods without tight coupling. Dynamic proxies enable cross-cutting concerns to be woven into method execution.
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
interface AnimalBehavior {
void displayInfo();
void move();
}
class Wolf implements AnimalBehavior {
@Override
public void displayInfo() {
System.out.println("I am a wild wolf");
}
@Override
public void move() {
System.out.println("Running swiftly through forest");
}
}
class UtilityMethods {
public static void preExecution() {
System.out.println("--- Pre-execution hook ---");
}
public static void postExecution() {
System.out.println("--- Post-execution hook ---");
}
}
class CustomHandler implements InvocationHandler {
private Object targetObject;
public void configureTarget(Object target) {
this.targetObject = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] parameters)
throws Throwable {
UtilityMethods.preExecution();
Object outcome = method.invoke(targetObject, parameters);
UtilityMethods.postExecution();
return outcome;
}
}
class ProxyBuilder {
public static Object createProxy(Object target) {
CustomHandler handler = new CustomHandler();
handler.configureTarget(target);
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
handler
);
}
}
public class AopDemo {
public static void main(String[] args) {
AnimalBehavior animal = (AnimalBehavior) ProxyBuilder.createProxy(new Wolf());
animal.displayInfo();
animal.move();
}
}
Output:
--- Pre-execution hook ---
I am a wild wolf
--- Post-execution hook ---
--- Pre-execution hook ---
Running swiftly through forest
--- Post-execution hook ---