Understanding Java Reflection, Class Loading, and Dynamic Proxy Mechanisms
This article provides a comprehensive guide to Java's reflection mechanism, class loading process, ClassLoader usage, and dynamic proxy techniques, illustrating how to inspect class structures, create objects, access fields and methods, retrieve generic type information, and implement both static and dynamic proxies with practical code examples.
01. Overview of Java Reflection Mechanism
Reflection is considered a key feature of dynamic languages; it allows a program at runtime to obtain any class's internal information via the Reflection API and directly manipulate its fields and methods.
After a class is loaded, a Class object is created in the method area (each class has exactly one Class object). This object contains the complete structural information of the class, acting like a mirror that reflects the class's structure.
1. Dynamic Language
A dynamic language can change its structure at runtime – new functions, objects, or even code can be introduced, and existing functions can be removed. Examples include Objective‑C, C#, JavaScript, PHP, Python, Erlang.
2. Static Language
Static languages have immutable runtime structures, such as Java, C, C++.
Java is not a dynamic language, but it can be regarded as a "quasi‑dynamic" language because it provides certain dynamic capabilities through reflection and bytecode manipulation, making programming more flexible.
Functions provided by Java reflection: Determine the class of any object at runtime. Instantiate any class at runtime. Inspect members (fields, methods) of any class at runtime. Obtain generic type information at runtime. Invoke any object's fields and methods at runtime. Process annotations at runtime. Create dynamic proxies.
Key Reflection APIs: java.lang.Class : represents a class. java.lang.reflect.Method : represents a method. java.lang.reflect.Field : represents a field. java.lang.reflect.Constructor : represents a constructor.
Test Class
import org.junit.Test;
public class ReflectionTest {
// Before reflection, operate on Person directly
@Test
public void test() {
// 1. Create an object of Person
Person p1 = new Person("jay", 21);
// 2. Access and modify fields/methods directly
p1.age = 15;
System.out.println(p1.toString());
p1.show();
// Private members cannot be accessed directly
}
}Person Class
package github;
public class Person {
private String name;
public int age;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public Person() {}
public Person(String name, int age) { this.name = name; this.age = age; }
private Person(String name) { this.name = name; }
@Override
public String toString() {
return "Person{" + "name='" + name + '\'' + ", age=" + age + '}';
}
public void show() { System.out.println("你好,我是🔔"); }
private String showNation(String nation) {
System.out.println("喷子实在太多了!!!" + nation);
return nation;
}
}1.1 Using Reflection to Perform the Same Operations
import org.junit.Test;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class ReflectionTest {
// After reflection, operate on Person
@Test
public void test2() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
Class clazz = Person.class;
// 1. Create Person via reflection
Constructor cons = clazz.getConstructor(String.class, int.class);
Object obj = cons.newInstance("Jon", 18);
Person p = (Person) obj;
System.out.println(p.toString());
// 2. Access fields
Field age = clazz.getDeclaredField("age");
age.set(p, 10);
System.out.println(p.toString());
// 3. Invoke methods
Method show = clazz.getDeclaredMethod("show");
show.invoke(p);
}
}1.2 Powerful Reflection: Accessing Private Members
import org.junit.Test;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class ReflectionTest {
@Test
public void test2() throws Exception {
Class clazz = Person.class;
// 1. Create Person via reflection
Constructor cons = clazz.getConstructor(String.class, int.class);
Object obj = cons.newInstance("Jon", 18);
Person p = (Person) obj;
System.out.println(p.toString());
// 2. Access fields
Field age = clazz.getDeclaredField("age");
age.set(p, 10);
System.out.println(p.toString());
// 3. Invoke methods
Method show = clazz.getDeclaredMethod("show");
show.invoke(p);
System.out.println("+++++++++++++++++++++++++");
// Access private constructor
Constructor cons2 = clazz.getDeclaredConstructor(String.class);
cons2.setAccessible(true);
Person p1 = (Person) cons2.newInstance("kalo");
System.out.println(p1);
// Access private field
Field name = clazz.getDeclaredField("name");
name.setAccessible(true);
name.set(p1, "Taoyao");
System.out.println(p1);
// Access private method
Method showNation = clazz.getDeclaredMethod("LiNin", String.class);
showNation.setAccessible(true);
String nation = (String) showNation.invoke(p1, "FaceBook");
System.out.println(nation);
}
/**
* Question 1: When to use direct new vs. reflection?
* Suggestion: Use direct new. Use reflection when dynamic behavior is required.
* Question 2: Does reflection conflict with OOP encapsulation?
* Answer: No conflict.
*/
}02. Understanding the Class Object and Obtaining Class Instances
2.3 Understanding the Class Class
/**
* About java.lang.Class
* 1. Class loading process: .class files are generated by javac, then loaded by java runtime, becoming runtime classes represented by a Class instance.
* 2. Each runtime class corresponds to a Class instance.
*/Common Methods of Class
2.4 Four Ways to Obtain a Class Instance
import org.junit.Test;
import java.lang.annotation.ElementType;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class ReflectionTest {
@Test
public void test3() throws ClassNotFoundException {
// Way 1
Class c1 = Person.class;
System.out.println(c1);
// Way 2
Person p1 = new Person();
Class c2 = p1.getClass();
System.out.println(c2);
// Way 3
Class c3 = Class.forName("www.gh110.com");
System.out.println(c3);
System.out.println(c1 == c2);
System.out.println(c1 == c3);
// Way 4
ClassLoader classLoader = ReflectionTest.class.getClassLoader();
Class c4 = classLoader.loadClass("www.gh110.com");
System.out.println(c4);
System.out.println(c1 == c4);
}
}2.5 Explanation of Class Instance Structures
Which types can have Class objects?
class : top‑level, member, local, anonymous classes.
interface : interfaces.
[] : arrays.
enum : enums.
annotation and @interface : annotations.
primitivetype : primitive types.
void
import org.junit.Test;
import java.lang.annotation.ElementType;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class ReflectionTest {
@Test
public void test4() {
Class s1 = Object.class;
Class s2 = Comparable.class;
Class s3 = String[].class;
Class s4 = int[][].class;
Class s5 = ElementType.class;
Class s6 = Override.class;
Class s7 = int.class;
Class s8 = void.class;
Class s9 = Class.class;
int[] a = new int[10];
int[] b = new int[100];
Class s10 = a.getClass();
Class s11 = b.getClass();
System.out.println(s10 == s11);
}
}03. Understanding Class Loading and ClassLoader
3.6 Understanding the Class Loading Process
When a program actively uses a class that has not yet been loaded, the JVM performs three steps to initialize the class.
Explanation
Loading: load bytecode into memory, create a Class object.
Linking: combine binary code into JVM state. Verification: ensure class file conforms to JVM specifications. Preparation: allocate memory for static variables and set default values. Resolution: replace symbolic references with direct references.
Initialization: execute class constructors ( <clinit> ), initialize static fields, and ensure parent class initialization first.
3.7 When Does Class Initialization Occur?
Active references (always trigger initialization): JVM startup – initialize the class containing main . Creating a new instance. Accessing static members (except final constants). Using reflection via java.lang.reflect package. Initializing a subclass also initializes its superclass.
Passive references (do not trigger initialization): Referencing a superclass's static field via a subclass. Accessing a static field declared in a superclass. Referencing a class via an array type. Referencing a compile‑time constant.
3.8 Understanding ClassLoader
Purpose of ClassLoader: Load .class bytecode into memory and create a Class object. Cache loaded classes; the JVM GC can reclaim them.
JVM defines several types of class loaders.
import org.junit.Test;
/**
* Understand ClassLoader
*/
public class ClassLoaderTest {
@Test
public void test1() {
// System class loader for custom classes
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
System.out.println(classLoader);
// Parent is the extension class loader
ClassLoader classLoader1 = classLoader.getParent();
System.out.println(classLoader1);
// Bootstrap class loader (null) cannot load custom classes
ClassLoader classLoader2 = classLoader1.getParent();
System.out.println(classLoader2);
// String's class loader (bootstrap) is null
ClassLoader classLoader3 = String.class.getClassLoader();
System.out.println(classLoader3);
}
}3.9 Using ClassLoader to Load Configuration Files
import org.junit.Test;
import java.io.InputStream;
import java.util.Properties;
/**
* Read configuration file using ClassLoader
*/
public class ClassLoaderTest {
@Test
public void test2() throws Exception {
Properties pros = new Properties();
// Use ClassLoader to load file from src directory
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
InputStream is = classLoader.getResourceAsStream("jdbc1.properties");
pros.load(is);
String user = pros.getProperty("user");
String password = pros.getProperty("password");
System.out.println("user = " + user + ",password = " + password);
}
}04. Creating Runtime Objects via Reflection
import org.junit.Test;
/**
* Create an instance of a runtime class using reflection
*/
public class NewInstanceTest {
@Test
public void test() throws Exception {
Class<Person> clazz = Person.class;
// newInstance() invokes the no‑arg constructor
Person obj = clazz.newInstance();
System.out.println(obj);
}
}4.1 Demonstrating the Dynamism of Reflection
import org.junit.Test;
import java.util.Random;
/**
* Dynamically create objects based on random class names
*/
public class NewInstanceTest {
@Test
public void test2() {
for (int i = 0; i < 100; i++) {
int num = new Random().nextInt(3); // 0,1,2
String classPath = "";
switch (num) {
case 0: classPath = "java.util.Date"; break;
case 1: classPath = "java.lang.Object"; break;
case 2: classPath = "www.java.Person"; break;
}
try {
Object obj = getInstance(classPath);
System.out.println(obj);
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* Create an instance of the specified class.
*/
public Object getInstance(String classPath) throws Exception {
Class clazz = Class.forName(classPath);
return clazz.newInstance();
}
}05. Obtaining the Complete Structure of a Runtime Class
5.1 Providing a Rich Person Class
1. Person class
@MyAnnotation(value="java")
public class Person extends Creature
implements Comparable
, MyInterface {
private String name;
int age;
public int id;
public Person() {}
@MyAnnotation(value="C++")
Person(String name) { this.name = name; }
private Person(String name, int age) { this.name = name; this.age = age; }
@MyAnnotation
private String show(String nation) { System.out.println("我来自" + nation + "星系"); return nation; }
@Override
public void info() { System.out.println("火星喷子"); }
public String display(String play) { return play; }
@Override
public int compareTo(String o) { return 0; }
}2. Creature class
import java.io.Serializable;
public abstract class Creature
implements Serializable {
private char gender;
public double weight;
private void breath() { System.out.println("太阳系"); }
public void eat() { System.out.println("银河系"); }
}3. MyInterface
public interface MyInterface { void info(); }4. MyAnnotation
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation { String value() default "hello world"; }5.2 Retrieving Field Structures
1. Person class
import github2.Person;
import org.junit.Test;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
public class FieldTest {
@Test
public void test() {
Class clazz = Person.class;
// getFields(): public fields of class and superclasses
Field[] fields = clazz.getFields();
for (Field f : fields) System.out.println(f);
System.out.println("++++++++++++++++++");
// getDeclaredFields(): all fields declared in the class
Field[] declaredFields = clazz.getDeclaredFields();
for (Field f : declaredFields) System.out.println(f);
}
@Test
public void test2() {
Class clazz = Person.class;
Field[] declaredFields = clazz.getDeclaredFields();
for (Field f : declaredFields) {
int modifier = f.getModifiers();
System.out.print(Modifier.toString(modifier) + "\t");
Class type = f.getType();
System.out.print(type.getName() + "\t");
System.out.print(f.getName());
}
}
}5.3 Retrieving Method Structures
1. Person class
package github2;
@MyAnnotation(value="java")
public class Person extends Creature
implements Comparable
, MyInterface {
private String name;
int age;
public int id;
public Person() {}
@MyAnnotation(value="C++")
Person(String name) { this.name = name; }
private Person(String name, int age) { this.name = name; this.age = age; }
@MyAnnotation
private String show(String nation) { System.out.println("我来自" + nation + "星系"); return nation; }
@Override
public void info() { System.out.println("火星喷子"); }
public String display(String interests, int age) throws Exception { return interests + age; }
@Override
public int compareTo(String o) { return 0; }
@Override
public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + ", id=" + id + '}'; }
}2. Test class for methods
package github3;
import github2.Person;
import org.junit.Test;
import java.lang.reflect.Method;
public class MythodTest {
@Test
public void test() {
Class clazz = Person.class;
// getMethods(): public methods of class and superclasses
Method[] methods = clazz.getMethods();
for (Method m : methods) System.out.println(m + "****");
System.out.println("++++++++++++++++++++++++++++");
// getDeclaredMethods(): all methods declared in the class
Method[] declaredMethods = clazz.getDeclaredMethods();
for (Method m : declaredMethods) System.out.println(m);
}
}5.4 Detailed Method Structure Information
1. Person class
@MyAnnotation(value="java")
public class Person extends Creature
implements Comparable
, MyInterface {
private String name;
int age;
public int id;
public Person() {}
@MyAnnotation(value="C++")
Person(String name) { this.name = name; }
private Person(String name, int age) { this.name = name; this.age = age; }
@MyAnnotation
private String show(String nation) { System.out.println("我来自" + nation + "星系"); return nation; }
@Override
public void info() { System.out.println("火星喷子"); }
public String display(String interests, int age) throws Exception { return interests + age; }
@Override
public int compareTo(String o) { return 0; }
@Override
public String toString() {
return "Person{" + "name='" + name + '\'' + ", age=" + age + ", id=" + id + '}';
}
}2. Test class for detailed method info
package github3;
import github2.Person;
import org.junit.Test;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
public class MythodTest {
/**
* Show method signature details
*/
@Test
public void test2() {
Class clazz = Person.class;
Method[] declaredMethods = clazz.getDeclaredMethods();
for (Method m : declaredMethods) {
// Annotations
Annotation[] annos = m.getAnnotations();
for (Annotation a : annos) System.out.println(a + "KKKK");
// Modifiers
System.out.print(Modifier.toString(m.getModifiers()) + "\t");
// Return type
System.out.print(m.getReturnType().getName() + "\t");
// Method name and parameters
System.out.print(m.getName() + "(");
Class[] pTs = m.getParameterTypes();
if (pTs != null && pTs.length != 0) {
for (int i = 0; i < pTs.length; i++) {
System.out.print(pTs[i].getName() + " args_" + i);
if (i != pTs.length - 1) System.out.print(",");
}
}
System.out.print(")");
// Exceptions
Class[] eTs = m.getExceptionTypes();
if (eTs.length > 0) {
System.out.print(" throws ");
for (int i = 0; i < eTs.length; i++) {
System.out.print(eTs[i].getName());
if (i != eTs.length - 1) System.out.print(",");
}
}
System.out.println("TQA");
}
}
}5.5 Retrieving Constructor Structures
package github3;
import github2.Person;
import org.junit.Test;
import java.lang.reflect.Constructor;
public class OtherTest {
/**
* Get constructor structures
*/
@Test
public void test() {
Class clazz = Person.class;
// public constructors
Constructor[] constructors = clazz.getConstructors();
for (Constructor c : constructors) System.out.println(c);
System.out.println("************************");
// all declared constructors
Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
for (Constructor c : declaredConstructors) System.out.println(c);
}
}5.6 Retrieving Superclass and Generic Information
package github3;
import github2.Person;
import org.junit.Test;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
public class OtherTest {
@Test
public void test2() {
Class clazz = Person.class;
Class superclass = clazz.getSuperclass();
System.out.println(superclass);
}
@Test
public void test3() {
Class clazz = Person.class;
Type genericSuperclass = clazz.getGenericSuperclass();
System.out.println(genericSuperclass);
}
@Test
public void test4() {
Class clazz = Person.class;
ParameterizedType paramType = (ParameterizedType) clazz.getGenericSuperclass();
Type[] actualTypeArguments = paramType.getActualTypeArguments();
System.out.println(((Class)actualTypeArguments[0]).getName());
}
}5.7 Retrieving Interfaces, Packages, and Annotations
package github3;
import github2.Person;
import org.junit.Test;
import java.lang.annotation.Annotation;
public class OtherTest {
@Test
public void test5() {
Class clazz = Person.class;
Class[] interfaces = clazz.getInterfaces();
for (Class c : interfaces) System.out.println(c);
System.out.println("++++++++++++++++++++++");
Class[] superInterfaces = clazz.getSuperclass().getInterfaces();
for (Class c : superInterfaces) System.out.println(c);
}
@Test
public void test6() {
Class clazz = Person.class;
System.out.println(clazz.getPackage());
}
@Test
public void test7() {
Class clazz = Person.class;
Annotation[] annotations = clazz.getAnnotations();
for (Annotation ann : annotations) System.out.println(ann);
}
}06. Invoking Specific Structures of a Runtime Class
6.1 Accessing Specified Fields
1. Person class
package github2;
@MyAnnotation(value="java")
public class Person extends Creature
implements Comparable
, MyInterface {
private String name;
int age;
public int id;
// constructors and methods omitted for brevity
}2. Test class
import github2.Person;
import org.junit.Test;
import java.lang.reflect.Field;
/**
* Invoke fields, methods, constructors via reflection
*/
public class ReflectionTest {
/**
* Simple field access (public field)
*/
@Test
public void testField() throws Exception {
Class clazz = Person.class;
Person p = (Person) clazz.newInstance();
Field id = clazz.getField("id");
id.set(p, 1001);
int pId = (int) id.get(p);
System.out.println(pId);
}
/**
* Access private field (needs setAccessible)
*/
@Test
public void testField1() throws Exception {
Class clazz = Person.class;
Person p = (Person) clazz.newInstance();
Field name = clazz.getDeclaredField("name");
name.setAccessible(true);
name.set(p, "Jam");
System.out.println(name.get(p));
}
}6.2 Invoking Specified Methods
package github3;
import github2.Person;
import org.junit.Test;
import java.lang.reflect.Method;
public class ReflectionTest {
@Test
public void testMethod() throws Exception {
Class clazz = Person.class;
Person p = (Person) clazz.newInstance();
// Invoke private method "show"
Method show = clazz.getDeclaredMethod("show", String.class);
show.setAccessible(true);
Object returnValue = show.invoke(p, "CCA");
System.out.println(returnValue);
System.out.println("+++++++++Calling static method+++++++++++");
Method showDesc = clazz.getDeclaredMethod("showDown");
showDesc.setAccessible(true);
Object returnVal = showDesc.invoke(Person.class);
System.out.println(returnVal);
}
}6.3 Invoking Specified Constructors
import github2.Person;
import org.junit.Test;
import java.lang.reflect.Constructor;
public class ReflectionTest {
@Test
public void testConstructor() throws Exception {
Class clazz = Person.class;
// private Person(String name)
Constructor constructor = clazz.getDeclaredConstructor(String.class);
constructor.setAccessible(true);
Person per = (Person) constructor.newInstance("Tom");
System.out.println(per);
}
}07. Applications of Reflection: Dynamic Proxy
7.1 Proxy Pattern and Dynamic Proxy
Proxy design pattern wraps an object with a proxy that decides when to forward method calls to the original object.
Static proxy requires compile‑time knowledge of both proxy and target classes, leading to many proxy classes.
Dynamic proxy creates proxy objects at runtime, allowing a single proxy class to handle multiple interfaces.
Typical uses: debugging, remote method invocation, AOP.
7.2 Static Proxy Example
/**
* Static proxy example – the proxy and target are fixed at compile time.
*/
interface ClothFactory { void produceCloth(); }
// Proxy class
class PersonTest implements ClothFactory {
private ClothFactory factory;
public PersonTest(ClothFactory factory) { this.factory = factory; }
@Override
public void produceCloth() {
System.out.println("造纸厂开始做一些准备工作");
factory.produceCloth();
System.out.println("造纸厂做一些后续收尾工作");
}
}
// Target class
class NeckTest implements ClothFactory {
@Override
public void produceCloth() { System.out.println("造纸厂计划生产一批卫生纸"); }
}
public class StaticProxyTest {
public static void main(String[] args) {
ClothFactory real = new NeckTest();
ClothFactory proxy = new PersonTest(real);
proxy.produceCloth();
}
}7.3 Dynamic Proxy Example
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
interface Moon { String getBelief(); void Object(String Moon); }
// Target class
class Venus implements Moon {
@Override public String getBelief() { return "The only planet in the solar system without a magnetic field."; }
@Override public void Object(String MinMoon) { System.out.println("周围有很多" + MinMoon); }
}
/**
* Utility to create a dynamic proxy for any object.
*/
class BookTest {
public static Object getProxyInstance(Object obj) {
DeskTest handler = new DeskTest();
handler.bind(obj);
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), handler);
}
}
class DeskTest implements InvocationHandler {
private Object obj;
public void bind(Object obj) { this.obj = obj; }
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// Common pre‑processing
SunTest util = new SunTest();
util.Star();
Object returnValue = method.invoke(obj, args);
// Common post‑processing
util.Star2();
return returnValue;
}
}
class SunTest { public void Star() { System.out.println("====================通用方法一===================="); }
public void Star2() { System.out.println("====================通用方法二===================="); } }
public class ProductTest {
public static void main(String[] args) {
Venus real = new Venus();
Moon proxy = (Moon) BookTest.getProxyInstance(real);
String belief = proxy.getBelief();
System.out.println(belief);
proxy.Object("面试突击");
System.out.println("+++++++++++++++++++");
NeckTest fox = new NeckTest();
ClothFactory ween = (ClothFactory) BookTest.getProxyInstance(fox);
ween.produceCloth();
}
}7.4 AOP and Dynamic Proxy Example
Dynamic proxy can be used to implement AOP by inserting common behavior before and after method execution.
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* Demonstrates AOP style dynamic proxy.
*/
class BookTest {
public static Object getProxyInstance(Object obj) {
DeskTest handler = new DeskTest();
handler.bind(obj);
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), handler);
}
}
class DeskTest implements InvocationHandler {
private Object obj;
public void bind(Object obj) { this.obj = obj; }
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SunTest util = new SunTest();
util.Star(); // before
Object returnValue = method.invoke(obj, args);
util.Star2(); // after
return returnValue;
}
}
class SunTest { public void Star() { System.out.println("====================通用方法一===================="); }
public void Star2() { System.out.println("====================通用方法二===================="); } }
// The rest of the code (interfaces, target classes, and main) is the same as in section 7.3.Wukong Talks Architecture
Explaining distributed systems and architecture through stories. Author of the "JVM Performance Tuning in Practice" column, open-source author of "Spring Cloud in Practice PassJava", and independently developed a PMP practice quiz mini-program.
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.