Java 反射详解

Java 反射(Reflection)是 Java 语言的核心特性之一,它允许程序在运行时获取类的信息、操作类的属性和方法,甚至访问私有成员。这种动态性使得反射成为框架开发(如 Spring、MyBatis)、注解处理、动态代理等场景的基石。本文将从反射的基本概念、核心 API、使用场景到优缺点,全面解析 Java 反射机制。

一、什么是反射?

反射是指程序在运行状态下,对于任意一个类,都能知道它的所有属性和方法;对于任意一个对象,都能调用它的任意方法和属性。这种 “看透” 类信息并动态操作的能力,打破了 Java 编译期的类型检查限制,赋予程序更高的灵活性。
 
简单来说,反射让 Java 从 “静态语言” 具备了部分 “动态语言” 的特性 —— 不需要在编译期知道类的具体信息,就能在运行时动态操作。

二、反射的核心 API

Java 反射的核心功能由 java.lang.reflect 包提供,配合 java.lang.Class 类实现。关键类包括:
 
类 / 接口作用
Class 代表类的字节码对象,是反射的入口
Constructor 代表类的构造方法,用于创建对象
Method 代表类的成员方法,用于调用方法
Field 代表类的成员变量,用于获取 / 修改属性值
Modifier 解析类 / 方法 / 字段的修饰符(如 public、private)

三、反射的基本使用步骤

反射的核心操作围绕 Class 对象展开,步骤可概括为:获取 Class 对象 → 操作类的成员(构造方法 / 方法 / 字段)

1. 获取 Class 对象(反射的入口)

Class 类是反射的基础,每个类在 JVM 中都有且仅有一个 Class 对象,它存储了该类的所有信息(类名、父类、方法、字段等)。获取 Class 对象有 3 种方式:

方式 1:Class.forName("全类名")

通过类的全限定名(包名 + 类名)获取,最常用(尤其框架配置)。
 
// 获取 java.lang.String 的 Class 对象
Class<?> cls = Class.forName("java.lang.String");
 

方式 2:类名.class

通过类的字面量获取,编译期即可确定类型,更高效。
 
// 获取 String 的 Class 对象
Class<String> cls = String.class;
 

方式 3:对象.getClass()

通过实例对象获取,适用于已有对象的场景。
 
String str = "hello";
Class<? extends String> cls = str.getClass();
 

2. 通过 Class 对象操作类成员

(1)获取构造方法并创建对象

Constructor 类用于操作构造方法,支持获取所有构造方法(包括私有)并创建实例。
 
import java.lang.reflect.Constructor;

public class ReflectionDemo {
    public static void main(String[] args) throws Exception {
        // 1. 获取 User 类的 Class 对象
        Class<User> userClass = User.class;

        // 2. 获取无参构造方法(public)
        Constructor<User> constructor1 = userClass.getConstructor();
        User user1 = constructor1.newInstance(); // 调用无参构造创建对象

        // 3. 获取有参构造方法(public User(String name, int age))
        Constructor<User> constructor2 = userClass.getConstructor(String.class, int.class);
        User user2 = constructor2.newInstance("张三", 20); // 传入参数创建对象

        // 4. 获取私有构造方法(private User(int id))
        Constructor<User> constructor3 = userClass.getDeclaredConstructor(int.class);
        constructor3.setAccessible(true); // 暴力访问私有构造(关键!)
        User user3 = constructor3.newInstance(1001); // 创建对象
    }
}

class User {
    private int id;
    private String name;
    private int age;

    // 无参构造
    public User() {}

    // 有参构造(public)
    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 私有构造
    private User(int id) {
        this.id = id;
    }
}
 
 
关键方法
 
  • getConstructor(Class<?>... parameterTypes):获取 public 构造方法(指定参数类型)
  • getDeclaredConstructor(Class<?>... parameterTypes):获取所有构造方法(包括 private)
  • setAccessible(true):关闭访问检查,允许访问私有成员(核心!)

(2)获取成员方法并调用

Method 类用于操作成员方法,支持调用任意方法(包括私有)并传递参数。
 
import java.lang.reflect.Method;

public class ReflectionDemo {
    public static void main(String[] args) throws Exception {
        User user = new User("李四", 25);
        Class<? extends User> userClass = user.getClass();

        // 1. 调用 public 方法:public String getName()
        Method getNameMethod = userClass.getMethod("getName");
        String name = (String) getNameMethod.invoke(user); // 调用方法,传入实例对象
        System.out.println("姓名:" + name); // 输出:李四

        // 2. 调用带参 public 方法:public void setAge(int age)
        Method setAgeMethod = userClass.getMethod("setAge", int.class);
        setAgeMethod.invoke(user, 30); // 传入参数 30
        System.out.println("修改后年龄:" + user.getAge()); // 输出:30

        // 3. 调用私有方法:private void printInfo()
        Method printInfoMethod = userClass.getDeclaredMethod("printInfo");
        printInfoMethod.setAccessible(true); // 允许访问私有方法
        printInfoMethod.invoke(user); // 调用私有方法,输出:User{name='李四', age=30}
    }
}

class User {
    private String name;
    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() { return name; }
    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }

    private void printInfo() {
        System.out.println("User{name='" + name + "', age=" + age + "}");
    }
}
 
 
关键方法
 
  • getMethod(String name, Class<?>... parameterTypes):获取 public 方法(指定方法名和参数类型)
  • getDeclaredMethod(String name, Class<?>... parameterTypes):获取所有方法(包括 private)
  • invoke(Object obj, Object... args):调用方法(obj 为实例对象,args 为方法参数)

(3)获取成员变量并修改值

Field 类用于操作成员变量,支持获取和修改任意字段(包括私有)的值。
 
import java.lang.reflect.Field;

public class ReflectionDemo {
    public static void main(String[] args) throws Exception {
        User user = new User("王五", 30);
        Class<? extends User> userClass = user.getClass();

        // 1. 获取 public 字段(实际很少用,通常字段都是 private)
        // 此处假设 User 有 public String address 字段
        // Field addressField = userClass.getField("address");

        // 2. 获取 private 字段:name
        Field nameField = userClass.getDeclaredField("name");
        nameField.setAccessible(true); // 允许访问私有字段
        String originalName = (String) nameField.get(user); // 获取字段值
        System.out.println("原始姓名:" + originalName); // 输出:王五

        nameField.set(user, "赵六"); // 修改字段值
        System.out.println("修改后姓名:" + user.getName()); // 输出:赵六

        // 3. 获取 private 字段:age 并修改
        Field ageField = userClass.getDeclaredField("age");
        ageField.setAccessible(true);
        ageField.set(user, 35);
        System.out.println("修改后年龄:" + user.getAge()); // 输出:35
    }
}

class User {
    private String name;
    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() { return name; }
    public int getAge() { return age; }
}
 
 
关键方法
 
  • getField(String name):获取 public 字段
  • getDeclaredField(String name):获取所有字段(包括 private)
  • get(Object obj):获取字段值(obj 为实例对象)
  • set(Object obj, Object value):修改字段值

四、反射的高级应用场景

反射的动态性使其在以下场景中不可或缺:

1. 框架开发(核心场景)

几乎所有主流 Java 框架(Spring、MyBatis、Hibernate)都依赖反射实现核心功能:
 
  • Spring IOC:通过配置文件(如 XML)中的类名,反射创建对象并注入依赖(Class.forName(类名).newInstance())。
  • MyBatis:通过 Mapper 接口的全类名,反射生成代理对象,动态执行 SQL。
  • JUnit:通过反射识别 @Test 注解的方法并执行测试。

2. 动态代理

反射是动态代理的基础(如 JDK 动态代理)。代理模式中,代理类在运行时通过反射调用目标类的方法,实现增强逻辑(如日志、事务)。
 
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// 接口
interface UserService {
    void save();
}

// 实现类
class UserServiceImpl implements UserService {
    @Override
    public void save() {
        System.out.println("保存用户数据");
    }
}

// 动态代理处理器
class LogHandler implements InvocationHandler {
    private Object target; // 目标对象

    public LogHandler(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;
    }
}

public class ProxyDemo {
    public static void main(String[] args) {
        // 创建目标对象
        UserService userService = new UserServiceImpl();
        // 创建代理对象(通过反射生成)
        UserService proxy = (UserService) Proxy.newProxyInstance(
            UserService.class.getClassLoader(),
            new Class[]{UserService.class},
            new LogHandler(userService)
        );
        proxy.save(); // 调用代理方法,实际通过反射执行目标方法
    }
}
 
 
输出:
日志:开始调用 save 方法
保存用户数据
日志:结束调用 save 方法
 

3. 注解处理

注解(Annotation)本身不具备功能,需通过反射解析注解并执行逻辑。例如自定义一个 @Log 注解,通过反射识别并打印日志:
 
import java.lang.annotation.*;
import java.lang.reflect.Method;

// 自定义注解
@Target(ElementType.METHOD) // 作用于方法
@Retention(RetentionPolicy.RUNTIME) // 运行时保留(反射可获取)
@interface Log {
    String value() default "执行方法";
}

class UserService {
    @Log("保存用户") // 使用注解
    public void save() {
        System.out.println("保存用户数据");
    }
}

public class AnnotationDemo {
    public static void main(String[] args) throws Exception {
        UserService service = new UserService();
        Class<? extends UserService> serviceClass = service.getClass();
        Method saveMethod = serviceClass.getMethod("save");

        // 反射检查方法是否有 @Log 注解
        if (saveMethod.isAnnotationPresent(Log.class)) {
            Log logAnnotation = saveMethod.getAnnotation(Log.class);
            System.out.println("日志:" + logAnnotation.value()); // 输出:日志:保存用户
        }

        saveMethod.invoke(service); // 调用方法
    }
}
 

五、反射的优缺点

优点

  1. 动态性:打破编译期限制,运行时动态操作类,适合框架开发(无需硬编码类信息)。
  2. 灵活性:可适配不同类的通用逻辑(如 ORM 框架的对象映射)。
  3. 扩展性:通过反射加载外部类(如插件),增强程序扩展性。

缺点

  1. 性能损耗:反射操作绕过编译期优化,比直接调用慢 10-100 倍(频繁调用需缓存 Class/Method 对象优化)。
  2. 破坏封装setAccessible(true) 可访问私有成员,违反类的封装设计。
  3. 可读性差:反射代码较繁琐,且逻辑隐藏在运行时,调试和维护难度高。
  4. 安全风险:若被恶意使用,可能通过反射调用私有方法或修改敏感字段(需配合安全管理器限制)。

六、反射的性能优化

反射的性能问题主要源于频繁获取 ClassMethod 等对象。优化方式:
 
  1. 缓存反射对象:将 ClassMethodField 缓存到 Map 中,避免重复获取。
     
    // 缓存 Method 对象示例
    private static final Map<String, Method> methodCache = new HashMap<>();
    
    public static Method getCachedMethod(Class<?> cls, String methodName, Class<?>... params) throws NoSuchMethodException {
        String key = cls.getName() + "#" + methodName + Arrays.toString(params);
        if (!methodCache.containsKey(key)) {
            methodCache.put(key, cls.getDeclaredMethod(methodName, params));
        }
        return methodCache.get(key);
    }
    
     
     
  2. 减少 setAccessible(true) 调用:该方法会触发安全检查,缓存已设置访问权限的对象。
  3. 优先使用接口或父类:反射调用接口方法比具体类方法更快(JVM 优化)。

七、总结

反射是 Java 动态编程的核心,它赋予程序在运行时操作类的能力,是框架、动态代理、注解等技术的基础。但反射也存在性能损耗和封装性问题,需在灵活性和安全性之间权衡。
 
最佳实践
 
  • 框架开发中合理使用反射(如 Spring IOC),提升通用性;
  • 业务代码中尽量避免反射,优先使用直接调用;
  • 必须使用反射时,通过缓存和权限控制优化性能与安全。

posted on 2025-09-29 14:34  coding博客  阅读(203)  评论(0)    收藏  举报