什么是Java反射机制?
一、什么是反射?
反射(Reflection) 是 Java 提供的一种 在运行时动态获取类的信息,并能直接操作类的属性、方法和构造器 的机制。
换句话说:
普通调用:在编译期就已经确定要调用哪个类、哪个方法。
反射调用:程序可以在运行时,通过字符串(类名、方法名)去加载类,并调用其中的方法或访问属性。
Java 中反射的核心类位于 java.lang.reflect 包下,主要包括:
Class: 表示类的字节码对象。
Method: 表示类的方法。
Field: 表示类的成员变量。
Constructor: 表示类的构造方法。
二、反射能做什么?
1.获取类的信息
类的名称、修饰符(public/private 等)、父类、实现的接口。
类中的字段(Field)、方法(Method)、构造器(Constructor)。
2.操作对象
动态创建对象(替代new)。
动态调用对象的方法(即使方法名在编译时未知)。
动态读取和修改对象的属性(即使是 private)。
3.操作类
动态加载一个类(通过Class.forName("类的全限定名") )。
判断一个类是否有特定的注解。
实现动态代理(Spring、MyBatis 等框架的底层原理)。
三、一个实际示例
假设我们有一个消息服务接口 MessageService,以及两个实现类 EmaliService 和 SmsService 。
接口定义
public interface MessageService {void send(String message);void send(String message, String to);
}
邮件服务实现
public class EmailService implements MessageService{private String fromAddress;public EmailService() {this.fromAddress = "noreply@example.com";}@Overridepublic void send(String message) {System.out.println("发送邮件到默认地址: " + fromAddress + " 内容是: " + message);}@Overridepublic void send(String message, String to) {System.out.println("发送邮件给: " + to + " 内容是: " + message);}
}
短信服务实现
public class SmsService implements MessageService{private String formPhone;public SmsService() {this.formPhone = "12345678901";}@Overridepublic void send(String message) {System.out.println("发送短信: " + message);}@Overridepublic void send(String message, String to) {System.out.println("发送短信: " + to + " 给 " + message);}
}
四、普通调用 vs 反射调用
传统写法(硬编码)
public class MessageTools {public static void main(String[] args) {String config = "EmailService"; // 模拟配置MessageService service = null;if ("EmailService".equals(config)) {service = new EmailService();} else if ("SmsService".equals(config)) {service = new SmsService();}if (service != null) {service.send("Hello World!!!");}}
}
运行结果:
问题:如果新增一个实现类(比如 WeChatService ),必须修改 if / else 并重新编译,扩展性差。
使用反射(动态加载)
import java.lang.reflect.Method;public class MessageTools {public static void main(String[] args) throws Exception {// 模拟配置:通过类全限定名来决定加载哪个类String className = "soft.study.App.EmailService";// 1. 加载类Class<?> clazz = Class.forName(className);// 2. 创建对象Object service = clazz.getDeclaredConstructor().newInstance();// 3. 获取方法Method method = clazz.getMethod("send", String.class);Method method1 = clazz.getMethod("send", String.class, String.class);// 4. 动态调用方法method.invoke(service, "你好!");method1.invoke(service, "你好!", "张三");}
}
运行结果:
优势:如果要替换成 SmsService
,只需要修改配置,不需要修改源码。
注意:这里的className 写死了"soft.study.App.EmailService" ,只是为了演示方便。
在真实项目中,我们通常通过以下优化方式避免硬编码:
五、反射调用的优化方式
1.使用配置文件
在 config.properties 写入:
message.service.class=soft.study.App.EmailService
程序启动时读取配置:
Properties props = new Properties();
props.load(new FileInputStream("config.properties"));
String className = props.getProperty("message.service.class");
这样切换服务时,只需修改配置文件,无需改源码。
2.使用注解 + 扫描(框架常用方式框架(如 Spring)会通过 反射扫描包路径,自动注册带有 @Service
的类,不需要我们手动指定类名。
@Service("emailService")
public class EmailService implements MessageService { ... }
3.结合工厂模式
工厂模式(Factory Pattern)本身就是为了解决 对象创建与业务逻辑解耦 的问题。
如果把反射和工厂模式结合,就能在保留反射灵活性的同时,让调用方完全不用关心反射的细节。
public class ServiceFactory {public static MessageService create(String type) throws Exception {String className = switch (type) {case "email" -> "soft.study.App.EmailService";case "sms" -> "soft.study.App.SmsService";default -> throw new IllegalArgumentException("未知类型");};return (MessageService) Class.forName(className).getDeclaredConstructor().newInstance();}
}
业务代码只需调用:
MessageService service = ServiceFactory.create("email");
service.send("测试消息");
六、反射在框架中的应用
1.Spring IoC 容器
通过反射实例化 Bean,并完成依赖注入。
@Autowired 注入的原理就是反射 + 注解。
2.MyBatis
通过反射读取 Mapper 接口信息,动态生成代理类。
SQL 执行时反射调用参数的 getter 方法。
3.JPA / Hibernate
通过反射获取实体类的字段、注解信息,映射到数据库表。
4.JUnit / 测试框架
自动扫描并执行带有 @Test 注解的方法。
七、反射的优缺点
优点
灵活性高:能在运行时动态加载类、调用方法。
解耦:结合配置文件或注解,扩展功能无需修改源码。
功能强大:许多框架(Spring、MyBatis、Hibernate)核心都依赖反射。
缺点
性能损耗:反射调用方法比直接调用慢。
安全风险:可访问 private 属性,破坏封装性。
可读性差:代码不直观,调试和维护困难。
八、总结
反射机制是 Java 提供的一项底层能力,它允许程序在运行时动态获取类的信息并调用方法。虽然直接在业务代码中频繁使用反射并不推荐,因为会带来一定的性能损耗和可读性问题,但在框架开发中反射却是实现核心功能的基础。例如,Spring 通过反射完成 IoC 容器中的对象实例化和依赖注入,MyBatis 借助反射动态生成 Mapper 的代理对象,Hibernate 依赖反射完成实体类与数据库表的映射。
在实际开发中,如果只是简单的业务逻辑,直接通过 new 创建对象即可,既高效又清晰;而在需要高扩展性、动态加载或解耦的场景下,反射的优势才会凸显出来。