反射

tim-qtp...大约 4 分钟Java基础

📌 反射(Reflection) 允许程序在运行时 动态获取类的信息、创建对象、调用方法、修改字段,而不是在编译时写死代码。

1. 为什么要用反射?

通常,在编写 Java 代码时,我们都是这样调用类的方法的:

java复制编辑Person p = new Person();
p.sayHello();

这里,Person 类的 sayHello() 方法是在编译时就已经确定的

但是,如果我们希望:

  1. 在运行时才决定要调用哪个类的方法(比如从配置文件读取类名和方法名)。
  2. 获取类的信息,比如查看它的属性、方法等。
  3. 创建对象,而不直接使用 new 关键字。

这时候,反射就派上用场了。


2. 反射的基本操作

2.1 获取 Class 对象

在 Java 反射中,所有的类信息都由 Class 对象表示,获取 Class 对象有三种方式:

// 方式 1:通过 Class.forName("类的全限定名")
Class<?> clazz1 = Class.forName("com.example.Person");

// 方式 2:通过 类名.class
Class<?> clazz2 = Person.class;

// 方式 3:通过 对象.getClass()
Person p = new Person();
Class<?> clazz3 = p.getClass();

无论哪种方式,clazz1clazz2clazz3 都指向 Person 类的 Class 对象。


2.2 通过反射创建对象

使用 newInstance() 创建对象:

Class<?> clazz = Class.forName("com.example.Person");
Object obj = clazz.getDeclaredConstructor().newInstance(); // 等价于 new Person()

2.3 通过反射调用方法

java复制编辑import java.lang.reflect.Method;

class Person {
    public void sayHello() {
        System.out.println("Hello, Java Reflection!");
    }
}

public class ReflectionDemo {
    public static void main(String[] args) throws Exception {
        // 获取 Class 对象
        Class<?> clazz = Class.forName("Person");

        // 创建对象
        Object obj = clazz.getDeclaredConstructor().newInstance();

        // 获取方法
        Method method = clazz.getMethod("sayHello");

        // 调用方法
        method.invoke(obj); // 输出:Hello, Java Reflection!
    }
}

这里:

  1. clazz.getMethod("sayHello") 获取 sayHello() 方法。
  2. method.invoke(obj)obj 上执行该方法。

2.4 通过反射访问字段(属性)

java复制编辑import java.lang.reflect.Field;

class Person {
    private String name = "John";
}

public class ReflectionDemo {
    public static void main(String[] args) throws Exception {
        Class<?> clazz = Class.forName("Person");
        Object obj = clazz.getDeclaredConstructor().newInstance();

        // 获取 private 字段
        Field field = clazz.getDeclaredField("name");
        field.setAccessible(true); // 允许访问私有字段
        System.out.println("修改前: " + field.get(obj)); // John

        // 修改字段的值
        field.set(obj, "Alice");
        System.out.println("修改后: " + field.get(obj)); // Alice
    }
}

3. 反射的应用场景

3.1 框架的底层实现

Java 的许多框架(Spring、Hibernate 等)都大量使用了反射,比如:

  • Spring框架

    Spring IoC(控制反转):反射创建 Bean 并自动注入依赖(@Autowired)。

    Spring AOP(面向切面编程):反射拦截方法调用(@Transactional、@Log)。

    Spring MVC:反射解析 @RequestMapping 绑定 URL 和方法。

  • JUnit 通过反射执行测试方法。

📌 例子:模拟 Spring 的 @Autowired 自动注入

在这个示例中,我们:

  1. 解析类中的 @Autowired 注解。
  2. 通过反射自动注入依赖,而不需要 new 关键字。
  3. 模拟 Spring 依赖注入的机制。

1️⃣ 创建 @Autowired 注解

Spring 中的 @Autowired 主要用于自动注入依赖,我们自己定义一个类似的注解:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

// 作用于字段
@Target(ElementType.FIELD)
// 运行时保留
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
}

2️⃣ 定义业务类

模拟一个后端开发中常见的Service 和 DAO 层

// 数据访问层
public class UserRepository {
    public String getUser() {
        return "Hello, Java Backend!";
    }
}

// 业务逻辑层
public class UserService {
    @Autowired // 依赖注入
    private UserRepository userRepository;

    public void printUser() {
        System.out.println(userRepository.getUser());
    }
}
  • UserRepository 负责访问数据库(这里简化)。
  • UserService 依赖 UserRepository,但没有手动 new,而是用 @Autowired 标记。

3️⃣ 通过反射实现自动依赖注入

import java.lang.reflect.Field;

public class IoCContainer {
    public static void main(String[] args) throws Exception {
        // 1. 创建 UserService 实例
        UserService userService = new UserService();

        // 2. 处理 @Autowired 注解,实现自动注入
        injectDependencies(userService);

        // 3. 调用方法,验证依赖是否被注入
        userService.printUser(); // 输出: Hello, Java Backend!
    }

    // 反射扫描 @Autowired 字段,并进行依赖注入
    private static void injectDependencies(Object obj) throws Exception {
        Class<?> clazz = obj.getClass(); // 获取类对象,这里获取到的是UserService

        for (Field field : clazz.getDeclaredFields()) { // 遍历所有字段
            if (field.isAnnotationPresent(Autowired.class)) { // 如果字段有 @Autowired 注解
                field.setAccessible(true); // 允许访问私有字段
                Object dependency = field.getType().getDeclaredConstructor().newInstance(); // 创建依赖对象,也就是UserRepository
                field.set(obj, dependency); // 注入到当前对象中
            }
        }
    }
}

4️⃣ 运行效果

Hello, Java Backend!

UserService 自动注入了 UserRepository,无需手动 new!

🔎 解析:反射做了什么?

  1. 获取类的所有字段clazz.getDeclaredFields()
  2. 判断字段是否有 @Autowired 注解field.isAnnotationPresent(Autowired.class)
  3. 创建依赖对象(反射调用构造方法):field.getType().getDeclaredConstructor().newInstance()
  4. 设置字段的值(绕过 private 限制):field.set(obj, dependency)

这就是 Spring IoC 依赖注入 的核心原理之一!

3.2 读取配置文件动态创建对象

假设我们有个 config.properties

className=com.example.Person
methodName=sayHello

然后通过反射动态加载:

import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Properties;

public class ReflectionConfig {
    public static void main(String[] args) throws Exception {
        // 读取配置文件
        Properties props = new Properties();
        InputStream is = ReflectionConfig.class.getClassLoader().getResourceAsStream("config.properties");
        props.load(is);

        // 获取类名和方法名
        String className = props.getProperty("className");
        String methodName = props.getProperty("methodName");

        // 反射创建对象并调用方法
        Class<?> clazz = Class.forName(className);
        Object obj = clazz.getDeclaredConstructor().newInstance();
        Method method = clazz.getMethod(methodName);
        method.invoke(obj);
    }
}

这样,我们不需要修改代码,只要改 config.properties 就可以调用不同的类和方法。