前端视角 Java Web 入门手册 2.9:Java Core ——反射

297 阅读9分钟

传统的 Java 编程依赖于静态编译,即在编译时确定所有类的结构和成员

  1. 源代码通过编译器静态编译成字节码
  2. 类加载器(ClassLoader)负责将字节码加载到 JVM 中
  3. 类被加载并初始化后,JVM 开始执行类中的代码

这种方式虽然能够带来高效的执行性能和强类型的安全保证,但在静态编译模式下,开发者需要在编译时知道所有类的存在和结构,这限制了程序在运行时动态加载和操作未知类的能力,无法适应需求的动态变化。最典型场景就是反序列化,只有运行期才能获取到反序列化的内容,无法提前编译

反射技术的诞生

面对静态编译模式的诸多局限,反射技术应运而生,旨在为 Java 带来更高的动态性和灵活性。反射允许程序在运行时动态地加载类、创建对象、访问和修改字段、调用方法等,补充了静态编译模式的不足

动态类加载与实例化

通过反射,程序可以在运行时根据类名动态加载类,并创建对象实例

Class<?> clazz = Class.forName("com.example.MyClass");
Object instance = clazz.getDeclaredConstructor().newInstance();

这使得应用程序能够根据外部配置或用户输入动态加载和操作不同的类,大大增强了系统的灵活性和扩展能力

访问和修改类的成员

反射允许程序访问和修改类的私有字段和方法,绕过了 Java 的访问控制机制:

Field field = clazz.getDeclaredField("privateField");
field.setAccessible(true);
field.set(instance, "newValue");

Method method = clazz.getDeclaredMethod("privateMethod", String.class);
method.invoke(instance, "argument");

这种能力使得开发者能够在不修改类源代码的情况下操作对象的内部状态,实现更通用和灵活的功能。

动态方法调用

通过反射,程序可以在运行时查找和调用对象的方法,无论这些方法在编译时是否已知

Method method = clazz.getDeclaredMethod("dynamicMethod", String.class);
method.setAccessible(true);
method.invoke(instance, "Hello");

这对于实现动态行为、方法拦截和代理模式等非常有用,进一步提升了代码的通用性和复用性

自定义最简单的反序列化

定义 Person 测试类

public class Person {
    private String name;
    private int age;
    private boolean isActive;

    // 默认构造器(反射反序列化需要)
    public Person() {}

    // 参数化构造器
    public Person(String name, int age, boolean isActive) {
        this.name = name;
        this.age = age;
        this.isActive = isActive;
    }

    // Getter 和 Setter
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }

    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }

    public boolean isActive() { return isActive; }
    public void setActive(boolean isActive) { this.isActive = isActive; }

    // toString 方法
    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + ", isActive=" + isActive + '}';
    }
}

设计反序列化格式

假设 Person 类序列化之后是这样的简单字符串,这种格式允许反序列化工具在解析数据时,根据@class字段自动识别并加载相应的类

@class=Person;name=John Doe;age=30;isActive=true

利用反射实现极简反序列化工具

简单讲需要做三件事

  1. 解析类信息:首先查找@class字段,获取类的全限定名,并通过Class.forName加载相应的类
  2. 实例化对象:通过反射创建类的实例,要求类具备无参构造器
  3. 字段赋值:遍历类的字段,通过反射设置相应的字段值,支持intStringboolean类型的转换
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class EnhancedDeserializer {

    /**
     * 将自定义格式的字符串反序列化为对象
     * 格式示例: "@class=Person;name=John Doe;age=30;isActive=true"
     *
     * @param serializedStr 序列化后的字符串
     * @return 反序列化后的对象
     * @throws Exception 如果反序列化过程中出错
     */
    public static Object deserialize(String serializedStr) throws Exception {
        // 将字符串解析为键值对
        String[] pairs = serializedStr.split(";");
        Map<String, String> map = new HashMap<>();
        for (String pair : pairs) {
            String[] keyValue = pair.split("=", 2); // 只分割第一次出现的 '='
            if (keyValue.length == 2) {
                map.put(keyValue[0].trim(), keyValue[1].trim());
            }
        }

        // 获取类名
        String className = map.get("@class");
        if (className == null || className.isEmpty()) {
            throw new IllegalArgumentException("Serialized data does not contain @class information.");
        }

        // 加载类
        Class<?> clazz = Class.forName(className);

        // 创建新实例
        Object obj = clazz.getDeclaredConstructor().newInstance();

        // 遍历字段,设置值
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            String fieldName = field.getName();
            if (map.containsKey(fieldName)) {
                field.setAccessible(true); // 允许访问私有字段
                String value = map.get(fieldName);
                Object convertedValue = convert(field.getType(), value);
                field.set(obj, convertedValue);
            }
        }

        return obj;
    }

    /**
     * 简单的类型转换,支持 int、String 和 boolean
     *
     * @param type  字段类型
     * @param value 字符串值
     * @return 转换后的对象
     */
    private static Object convert(Class<?> type, String value) {
        if (type == int.class || type == Integer.class) {
            return Integer.parseInt(value);
        } else if (type == boolean.class || type == Boolean.class) {
            return Boolean.parseBoolean(value);
        } else if (type == String.class) {
            return value;
        }
        // 可以根据需要添加更多类型的转换
        return null;
    }
}

EnhancedDeserializer的实现过程中,反射发挥了以下关键作用:

动态类加载

Class<?> clazz = Class.forName(className);

反射允许在运行时根据类的全限定名动态加载类,而无需在编译时知道具体的类。这使得反序列化工具具有高度的通用性,能够处理任何包含类信息的序列化数据

动态实例化对象

Object obj = clazz.getDeclaredConstructor().newInstance();

通过反射,工具可以动态地创建类的实例,无需事先构造特定类型的对象。这对于通用反序列化工具尤为重要

访问和修改私有字段

field.setAccessible(true);
field.set(obj, convertedValue);

反射允许工具访问和修改类的私有字段,绕过了 Java 的访问控制机制。这使得反序列化工具能够将序列化数据映射到对象的私有成员变量中,无需类本身提供公开的 setter 方法

动态类型转换

Object convertedValue = convert(field.getType(), value);

通过反射获取字段的类型,动态地将字符串值转换为相应的Java类型。这使得反序列化工具能够灵活地处理多种数据类型,而无需预先硬编码类型信息

测试验证效果

编写一个测试类,验证EnhancedDeserializer的功能

public class EnhancedDeserializationTest {
    public static void main(String[] args) {
        String serializedPerson = "@class=Person;name=John Doe;age=30;isActive=true";
        try {
            Object obj = EnhancedDeserializer.deserialize(serializedPerson);
            System.out.println(obj);
            // 输出: Person{name='John Doe', age=30, isActive=true}

            // 验证类型
            if (obj instanceof Person) {
                Person person = (Person) obj;
                System.out.println("Name: " + person.getName());
                System.out.println("Age: " + person.getAge());
                System.out.println("Is Active: " + person.isActive());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

反射技术的应用

反射操作比直接代码执行更慢,过度使用可能影响程序性能,因此在日常 CRUD 场景开发同学很少直接写反射代码,但日常框架、工具应用过程中,几乎无时无刻不在利用反射

框架和库的开发

许多 Java 框架(如Spring、Hibernate)依赖反射来实现核心功能,如依赖注入(Dependency Injection, DI)和对象关系映射(Object-Relational Mapping, ORM)。通过反射,框架能够在运行时扫描和管理应用程序中的类和对象,自动配置依赖关系,简化开发流程。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

// 服务接口
interface UserService {
    void createUser(String name);
}

// 服务实现
@Component
class UserServiceImpl implements UserService {
    public void createUser(String name) {
        System.out.println("User " + name + " created.");
    }
}

// 控制器
@Component
class UserController {
    @Autowired
    private UserService userService;

    public void register(String name) {
        userService.createUser(name);
    }
}

// 模拟Spring容器的依赖注入过程
public class SpringDIExample {
    public static void main(String[] args) throws Exception {
        // 创建实例
        UserServiceImpl userService = new UserServiceImpl();
        UserController userController = new UserController();

        // 使用反射扫描并注入依赖
        for (Field field : userController.getClass().getDeclaredFields()) {
            if (field.isAnnotationPresent(Autowired.class)) {
                field.setAccessible(true);
                field.set(userController, userService); // 注入UserServiceImpl实例
            }
        }

        // 使用控制器
        userController.register("Alice");
    }
}

动态代理与 AOP

反射是实现动态代理AOP的基础。通过创建代理对象,拦截方法调用,在方法执行前后添加额外的逻辑(如日志记录、事务管理等),实现代码的横向切分和功能增强。

例如,在方法调用前后进行日志记录或权限检查。Java 通过java.lang.reflect.ProxyInvocationHandler接口实现动态代理

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// 服务接口
interface Service {
    void performTask(String task);
}

// 服务实现
class ServiceImpl implements Service {
    public void performTask(String task) {
        System.out.println("Performing task: " + task);
    }
}

// 动态代理处理器
class LoggingInvocationHandler implements InvocationHandler {
    private Object target;

    public LoggingInvocationHandler(Object target) {
        this.target = target;
    }

    // 代理的方法调用
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Before method: " + method.getName());
        Object result = method.invoke(target, args); // 调用实际方法
        System.out.println("After method: " + method.getName());
        return result;
    }
}

public class DynamicProxyExample {
    public static void main(String[] args) {
        // 创建实际对象
        Service realService = new ServiceImpl();

        // 创建代理对象
        Service proxyService = (Service) Proxy.newProxyInstance(
                realService.getClass().getClassLoader(), // 类加载器
                realService.getClass().getInterfaces(),  // 接口
                new LoggingInvocationHandler(realService) // 处理器
        );

        // 调用代理方法
        proxyService.performTask("Data Processing");
    }
}

AOP(Aspect-Oriented Programming)是一种编程范式,旨在将横切关注点(如日志记录、事务管理、安全控制等)与业务逻辑分离。通过动态代理和反射,AOP框架能够在不修改业务逻辑代码的情况下,为方法调用前后添加额外的功能

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

// 服务接口
interface PaymentService {
    void pay(String item, double amount);
}

// 服务实现
class PaymentServiceImpl implements PaymentService {
    public void pay(String item, double amount) {
        System.out.println("Paying $" + amount + " for " + item);
    }
}

// AOP切面
@Aspect
class LoggingAspect {
    // 定义切点,匹配PaymentService接口的所有方法
    @Pointcut("execution(* PaymentService.*(..))")
    public void paymentMethods() {}

    // 环绕通知,在方法执行前后添加日志
    @Around("paymentMethods()")
    public Object logAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("Payment process started.");
        Object result = joinPoint.proceed(); // 执行目标方法
        System.out.println("Payment process completed.");
        return result;
    }
}

// 使用Spring AOP配置
public class SpringAOPExample {
    public static void main(String[] args) {
        // 创建实际对象
        PaymentService realService = new PaymentServiceImpl();

        // 创建切面
        LoggingAspect aspect = new LoggingAspect();

        // 创建代理对象
        PaymentService proxyService = (PaymentService) Proxy.newProxyInstance(
                realService.getClass().getClassLoader(),
                realService.getClass().getInterfaces(),
                (proxy, method, methodArgs) -> {
                    // 手动应用切面逻辑
                    System.out.println("Payment process started.");
                    Object result = method.invoke(realService, methodArgs);
                    System.out.println("Payment process completed.");
                    return result;
                }
        );

        // 调用代理方法
        proxyService.pay("Book", 29.99);
    }
}

序列化与反序列化

反射在序列化反序列化过程中,帮助框架动态访问对象的字段和方法,实现对象与存储格式(如JSON、XML)的相互转换。

测试与工具开发

在单元测试框架(如 JUnit)和开发工具(如 IDE 插件)中,反射用于动态发现和调用测试方法、访问对象的私有成员,提升测试覆盖率和工具功能性。

**JUnit **作为 Java 最流行的单元测试框架,通过反射机制动态发现和调用被标注为测试的方法,自动化执行测试用例并报告结果,这大大简化了测试代码的编写和维护。

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Method;

// 自定义测试注解
@Retention(RetentionPolicy.RUNTIME)
@interface MyTest {}

// 被测试的类
class Calculator {
    public int add(int a, int b) { return a + b; }
    public int multiply(int a, int b) { return a * b; }
}

// 测试类
class CalculatorTest {
    @MyTest
    public void testAdd() {
        Calculator calc = new Calculator();
        assert calc.add(2, 3) == 5 : "add method failed";
        System.out.println("testAdd passed");
    }

    @MyTest
    public void testMultiply() {
        Calculator calc = new Calculator();
        assert calc.multiply(2, 3) == 6 : "multiply method failed";
        System.out.println("testMultiply passed");
    }

    // 一个未标注的测试方法
    public void helperMethod() {
        // 辅助方法,不作为测试用例执行
    }
}

// 简单的测试运行器
public class SimpleTestRunner {
    public static void main(String[] args) {
        try {
            // 获取测试类
            Class<?> testClass = Class.forName("CalculatorTest");
            Object testInstance = testClass.getDeclaredConstructor().newInstance();

            // 遍历所有方法,查找标注了@MyTest的测试方法
            for (Method method : testClass.getDeclaredMethods()) {
                if (method.isAnnotationPresent(MyTest.class)) {
                    try {
                        method.invoke(testInstance); // 动态调用测试方法
                    } catch (Exception e) {
                        System.out.println(method.getName() + " failed: " + e.getCause().getMessage());
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}