1. 通过反射进行序列化
你已经知道,序列化是把对象转换为字节流或文本表示的过程。Java 中有标准序列化(Serializable),但更常见的是通过 Jackson 和 Gson 等库使用 JSON/XML 格式。
为什么这里需要反射?
要序列化对象,需要获知其字段及其值。字段可能是 private,普通代码无法访问——而反射可以。因此 JSON 类库会动态遍历对象结构,利用 Field 和 Method 读取/写入字段。
示例:将对象简单序列化为字符串
数据类:
public class Person {
private String name;
private int age;
private boolean active;
public Person(String name, int age, boolean active) {
this.name = name;
this.age = age;
this.active = active;
}
}
基于反射的最简序列化器:
import java.lang.reflect.Field;
public class SimpleSerializer {
public static String serialize(Object obj) {
StringBuilder sb = new StringBuilder();
Class<?> clazz = obj.getClass();
sb.append(clazz.getSimpleName()).append("{");
Field[] fields = clazz.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
Field field = fields[i];
field.setAccessible(true); // 允许访问私有字段
try {
sb.append(field.getName()).append("=")
.append(field.get(obj));
} catch (IllegalAccessException e) {
sb.append(field.getName()).append("=<?>");
}
if (i < fields.length - 1) sb.append(", ");
}
sb.append("}");
return sb.toString();
}
}
用法:
Person p = new Person("Alice", 30, true);
System.out.println(SimpleSerializer.serialize(p));
Person{name=Alice, age=30, active=true}
它是如何工作的?
- 通过 getDeclaredFields() 获取声明的字段。
- 通过 setAccessible(true) 使其可访问。
- 读取字段名和值并拼接成字符串。
示例的限制:没有处理嵌套对象、集合、数组和循环引用——这只是演示原理。
成熟库是如何做到的?
Jackson/Gson 能处理嵌套对象和集合,支持注解(@JsonIgnore、@SerializedName)、日期格式等——这一切都建立在反射之上。
Jackson 示例:
import com.fasterxml.jackson.databind.ObjectMapper;
Person p = new Person("Bob", 25, false);
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(p);
// {"name":"Bob","age":25,"active":false}
2. Dependency Injection (DI) 与反射
Dependency Injection 是一种模式,依赖由外部“注入”,而不是在类内部创建。这使得代码更灵活、可测试且可扩展。在 Java 中,Spring、Guice、Dagger 等框架通过注解(如 @Autowired、@Inject)标注注入点来实现。
为什么这里需要反射?
DI 容器需要在运行时查找字段/构造器、读取注解并创建实例——这些都通过 Class/Constructor/Field API 完成。
示例:基于反射的迷你 DI
注入注解:
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Inject {
}
含有依赖的类:
public class Service {
public void doWork() {
System.out.println("Service is working!");
}
}
public class Client {
@Inject
private Service service;
public void useService() {
service.doWork();
}
}
迷你容器:
import java.lang.reflect.*;
public class MiniDIContainer {
// 按类创建对象,并向带有 @Inject 的字段注入依赖
public static Object createObject(Class<?> clazz) throws Exception {
Object obj = clazz.getDeclaredConstructor().newInstance();
for (Field field : clazz.getDeclaredFields()) {
if (field.isAnnotationPresent(Inject.class)) {
Object dependency = createObject(field.getType()); // 递归地
field.setAccessible(true);
field.set(obj, dependency);
}
}
return obj;
}
}
用法:
public class Main {
public static void main(String[] args) throws Exception {
Client client = (Client) MiniDIContainer.createObject(Client.class);
client.useService(); // Service is working!
}
}
它如何工作?容器查找带有 @Inject 的字段,根据其类型创建依赖,并通过反射把它们设置到私有字段。
重要:这只是简化方案。真实的 DI 容器支持作用域、单例、配置、代理、处理循环依赖等。
3. 动态代理
代理是一种“替身”对象,用来拦截调用并添加行为:日志、安全、事务等。在 Java 中,这由 java.lang.reflect.Proxy 与 InvocationHandler 完成。它是许多 Spring AOP 能力、Mockito 中的 mock 等的基础。
示例:记录日志的代理
public interface HelloService {
void sayHello(String name);
}
public class HelloServiceImpl implements HelloService {
public void sayHello(String name) {
System.out.println("Hello, " + name + "!");
}
}
import java.lang.reflect.*;
public class LoggingProxy {
@SuppressWarnings("unchecked")
public static <T> T createProxy(T target, Class<T> iface) {
return (T) Proxy.newProxyInstance(
iface.getClassLoader(),
new Class<?>[]{iface},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("调用方法: " + method.getName());
return method.invoke(target, args);
}
}
);
}
}
用法:
HelloService original = new HelloServiceImpl();
HelloService proxy = LoggingProxy.createProxy(original, HelloService.class);
proxy.sayHello("World");
调用方法: sayHello
Hello, World!
它如何工作?Proxy.newProxyInstance 会创建一个实现接口的对象,所有方法调用都会进入 InvocationHandler.invoke,你可以在委托前/后执行任意“横切”代码。
4. 真实场景与限制
反射在哪些“实战”场景中使用?
- JUnit — 查找带有 @Test 的方法并通过反射调用它们。
- Spring — 创建 bean、注入依赖、扫描注解、生成代理。
- Jackson/Gson — 在序列化/反序列化时甚至会读取私有字段。
- Hibernate — 根据类结构构建 ORM 模型,管理字段和代理对象。
- Mockito — 通过代理创建 mock 并拦截调用。
为什么并非总是应该使用反射?
- 性能。通常比普通调用更慢(可通过缓存/字节码生成缓解)。
- 安全性。破坏封装;可访问私有数据。
- Java 9+ 的模块化。若未显式打开包/模块,可能出现 InaccessibleObjectException。
5. 常见错误
错误 1:忽略受检异常。反射方法会抛出 NoSuchFieldException、IllegalAccessException、InvocationTargetException 等。请处理它们或包装成你自己的异常。
错误 2:setAccessible(true) 并非总是有效。在 Java 9+ 的模块化应用中可能发生 InaccessibleObjectException。需要 JVM/模块参数(--add-opens)或使用公共 API。
错误 3:DI 中的循环依赖。在天真的递归中(A 依赖 B,而 B 又依赖 A)会导致 StackOverflowError。真实容器会跟踪依赖图,并使用专门技术来解决循环。
错误 4:对象序列化不完整。若没有递归遍历,引用类型的字段会被序列化为 ClassName@hash。要正确序列化,需要处理嵌套对象/集合并防止循环。
错误 5:性能损失。频繁的反射操作(在循环中、热点路径上)会成为瓶颈。使用缓存 Field/Method、字节码生成、MethodHandle 或注解处理来优化。
错误 6:破坏封装。通过反射修改私有字段会导致脆弱性和难以定位的 bug。尽量优先使用公开契约。
GO TO FULL VERSION