反射与框架
你以为你写的是 Java 代码。
其实你写的是「让框架帮你写代码」的代码。
这句话有点绕,但理解它,你就理解了反射在 Java 生态中的地位。
为什么框架需要反射?
正常写代码,逻辑是直来直去的:
// 用户代码
UserService service = new UserServiceImpl();
service.save(user);但 Spring 说:不,你不需要 new,你只需要声明:
// 用户代码
@Autowired
private UserService userService;中间那些创建对象、注入依赖的代码去哪了?答案是:框架用反射帮你写了。
没有反射的世界
想象一个没有反射的 Spring:
// 没有反射,你要这样写
public class UserController {
private UserService userService;
// 手动创建、手动注入
public UserController() {
this.userService = new UserServiceImpl();
}
}问题来了:
- 如果要换成
UserServiceImplV2怎么办?改代码? - 如果
UserServiceImpl依赖UserRepository怎么办?手动创建? - 如果要加切面(日志、事务)怎么办?硬编码进去?
这就是为什么需要反射——框架需要在运行时「替你」决定创建什么、注入什么、调用什么。
场景一:Spring 的依赖注入
依赖注入(DI)是 Spring 的核心功能。它的原理说穿了很简单:
public class SimpleSpringContainer {
// 存储 Bean 定义
private Map<Class<?>, Object> beans = new HashMap<>();
// 注册 Bean
public <T> void registerBean(Class<T> clazz) {
try {
T instance = clazz.getDeclaredConstructor().newInstance();
beans.put(clazz, instance);
} catch (Exception e) {
throw new RuntimeException("无法创建 Bean: " + clazz.getName(), e);
}
}
// 注入依赖
public void autowire(Object bean) throws Exception {
for (Field field : bean.getClass().getDeclaredFields()) {
if (field.isAnnotationPresent(Autowired.class)) {
Class<?> fieldType = field.getType();
Object dependency = beans.get(fieldType);
if (dependency != null) {
field.setAccessible(true);
field.set(bean, dependency); // 关键:反射注入
}
}
}
}
public <T> T getBean(Class<T> clazz) {
return clazz.cast(beans.get(clazz));
}
}
// 使用
public class Demo {
public static void main(String[] args) throws Exception {
SimpleSpringContainer container = new SimpleSpringContainer();
container.registerBean(UserServiceImpl.class);
container.registerBean(OrderServiceImpl.class);
UserServiceImpl service = container.getBean(UserServiceImpl.class);
container.autowire(service); // 注入依赖
service.doSomething();
}
}这就是 Spring IOC 容器的简化版原理。框架用反射读取 @Autowired 注解,找到对应的 Bean,然后用反射设值。
场景二:JUnit 的测试发现
JUnit 是怎么发现并执行测试方法的?
public class SimpleJUnitRunner {
public static void main(String[] args) throws Exception {
Class<?> testClass = Class.forName(args[0]);
Object instance = testClass.getDeclaredConstructor().newInstance();
int passed = 0;
int failed = 0;
// 遍历所有方法,找到 @Test 标注的
for (Method method : testClass.getDeclaredMethods()) {
if (method.isAnnotationPresent(Test.class)) {
try {
// 执行 @BeforeEach
invokeIfPresent(testClass, instance, BeforeEach.class);
// 执行测试
method.invoke(instance);
// 执行 @AfterEach
invokeIfPresent(testClass, instance, AfterEach.class);
System.out.println("✓ " + method.getName());
passed++;
} catch (InvocationTargetException e) {
System.out.println("✗ " + method.getName() + ": " + e.getCause());
failed++;
}
}
}
System.out.println("\n结果: " + passed + " passed, " + failed + " failed");
}
private static void invokeIfPresent(Class<?> clazz, Object instance,
Class<? extends Annotation> annotationClass)
throws Exception {
for (Method method : clazz.getDeclaredMethods()) {
if (method.isAnnotationPresent(annotationClass)) {
method.invoke(instance);
}
}
}
}
// 自定义注解
@Retention(RetentionPolicy.RUNTIME)
@interface Test {}
@Retention(RetentionPolicy.RUNTIME)
@interface BeforeEach {}
@Retention(RetentionPolicy.RUNTIME)
@interface AfterEach {}
// 用户写的测试
class CalculatorTest {
private Calculator calc;
@BeforeEach
void setup() {
calc = new Calculator();
}
@Test
void testAdd() {
assertEquals(3, calc.add(1, 2));
}
@AfterEach
void teardown() {
calc = null;
}
}没有反射,这段代码无法实现——JUnit 根本不知道你的测试类里有哪些方法,更不知道哪些方法应该被执行。
场景三:Jackson 的 JSON 序列化
Jackson 是怎么把 JSON 转成 Java 对象的?
public class SimpleJsonMapper {
public <T> T fromJson(String json, Class<T> clazz) throws Exception {
// 1. 解析 JSON(简化版)
Map<String, Object> data = parseJson(json);
// 2. 创建对象
T obj = clazz.getDeclaredConstructor().newInstance();
// 3. 反射设值
for (Field field : clazz.getDeclaredFields()) {
if (field.isAnnotationPresent(JsonProperty.class)) {
String jsonKey = field.getAnnotation(JsonProperty.class).value();
if (data.containsKey(jsonKey)) {
field.setAccessible(true);
field.set(obj, convertValue(data.get(jsonKey), field.getType()));
}
} else {
// 没有注解,用字段名匹配
if (data.containsKey(field.getName())) {
field.setAccessible(true);
field.set(obj, convertValue(data.get(field.getName()), field.getType()));
}
}
}
return obj;
}
private Map<String, Object> parseJson(String json) {
// 简化实现
return new HashMap<>();
}
private Object convertValue(Object value, Class<?> targetType) {
// 类型转换
return value;
}
}
// 注解定义
@Retention(RetentionPolicy.RUNTIME)
@interface JsonProperty {
String value();
}
// 用户写的类
class User {
@JsonProperty("user_name")
private String name;
@JsonProperty("user_age")
private int age;
}这就是为什么 Jackson 需要 setAccessible(true)——getter/setter 方法可能是 private 的,而 JSON 数据中的字段名可能和 Java 字段名不一致,需要注解来映射。
场景四:MyBatis 的结果映射
MyBatis 从数据库查询结果到 Java 对象,是怎么做的?
public class SimpleResultSetHandler {
public <T> List<T> handle(ResultSet rs, Class<T> clazz) throws Exception {
List<T> results = new ArrayList<>();
while (rs.next()) {
// 1. 创建对象
T obj = clazz.getDeclaredConstructor().newInstance();
// 2. 获取列名到字段的映射
Map<String, Field> columnFieldMap = buildColumnFieldMap(clazz);
// 3. 反射设值
for (String columnName : columnFieldMap.keySet()) {
Object value = rs.getObject(columnName);
Field field = columnFieldMap.get(columnName);
field.setAccessible(true);
field.set(obj, value);
}
results.add(obj);
}
return results;
}
private <T> Map<String, Field> buildColumnFieldMap(Class<T> clazz) {
Map<String, Field> map = new HashMap<>();
for (Field field : clazz.getDeclaredFields()) {
// 映射列名到字段
Column column = field.getAnnotation(Column.class);
String columnName = (column != null) ? column.value() : field.getName();
map.put(columnName.toUpperCase(), field);
}
return map;
}
}
// 用户写的实体
class UserEntity {
@Column("ID")
private int id;
@Column("USER_NAME")
private String name;
@Column("EMAIL")
private String email;
}MyBatis 的强大之处在于它能自动处理列名和字段名的映射(通过注解)、类型转换、空值处理——这些全靠反射实现。
场景五:Spring AOP 的方法拦截
AOP(面向切面编程)的原理是什么?
public class SimpleAOPProxy {
public static Object createProxy(Object target, MethodInterceptor interceptor) {
return java.lang.reflect.Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
(proxy, method, args) -> {
// 前置通知
interceptor.before(method, args);
Object result;
try {
// 调用原始方法
method.setAccessible(true);
result = method.invoke(target, args);
// 返回通知
return interceptor.afterReturning(method, args, result);
} catch (InvocationTargetException e) {
// 异常通知
return interceptor.afterThrowing(method, args, e.getCause());
}
}
);
}
}
// 方法拦截器
interface MethodInterceptor {
Object before(Method method, Object[] args);
Object afterReturning(Method method, Object[] args, Object result);
Object afterThrowing(Method method, Object[] args, Throwable e);
}
// 使用
class AOPDemo {
public static void main(String[] args) {
UserService realService = new UserServiceImpl();
UserService proxiedService = (UserService) createProxy(realService,
new MethodInterceptor() {
@Override
public Object before(Method method, Object[] args) {
System.out.println("调用前: " + method.getName());
return null;
}
@Override
public Object afterReturning(Method method, Object[] args, Object result) {
System.out.println("调用后: " + method.getName());
return result;
}
@Override
public Object afterThrowing(Method method, Object[] args, Throwable e) {
System.out.println("异常: " + e.getMessage());
throw new RuntimeException(e);
}
});
// 实际调用的是代理对象
proxiedService.save(new User());
}
}Spring 用 Proxy.newProxyInstance() 创建代理对象,然后在 InvocationHandler 中用反射调用原始方法。这就是 Spring 事务管理、日志切面的原理。
框架的共同模式
看了这么多例子,你会发现框架使用反射的模式是固定的:
1. 定义注解 → 标记「元数据」
2. 扫描类/方法/字段 → 找到被标记的目标
3. 读取注解/成员信息 → 获取配置
4. 反射操作 → 执行实际逻辑理解了这个模式,你就能看透任何 Java 框架的本质——它们都是反射的包装器。
为什么框架能活,裸反射代码该死?
框架用反射为什么不会死?因为它们做了一件事:缓存。
// 框架的做法:启动时一次性获取
class MyBatisConfig {
private final Map<Class<?>, Map<String, Field>> fieldCache = new ConcurrentHashMap<>();
private final Map<Class<?>, Map<String, Method>> setterCache = new ConcurrentHashMap<>();
public void init(Class<?> clazz) {
// 启动时一次性构建缓存
fieldCache.put(clazz, buildFieldMap(clazz));
setterCache.put(clazz, buildSetterMap(clazz));
}
public void setValue(Object obj, String property, Object value) {
// 运行时从缓存取,不需要重复查找
Method setter = setterCache.get(obj.getClass()).get(property);
setter.invoke(obj, value);
}
}Spring 启动慢,但运行快——因为它在启动时就把反射结果缓存好了。
反过来,如果你每次请求都重新获取 Method,性能肯定爆炸。
要点回顾
| 框架 | 反射的核心用法 |
|---|---|
| Spring | DI、AOP、Bean 生命周期 |
| JUnit | 测试发现、方法调用 |
| Jackson | JSON ↔ 对象转换 |
| MyBatis | ResultSet → 对象映射 |
| Hibernate | ORM、延迟加载 |
| Mockito | 方法 stubbing |
框架使用反射的秘诀:
- 注解是入口 — 告诉框架「我要处理这个地方」
- 扫描是发现 — 框架找到所有被标记的目标
- 反射是执行 — 框架替你做那些重复性的操作
- 缓存是优化 — 把反射结果存起来,避免重复查找
理解了这四点,你不仅能更好地使用框架,还能自己写框架。
