Title: 初尝Java动态代理
Date: 2018-11-22 12:30
Category: 技术博客
Modified: 2018-11-22 12:30
Tags: 动态代理
Slug: Dynamic_Proxy
Authors: Victor Lv
Summary: 有动自有静,在看动态代理之前先看更简单明了的“静态代理”。下面以实例描述。
有动自有静,在看动态代理之前先看更简单明了的“静态代理”。下面以实例描述。
(静态)代理
定义一个 Human 接口:
Human.java
public interface Human {
void goToWork();
}
我们假设所有 Human 都有一个动作就是 goToWork。然后在 Human 的基础上衍生出 Man 和 Woman :
Man.java
public class Man implements Human {
@Override
public void goToWork() {
System.out.println("Man go to work");
}
}
Woman.java
public class Woman implements Human {
@Override
public void goToWork() {
System.out.println("Woman go to work");
}
}
当我们想分别驱使上面的 Man 和 Woman 都去工作时,很简单,分别 new 一个 Man 和 Woman 出来,然后分别调用 goToWork 方法即可:
Man man = new Man();
man.goToWork();
Woman woman = new Woman();
woman.goToWork();
/**
* Output:
* Man go to work
* Woman go to work
*/
然后,如果我们想在人们 goToWork 之前叫他们干点别的必备的事情呢?比如让他们出门 goToWork 之前都把工作服穿上。或者让他们 goToWork 完成之后把工作服脱掉?
把 Man 和 Woman 的实现都修改一遍?No,我们可以用代理来帮我们实现这件事。
一开始,当我们(假设我们是有钱的大老板)想驱使 Man 和 Woman 去干活时,我们需要一一去叫他们:“Hey, Man,go to work!” 和 “Hey, Woman, go to work!",然后,我们觉得他们的穿的衣服太丑了,想叫他们工作之前统一穿上工作服,那我们同样需要给他们分别进行“思想改造”:“Hey, Man, dress up before you go to work!”,“Hey, Woman, dress up before you go to work!”。
这样传统的方法,有两个问题,第一:作为有钱的大老板,对于这些琐碎而重复的事,当然想节省点口舌;第二:这些底层劳工,要对他们进行“思想改造”,难滴很。
于是,机智的老板想到了一个办法:请一个监工(代理),让这个监工自动帮他们穿上和脱下工作服,用代码描述如下:
HumanProxy.java
/**
* @ClassName: HumanProxy
* @Description: TODO
* @Author: Victor Lv (https://Victor-Lv.github.io)
* @Date: 2018/11/22 10:41
* @Version: 1.0
*/
public class HumanProxy implements Human{
Human human;
HumanProxy(Human human) {
this.human = human;
}
@Override
public void goToWork() {
beforeGoToWork();
this.human.goToWork();
afterGoToWork();
}
private void beforeGoToWork(){
System.out.println("Dress up work clothes before go to work");
}
private void afterGoToWork(){
System.out.println("Take off work clothes after go to work");
}
}
然后,老板只需要做同样的工作量,如下:
Human man = new HumanProxy(new Man());
man.goToWork();
Human woman = new HumanProxy(new Woman());
woman.goToWork();
/**
* Output:
*
* Dress up work clothes before go to work
* Man go to work
* Take off work clothes after go to work
* Dress up work clothes before go to work
* Woman go to work
* Take off work clothes after go to work
*/
就可以做到更多的事情了。一来解放了自己的双手,同样的工作量;二来,也无须对 Man 和 Woman 进行艰难的“思想改造”了,是不是很棒!
上面就是所谓的静态代理模式。
动态代理
上面还只是一个很简单的任务叠加,使用静态代理模式还没啥问题。但是,随着本工厂的业务的扩展和工人数量的增多,我们生产出了一个打卡器用来监控每个工人的上下班时间,工人们上班前和下班后都要打一次卡。
于是,用上面静态代理的方式,我们很容易用同样的方法造出一个“打卡器代理”:
TimeProxy.java
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @ClassName: TimeProxy
* @Description: TODO
* @Author: Victor Lv (https://Victor-Lv.github.io)
* @Date: 2018/11/22 10:54
* @Version: 1.0
*/
public class TimeProxy implements Human {
Human human;
TimeProxy(Human human) {
this.human = human;
}
@Override
public void goToWork() {
logTime();
human.goToWork();
logTime();
}
private void logTime(){
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
String currentTime = dateFormat.format(new Date());
System.out.println("Current time is: " + currentTime);
}
}
进行调用:
Human man = new TimeProxy(new Man());
man.goToWork();
Human woman = new TimeProxy(new Woman());
woman.goToWork();
/**
* Output:
*
* Current time is: 2018-11-22 16:07:36:161
* Man go to work
* Current time is: 2018-11-22 16:07:36:163
* Current time is: 2018-11-22 16:07:36:164
* Woman go to work
* Current time is: 2018-11-22 16:07:36:165
*/
老板发现“打卡器–TimeProxy”这玩意很好用,于是想把它推向市场,适配更多用户,而不仅仅是本工厂的 Man 和 Woman。
然后问题来了,我们有几百类目标用户,总不能让"TimeProxy" 去 implements
这几百类接口或者写几百个这样的静态代理吧?
而动态代理就能解决这个问题,把静态代理进行动态的拓展。下面就来看看动态代理是怎么做到这件事的:
DynamicTimeProxy.java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @ClassName: DynamicTimeProxy
* @Description: TODO
* @Author: Victor Lv (https://Victor-Lv.github.io)
* @Date: 2018/11/22 11:10
* @Version: 1.0
*/
public class DynamicTimeProxy implements InvocationHandler {
private Object target;
public Object bind(Object target) {
this.target = target;
Object result = Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this);
System.out.println("target address:" + System.identityHashCode(target));
System.out.println("result address:" + System.identityHashCode(result));
return result;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
logTime();
result = method.invoke(target, args);
logTime();
return result;
}
private void logTime(){
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
String currentTime = dateFormat.format(new Date());
System.out.println("Current time is: " + currentTime);
}
}
调用测试:
DynamicTimeProxy dynamicTimeProxy = new DynamicTimeProxy();
System.out.println("dynamicTimeProxy address:" + System.identityHashCode(dynamicTimeProxy));
Human human1 = (Human) dynamicTimeProxy.bind(new Man());
System.out.println("human1 address:" + System.identityHashCode(human1));
human1.goToWork();
Human human2 = (Human) dynamicTimeProxy.bind(new Woman());
System.out.println("human2 address:" + System.identityHashCode(human2));
human2.goToWork();
/**
* Output:
dynamicTimeProxy address:1163157884
target address:2125039532
result address:312714112
human1 address:312714112
Current time is: 2018-11-23 10:48:33:055
Man go to work
Current time is: 2018-11-23 10:48:33:057
target address:1878246837
result address:929338653
human2 address:929338653
Current time is: 2018-11-23 10:48:33:060
Woman go to work
Current time is: 2018-11-23 10:48:33:060
*/
关于动态代理的原理,网上很多文章都写的不明不白,自己也翻了 JDK 源码,有点被层层调用绕晕了,并且到了最底层调用的是无法直接追寻源码的 native 方法(native 方法表明它的具体实现用 JDK 底层语言实现的,比如 C 语言)。直到我看到了 IBM developerWorks 的这篇剖析地很棒的文章:Java 动态代理机制分析及扩展,第 1 部分。
用一张图就能看的很直白了:
动态代理类的继承图(转载自Java 动态代理机制分析及扩展,第 1 部分)
解释:ProxyN 继承_extends 了 Proxy(对应上述例子的 DynamicTimeProxy),并且实现_implements 了 InterfaceA/B/X(对应上述例子的 Bird 接口)。
有了这张图我们就能很好地解释动态代理的原理了。上述动态代理最关键的一处代码是
DynamicTimeProxy
的bind()
方法中的Proxy.newProxyInstance
。这里干了些什么呢?就是上面继承图描述的,这里它生成了一个对象 ProxyN,这个 ProxyN 继承于 DynamicTimeProxy ,并且实现了 Human 接口(具体实现就是用的 invoke 方法里面的内容),一个 extends 加一个 implements,于是,这个 ProxyN 既拥有 Human 所有的方法,又拥有一个内部属性 target。
1、所以我们也就能理解为什么我们能把 Proxy.newProxyInstance
生成出来的对象(ProxyN)安全转换成 Human
了,因为它 implements 了 Human。那这个 ProxyN 是不是一个 Man/Woman 呢?测试一下就知道了:
DynamicTimeProxy dynamicTimeProxy = new DynamicTimeProxy();
Man man = (Man) dynamicTimeProxy.bind(new Man());
man.goToWork();
/**
* Output(Exception):
Exception in thread "main" java.lang.ClassCastException: com.sun.proxy.$Proxy0 cannot be cast to Man
*/
可以发现 ProxyN 并不能转成 Man,说明它并不是(extends)一个 Man。
2、所以之后对于Proxy.newProxyInstance
生成对象 human1/human2 方法的调用,实际上调用的是这个 ProxyN Override出来的方法(假设是 newGoToWork()),而 newGoToWork() 一方面在 goToWork 之前和之后加上了新功能,另一方面它内部调用的 goToWork 实际上又是 ProxyN 内部的 target(Man/Woman)的 goToWork 方法。于是这就能完美解释程序的输出结果了。
此外,上述程序中为了证明生成的对象是不一样的(地址),我把他们的内存地址用 System.identityHashCode
打印了出来。
于是乎,有了这个动态代理,工厂老板就可以把它的“打卡器“推向更广阔的市场应用场景了。
最后,再次推荐看下这篇文章,关于动态代理的原理说的够清楚了:
Java 动态代理机制分析及扩展,第 1 部分