Skip to content

反射与框架

你以为你写的是 Java 代码。

其实你写的是「让框架帮你写代码」的代码。

这句话有点绕,但理解它,你就理解了反射在 Java 生态中的地位。

为什么框架需要反射?

正常写代码,逻辑是直来直去的:

java
// 用户代码
UserService service = new UserServiceImpl();
service.save(user);

但 Spring 说:不,你不需要 new,你只需要声明:

java
// 用户代码
@Autowired
private UserService userService;

中间那些创建对象、注入依赖的代码去哪了?答案是:框架用反射帮你写了

没有反射的世界

想象一个没有反射的 Spring:

java
// 没有反射,你要这样写
public class UserController {
    private UserService userService;

    // 手动创建、手动注入
    public UserController() {
        this.userService = new UserServiceImpl();
    }
}

问题来了:

  1. 如果要换成 UserServiceImplV2 怎么办?改代码?
  2. 如果 UserServiceImpl 依赖 UserRepository 怎么办?手动创建?
  3. 如果要加切面(日志、事务)怎么办?硬编码进去?

这就是为什么需要反射——框架需要在运行时「替你」决定创建什么、注入什么、调用什么

场景一:Spring 的依赖注入

依赖注入(DI)是 Spring 的核心功能。它的原理说穿了很简单:

java
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 是怎么发现并执行测试方法的?

java
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 对象的?

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 对象,是怎么做的?

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(面向切面编程)的原理是什么?

java
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 框架的本质——它们都是反射的包装器

为什么框架能活,裸反射代码该死?

框架用反射为什么不会死?因为它们做了一件事:缓存

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,性能肯定爆炸。

要点回顾

框架反射的核心用法
SpringDI、AOP、Bean 生命周期
JUnit测试发现、方法调用
JacksonJSON ↔ 对象转换
MyBatisResultSet → 对象映射
HibernateORM、延迟加载
Mockito方法 stubbing

框架使用反射的秘诀

  1. 注解是入口 — 告诉框架「我要处理这个地方」
  2. 扫描是发现 — 框架找到所有被标记的目标
  3. 反射是执行 — 框架替你做那些重复性的操作
  4. 缓存是优化 — 把反射结果存起来,避免重复查找

理解了这四点,你不仅能更好地使用框架,还能自己写框架。

基于 VitePress 构建