今天我们将深入学习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 语句。

反射的优缺点

优点

  1. 灵活性高:突破编译期限制,能够动态操作类和对象
  2. 解耦:通过配置文件(如 XML)指定类名,无需硬编码,便于扩展
  3. 框架基础:是主流框架(Spring、MyBatis 等)的核心技术
  4. 可扩展性强:可以在不修改原有代码的情况下扩展功能

缺点

  1. 性能开销:反射操作需要解析字节码,性能比直接调用低(约慢 10-100 倍)
  2. 安全性问题:可以访问私有成员,破坏封装性
  3. 代码可读性差:反射代码较复杂,不如直接调用直观
  4. 编译期检查缺失:错误只能在运行时发现

优化反射性能的方法

  • 缓存ClassMethodField等对象,避免频繁获取
  • 尽量使用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;
                    }
                }
            }
        }
    }
}

总结与实践

知识点回顾

  1. 反射机制:程序运行时获取类信息并操作类成员的能力
  2. 核心类Class(入口)、Constructor(构造方法)、Method(方法)、Field(属性)
  3. 获取 Class 对象的方式Class.forName()类名.class对象.getClass()
  4. 反射操作
    • 创建对象:通过Constructor.newInstance()
    • 调用方法:通过Method.invoke()
    • 操作属性:通过Field.get()Field.set()
  5. 与注解结合:通过反射解析注解,实现灵活配置
  6. 优缺点:灵活性高但性能有开销,是框架开发的基础

实践任务

  1. 简易 ORM 工具

    • 创建@Table注解(标记类与数据库表的映射)
    • 创建@Column注解(标记属性与表字段的映射)
    • 编写SqlGenerator类,通过反射解析带注解的实体类
    • 生成插入数据的 SQL 语句(如INSERT INTO user (user_name, age) VALUES (?, ?)
  2. 动态代理实现

    • 创建UserService接口和UserServiceImpl实现类
    • 使用InvocationHandler和反射实现动态代理
    • 在代理中添加日志记录功能(调用方法前打印 “开始执行”,调用后打印 “执行结束”)
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐