反射
...大约 4 分钟Java基础
📌 反射(Reflection) 允许程序在运行时 动态获取类的信息、创建对象、调用方法、修改字段,而不是在编译时写死代码。
1. 为什么要用反射?
通常,在编写 Java 代码时,我们都是这样调用类的方法的:
java复制编辑Person p = new Person();
p.sayHello();
这里,Person
类的 sayHello()
方法是在编译时就已经确定的。
但是,如果我们希望:
- 在运行时才决定要调用哪个类的方法(比如从配置文件读取类名和方法名)。
- 获取类的信息,比如查看它的属性、方法等。
- 创建对象,而不直接使用
new
关键字。
这时候,反射就派上用场了。
2. 反射的基本操作
Class
对象
2.1 获取 在 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();
无论哪种方式,clazz1
、clazz2
和 clazz3
都指向 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!
}
}
这里:
clazz.getMethod("sayHello")
获取sayHello()
方法。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 自动注入
在这个示例中,我们:
- 解析类中的
@Autowired
注解。 - 通过反射自动注入依赖,而不需要
new
关键字。 - 模拟 Spring 依赖注入的机制。
@Autowired
注解
1️⃣ 创建 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!
🔎 解析:反射做了什么?
- 获取类的所有字段:
clazz.getDeclaredFields()
- 判断字段是否有
@Autowired
注解:field.isAnnotationPresent(Autowired.class)
- 创建依赖对象(反射调用构造方法):
field.getType().getDeclaredConstructor().newInstance()
- 设置字段的值(绕过 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
就可以调用不同的类和方法。