MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Java反射机制与动态代理的关系

2021-05-134.5k 阅读

Java反射机制基础

Java反射机制是Java语言中一项强大的特性,它允许程序在运行时获取、检查和修改类的属性、方法和构造函数等信息。通过反射,Java程序可以在运行时加载、探知、使用编译期间完全未知的类。这使得Java程序具备了极大的灵活性。

类的加载与Class对象

在Java中,每个类被加载到JVM后,都会有一个对应的Class对象。这个Class对象包含了类的所有元数据信息,比如类名、包名、父类、接口、成员变量、方法等。可以通过多种方式获取Class对象:

  1. 通过类字面常量:对于已知类,可以使用类名.class的方式获取Class对象。例如:
Class<String> stringClass = String.class;
  1. 通过对象的getClass()方法:如果已经有一个对象实例,可以调用其getClass()方法获取对应的Class对象。例如:
String str = "Hello";
Class<? extends String> clazz = str.getClass();
  1. 通过Class.forName()方法:这种方式可以通过类的全限定名来获取Class对象,常用于在运行时根据配置文件或用户输入加载类。例如:
try {
    Class<?> cls = Class.forName("java.lang.String");
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}

获取类的成员信息

  1. 获取成员变量:通过Class对象的getFields()方法可以获取类的所有公共成员变量,getDeclaredFields()方法可以获取类声明的所有成员变量(包括私有、受保护和默认访问权限的)。例如:
class Person {
    private String name;
    public int age;
}

public class ReflectionExample {
    public static void main(String[] args) {
        try {
            Class<Person> personClass = Person.class;
            // 获取所有公共成员变量
            java.lang.reflect.Field[] publicFields = personClass.getFields();
            for (java.lang.reflect.Field field : publicFields) {
                System.out.println("Public field: " + field.getName());
            }
            // 获取所有声明的成员变量
            java.lang.reflect.Field[] declaredFields = personClass.getDeclaredFields();
            for (java.lang.reflect.Field field : declaredFields) {
                System.out.println("Declared field: " + field.getName());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

在上述代码中,getFields()只会输出age,因为name是私有的。而getDeclaredFields()会输出nameage

  1. 获取方法getMethods()方法用于获取类及其父类的所有公共方法,getDeclaredMethods()方法用于获取类声明的所有方法(包括私有、受保护和默认访问权限的)。例如:
class Animal {
    public void eat() {
        System.out.println("Animal is eating");
    }
}

class Dog extends Animal {
    private void bark() {
        System.out.println("Dog is barking");
    }
}

public class MethodReflection {
    public static void main(String[] args) {
        try {
            Class<Dog> dogClass = Dog.class;
            // 获取所有公共方法
            java.lang.reflect.Method[] publicMethods = dogClass.getMethods();
            for (java.lang.reflect.Method method : publicMethods) {
                System.out.println("Public method: " + method.getName());
            }
            // 获取所有声明的方法
            java.lang.reflect.Method[] declaredMethods = dogClass.getDeclaredMethods();
            for (java.lang.reflect.Method method : declaredMethods) {
                System.out.println("Declared method: " + method.getName());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

这里getMethods()会输出eat以及从Object类继承的一些公共方法,而getDeclaredMethods()会额外输出bark

  1. 获取构造函数getConstructors()方法获取类的所有公共构造函数,getDeclaredConstructors()方法获取类声明的所有构造函数。例如:
class Car {
    private String brand;
    public Car(String brand) {
        this.brand = brand;
    }
    private Car() {}
}

public class ConstructorReflection {
    public static void main(String[] args) {
        try {
            Class<Car> carClass = Car.class;
            // 获取所有公共构造函数
            java.lang.reflect.Constructor<?>[] publicConstructors = carClass.getConstructors();
            for (java.lang.reflect.Constructor<?> constructor : publicConstructors) {
                System.out.println("Public constructor: " + constructor.getParameterCount());
            }
            // 获取所有声明的构造函数
            java.lang.reflect.Constructor<?>[] declaredConstructors = carClass.getDeclaredConstructors();
            for (java.lang.reflect.Constructor<?> constructor : declaredConstructors) {
                System.out.println("Declared constructor: " + constructor.getParameterCount());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

getConstructors()只会输出参数个数为1的公共构造函数,而getDeclaredConstructors()会输出参数个数为0的私有构造函数和参数个数为1的公共构造函数。

使用反射创建对象、调用方法和访问成员变量

  1. 创建对象:通过反射获取构造函数后,可以使用newInstance()方法创建对象。对于私有构造函数,需要先设置setAccessible(true)来打破访问限制。例如:
class Fruit {
    private String name;
    public Fruit(String name) {
        this.name = name;
    }
    private Fruit() {}
}

public class ObjectCreationByReflection {
    public static void main(String[] args) {
        try {
            Class<Fruit> fruitClass = Fruit.class;
            // 通过公共构造函数创建对象
            java.lang.reflect.Constructor<Fruit> publicConstructor = fruitClass.getConstructor(String.class);
            Fruit fruit1 = publicConstructor.newInstance("Apple");
            // 通过私有构造函数创建对象
            java.lang.reflect.Constructor<Fruit> privateConstructor = fruitClass.getDeclaredConstructor();
            privateConstructor.setAccessible(true);
            Fruit fruit2 = privateConstructor.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  1. 调用方法:获取方法对象后,可以使用invoke()方法调用方法。对于私有方法同样需要设置setAccessible(true)。例如:
class MathUtils {
    public int add(int a, int b) {
        return a + b;
    }
    private int multiply(int a, int b) {
        return a * b;
    }
}

public class MethodInvocationByReflection {
    public static void main(String[] args) {
        try {
            Class<MathUtils> mathUtilsClass = MathUtils.class;
            MathUtils mathUtils = mathUtilsClass.newInstance();
            // 调用公共方法
            java.lang.reflect.Method addMethod = mathUtilsClass.getMethod("add", int.class, int.class);
            int result1 = (int) addMethod.invoke(mathUtils, 2, 3);
            // 调用私有方法
            java.lang.reflect.Method multiplyMethod = mathUtilsClass.getDeclaredMethod("multiply", int.class, int.class);
            multiplyMethod.setAccessible(true);
            int result2 = (int) multiplyMethod.invoke(mathUtils, 2, 3);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  1. 访问成员变量:获取成员变量对象后,可以使用set()get()方法来设置和获取成员变量的值。对于私有成员变量也需要设置setAccessible(true)。例如:
class User {
    private String username;
    public User(String username) {
        this.username = username;
    }
}

public class FieldAccessByReflection {
    public static void main(String[] args) {
        try {
            Class<User> userClass = User.class;
            User user = userClass.getConstructor(String.class).newInstance("John");
            // 访问私有成员变量
            java.lang.reflect.Field usernameField = userClass.getDeclaredField("username");
            usernameField.setAccessible(true);
            String username = (String) usernameField.get(user);
            System.out.println("Username: " + username);
            usernameField.set(user, "Jane");
            username = (String) usernameField.get(user);
            System.out.println("New username: " + username);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

动态代理概述

动态代理是一种设计模式,它允许在运行时创建代理对象,该代理对象可以代理一个或多个接口,并在调用代理对象的方法时,将调用转发到实际的目标对象,同时可以在转发前后执行一些额外的逻辑。动态代理在Java中有两种常见的实现方式:JDK动态代理和CGLIB动态代理。

JDK动态代理

JDK动态代理是基于Java反射机制实现的。它要求被代理的对象必须实现至少一个接口。

  1. 创建InvocationHandlerInvocationHandler是一个接口,需要实现其中的invoke()方法。invoke()方法包含了代理对象方法调用时的实际逻辑,包括调用目标对象的方法以及在调用前后执行的额外逻辑。例如:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class MyInvocationHandler implements InvocationHandler {
    private Object target;
    public MyInvocationHandler(Object target) {
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Before method invocation");
        Object result = method.invoke(target, args);
        System.out.println("After method invocation");
        return result;
    }
}
  1. 创建代理对象:使用Proxy类的newProxyInstance()方法来创建代理对象。该方法接受三个参数:类加载器、被代理对象实现的接口数组以及InvocationHandler实例。例如:
import java.lang.reflect.Proxy;

interface HelloService {
    void sayHello();
}

class HelloServiceImpl implements HelloService {
    @Override
    public void sayHello() {
        System.out.println("Hello, World!");
    }
}

public class JDKDynamicProxyExample {
    public static void main(String[] args) {
        HelloService target = new HelloServiceImpl();
        MyInvocationHandler handler = new MyInvocationHandler(target);
        HelloService proxy = (HelloService) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                handler);
        proxy.sayHello();
    }
}

在上述代码中,当调用proxy.sayHello()时,会先执行MyInvocationHandler中的invoke()方法,输出Before method invocation,然后调用目标对象HelloServiceImplsayHello()方法,最后输出After method invocation

CGLIB动态代理

CGLIB(Code Generation Library)动态代理是通过继承被代理类来实现的,因此不需要被代理类实现接口。CGLIB使用字节码生成技术在运行时生成被代理类的子类,并重写其中的方法来实现代理逻辑。

  1. 引入CGLIB依赖:如果使用Maven,可以在pom.xml中添加如下依赖:
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>
  1. 创建MethodInterceptorMethodInterceptor是CGLIB中的核心接口,类似于JDK动态代理中的InvocationHandler。需要实现其中的intercept()方法,该方法包含了代理逻辑。例如:
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class MyMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("Before method invocation in CGLIB");
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("After method invocation in CGLIB");
        return result;
    }
}
  1. 创建代理对象:使用Enhancer类来创建代理对象。Enhancer类用于生成一个被代理类的子类,通过设置CallbackMyMethodInterceptor实例来实现代理逻辑。例如:
class Calculator {
    public int add(int a, int b) {
        return a + b;
    }
}

public class CGLIBDynamicProxyExample {
    public static void main(String[] args) {
        net.sf.cglib.proxy.Enhancer enhancer = new net.sf.cglib.proxy.Enhancer();
        enhancer.setSuperclass(Calculator.class);
        enhancer.setCallback(new MyMethodInterceptor());
        Calculator proxy = (Calculator) enhancer.create();
        int result = proxy.add(2, 3);
        System.out.println("Result: " + result);
    }
}

在上述代码中,当调用proxy.add(2, 3)时,会先执行MyMethodInterceptor中的intercept()方法,输出Before method invocation in CGLIB,然后调用Calculator类的add()方法,最后输出After method invocation in CGLIB

Java反射机制与动态代理的关系

  1. 动态代理基于反射机制实现:无论是JDK动态代理还是CGLIB动态代理,都在底层依赖于Java反射机制。在JDK动态代理中,InvocationHandlerinvoke()方法通过反射调用目标对象的方法,即method.invoke(target, args)。这里method是通过反射获取的目标对象的方法,invoke()方法利用反射机制实现了方法的动态调用。在CGLIB动态代理中,虽然它使用字节码生成技术创建代理类,但在MethodInterceptorintercept()方法中,proxy.invokeSuper(obj, args)本质上也是通过反射相关的字节码操作来调用目标对象的方法。这表明反射机制为动态代理提供了方法动态调用的基础能力,使得代理对象能够在运行时根据实际情况调用目标对象的方法。
  2. 反射为动态代理提供元数据获取能力:动态代理在运行时需要获取目标对象的类信息、方法信息等元数据,以便正确地进行方法转发和额外逻辑的添加。Java反射机制提供了丰富的API来获取这些元数据。例如,在JDK动态代理中,通过Proxy.newProxyInstance()方法创建代理对象时,需要传入目标对象实现的接口数组,这就需要通过反射获取目标对象的接口信息。同样,在处理方法调用时,InvocationHandlerinvoke()方法中的Method对象也是通过反射获取的,该对象包含了方法的名称、参数类型、返回类型等重要元数据。在CGLIB动态代理中,虽然是通过字节码操作创建代理类,但在处理方法拦截逻辑时,也依赖反射机制获取目标类和方法的元数据,以便正确地进行方法调用和逻辑处理。
  3. 动态代理扩展了反射的应用场景:反射机制本身主要用于在运行时对类的成员进行获取、修改和调用等操作。而动态代理在此基础上,通过创建代理对象,实现了对目标对象方法调用的拦截和增强。动态代理可以在不修改目标对象代码的前提下,为目标对象添加通用的功能,如日志记录、事务管理、权限检查等。这种基于代理的功能增强方式是反射机制在实际应用中的一种扩展。例如,在企业级应用开发中,常常使用动态代理来实现AOP(面向切面编程),将横切关注点(如日志、事务)从业务逻辑中分离出来,通过代理对象在方法调用前后织入这些横切逻辑。而这一过程离不开反射机制提供的基础支持,同时动态代理又将反射的应用提升到了一个更高的层次,使其在软件工程领域有了更广泛的应用。
  4. 两者在性能和适用场景上相互补充:反射机制由于涉及到运行时的类信息获取和方法调用,性能相对较低。而动态代理在一定程度上对反射的使用进行了封装和优化,通过代理对象缓存等机制提高了性能。例如,JDK动态代理创建的代理对象在多次调用同一方法时,由于方法对象的缓存,性能会有所提升。CGLIB动态代理通过字节码生成技术,避免了反射调用的一些开销,在性能上有进一步的优化。在适用场景上,JDK动态代理适用于被代理对象实现了接口的情况,而CGLIB动态代理适用于没有实现接口的类。反射机制则在需要直接操作类的元数据,如动态创建对象、修改成员变量等场景下更为适用。三者在不同的场景下相互补充,为Java开发者提供了强大而灵活的编程能力。

综合示例:使用反射和动态代理实现AOP

假设我们要实现一个简单的AOP功能,在方法调用前后记录日志。

  1. 定义接口和实现类
interface UserService {
    void addUser(String username);
    void deleteUser(String username);
}

class UserServiceImpl implements UserService {
    @Override
    public void addUser(String username) {
        System.out.println("Adding user: " + username);
    }
    @Override
    public void deleteUser(String username) {
        System.out.println("Deleting user: " + username);
    }
}
  1. 使用JDK动态代理实现AOP
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class JDKAOPExample {
    public static void main(String[] args) {
        UserService target = new UserServiceImpl();
        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("Before method " + method.getName() + " invocation");
                Object result = method.invoke(target, args);
                System.out.println("After method " + method.getName() + " invocation");
                return result;
            }
        };
        UserService proxy = (UserService) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                handler);
        proxy.addUser("Tom");
        proxy.deleteUser("Jerry");
    }
}
  1. 使用CGLIB动态代理实现AOP
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CGLIBAOPExample {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(UserServiceImpl.class);
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                System.out.println("Before method " + method.getName() + " invocation in CGLIB");
                Object result = proxy.invokeSuper(obj, args);
                System.out.println("After method " + method.getName() + " invocation in CGLIB");
                return result;
            }
        });
        UserService proxy = (UserService) enhancer.create();
        proxy.addUser("Alice");
        proxy.deleteUser("Bob");
    }
}

在上述两个示例中,无论是JDK动态代理还是CGLIB动态代理,都利用了反射机制来实现方法的动态调用和元数据的获取,同时通过代理对象实现了在方法调用前后记录日志的AOP功能。这充分展示了反射机制和动态代理在实际应用中的紧密结合和强大威力。

综上所述,Java反射机制和动态代理是相辅相成的关系。反射机制为动态代理提供了底层的方法调用和元数据获取能力,而动态代理则在反射的基础上扩展了应用场景,实现了对目标对象的功能增强。深入理解它们之间的关系,对于编写高效、灵活和可维护的Java程序具有重要意义。无论是在框架开发、企业级应用还是其他Java项目中,合理运用反射和动态代理技术,都能够极大地提升程序的质量和开发效率。