JavaWeb 30 天入门:第十一天 ——Java 反射机制详解
今天我们将深入学习Java 反射机制(Reflection)—— 这是 Java 语言的动态特性之一,允许程序在运行时获取类的信息并操作类的成员(属性、方法、构造器等)。在 JavaWeb 开发中,反射是框架设计的灵魂(如 Spring 的 IOC 容器、MyBatis 的 ORM 映射),掌握反射机制能帮助我们理解框架底层原理,编写更灵活的代码。
什么是反射机制?
反射机制是指程序在运行时能够获取自身的信息:
- 可以知道一个类的所有属性和方法
- 可以调用一个对象的任意方法
- 可以修改一个对象的任意属性
- 可以动态创建类的实例
简单来说,反射让 Java 程序在运行时拥有了 “自审” 和 “操作自身” 的能力,突破了编译期的类型限制,实现了动态性。
反射的应用场景:
- 框架开发:Spring、MyBatis 等框架大量使用反射实现配置化操作
- 动态代理:AOP(面向切面编程)的实现基础
- 注解处理:解析自定义注解并执行相应逻辑
- 序列化与反序列化:对象与字节流的转换
- 数据库 ORM 映射:对象与表结构的映射
反射的核心类
Java 反射机制主要通过java.lang.reflect
包中的类实现,核心类包括:
类名 | 说明 |
---|---|
Class | 表示类的字节码对象,是反射的入口 |
Constructor | 表示类的构造方法 |
Method | 表示类的方法 |
Field | 表示类的属性 |
Modifier | 提供方法解析类和成员的修饰符(如 public、private) |
这些类共同协作,让我们能够在运行时操作类的各种成员。
反射的入口:Class 对象
Class
类是反射的核心,每个类在 JVM 中都有一个对应的Class
对象,它包含了该类的所有信息。获取Class
对象有三种方式:
方式 1:通过Class.forName()
方法(最常用)
try {// 参数:类的全限定名(包名+类名)Class<?> clazz = Class.forName("com.example.User");System.out.println("获取到的Class对象:" + clazz);
} catch (ClassNotFoundException e) {e.printStackTrace();
}
这种方式最灵活,不需要在编译期知道具体类名,可以通过字符串动态加载类(常用于配置文件中指定类名)。
方式 2:通过类名.class
属性
// 直接通过类名获取
Class<User> clazz = User.class;
System.out.println("获取到的Class对象:" + clazz);
这种方式需要在编译期知道具体类,编译时会检查类是否存在。
方式 3:通过对象.getClass()
方法
User user = new User();
// 通过对象实例获取
Class<? extends User> clazz = user.getClass();
System.out.println("获取到的Class对象:" + clazz);
这种方式需要先有对象实例,适合已有对象的场景。
注意:一个类在 JVM 中只有一个Class
对象,无论通过哪种方式获取,都是同一个对象:
Class<?> clazz1 = Class.forName("com.example.User");
Class<User> clazz2 = User.class;
User user = new User();
Class<? extends User> clazz3 = user.getClass();System.out.println(clazz1 == clazz2); // true
System.out.println(clazz1 == clazz3); // true
通过反射操作类的构造方法
Constructor
类用于表示和操作类的构造方法,通过Class
对象可以获取类的所有构造方法,并创建对象实例。
示例:User 类
首先定义一个用于测试的User
类:
package com.example;public class User {private String username;private int age;private String email;// 无参构造public User() {}// 有参构造1public User(String username, int age) {this.username = username;this.age = age;}// 有参构造2(私有)private User(String username, int age, String email) {this.username = username;this.age = age;this.email = email;}// getter和setter方法public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public String getEmail() {return email;}public void setEmail(String email) {this.email = email;}@Overridepublic String toString() {return "User{" +"username='" + username + '\'' +", age=" + age +", email='" + email + '\'' +'}';}
}
获取构造方法并创建对象
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;public class ConstructorReflectionDemo {public static void main(String[] args) {try {// 获取User类的Class对象Class<?> clazz = Class.forName("com.example.User");// 1. 获取无参构造方法并创建对象Constructor<?> constructor1 = clazz.getConstructor();User user1 = (User) constructor1.newInstance(); // 调用无参构造user1.setUsername("张三");user1.setAge(20);System.out.println("无参构造创建的对象:" + user1);// 2. 获取有参构造方法并创建对象// 参数:构造方法的参数类型.classConstructor<?> constructor2 = clazz.getConstructor(String.class, int.class);User user2 = (User) constructor2.newInstance("李四", 25); // 传递参数System.out.println("有参构造创建的对象:" + user2);// 3. 获取私有构造方法并创建对象// getDeclaredConstructor()可以获取私有构造Constructor<?> constructor3 = clazz.getDeclaredConstructor(String.class, int.class, String.class);constructor3.setAccessible(true); // 暴力访问(忽略访问权限修饰符)User user3 = (User) constructor3.newInstance("王五", 30, "wangwu@example.com");System.out.println("私有构造创建的对象:" + user3);} catch (ClassNotFoundException | NoSuchMethodException | InstantiationException |IllegalAccessException | InvocationTargetException e) {e.printStackTrace();}}
}
关键方法说明:
getConstructor(Class<?>... parameterTypes)
:获取 public 修饰的构造方法getDeclaredConstructor(Class<?>... parameterTypes)
:获取所有构造方法(包括 private)newInstance(Object... initargs)
:调用构造方法创建对象setAccessible(true)
:关闭访问权限检查,用于访问私有成员
通过反射操作类的方法
Method
类用于表示和操作类的方法,通过反射可以调用类的任意方法(包括私有方法)。
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;public class MethodReflectionDemo {public static void main(String[] args) {try {// 获取Class对象和实例Class<?> clazz = Class.forName("com.example.User");User user = (User) clazz.getConstructor().newInstance();// 1. 获取并调用public方法(setUsername)// 参数:方法名,参数类型.classMethod setUsernameMethod = clazz.getMethod("setUsername", String.class);// 调用方法:invoke(对象, 参数值)setUsernameMethod.invoke(user, "赵六");// 获取getUsername方法并调用Method getUsernameMethod = clazz.getMethod("getUsername");String username = (String) getUsernameMethod.invoke(user);System.out.println("用户名:" + username);// 2. 调用有多个参数的方法Method setAgeMethod = clazz.getMethod("setAge", int.class);setAgeMethod.invoke(user, 28);// 3. 调用私有方法(假设User类有一个私有方法)// 先在User类中添加一个私有方法:// private void showInfo() {// System.out.println("用户名:" + username + ", 年龄:" + age);// }Method showInfoMethod = clazz.getDeclaredMethod("showInfo");showInfoMethod.setAccessible(true); // 暴力访问私有方法showInfoMethod.invoke(user); // 调用私有方法} catch (ClassNotFoundException | NoSuchMethodException | InstantiationException |IllegalAccessException | InvocationTargetException e) {e.printStackTrace();}}
}
关键方法说明:
getMethod(String name, Class<?>... parameterTypes)
:获取 public 修饰的方法getDeclaredMethod(String name, Class<?>... parameterTypes)
:获取所有方法(包括 private)invoke(Object obj, Object... args)
:调用方法,第一个参数是对象实例(静态方法可以为 null)setAccessible(true)
:同样适用于方法,用于访问私有方法
通过反射操作类的属性
Field
类用于表示和操作类的属性,通过反射可以获取和修改类的任意属性(包括私有属性)。
import java.lang.reflect.Field;public class FieldReflectionDemo {public static void main(String[] args) {try {// 创建User对象User user = new User("孙七", 35);Class<?> clazz = user.getClass();// 1. 获取并修改public属性(如果有的话)// 假设User有public属性:public String address;// Field addressField = clazz.getField("address");// addressField.set(user, "北京市"); // 设置属性值// String address = (String) addressField.get(user); // 获取属性值// 2. 获取并修改private属性(username)Field usernameField = clazz.getDeclaredField("username");usernameField.setAccessible(true); // 暴力访问私有属性// 获取属性值String username = (String) usernameField.get(user);System.out.println("修改前的用户名:" + username);// 修改属性值usernameField.set(user, "孙七_修改后");System.out.println("修改后的用户名:" + user.getUsername());// 3. 获取并修改private属性(age)Field ageField = clazz.getDeclaredField("age");ageField.setAccessible(true);ageField.set(user, 36); // 修改年龄System.out.println("修改后的年龄:" + user.getAge());// 4. 获取静态属性(如果有的话)// 假设User有静态属性:public static String version;// Field versionField = clazz.getField("version");// versionField.set(null, "1.0.0"); // 静态属性,第一个参数为null// String version = (String) versionField.get(null);} catch (NoSuchFieldException | IllegalAccessException e) {e.printStackTrace();}}
}
关键方法说明:
getField(String name)
:获取 public 修饰的属性getDeclaredField(String name)
:获取所有属性(包括 private)get(Object obj)
:获取属性值set(Object obj, Object value)
:设置属性值(静态属性第一个参数为 null)setAccessible(true)
:用于访问私有属性
反射与注解结合
注解(Annotation)是 Java 5 + 引入的特性,与反射结合可以实现强大的功能(如 Spring 的@Autowired
、MyBatis 的@Select
)。
示例:自定义注解与反射解析
import java.lang.annotation.*;
import java.lang.reflect.Field;// 1. 定义自定义注解
@Target(ElementType.FIELD) // 注解作用在属性上
@Retention(RetentionPolicy.RUNTIME) // 注解保留到运行时(反射可见)
public @interface Column {String name(); // 数据库列名boolean nullable() default true; // 是否允许为null
}// 2. 使用注解的实体类
class User {@Column(name = "user_name", nullable = false)private String username;@Column(name = "user_age")private int age;private String email; // 没有注解// 构造方法、getter、setter省略public User(String username, int age) {this.username = username;this.age = age;}
}// 3. 通过反射解析注解
public class AnnotationReflectionDemo {public static void main(String[] args) {try {Class<?> clazz = User.class;// 获取所有属性Field[] fields = clazz.getDeclaredFields();System.out.println("解析User类的注解信息:");for (Field field : fields) {// 判断属性是否有@Column注解if (field.isAnnotationPresent(Column.class)) {// 获取注解实例Column column = field.getAnnotation(Column.class);System.out.println("属性名:" + field.getName());System.out.println("数据库列名:" + column.name());System.out.println("是否允许为null:" + column.nullable());System.out.println("---------------------");} else {System.out.println("属性名:" + field.getName() + "(无@Column注解)");System.out.println("---------------------");}}} catch (Exception e) {e.printStackTrace();}}
}
运行结果:
解析User类的注解信息:
属性名:username
数据库列名:user_name
是否允许为null:false
---------------------
属性名:age
数据库列名:user_age
是否允许为null:true
---------------------
属性名:email(无@Column注解)
---------------------
这种方式是 ORM 框架(如 MyBatis)实现对象与数据库表映射的核心原理:通过注解标记属性与列的映射关系,再通过反射解析注解生成 SQL 语句。
反射的优缺点
优点
- 灵活性高:突破编译期限制,能够动态操作类和对象
- 解耦:通过配置文件(如 XML)指定类名,无需硬编码,便于扩展
- 框架基础:是主流框架(Spring、MyBatis 等)的核心技术
- 可扩展性强:可以在不修改原有代码的情况下扩展功能
缺点
- 性能开销:反射操作需要解析字节码,性能比直接调用低(约慢 10-100 倍)
- 安全性问题:可以访问私有成员,破坏封装性
- 代码可读性差:反射代码较复杂,不如直接调用直观
- 编译期检查缺失:错误只能在运行时发现
优化反射性能的方法:
- 缓存
Class
、Method
、Field
等对象,避免频繁获取 - 尽量使用
setAccessible(true)
关闭访问检查(可提升性能) - 非必要场景避免使用反射
反射在 JavaWeb 中的应用
反射是 JavaWeb 开发的核心技术之一,以下是几个典型应用场景:
1. Servlet 容器初始化
Tomcat 等 Servlet 容器通过反射初始化 Servlet:
// Tomcat内部工作原理简化
public class ServletContainer {public void initServlet(String servletClassName) {try {// 1. 通过类名加载Servlet类Class<?> servletClass = Class.forName(servletClassName);// 2. 实例化ServletObject servlet = servletClass.getConstructor().newInstance();// 3. 调用init方法Method initMethod = servletClass.getMethod("init", ServletConfig.class);initMethod.invoke(servlet, new MyServletConfig());System.out.println("Servlet初始化完成:" + servletClassName);} catch (Exception e) {e.printStackTrace();}}
}
2. Spring IOC 容器
Spring 的 IOC(控制反转)容器通过反射创建和管理 Bean:
<!-- Spring配置文件 -->
<bean id="userService" class="com.example.UserService"><property name="userDao" ref="userDao"/>
</bean>
Spring 容器解析配置文件,通过反射创建UserService
实例,并调用setUserDao
方法注入依赖。
3. 自定义 MVC 框架
简单 MVC 框架通过反射处理请求:
// 简化的控制器
@Controller
public class UserController {@RequestMapping("/user/list")public String listUsers() {// 处理用户列表请求return "userList";}
}// 框架通过反射解析@RequestMapping注解
public class DispatcherServlet {public void doGet(HttpServletRequest request, HttpServletResponse response) {String path = request.getRequestURI(); // 获取请求路径// 扫描所有@Controller类for (Class<?> clazz : controllerClasses) {// 解析@RequestMapping注解,找到匹配的方法for (Method method : clazz.getDeclaredMethods()) {if (method.isAnnotationPresent(RequestMapping.class)) {RequestMapping mapping = method.getAnnotation(RequestMapping.class);if (mapping.value().equals(path)) {// 反射调用方法处理请求Object controller = clazz.getConstructor().newInstance();String view = (String) method.invoke(controller);// 转发到视图request.getRequestDispatcher(view + ".jsp").forward(request, response);return;}}}}}
}
总结与实践
知识点回顾
- 反射机制:程序运行时获取类信息并操作类成员的能力
- 核心类:
Class
(入口)、Constructor
(构造方法)、Method
(方法)、Field
(属性) - 获取 Class 对象的方式:
Class.forName()
、类名.class
、对象.getClass()
- 反射操作:
- 创建对象:通过
Constructor.newInstance()
- 调用方法:通过
Method.invoke()
- 操作属性:通过
Field.get()
和Field.set()
- 创建对象:通过
- 与注解结合:通过反射解析注解,实现灵活配置
- 优缺点:灵活性高但性能有开销,是框架开发的基础
实践任务
简易 ORM 工具:
- 创建
@Table
注解(标记类与数据库表的映射) - 创建
@Column
注解(标记属性与表字段的映射) - 编写
SqlGenerator
类,通过反射解析带注解的实体类 - 生成插入数据的 SQL 语句(如
INSERT INTO user (user_name, age) VALUES (?, ?)
)
- 创建
动态代理实现:
- 创建
UserService
接口和UserServiceImpl
实现类 - 使用
InvocationHandler
和反射实现动态代理 - 在代理中添加日志记录功能(调用方法前打印 “开始执行”,调用后打印 “执行结束”)
- 创建