文章目录

Java 高级特性:反射机制与动态代理

发布于 2026-04-05 15:12:18 · 浏览 10 次 · 评论 0 条

Java 高级特性:反射机制与动态代理

在 Java 开发中,反射机制与动态代理属于进阶技能。掌握它们,你才能真正理解 Spring、Hibernate 等框架的底层原理,也能写出更灵活、更具扩展性的代码。这篇文章将用最直白的方式,带你彻底搞懂这两个核心概念。


一、反射机制:程序自我的"X光透视"

1.1 什么是反射?

反射是指在程序运行期间,能够动态获取类的信息、调用对象方法的能力。普通代码是"编译时确定"的——你写的代码在写死的那一刻就知道要操作哪个类、哪个方法。而反射让你在程序运行时"现场发现"类的结构,就像给程序装了一双透视眼。

举个例子:传统开发中,你写 userService.save(),编译器早就知道 userService 是哪个类、是否有 save 方法。但如果你想让程序自己决定"今天要调用哪个类的哪个方法",传统写法就办不到了——这正是反射的用武之地。

1.2 反射的核心入口:Class 对象

所有反射操作的起点都是 Class 对象。这个对象代表了一个类的完整信息:它的构造方法有哪些、有什么成员变量、实现了哪些接口、继承了哪个父类。获取 Class 对象有三种常用方式:

方式一:通过对象的 getClass() 方法

String str = "Hello";
Class<?> clazz = str.getClass();
System.out.println(clazz.getName());  // 输出:java.lang.String

方式二:通过类的 class 属性

Class<?> clazz = String.class;
System.out.println(clazz.getName());  // 输出:java.lang.String

方式三:通过 Class.forName() 动态加载

Class<?> clazz = Class.forName("java.lang.String");
System.out.println(clazz.getName());  // 输出:java.lang.String

第三种方式最灵活,因为类的名字可以放在配置文件里,实现真正的"运行时决定"。

1.3 反射操作三板斧

掌握反射,实际上就是学会三类操作:操作构造方法、操作成员变量、操作成员方法。

操作构造方法——创建对象

Class<?> personClass = Person.class;

// 获取无参构造方法
Constructor<?> noArgConstructor = personClass.getDeclaredConstructor();
Object person1 = noArgConstructor.newInstance();

// 获取有参构造方法
Constructor<?> paramConstructor = personClass.getDeclaredConstructor(String.class, int.class);
Object person2 = paramConstructor.newInstance("张三", 25);

如果你想调用私有构造方法,需要先设置可访问权限:

noArgConstructor.setAccessible(true);  // 强制访问私有构造方法

操作成员变量——读取和修改值

Field nameField = personClass.getDeclaredField("name");
nameField.setAccessible(true);  // 访问私有字段

Object person = ...;  // 已有的Person对象
String name = (String) nameField.get(person);      // 读取字段值
nameField.set(person, "李四");                      // 修改字段值

操作成员方法——动态调用

Method sayHelloMethod = personClass.getMethod("sayHello", String.class);
Object result = sayHelloMethod.invoke(person, "World");  // 调用方法
System.out.println(result);

invoke 方法的第一个参数是方法所属的对象,如果是静态方法则传 null

1.4 反射的实际应用场景

反射不是花架子,它在真实项目中用途广泛。

场景一:通用序列化框架

像 Jackson、Gson 这样的 JSON 序列化框架,不可能为每个类写解析逻辑。它们通过反射遍历类的所有字段,自动把对象转换成 JSON。这正是反射最典型的应用。

场景二:依赖注入容器

Spring 的 IoC 容器启动时,扫描所有被 @Component@Service 注解标记的类,通过反射创建对象实例,并自动注入依赖。这就是 Spring"控制反转"的底层实现。

场景三:绕过编译期检查调用私有方法

在单元测试中,有时候需要测试一个类的私有方法是否正确。直接调用私有方法会编译报错,但用反射可以绕过这个限制。


二、动态代理:给对象"贴牌"的能力

2.1 为什么需要代理?

理解代理最好的方式是先看它的对立面:直接调用。

UserService userService = new UserServiceImpl();
userService.addUser(new User());  // 直接调用

这段代码没有任何问题。但如果现在有个新需求:在每次调用 addUser 前后记录日志。直接修改 UserServiceImpl 的代码不是好主意——代码会变脏,而且如果 UserServiceImpl 是第三方提供的,你就无权修改。

代理模式的核心思想是:创建一个"代理对象",代为处理请求。代理对象持有真实对象的引用,在调用真实方法前后可以插入额外逻辑。

2.2 静态代理:自己写代理类

先来看静态代理的实现方式。假设有个接口和实现类:

interface UserService {
    void addUser(User user);
    void deleteUser(Long id);
}

class UserServiceImpl implements UserService {
    @Override
    public void addUser(User user) {
        System.out.println("添加用户成功");
    }

    @Override
    public void deleteUser(Long id) {
        System.out.println("删除用户成功");
    }
}

现在写一个代理类,在调用真实方法前后打印日志:

class UserServiceProxy implements UserService {
    private UserService target;  // 持有真实对象的引用

    public UserServiceProxy(UserService target) {
        this.target = target;
    }

    @Override
    public void addUser(User user) {
        System.out.println("[日志] 开始添加用户: " + user.getName());
        target.addUser(user);  // 调用真实方法
        System.out.println("[日志] 添加用户完成");
    }

    @Override
    public void deleteUser(Long id) {
        System.out.println("[日志] 开始删除用户: " + id);
        target.deleteUser(id);
        System.out.println("[日志] 删除用户完成");
    }
}

使用时,替换成代理对象:

UserService userService = new UserServiceProxy(new UserServiceImpl());
userService.addUser(new User("张三"));

运行这段代码,你会看到日志被打印出来了。静态代理确实解决了问题,但它的缺点也很明显:每个接口都需要写一个代理类。如果接口有 20 个方法,代理类就要写 20 个方法调用——这太繁琐了。

2.3 JDK 动态代理:自动生成代理类

动态代理的核心是:代理类不是手写的,而是在运行时由 JVM 自动生成的。JDK 提供了 java.lang.reflect.Proxy 类来干这件事。

使用 JDK 动态代理,只需实现 InvocationHandler 接口:

class LogInvocationHandler implements InvocationHandler {
    private Object target;  // 真实对象

    public LogInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("[日志] 调用方法 " + method.getName() + " 开始");
        Object result = method.invoke(target, args);  // 调用真实方法
        System.out.println("[日志] 调用方法 " + method.getName() + " 结束");
        return result;
    }
}

然后,用 Proxy.newProxyInstance() 创建代理对象:

UserService userServiceImpl = new UserServiceImpl();

// 创建代理对象
UserService userService = (UserService) Proxy.newProxyInstance(
    userServiceImpl.getClass().getClassLoader(),           // 类加载器
    userServiceImpl.getClass().getInterfaces(),            // 被代理类实现的接口
    new LogInvocationHandler(userServiceImpl)              // 调用处理器
);

userService.addUser(new User("张三"));

这就是动态代理的精妙之处:一个 InvocationHandler 可以代理所有接口方法,你不用为每个方法单独写代理逻辑。运行时,JVM 会根据接口自动生成代理类。

2.4 动态代理的原理

动态代理生成的代理类结构大致如下:

public class $Proxy0 implements UserService {
    private InvocationHandler h;
    
    public $Proxy0(InvocationHandler h) {
        this.h = h;
    }

    @Override
    public void addUser(User user) {
        // 实际上是这样的调用
        h.invoke(this, addUser方法对象, new Object[]{user});
    }
}

所有的方法调用都会转发到 InvocationHandlerinvoke 方法。这就是为什么你只需要在一个地方写增强逻辑,所有方法都能享受到增强效果。

2.5 CGLIB 代理:没有接口也能代理

JDK 动态代理有个硬性要求:被代理的类必须实现接口。但如果一个类没有实现任何接口,JDK 代理就无能为力了。这时候要用 CGLIB(Code Generation Library)。

CGLIB 的原理不同于 JDK 代理:它是通过继承被代理类来创建代理类的。代理类是目标类的子类,重写父类的方法来实现增强。

使用 CGLIB 需要引入额外依赖:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

实现 MethodInterceptor 接口:

class CglibInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("[CGLIB日志] 调用方法 " + method.getName() + " 开始");
        Object result = proxy.invokeSuper(obj, args);  // 调用父类方法
        System.out.println("[CGLIB日志] 调用方法 " + method.getName() + " 结束");
        return result;
    }
}

创建代理对象:

Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserServiceImpl.class);  // 设置父类
enhancer.setCallback(new CglibInterceptor());   // 设置回调

UserServiceImpl proxy = (UserServiceImpl) enhancer.create();
proxy.addUser(new User("李四"));

2.6 代理方式对比与选择

特性 JDK 动态代理 CGLIB 代理
实现原理 基于接口的动态代理类 基于继承的子类
是否需要接口 必须实现接口 不需要接口
执行效率 较高 略低于 JDK 代理
能否代理 final 类 不能(无法继承)
能否代理 final 方法 不能(无法重写)

实际开发中的选择建议很简单:如果目标对象实现了接口,优先用 JDK 动态代理;如果没有接口,用 CGLIB。Spring 框架就是按照这个策略自动选择的。


三、实战案例:实现一个简易数据库访问框架

理论讲完了,现在用一个完整案例把反射和动态代理结合起来。目标是实现一个类似 MyBatis 的简化版:把对象自动保存到数据库,开发者只需定义接口和 POJO 类。

3.1 定义注解

首先定义两个注解:一个标记实体类,一个标记主键字段。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Entity {
    String tableName();
}

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface Id {}

3.2 定义实体类和 DAO 接口

@Entity(tableName = "users")
class User {
    @Id
    private Long id;
    private String name;
    private Integer age;

    // getter 和 setter 省略
}

interface UserDao {
    void insert(User user);
    User findById(Long id);
    void update(User user);
    void delete(Long id);
}

3.3 实现动态 DAO 框架

核心实现是一个 DaoInvocationHandler,它利用反射把方法调用转换成 SQL 执行。

class DaoInvocationHandler implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
        Class<?> entityClass = method.getDeclaringClass().getInterfaces()[0].getGenericInterfaces()[0];

        if (methodName.startsWith("insert")) {
            return insert(args[0], entityClass);
        } else if (methodName.startsWith("findById")) {
            return findById(args[0], entityClass);
        } else if (methodName.startsWith("update")) {
            return update(args[0], entityClass);
        } else if (methodName.startsWith("delete")) {
            return delete(args[0], entityClass);
        }
        return null;
    }

    private Object insert(Object entity, Class<?> entityClass) {
        Entity entityAnnotation = entityClass.getAnnotation(Entity.class);
        String tableName = entityAnnotation.tableName();

        StringBuilder sql = new StringBuilder();
        sql.append("INSERT INTO ").append(tableName).append(" (");

        Field[] fields = entityClass.getDeclaredFields();
        List<Object> values = new ArrayList<>();

        for (Field field : fields) {
            if (field.isAnnotationPresent(Id.class)) continue;  // 假设主键自增

            field.setAccessible(true);
            sql.append(field.getName()).append(",");
            values.add(field.get(entity));
        }

        sql.deleteCharAt(sql.length() - 1);
        sql.append(") VALUES (");
        for (int i = 0; i < values.size(); i++) {
            sql.append("?,");
        }
        sql.deleteCharAt(sql.length() - 1);
        sql.append(")");

        System.out.println("执行SQL: " + sql);
        System.out.println("参数: " + values);
        return null;
    }

    private Object findById(Object id, Class<?> entityClass) {
        Entity entityAnnotation = entityClass.getAnnotation(Entity.class);
        String tableName = entityAnnotation.tableName();

        String sql = "SELECT * FROM " + tableName + " WHERE id = ?";
        System.out.println("执行SQL: " + sql);
        System.out.println("参数: " + id);

        // 简化处理,返回一个空对象
        Object entity = entityClass.getDeclaredConstructor().newInstance();
        Field idField = null;
        for (Field field : entityClass.getDeclaredFields()) {
            if (field.isAnnotationPresent(Id.class)) {
                idField = field;
                break;
            }
        }
        if (idField != null) {
            idField.setAccessible(true);
            idField.set(entity, id);
        }
        return entity;
    }

    private Object update(Object entity, Class<?> entityClass) {
        Entity entityAnnotation = entityClass.getAnnotation(Entity.class);
        String tableName = entityAnnotation.tableName();

        StringBuilder sql = new StringBuilder();
        sql.append("UPDATE ").append(tableName).append(" SET ");

        Field[] fields = entityClass.getDeclaredFields();
        Object idValue = null;
        Field idField = null;

        for (Field field : fields) {
            field.setAccessible(true);
            if (field.isAnnotationPresent(Id.class)) {
                idField = field;
                idValue = field.get(entity);
                continue;
            }
            sql.append(field.getName()).append(" = ?,");
        }

        sql.deleteCharAt(sql.length() - 1);
        sql.append(" WHERE id = ?");

        System.out.println("执行SQL: " + sql);
        return null;
    }

    private Object delete(Object id, Class<?> entityClass) {
        Entity entityAnnotation = entityClass.getAnnotation(Entity.class);
        String tableName = entityAnnotation.tableName();

        String sql = "DELETE FROM " + tableName + " WHERE id = ?";
        System.out.println("执行SQL: " + sql);
        System.out.println("参数: " + id);
        return null;
    }
}

3.4 创建 DAO 工厂

最后写一个工厂方法,动态创建 DAO 实现:

class DaoFactory {
    @SuppressWarnings("unchecked")
    public static <T> T createDao(Class<T> daoInterface) {
        Class<?> entityClass = getEntityClass(daoInterface);

        return (T) Proxy.newProxyInstance(
            daoInterface.getClassLoader(),
            new Class<?>[] { daoInterface },
            new DaoInvocationHandler()
        );
    }

    private static Class<?> getEntityClass(Class<?> daoInterface) {
        // 简化处理:假设 DAO 接口有一个泛型参数
        Type[] types = daoInterface.getGenericInterfaces();
        for (Type type : types) {
            if (type instanceof ParameterizedType) {
                return (Class<?>) ((ParameterizedType) type).getActualTypeArguments()[0];
            }
        }
        return null;
    }
}

3.5 使用框架

现在,开发者只需要写接口,不用写实现类:

public class FrameworkDemo {
    public static void main(String[] args) {
        UserDao userDao = DaoFactory.createDao(UserDao.class);

        User user = new User();
        user.setName("王五");
        user.setAge(30);

        userDao.insert(user);
        User foundUser = userDao.findById(1L);
        userDao.update(user);
        userDao.delete(1L);
    }
}

运行这段代码,你会看到框架自动生成了 SQL 语句打印出来。这个简易框架展示了反射和动态代理如何配合工作:反射负责读取类和注解信息,把 POJO 转换成 SQL;动态代理负责拦截 DAO 方法调用,把参数传递给框架处理


四、总结关键点

这篇文章介绍了 Java 高级特性中的反射机制与动态代理。反射的核心是 Class 对象,通过它可以动态获取类的构造方法、成员变量和成员方法信息,实现"运行时决定调用哪个类"的能力。动态代理则解决了一类通用问题:通过代理对象在方法调用前后插入额外逻辑,JDK 动态代理基于接口实现,而 CGLIB 基于继承实现。在实际项目中,这两个特性经常配合使用,掌握它们对于理解框架底层和设计灵活架构都至关重要。

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文