Java中的反射(Reflection)和动态代理(Dynamic Proxy)是两个强大的特性,它们在处理对象、类和方法时提供了高度的灵活性。
- 反射主要用于需要在运行时检查或反射关于类、接口、字段和方法信息的场景;
- 动态代理则更适用于需要在不修改原始代码的情况下增加额外处理逻辑的情况,如拦截方法调用以添加日志记录或安全控制等。
反射
反射允许运行中的Java程序获取类信息,并操作对象。它提供了以下主要功能:
- 获取类的信息:可以通过
Class 对象获取类的构造器、字段(成员变量)、方法等信息。
- 创建实例:通过反射可以在运行时创建类的实例,即使这个类的名字是在编译期未知的。
- 调用方法和访问字段:可以调用一个对象的方法或访问其字段(包括私有字段),而不需要在代码中直接引用这些方法或字段。
- 修改类的行为:例如,可以在运行时改变某个字段的值或者调用通常不可访问的私有方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| public class ReflectionExample { public static void main(String[] args) throws Exception { Class<?> clazz = Class.forName("com.ares.basic.proxy.Person"); Constructor<?> constructor = clazz.getConstructor(String.class, int.class); Person person = (Person) constructor.newInstance("ares", 20); Method getNameMethod = clazz.getMethod("getName"); Object name = getNameMethod.invoke(person); System.out.println("Name: " + name); Field ageField = clazz.getDeclaredField("age"); ageField.setAccessible(true); ageField.set(person, 31); Method getAgeMethod = clazz.getMethod("getAge"); Object age = getAgeMethod.invoke(person); System.out.println("Updated Age: " + age); System.out.println("person= " + person); } }
|
代理模式
代理模式是常用的设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。简单的说就是,我们在访问实际对象时,是通过代理对象来访问的,代理模式就是在访问实际对象时引入一定程度的间接性。

代理模式示例
Log接口:
1 2 3
| public interface Log { void info(String msg); }
|
ConsoleLog具体类:
1 2 3 4 5 6 7
| public class ConsoleLog implements Log { @Override public void info(String msg) { System.out.println("console log: " + msg); } }
|
FileLog具体类:
1 2 3 4 5 6
| public class FileLog implements Log { @Override public void info(String msg) { System.out.println("file log:" + msg); } }
|
LogProxy代理类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class LogProxy implements Log {
Log log; public LogProxy(Log log) { if (log instanceof ConsoleLog) { this.log = (ConsoleLog) log; }else if (log instanceof FileLog) { this.log = (FileLog) log; } }
@Override public void info(String msg) { log.info(msg); } }
|
客户端调用:
1 2 3 4 5 6 7 8 9 10 11
| public class StaticProxyExample { public static void main(String[] args) { Log consoleLog = new ConsoleLog(); LogProxy logProxy = new LogProxy(consoleLog); logProxy.info("console log by proxy");
Log fileLog = new FileLog(); LogProxy fileLogProxy = new LogProxy(fileLog); fileLogProxy.info("file log by proxy"); } }
|
动态代理
代理类在程序运行时创建的代理方式被成为动态代理。相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。
jdk动态代理
利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。JDK从1.3版本就开始支持动态代理类的创建。主要核心类只有2个:java.lang.reflect.Proxy 和 java.lang.reflect.InvocationHandler 。创建步骤如下:
- 定义业务接口:首先需要有一个业务接口(或多个),这是代理对象要实现的接口。
- 实现InvocationHandler接口:编写一个处理器类,它实现了InvocationHandler接口,并覆盖invoke方法。这个方法会在代理对象调用任何方法时被触发。
- 创建代理对象:使用
Proxy.newProxyInstance()方法创建代理对象。此方法需要三个参数:
- 类加载器(ClassLoader):用于加载代理类。
- 业务接口的Class数组:代理对象需要实现的接口列表。
- InvocationHandler实例:处理方法调用的处理器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class JdkDynamicProxy implements InvocationHandler { private final Object target;
public JdkDynamicProxy(Object target) { this.target = target; }
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("Before method call: " + method.getName()); Object result = method.invoke(target, args); System.out.println("After method call: " + method.getName()); return result; } }
|
cglib动态代理
利用ASM开源包,将代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。Cglib底层是字节码处理框架ASM。相较于jdk动态代理只能基于接口,CGLIB通过继承的方式创建目标类的子类来实现代理,代理对象既可以赋值给实现类,又可以赋值给接口,但这种通过继承类的实现方式,不能代理final修饰的类 。
1 2 3 4 5 6 7 8 9 10
| public class CglibDynamicProxy implements MethodInterceptor { @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy proxy) throws Throwable { System.out.println("Before method: " + method.getName()); Object result = proxy.invokeSuper(o, objects); System.out.println("After method: " + method.getName()); return result; } }
|
ByteBuddy动态代理
ByteBuddy和Cglib一样,也是基于ASM实现。ByteBuddy常用的应用就是java agent,其主要作用就是在class被加载之前对其拦截,插入自己的代码。与CGLIB和JDK动态代理相比,ByteBuddy提供了更加灵活和高效的代理机制。新版本的Spring的动态代理使用ByteBuddy实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| public class ByteBuddyDynamicProxy { private final Object bean; private final Method[] methods;
public ByteBuddyDynamicProxy(Object bean, Method[] methods) { this.bean = bean; this.methods = methods; }
public Object getProxyClass() throws Exception { Class<?> clazz = new ByteBuddy() .subclass(bean.getClass()) .method(ElementMatchers.anyOf(methods)) .intercept(MethodDelegation.to(Interceptor.class)) .make() .load(ByteBuddyDynamicProxy.class.getClassLoader()) .getLoaded(); return clazz.getDeclaredConstructor().newInstance(); }
public static class Interceptor { public static void intercept(@SuperCall Callable<Void> zuper)throws Exception { System.out.println("Before method execution."); zuper.call(); System.out.println("After method execution."); } @RuntimeType public void intercept(@Origin Method method, @AllArguments Object[] args, @SuperCall Callable<?> callable ) throws Exception { System.out.println("Before method execution."); callable.call(); System.out.println("After method execution."); } } }
|
javassist动态代理
Javassist(Java Programming Assistant)是一个用于编辑或创建Java字节码(即.class文件)的库。它使得在运行时处理字节码变得更加容易。Javassist允许开发者以接近源代码的形式操作类和方法,而不是直接处理字节码。这使得它相比于直接操作字节码的方式更易于理解和使用。
1 2 3 4 5 6 7 8 9 10 11 12
| public class JavassistDynamicProxy { public Object getProxyInstance(Log log) throws Exception { ProxyFactory factory = new ProxyFactory(); factory.setSuperclass(log.getClass()); return factory.create(new Class[0], new Object[0], (self, thisMethod, proceed, args) -> { System.out.println("Before method: " + thisMethod.getName()); Object result = thisMethod.invoke(self, args); System.out.println("After method: " + thisMethod.getName()); return result; }); } }
|