java的静态代理和动态代理
一、Java的静态代理
静态代理是一种在编译时就确定代理类的代理方式,与动态代理不同,它不需要在运行时生成代理类。静态代理主要通过直接编写代理类的代码实现,其优点是实现简单,性能较高,但缺点是代理类和目标类的耦合度较高,增加了代码量和维护难度。
工作原理
静态代理通过在代理类中显式地调用目标对象的方法,并在方法调用前后添加额外的逻辑,从而实现对目标对象方法的增强。通常,静态代理需要以下几个步骤:
- 定义接口:定义目标对象和代理对象共同实现的接口。
- 实现目标类:实现目标接口的具体类。
- 实现代理类:实现目标接口的代理类,持有目标对象的引用,并在方法中调用目标对象的方法。
实现步骤
以下是静态代理的详细实现步骤及示例代码。
1. 定义接口
首先,定义一个通用的接口,目标对象和代理对象都将实现这个接口。
java
复制代码
public interface Calculator {
int add(int a, int b);
int subtract(int a, int b);
}
2. 实现目标类
实现目标接口的具体类,提供接口方法的具体实现。
java
复制代码
public class CalculatorImpl implements Calculator {
@Override
public int add(int a, int b) {
return a + b;
}
@Override
public int subtract(int a, int b) {
return a - b;
}
}
3. 实现代理类
实现目标接口的代理类,持有目标对象的引用,并在方法中调用目标对象的方法。
java
复制代码
public class CalculatorProxy implements Calculator {
private final Calculator target;
public CalculatorProxy(Calculator target) {
this.target = target;
}
@Override
public int add(int a, int b) {
System.out.println("Before method add");
int result = target.add(a, b);
System.out.println("After method add");
return result;
}
@Override
public int subtract(int a, int b) {
System.out.println("Before method subtract");
int result = target.subtract(a, b);
System.out.println("After method subtract");
return result;
}
}
4. 使用代理类
创建目标对象和代理对象,并通过代理对象调用方法。
java
复制代码
public class Main {
public static void main(String[] args) {
Calculator target = new CalculatorImpl();
Calculator proxy = new CalculatorProxy(target);
System.out.println("Add: " + proxy.add(1, 2));
System.out.println("Subtract: " + proxy.subtract(5, 3));
}
}
详细分析
代理类的结构
代理类CalculatorProxy
持有目标对象Calculator
的引用,通过构造方法传入目标对象。在每个接口方法中,代理类会在调用目标对象的方法之前和之后添加额外的逻辑(例如打印日志)。
方法调用流程
- 创建目标对象:创建目标对象
CalculatorImpl
。 - 创建代理对象:创建代理对象
CalculatorProxy
,并将目标对象传入代理对象的构造方法。 - 调用代理方法:通过代理对象调用接口方法,例如
proxy.add(1, 2)
。 - 执行代理逻辑:代理对象的方法被调用,在方法内部调用目标对象的方法之前和之后添加额外的逻辑(例如打印日志)。
- 调用目标方法:代理对象的方法内部调用目标对象的实际方法,并返回结果。
优点和缺点
优点:
- 实现简单:静态代理的实现非常直观,代码可读性高。
- 性能较高:由于代理类在编译时已经确定,运行时不需要动态生成代理类,性能较高。
缺点:
- 耦合度高:代理类和目标类的耦合度较高,增加了代码量和维护难度。
- 扩展性差:每增加一个新的接口方法,需要在代理类中增加相应的方法,增加了代码维护成本。
适用场景
静态代理适用于以下场景:
- 接口和实现类较少,代码变动不频繁。
- 需要在方法调用前后添加固定的额外逻辑,如日志记录、权限检查等。
扩展
多接口静态代理
当一个类实现了多个接口时,静态代理需要代理每个接口的方法。可以通过实现多个接口的代理类来实现。
组合代理
将多个代理逻辑组合在一起,形成一个复合代理类,通过组合代理类来实现复杂的代理逻辑。
总结
静态代理是一种在编译时就确定代理类的代理方式,通过显式地在代理类中调用目标对象的方法,实现对目标对象方法的增强。虽然实现简单,性能较高,但由于耦合度较高和扩展性差,不适用于接口较多或变化频繁的场景。在实际应用中,需要根据具体需求选择合适的代理方式。
二、JDK动态代理
当我们使用Proxy.newProxyInstance
方法来创建代理对象时,JVM在运行时会动态生成一个实现了指定接口的代理类。这个生成的代理类会将所有方法调用委托给我们提供的InvocationHandler
的invoke
方法。
代理类生成过程
- 调用
Proxy.newProxyInstance
方法: 当我们调用Proxy.newProxyInstance
方法时,JVM会动态生成一个代理类。 - 代理类的字节码生成: JVM通过使用
ProxyGenerator
类来生成代理类的字节码。这个类会根据我们提供的接口信息,生成一个包含所有接口方法实现的代理类。 - 代理类的加载: 生成的代理类字节码会通过指定的
ClassLoader
加载到JVM中,形成一个新的类。 - 代理类实例的创建: JVM创建这个代理类的实例,并将我们提供的
InvocationHandler
实例传递给这个代理类实例。代理类的所有方法调用都会被转发到InvocationHandler
的invoke
方法。
详细示例
下面通过一个详细的示例来展示这个过程。
定义接口和实际类
java
复制代码
public interface Calculator {
int add(int a, int b);
int subtract(int a, int b);
}
public class CalculatorImpl implements Calculator {
@Override
public int add(int a, int b) {
return a + b;
}
@Override
public int subtract(int a, int b) {
return a - b;
}
}
实现InvocationHandler接口
java
复制代码
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Arrays;
public class CalculatorInvocationHandler implements InvocationHandler {
private final Object target;
public CalculatorInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Method " + method.getName() + " is called with args " + Arrays.toString(args));
Object result = method.invoke(target, args);
System.out.println("Method " + method.getName() + " returns " + result);
return result;
}
}
创建代理对象并使用
java
复制代码
import java.lang.reflect.Proxy;
public class Main {
public static void main(String[] args) {
Calculator target = new CalculatorImpl();
CalculatorInvocationHandler handler = new CalculatorInvocationHandler(target);
Calculator proxy = (Calculator) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
handler
);
System.out.println("Add: " + proxy.add(1, 2));
System.out.println("Subtract: " + proxy.subtract(5, 3));
}
}
动态生成的代理类
当我们调用Proxy.newProxyInstance
方法时,JVM会动态生成一个代理类,例如$Proxy0
(名字会有所不同),并实现我们指定的接口Calculator
。这个生成的代理类的字节码大致如下(简化版):
java
复制代码
public class $Proxy0 implements Calculator {
private InvocationHandler handler;
public $Proxy0(InvocationHandler handler) {
this.handler = handler;
}
@Override
public int add(int a, int b) {
try {
Method method = Calculator.class.getMethod("add", int.class, int.class);
return (Integer) handler.invoke(this, method, new Object[]{a, b});
} catch (Throwable t) {
throw new RuntimeException(t);
}
}
@Override
public int subtract(int a, int b) {
try {
Method method = Calculator.class.getMethod("subtract", int.class, int.class);
return (Integer) handler.invoke(this, method, new Object[]{a, b});
} catch (Throwable t) {
throw new RuntimeException(t);
}
}
}
代理类的加载和调用
- 代理类的加载: JVM使用我们指定的
ClassLoader
加载生成的代理类。Proxy.newProxyInstance
方法会调用内部的ProxyClassFactory
来生成代理类的字节码,并通过ClassLoader
加载。 - 代理类实例的创建:
Proxy.newProxyInstance
方法返回生成的代理类的实例。在实例化过程中,构造方法会接受InvocationHandler
实例,并将其保存在一个私有字段中。 - 方法调用的转发: 代理类实现了我们指定的接口中的所有方法,并在这些方法中调用
InvocationHandler
的invoke
方法。invoke
方法会接收代理对象、调用的方法对象以及方法参数,并执行具体的逻辑(如日志记录、权限检查等)。
代理类的实际调用流程
- 调用代理对象的方法: 例如,调用
proxy.add(1, 2)
。 - 代理类的实现方法: 代理类的
add
方法被调用。该方法通过反射获取Calculator
接口的add
方法对象,并调用InvocationHandler
的invoke
方法。 InvocationHandler
的invoke
方法:invoke
方法被调用,并接收代理对象、方法对象以及方法参数。我们可以在这里添加额外的逻辑,例如打印日志、权限检查等。- 实际方法的执行:
invoke
方法中,通过反射调用目标对象的实际方法,并将结果返回给代理类的方法调用者。
通过以上的详细描述和示例,我们可以清楚地看到JDK动态代理在运行时生成代理类的过程,以及代理类如何将方法调用转发给InvocationHandler
进行处理。
转载自:https://juejin.cn/post/7379431978814046220