动态代理原理
代理模式
代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗的来说代理对象就是我们生活中常见的中介。
目的:
- 通过引入代理对象的方式来间接访问目标对象,防止直接访问目标对象给系统带来不必要复杂性
- 通过代理对象对访问进行控制
代理模式的三个对象
抽象角色(Subject):指代理角色和真实角色对外提供的公共方法,一般为一个接口
真实角色(Real Subject):需要实现抽象角色接口,定义了真实角色所要实现的业务逻辑,以便供代理角色调用,也就是真正的业务逻辑在此。
代理角色(Proxy Subject):需要实现抽象角色接口,这是用来代理和控制对真实角色的访问,它通常会持有一个真实角色的实例,然后通过调用真实角色的方法来完成其功能。代理在调用真实主题的方法前后,可以执行一些附加操作。
静态代理
在编译时就确定了代理类和被代理类的关系。每一个被代理类都需要一个代理类,代理类需要实现被代理类相同的接口。
下面是以一个简单的静态代理的例子:
// 步骤1:定义一个接口
public interface MyInterface {
void myMethod();
}
// 步骤2:创建原始类,实现这个接口
public class OriginalClass implements MyInterface {
public void myMethod() {
System.out.println("Original method");
}
}
// 步骤3:创建代理类,也实现这个接口
public class ProxyClass implements MyInterface {
private MyInterface myInterface;
public ProxyClass(MyInterface myInterface) {
this.myInterface = myInterface;
}
public void myMethod() {
System.out.println("Before original method");
myInterface.myMethod();
System.out.println("After original method");
}
}
// 步骤4:在客户端代码中使用代理类
public class Client {
public static void main(String[] args) {
MyInterface original = new OriginalClass();
MyInterface proxy = new ProxyClass(original);
proxy.myMethod(); // 输出 "Before original method" "Original method" "After original method"
}
}
静态代理的特点
静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者继承相同父类。一般来说,被代理对象是一对一的关系,当然一个代理对象对应多个被代理对象也是可以的。
静态代理,一对一则会出现静态代理对象多,从而导致代码复杂,可维护性差的问题,一对多则代理对象会出现扩展能力差的问题。
动态代理
在运行时动态地创建代理对象,不需要手动创建代理类。Java的动态代理需要使用
java.lang.reflect.Proxy
类和java.lang.reflect.InvocationHandler
接口。
在运行时再创建类和其实例,因此效率更低。要完成这个场景。需要在运行期动态创建一个Class。JDK提供了Proxy
来完成这件事情。
Proxy类
在Java编程中,java.lang.reflect.Proxy
类是动态代理的核心。它的主要作用是在运行时动态地创建代理对象。
Proxy
类提供了一个静态方法newProxyInstance
,这个方法可以用来创建一个新的代理对象。这个方法需要三个参数:
-
类加载器(ClassLoader):用来加载代理类的类加载器。通常可以通过被代理对象的类加载器来获取。
-
接口数组(Class[]):代理类需要实现的接口列表。通常可以通过被代理对象的
getClass().getInterfaces()
方法来获取。 -
调用处理器(InvocationHandler):当我们调用代理对象的方法时,这些调用会被转发到调用处理器的
invoke
方法。我们可以在这个方法中添加一些额外的操作,例如日志记录、权限检查、事务管理等。
以下是一个简单的例子:
MyInterface original = new OriginalClass();
InvocationHandler handler = new MyInvocationHandler(original);
MyInterface proxy = (MyInterface) Proxy.newProxyInstance(
original.getClass().getClassLoader(),
original.getClass().getInterfaces(),
handler
);
proxy.myMethod(); // 调用这个方法时,会被转发到handler的invoke方法
在这个例子中,我们首先创建了一个原始对象和一个调用处理器。然后我们使用Proxy.newProxyInstance
方法创建了一个代理对象。当我们调用proxy.myMethod()
时,这个调用会被转发到handler
的invoke
方法。
总的来说,Proxy
类的作用是创建动态代理对象,这使得我们可以在运行时动态地添加或修改对象的行为。
InvocationHandler接口
InvocationHandler
是Java动态代理机制的一部分,它是一个接口,定义了一个方法invoke
。当我们通过代理对象调用一个方法时,这个调用会被转发到InvocationHandler
的invoke
方法。
invoke
方法有三个参数:
- proxy:代表动态代理对象
- method:代表我们所调用的方法
- args:代表调用方法时传递的参数
我们可以在invoke
方法中添加一些额外的操作,例如日志记录、权限检查、事务管理等。然后,我们可以通过method.invoke
来调用原始对象的方法。
以下是一个简单的例子:
public class MyInvocationHandler implements InvocationHandler {
private Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before method");
Object result = method.invoke(target, args);
System.out.println("After method");
return result;
}
}
在这个例子中,我们在调用原始方法前后添加了日志记录。当我们通过代理对象调用一个方法时,这个调用会被转发到MyInvocationHandler
的invoke
方法。
总的来说,InvocationHandler
的作用是定义了代理对象的行为,它决定了当我们通过代理对象调用方法时,应该做什么。
动态代理基本使用
基本使用如下:
// 抽象角色
public interface Api {
void test(String a);
}
// 真实角色
public class ApiImpl implements Api {
@Override
public void test(String a) {
System.out.println("真实实现:" + a);
}
}
public static void main(String[] args) throws Exception {
// 创建真实角色实例
ApiImpl api = new ApiImpl();
// JDK 动态代理
Object proxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
new Class[]{Api.class}, // JDK实现只能代理接口)
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 执行真实对象方法
return method.invoke(api, args);
}
});
((Api) proxy).test("哈哈哈");
}
实际上,Proxy.newProxyInstance
会创建一个Class,与静态代理不同,这个Class不是由具体的.java源文件编译而来,即没有真正的文件,而是在内存中按照Class格式生成了一个Class
下面的代码可以将生成的Class保存到文件中:
String name = Api.class.getName() + "&Proxy0";
// 生成代理指定接口的Class数据
byte[] bytes = ProxyGenerator.generateProxyClass(name, new Class[] {Api.class});
FileOutputStream fos = new FileOutputStream("lib/" + name + ".class");
fos.write(bytes);
fos.close();
然后可以在生成的文件中查看我们的代理类:
在初始化时,获得method
备用。而这个代理类中所有方法的实现变为:
这里的h
其实就是InvocationHandler
接口,所以我们使用动态代理时,传递的InvocationHandler
就是一个监听,在代理对象上执行方法,都会由这个监听回调出来。
静态代理和动态代理的区别
分类 | 特点 | 优点 | 缺点 | 应用场景 |
---|---|---|---|---|
静态代理 | 在编译时就确定了代理类和被代理类的关系。每一个被代理类都需要一个代理类,代理类需要实现与被代理类相同的接口 | 可以在编译时就进行类型检查 | 对于每一个接口都需要创建一个代理类,这会导致代码冗余。 | 需要大量创建代理类,或者代理类和被代理类的关系在编译时就可以确定的情况下,静态代理可能是一个更好的选择 |
动态代理 | 在运行时动态地创建代理对象,不需要手动创建代理类 | 可以减少代码冗余,并提高代码的可维护性 | 可以减少代码冗余,并提高代码的可维护性 | 需要在运行时动态创建代理对象,或者代理类和被代理类的关系在编译时无法确定的情况下,动态代理可能是一个更好的选择 |