动态代理是什么?

  
  代理这个词并不陌生,是一个比较常用的设计模式,可以叫他委托模式,也可以叫他代理模式。我也比较常用这个模式,但是在使用的时候往往会出现一些不是很让人舒服的地方(下文会具体说明),因此我常常会怀疑自己是不是不该用这个模式,最终想不出个所以然来,导致有部分代码不知道应该怎么美化。
  本文目的仅仅在于 认知 & 学会使用 动态代理,具体的深究与代理的生成只会稍微一笔带过,不打算详细说明,有兴趣可以自己去查阅资料深究一下,我这三脚猫功夫就不误人子弟了。

代理模式

  首先稍微复习一下代理模式,也就是和动态代理模式相对的静态代理模式,将他的缺点暴露出来,也就能更加理解动态代理模式的用处了。

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
//动作接口
public interface Action{
public void doSth();
}

//真实对象
public class RealObj implements Action{
public void doSth(){
sout("我是真正做事情的对象,就叫真实对象好了。。");
}
}

//动作代理者
public class ActionProxy implements Action{
Action realObj;

ActionProxy(Action obj){
this.realrealObj = obj;
}

public void doSth(){
sout("我是代理者,并不实际做事情");
realObj.doSth();//正真做事情的还是realObj
}

}

public class Simulator{
public static void main(String[] args){
ActionProxy proxy = new ActionProxy(new RealObj());
proxy.doSth();
}
}

  上面是一个 代理接口 实际对象 代理对象 的代理模式,代理模式的优点: 扩展原功能,不侵入原代码。然而在使用的时候,并非只会有一个需要代理的动作,比如有doSth,就会有doSthTwo,doSthThree等;另外每一个Action的真实实现者并非只有一个,有RealObj就会有RealObj2、RealObj3。那么想一下,在这样的情况下静态代理会变成怎么样呢???

   首先,不使用代理是这样的:

1
2
3
new RealObj().doSth();
new RealObj2().doSthTwo();
new RealObj3().doSthThree();

   接着,方案一:为每一个Action分别用上静态代理,就会像这样:

1
2
3
proxy1.doSth();
proxy2.doSthTwo();
proxy3.doSthThree();

   问题是什么呢?你需要给每一个动作写一个专门的ActionProxy类,每新增一个动作,多一个代理类,久而久之类就爆炸了..不方便与维护。对于这个问题,虽然有一个解决方案二:只需要一个ActionProxy类,但是实现多个Action接口,这样的缺点就是需要不停的修改这个类的代码;或者是将Action接口中规定多个需要实现的方法,缺点同上一个,需要不停扩展interface以及proxy类。
   毫无疑问,仅仅为了扩展同样的功能,在方案一中,我们会重复创建多个逻辑相同,仅仅RealObject引用不同的Proxy。而在方案二中,会导致proxy的膨胀,而且这种膨胀往往是无意义的。此外,假如方法签名是相同的,更需要在调用的时候引入额外的判断逻辑。这也就是我经常会遇到的一个让我不爽的事情了。。。

动态代理模式

  
  上面花了挺多篇幅来说明静态代理的问题,这是很有必要的,因为只有知道了静态代理会有什么缺点,才能更深刻了解,动态代理在上什么时候适合使用。
   动态代理类需要实现一个关键的接口:InvocationHandler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//这个是java对InvocationHandler接口的定义,我们发现需要实现invoke方法
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}

//这个Handler中的invoke方法中实现了代理类要扩展的公共功能。
public class DynamicProxyHandler implements InvocationHandler{

private Object realObject;

public DynamicProxyHandler(Object realObject) {
this.realObject = realObject;
}

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//代理扩展逻辑
System.out.println("proxy do");
return method.invoke(realObject, args);
}

}

   DynamicProxyHandler 写完了,可以看到在handler中大量用到了object,使用超类是为了能够让他处理所有类型的对象。接下来的问题是怎么用呢?怎么让java自动帮我们生成所需的代理对象呢?

1
2
3
4
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException

   使用Proxy.newProxyInstance的方法来获取一个object对象,这个object对象就是我们所需要的proxy代理者。再来看看这个newProxyInstance方法所需要的参数,看起来很复杂的样子。。
   第一个参数 ClassLoader loader 需要一个类加载器,这个要扩展开就很复杂了,我也说不清,请自行查询。
   第二个参数 Class<?>[] interfaces ,需要的是实现的接口,比如一开始的例子中,实现了Action接口,那么在这里就传入Action 接口 new Class[]{Action.class}(由于可能实现多个接口,那就使用一个class数组来传递)
   第三个参数 InvocationHandler h,一个实现了InvocationHandler接口的对象,那一想就能猜到,就是我们上面写的DynamicProxyHandler类了
  最后完整的看一下使用方法:

1
2
3
4
5
RealObject realObject = new RealObject();

Action proxy = (Action) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Action.class}, new DynamicProxyHandler(realObject));

proxy.doSth();

  对比静态代理中的方案一,生成一个包含我们扩展功能,持有RealObject引用,实现Action接口的代理实例Proxy。只不过这个Proxy不是我们自己写的,而是java帮我们生成的。

  让我们再回顾一下代理三要素:实际对象:RealObject,代理接口:Action,代理对象:Proxy
  上面的代码实含义也就是,输入 RealObject、Action,返回一个Proxy。妥妥的代理模式。
  综上,动态生成+代理模式,也就是动态代理。

  附上个人觉得比较好的模式

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
//这个Handler中的invoke方法中实现了代理类要扩展的公共功能。
public class DynamicProxyHandler implements InvocationHandler{

private Object realObject;

/**
public DynamicProxyHandler(Object realObject) {
this.realObject = realObject;
}
**/

Object getProxy(Object realObject){
this.realObject = realObject;
return Proxy.newProxyInstance(realObject.getClass().getClassLoader()
,realObject.getClass().getInterfaces()
,this);
}

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//代理扩展逻辑
System.out.println("proxy do");
return method.invoke(realObject, args);
}

}

  可以看到,我取消了构造方法,封装了一个getProxy方法来获得proxy对象,只要传入一个realObject对象即可获得代理对象。就像这样:

1
2
Action proxy = (Action) new DynamicProxyHandler().getProxy(new RealObj());
proxy.doSth();

  好了,到这里,大概讲了一下动态代理是什么,能解决什么问题,简单的使用方法。也止步于这里了,深入的研究比如newProxyInstance 究竟干了什么 ,是如何生成对象的,这个就自己研究吧,我就不丢人了。