设计模式中有一种模式叫代理模式,Spring框架离不开动态代理技术,Android hook技术用到了反射 + 动态代理,Framework中我们也经常看到各种proxy,如ApplicationThreadProxy, ActivityManagerProxy。
那么,今天就来说下Java中的代理模式和动态代理。
目录:
- 代理模式
- 静态代理
- 动态代理
- 代理模式的优缺点
- 代理模式的使用场景
- 动态代理原理分析
1. 代理模式
代理模式是常用的java设计模式,它的特征是代理类与委托类有相同的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。简单的说就是,我们在访问实际对象时,是通过代理对象来访问的,代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性可以附加多种用途。
举一个通俗点的例子:
我们男生都爱看NBA,比如詹姆斯转会湖人,球队老板不会直接找詹姆斯,而是找他的经纪人商谈,这中间就拦了一道。詹姆斯可以打球,代言,他的经纪人也具有他一样的属性:打球,代言,但是真正打球,代言的是詹姆斯本人。
画一个草图理解下:
代理模式根据创建代理类的时间点,又可以分为静态代理和动态代理。
2. 静态代理
- 2.1 概念
由程序员创建或特定工具自动生成源代码,也就是在编译时就已经将接口,被代理类,代理类等确定下来。在程序运行之前,代理类的.class文件就已经生成。
- 2.2 例子
private interface IProxy {
void playBasketball();
void endorsement();
}
private static final class James implements IProxy {
@Override
public void playBasketball() {
System.out.println("James play basketball");
}
@Override
public void endorsement() {
System.out.println("James endorsement");
}
}
private static final class Middleman implements IProxy {
final James james;
Middleman(James james) {
this.james = james;
}
@Override
public void playBasketball() {
System.out.println("Let's talk about the money, let James play basketball");
james.playBasketball();
}
@Override
public void endorsement() {
System.out.println("Let's talk about the money, let James endorsement");
james.playBasketball();
}
}
public static void main(String[] args) {
Middleman middleman = new Middleman(new James());
middleman.playBasketball();
middleman.endorsement();
}
执行输出:
Let's talk about the money, let James play basketball
James play basketball
Let's talk about the money, let James endorsement
James play basketball
3. 动态代理
- 3.1 概念
代理类在程序运行时创建的代理被成为动态代理。 我们上面静态代理的例子中,代理类(Middleman)是自己定义好的,在程序运行之前就已经编译完成。然而动态代理,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的"指示"动态生成的。相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。
- 3.2 "java.lang.reflect.Proxy"类
现在要生成某一个对象的代理对象,这个代理对象通常也要编写一个类来生成,所以首先要编写用于生成代理对象的类。在java中如何用程序去生成一个对象的代理对象呢,java在JDK1.5之后提供了一个"java.lang.reflect.Proxy"类,通过"Proxy"类提供的一个newProxyInstance方法用来创建一个对象的代理对象,如下所示:
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
newProxyInstance()方法用来返回一个代理对象,这个方法总共有3个参数,ClassLoader loader用来指明生成代理对象使用哪个类装载器,Class<?>[] interfaces用来指明生成哪个对象的代理对象,通过接口指定,InvocationHandler h用来指明产生的这个代理对象要做什么事情。所以我们只需要调用newProxyInstance方法就可以得到某一个对象的代理对象了。
- 3.3 例子
private interface IProxy {
void playBasketball();
void endorsement();
}
private static final class James implements IProxy {
@Override
public void playBasketball() {
System.out.println("James play basketball");
}
@Override
public void endorsement() {
System.out.println("James endorsement");
}
}
private static final class MiddlemanProxy {
private IProxy james = new James();
IProxy getProxy() {
return (IProxy) Proxy.newProxyInstance(James.class.getClassLoader(), james.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("playBasketball")) {
System.out.println("Let's talk about the money, let James play basketball");
return method.invoke(james, args);
} else if (method.getName().equals("endorsement")) {
System.out.println("Let's talk about the money, let James endorsement");
return method.invoke(james, args);
}
return null;
}
});
}
}
public static void main(String[] args) {
MiddlemanProxy middlemanProxy = new MiddlemanProxy();
IProxy proxy = middlemanProxy.getProxy();
proxy.playBasketball();
proxy.endorsement();
}
执行输出:
Let's talk about the money, let James play basketball
James play basketball
Let's talk about the money, let James endorsement
James endorsement
可以看到,效果与静态代理一样,后面将分析动态代理的实现原理。
4. 代理模式的优缺点
优点:
- 代理模式可以将代理对象和真实被调用的目标对象隔离。
- 一定程度上降低了系统耦合度,扩展性好。
- 保护目标对象。
- 增强目标对象。
缺点:
- 代理模式会造成系统设计中类的数量增加。
- 在客户端和目标对象中间增加一个代理对象,会造成请求处理速度变慢。
- 增加系统的复杂度。
5. 代理模式的使用场景
- 1.远程(Remote)代理:为一个位于不同的地址空间的对象提供一个局域代表对象。这个不同的地址空间可以是在本机器中,也可是在另一台机器中。远程代理又叫做大使(Ambassador)。好处是系统可以将网络的细节隐藏起来,使得客户端不必考虑网络的存在。客户完全可以认为被代理的对象是局域的而不是远程的,而代理对象承担了大部份的网络通讯工作。由于客户可能没有意识到会启动一个耗费时间的远程调用,因此客户没有必要的思想准备。
- 2.虚拟(Virtual)代理:懒加载,根据需要创建一个资源消耗较大的对象,使得此对象只在需要时才会被真正创建。使用虚拟代理模式的好处就是代理对象可以在必要的时候才将被代理的对象加载,代理可以对加载的过程加以必要的优化。当一个模块的加载十分耗费资源的情况下,虚拟代理的好处就非常明显。
- 3.Copy-on-Write代理:虚拟代理的一种。把复制(克隆)拖延到只有在客户端需要时,才真正采取行动。
- 4.保护(Protect or Access)代理:控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限。保护代理的好处是它可以在运行时间对用户的有关权限进行检查,然后在核实后决定将调用传递给被代理的对象。
- 5.Cache代理:为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。
- 6.防火墙(Firewall)代理:保护目标,不让恶意用户接近。
- 7.同步化(Synchronization)代理:使几个用户能够同时使用一个对象而没有冲突。
- 8.智能引用(Smart Reference)代理:当一个对象被引用时,提供一些额外的操作,比如将对此对象调用的次数记录下来等。
6. 动态代理原理分析
以Proxy.newProxyInstance()作为入口分析。
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
// InvocationHandler不能为空
Objects.requireNonNull(h);
// 将代理接口clone生成intfs
final Class<?>[] intfs = interfaces.clone();
// 获取系统的安全管理类
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
// 检查访问权限
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
// 关键的一句,获取代理类的Class对象
Class<?> cl = getProxyClass0(loader, intfs);
try {
if (sm != null) {
// 校验权限
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
// 获取代理类的构造器
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
// 生成代理对象
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
可以看到,利用了反射的机制,关键一句:Class<?> cl = getProxyClass0(loader, intfs)
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
return proxyClassCache.get(loader, interfaces);
}
这里产生了代理类,后面代码中的构造器也是通过这里产生的类来获得,可以看出这个类的产生就是整个动态代理的关键,由于是动态生成的类文件,这里不具体进入分析如何产生的这个类文件,只需要知道这个类文件是缓存在java虚拟机中的。
我们可以来看看系统生成的代理类class是什么样的:
public static void main(String[] args) {
MiddlemanProxy middlemanProxy = new MiddlemanProxy();
IProxy proxy = middlemanProxy.getProxy();
proxy.playBasketball();
proxy.endorsement();
byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", James.class.getInterfaces());
FileOutputStream fos = null;
try {
fos = new FileOutputStream(new File("Proxy0.class"));
fos.write(classFile);
fos.flush();
} catch (Exception e) {
System.out.println(e.getMessage());
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
看看生成的Proxy0.class:
public final class $Proxy0 extends Proxy implements IProxy {
private static Method m1;
private static Method m4;
private static Method m2;
private static Method m3;
private static Method m0;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final void endorsement() throws {
try {
super.h.invoke(this, m4, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void playBasketball() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m4 = Class.forName("io.kzw.advance.csdn_blog.TestDynamicProxy$IProxy").getMethod("endorsement");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("io.kzw.advance.csdn_blog.TestDynamicProxy$IProxy").getMethod("playBasketball");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
可以看到上面JDK生成的代理类class,和静态代理类的结构差不多,只不过调用目标对象是通过反射的方式。