Java Reflection, Dynamic Proxy & MethodHandle: Core Internals & Performance Compared
This article explores Java's three core metaprogramming mechanisms—reflection, dynamic proxy, and MethodHandle—with code examples, API references, performance comparisons, and real-world usage in frameworks like Spring AOP, MyBatis, and RPC systems.
Introduction: Java's Three Core Magic Features
Writing Java long enough, you have certainly written code like this: using Class.forName to load a class by configuration, instantiating it via reflection, and invoking methods dynamically. You have also used MyBatis Mapper interfaces without implementation classes, and Spring AOP proxies that automatically open transactions. Behind all these are Java's three great magic powers: Reflection , Dynamic Proxy , and MethodHandle .
Many developers have only a partial understanding of these three concepts. This article explains them from the ground up: what they are, how to use them, underlying principles, performance comparisons, application scenarios, and common pitfalls. After reading, you will understand the technical principles behind MyBatis Mapper, Spring AOP, RPC frameworks, and serialization frameworks, and be able to correctly apply these mechanisms in your own projects.
1. Reflection: Runtime Class Inspection and Manipulation
1.1 What Is Reflection?
Reflection is Java's runtime capability: dynamically obtaining class information (fields, methods, constructors) at runtime, and dynamically invoking methods or accessing fields .
Normal Java code knows the class structure at compile time:
User user = new User();
user.setName("张三"); // Compiler knows User has setName methodReflection lets you work with classes only known at runtime:
Object obj = ...; // Runtime type unknown
Method m = obj.getClass().getMethod("setName", String.class);
m.invoke(obj, "张三"); // Runtime method invocation1.2 Core Reflection APIs
Get Class object: obj.getClass(), Class.forName("fully.qualified.Name"), Xxx.class Get constructors: getConstructor(...), getDeclaredConstructor(...) Create instance: constructor.newInstance(...), clazz.newInstance() (deprecated)
Get methods: getMethod(...), getDeclaredMethod(...) Invoke method: method.invoke(obj, args...) Get fields: getField(...), getDeclaredField(...) Read/write fields: field.get(obj), field.set(obj, value) Bypass access checks:
setAccessible(true)1.3 Complete Example: Reflectively Operating a Class
public class User {
private String name;
private int age;
public User() {}
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String sayHello(String greeting) {
return greeting + ", " + name;
}
private void secretMethod() {
System.out.println("私有方法");
}
}Using reflection:
public class ReflectionDemo {
public static void main(String[] args) throws Exception {
// 1. Get Class object
Class<?> clazz = Class.forName("com.example.User");
// 2. Create instance
Object user = clazz.getDeclaredConstructor().newInstance();
// 3. Invoke public method
Method sayHello = clazz.getMethod("sayHello", String.class);
Object result = sayHello.invoke(user, "你好");
System.out.println(result); // 你好, null
// 4. Access private field
Field nameField = clazz.getDeclaredField("name");
nameField.setAccessible(true); // Bypass private restriction
nameField.set(user, "张三");
System.out.println(nameField.get(user)); // 张三
// 5. Invoke private method
Method secretMethod = clazz.getDeclaredMethod("secretMethod");
secretMethod.setAccessible(true);
secretMethod.invoke(user); // 私有方法
// 6. Get all information
System.out.println("Class name: " + clazz.getName());
System.out.println("Methods: " + Arrays.toString(clazz.getMethods()));
System.out.println("Fields: " + Arrays.toString(clazz.getDeclaredFields()));
}
}1.4 getMethod vs getDeclaredMethod
getMethod(name, ...): Returns only public methods, including those inherited from superclasses. getDeclaredMethod(name, ...): Returns all declared methods (public, protected, private), but excludes inherited methods .
Fields follow the same pattern: getField: public fields, including superclasses. getDeclaredField: all fields, excluding superclasses.
This is the most confusing point. To access private fields/methods, you must use getDeclaredXxx + setAccessible(true) .
2. Dynamic Proxy: Runtime Interface Implementation Generation
2.1 What Is Dynamic Proxy?
Dynamic proxy is Java's runtime capability: at runtime, dynamically generate an implementation class for an interface (the proxy class) without manually writing the implementation .
You define an interface, and Java automatically generates an implementation class at runtime. Method calls are forwarded to an InvocationHandler.
2.2 Why Do We Need Dynamic Proxy?
Typical scenarios:
MyBatis Mapper : You only define a UserMapper interface with no implementation; MyBatis uses dynamic proxy to generate the implementation.
Spring AOP : Add logging, transactions, security before/after methods using dynamic proxy.
RPC Frameworks : Call remote interfaces as if they were local methods; dynamic proxy handles network requests.
Logging/Monitoring : Uniformly record method invocation logs.
2.3 Core Dynamic Proxy APIs
// Generate proxy object
Object proxy = Proxy.newProxyInstance(
classLoader, // Class loader
interfaces, // Array of interfaces to proxy
invocationHandler // Invocation handler
); InvocationHandler:
public interface InvocationHandler {
Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}2.4 Complete Example: Dynamic Proxy for Logging
Define interface:
public interface UserService {
User getUserById(Long id);
void createUser(User user);
}Implementation class:
public class UserServiceImpl implements UserService {
@Override
public User getUserById(Long id) {
return new User(id, "张三");
}
@Override
public void createUser(User user) {
System.out.println("创建用户:" + user);
}
}Dynamic proxy adding logging:
public class LogInvocationHandler implements InvocationHandler {
private final Object target; // Real object being proxied
public LogInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// Before method call
System.out.println("Calling method: " + method.getName() + ", args: " + Arrays.toString(args));
// Invoke real method
Object result = method.invoke(target, args);
// After method call
System.out.println("Method returned: " + result);
return result;
}
}Using the proxy:
public class ProxyDemo {
public static void main(String[] args) {
UserService realService = new UserServiceImpl();
// Generate proxy object
UserService proxy = (UserService) Proxy.newProxyInstance(
ProxyDemo.class.getClassLoader(),
new Class[]{UserService.class},
new LogInvocationHandler(realService)
);
// Call proxy methods
User user = proxy.getUserById(1L);
proxy.createUser(user);
}
}Output:
Calling method: getUserById, args: [1]
Method returned: User{id=1, name='张三'}
Calling method: createUser, args: [User{id=1, name='张三'}]
创建用户:User{id=1, name='张三'}
Method returned: null2.5 Dynamic Proxy Limitations
JDK dynamic proxy can only proxy interfaces!
The proxy class implements the interfaces you pass in.
It cannot proxy classes (classes that don't implement an interface).
The proxy object and the real object are siblings; both implement the same interface.
In Spring AOP:
If the target class implements an interface → use JDK dynamic proxy.
If the target class does not implement an interface → use CGLIB proxy (generates a subclass).
3. MethodHandle: Java 7's Method Handles
3.1 What Is MethodHandle?
MethodHandleis a core concept in the java.lang.invoke package introduced in Java 7. It represents a reference to a method, field, or constructor and can be invoked like a method.
Similar to reflection but with different design philosophy:
Reflection : Early Java API, based on java.lang.reflect.
MethodHandle : JVM-level lightweight method reference, more modern design.
Lambda expressions : Under the hood, they are implemented via MethodHandle.
3.2 Core MethodHandle Concepts
MethodHandle: Method handle, can be invoked directly. MethodType: Method type (return type + parameter types). MethodHandles.Lookup: Entry point for finding methods, simulating different class lookup permissions.
3.3 Complete Example: MethodHandle Invoking Methods
public class MethodHandleDemo {
public static void main(String[] args) throws Throwable {
User user = new User("张三", 25);
// 1. Get Lookup
MethodHandles.Lookup lookup = MethodHandles.lookup();
// 2. Find method (sayHello(String) returns String)
MethodType methodType = MethodType.methodType(String.class, String.class);
MethodHandle sayHello = lookup.findVirtual(User.class, "sayHello", methodType);
// 3. Invoke method (first arg is receiver, then method args)
String result = (String) sayHello.invokeExact(user, "你好");
System.out.println(result); // 你好, 张三
// 4. Invoke static method
MethodHandle staticMethod = lookup.findStatic(
UserUtils.class, "staticMethod",
MethodType.methodType(void.class)
);
staticMethod.invokeExact();
// 5. Access field
MethodHandle nameGetter = lookup.findGetter(User.class, "name", String.class);
String name = (String) nameGetter.invokeExact(user);
System.out.println(name); // 张三
}
}3.4 MethodHandle vs Reflection Comparison
Introduced version: Reflection since JDK 1.0; MethodHandle since JDK 7.
API level: Reflection at Java language level; MethodHandle at JVM level.
Performance: Reflection slower (security checks on every invoke); MethodHandle faster (compiled to bytecode, JIT optimized).
Invocation style: Reflection uses method.invoke(obj, args); MethodHandle uses methodHandle.invokeExact(args).
Type safety: Reflection checks parameter types at runtime; MethodHandle checks at compile time and runtime.
Flexibility: Reflection high (can inspect all class info); MethodHandle lower (focuses only on invocation).
Design purpose: Reflection for frameworks/tools; MethodHandle for lambdas and dynamic language support.
3.5 MethodHandle Application Scenarios
Lambda expressions : () -> System.out.println("hello") generates MethodHandle underneath.
Dynamic languages : Groovy, Kotlin, Scala, and other JVM dynamic languages.
High-performance frameworks : Replace reflection to improve invocation performance.
VarHandle : Java 9 introduction, MethodHandle version for volatile fields.
4. Three-Way Comparison: Reflection, Dynamic Proxy, MethodHandle
4.1 Comparison Summary
Purpose: Reflection – runtime class/method/field manipulation; Dynamic Proxy – runtime interface implementation generation; MethodHandle – lightweight method reference.
Core API: Reflection – java.lang.reflect.Method; Dynamic Proxy – java.lang.reflect.Proxy, InvocationHandler; MethodHandle – java.lang.invoke.MethodHandle.
Introduced: Reflection – JDK 1.0; Dynamic Proxy – JDK 1.3; MethodHandle – JDK 7.
Performance: Reflection – moderate; Dynamic Proxy – moderate (relies on reflection for invocation); MethodHandle – high.
Requires interface: Reflection – no; Dynamic Proxy – yes (JDK dynamic proxy); MethodHandle – no.
Typical applications: Reflection – JDBC, serialization, ORM; Dynamic Proxy – MyBatis Mapper, Spring AOP; MethodHandle – Lambda, dynamic languages.
Flexibility: Reflection – high; Dynamic Proxy – medium; MethodHandle – medium.
4.2 Relationships
Dynamic proxy uses reflection internally : The InvocationHandler.invoke parameter is a Method object; proxy method calls ultimately go through reflection's method.invoke.
MethodHandle is the modern replacement for reflection : Lighter design, better performance, but less flexible than reflection.
The three are not mutually exclusive : Spring AOP uses dynamic proxy, which uses reflection; Lambdas use MethodHandle.
5. Underlying Principles Overview
5.1 Reflection Internals: Method.invoke
What does Method.invoke do?
Security check (verify caller has permission to invoke).
Parameter type checking and conversion.
Invoke underlying JVM method.
Handle return value and exceptions.
Every invoke performs these checks, making it slower than direct calls. The JIT compiler optimizes hot code, but reflection optimization is less effective than direct invocation.
5.2 Dynamic Proxy Internals: Bytecode Generation
What does Proxy.newProxyInstance do at runtime?
Generate proxy class bytecode (e.g., $Proxy0).
The proxy class implements the provided interfaces.
Each method implementation calls InvocationHandler.invoke(proxy, method, args).
Load the proxy class into the JVM.
Create proxy instance.
MyBatis Mapper works exactly this way: UserMapper interface → dynamic proxy generates UserMapper implementation → method calls forwarded to MapperProxy.invoke → SQL execution.
5.3 MethodHandle Internals: Bytecode Level
MethodHandle is a JVM-level concept:
It is a type-safe, directly executable method reference .
The JVM can compile it into efficient bytecode.
Supports invokedynamic instruction (introduced in Java 7).
Lambda expressions are implemented via invokedynamic + MethodHandle.
invokedynamic is a JVM instruction designed for dynamic languages and lambdas. Unlike invokevirtual , invokestatic , and other ordinary method invocation instructions, it dynamically decides which method to call at runtime.
6. Application Scenarios
6.1 MyBatis Mapper: Dynamic Proxy
@Mapper
public interface UserMapper {
User selectById(Long id);
}You don't write a UserMapper implementation, yet userMapper.selectById(1) executes SQL. Principle:
MyBatis uses MapperProxyFactory to generate a dynamic proxy for UserMapper.
The proxy class implements the UserMapper interface.
Calling selectById forwards to MapperProxy.invoke. MapperProxy finds the corresponding SQL statement by method name.
Execute SQL and return result.
6.2 Spring AOP: Dynamic Proxy
@Transactional
public void createOrder(Order order) {
// ...
}Spring generates a proxy for OrderService:
If OrderService implements an interface → JDK dynamic proxy.
If no interface → CGLIB proxy.
On method call, proxy starts transaction, invokes real method, then commits/rolls back transaction.
6.3 JSON Serialization: Reflection
Jackson, Fastjson, and other JSON libraries:
When converting object to JSON string, use reflection to read field values.
When parsing JSON string to object, use reflection to call setters or write fields directly.
6.4 JDBC: Reflection + Factory Pattern
JDBC driver loading: Class.forName("com.mysql.cj.jdbc.Driver"); Reflection loads the driver class, which registers itself with DriverManager.
6.5 Spring IoC: Reflection + Dynamic Proxy
Bean creation: reflection invokes constructors.
Dependency injection: reflection calls setters or writes fields directly.
AOP: dynamic proxy.
6.6 RPC Frameworks: Dynamic Proxy
Dubbo, Feign, and other RPC frameworks:
You define an interface; the framework generates a proxy.
Calling the interface method serializes method name and arguments, sends to remote service.
Remote service executes and returns result; proxy deserializes and returns.
Summary
Reflection, dynamic proxy, and MethodHandle are Java's three core magic powers and the foundation of framework development.
Key Points Recap
Reflection :
Runtime class info retrieval, dynamic method invocation, field access.
Core APIs: Class, Method, Field, Constructor. getDeclaredXxx + setAccessible(true) to access private members.
Performance moderate but sufficient for business scenarios.
Dynamic Proxy :
Runtime interface implementation generation.
Core APIs: Proxy.newProxyInstance, InvocationHandler.
Can only proxy interfaces, not classes.
Typical applications: MyBatis Mapper, Spring AOP, RPC frameworks.
MethodHandle :
Java 7 lightweight method reference.
Performance better than reflection, close to direct invocation.
Lambda expressions use MethodHandle underneath.
Used for dynamic languages and high-performance scenarios.
Relationships :
Dynamic proxy uses reflection internally.
MethodHandle is the modern replacement for reflection.
All three are cornerstones of framework development.
Application Scenarios :
MyBatis Mapper: dynamic proxy.
Spring AOP: dynamic proxy.
JSON serialization: reflection.
RPC frameworks: dynamic proxy.
Lambda: MethodHandle.
These magic features look profound, but understanding the principles reveals: Reflection is "query class info at runtime then invoke"; Dynamic proxy is "generate interface implementation at runtime"; MethodHandle is "JVM-level method pointer". Frameworks are frameworks because they encapsulate these complex magics, letting business code focus only on business logic.
Reflection, dynamic proxy, and MethodHandle are core to Java framework development and frequent topics in senior interviews. Understanding these principles makes reading Spring, MyBatis, and Dubbo source code much easier, and helps diagnose "why it doesn't work" or "why it throws an error" from the bottom up. Stay tuned for upcoming JVM and internals series: Java bytecode & ASM, class loading deep dive, JVM memory model, garbage collection algorithms, JIT compilation optimization, and more.
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.
Java Tech Workshop
Focused on Java backend technologies, sharing fundamentals, multithreading, JVM, the Spring ecosystem, microservices, distributed systems, high concurrency, source‑code analysis, and practical experience. Continuously delivers high‑quality original content, interview guides, and learning roadmaps to help Java developers progress from beginner to advanced, enhancing technical skills and core competitiveness.
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.
