云计算百科
云计算领域专业知识百科平台

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() {
}

// 有参构造1
public 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;
}

@Override
public 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. 获取有参构造方法并创建对象
// 参数:构造方法的参数类型.class
Constructor<?> 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)
// 参数:方法名,参数类型.class
Method 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. 实例化Servlet
    Object 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和反射实现动态代理
    • 在代理中添加日志记录功能(调用方法前打印 “开始执行”,调用后打印 “执行结束”)
  • 赞(0)
    未经允许不得转载:网硕互联帮助中心 » JavaWeb 30 天入门:第十一天 ——Java 反射机制详解
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!