我们以买房子来举例,小明和对象小花相处已经 3 年了,却仍然没有步入婚姻的殿堂。亲爱的小花说家里要求小明给她买套房子才肯结婚,因为觉得这样子小花以后的生活比较有保障,至少有个住的地方,同时也想考察一下小明的经济实力行不行。
小明很无奈,但是必须得先买房。小明来到了省城买房,刚下车就有卖房中介挤过来,一个劲儿地介绍:想要多大平方的房子?我能帮你找到最合适的房子,我还会帮你搞定银行贷款,不用你来回跑。小明想啊,“靠人不如靠己”,我不能直接去房地产公司买吗?就没有理会这些中介的话。
经过一番周折,小明发现房地产公司大多不负责直接卖房子,而是交给中介处理;还有流程确实很复杂:办理买房手续,资格,以及贷款申请,资格审查。这些如果都是自己去做,不知道效率有多低。看来,“出门还得靠朋友”。好吧,小明只好去和中介周旋了。
其实,上面的例子,就是一个典型的代理模式在实际生活中的应用。在代理模式中,上面的角色也都有对应的表示。
上面说了那么多,那么什么是代理模式呢?
代理模式,为其它对象提供一种代理以控制对这个对象的访问。
下面我们通过上面的例子,来引出代理模式中的角色。
中介在代理模式中是 Proxy
类,他有卖房子的功能;
房地产公司在代理模式中是RealSubject
类,他当然有卖房子的功能;
在程序中,两个类有共同的功能,那么我们就会把这个共同的功能抽取出一个接口来。这里中介和房地产公司的共同功能是卖房子,现在我们抽取一个卖房子的接口,即Subject
接口。
中介和房地产公司之间的关系:中介必须要知道房地产公司,在程序中用关联关系来表示。
中介卖的仍然是房地产公司的房子,真正卖房子的是房地产公司,所以我们说房地产公司是中介所代表的真实实体,也就是说 RealSubject
是 Proxy
所代表的真实实体。
对于小明来说,只要跟中介打交道就行,不用再去跟房地产公司打交道了。
Subject
接口,是抽象角色,是 RealSubject
和 Proxy
的共同接口;RealSubject
类,是真实角色,它实现了抽象角色接口。真实角色实现了真正的业务逻辑;Proxy
类,是代理角色,它同样实现了抽象角色接口。它是真实角色的代理,它保存了真实角色的引用。客户调用 Proxy
类所实现的抽象角色接口,而在这个实现方法里面,就调用真实角色的抽象方法实现。把上面场景用代码实现一下,就是典型的静态代理。
ASellHouseInterface
对应于抽象角色Subject
类:
/**
* A 卖房接口
*/
public interface ASellHouseInterface {
void sellAHouse(float size);
}
ARealEstateCompany
对应于真实角色RealSubject
:
/**
* A 房地产公司
*/
public class ARealEstateCompany implements ASellHouseInterface {
@Override
public void sellAHouse(float size) {
System.out.println("这是一套面积为" + size + "平方的房子。");
}
}
AMediation
对应于代理角色Proxy
:
/**
* A 中介
*/
public class AMediation implements ASellHouseInterface {
/**
* 持有的真实角色对象引用
*/
private ARealEstateCompany company;
public AMediation(ARealEstateCompany company) {
this.company = company;
}
/*前置处理器*/
private void doSomethingBefore() {
System.out.println("帮您分析买房需求,找到最适合您的房子。");
}
/*后置处理器*/
private void doSomethingAfter() {
System.out.println("帮您搞定繁琐的贷款审批,让您购房无忧!");
}
@Override
public void sellAHouse(float size) {
doSomethingBefore();
company.sellAHouse(size);
doSomethingAfter();
}
}
测试代理如下:
public class HouseBuyer {
public static void main(String[] args) {
// 1, 静态代理模式
ARealEstateCompany aRealEstateCompany = new ARealEstateCompany();
AMediation aMediation = new AMediation(aRealEstateCompany);
aMediation.sellAHouse(150f);
}
}
/*
打印结果:
帮您分析买房需求,找到最适合您的房子。
这是一套面积为150.0平方的房子。
帮您搞定繁琐的贷款审批,让您购房无忧!
*/
到这里,我们总结一下静态代理的步骤:
但是,静态代理虽然很好地描述了上面的例子,但是它仍有些不足之处。
我们继续上面的例子来说明:小花知道亲爱的小明在看房子了,小花当然很开心,但是小花不想背负太大的经济压力,所以小花重点关注了房价这个指标,她也找了相应的房地产公司以及卖房中介。
相应的类如下:
BSellHouseInterface
对应于抽象角色Subject
类:
/**
* B 卖房接口
*/
public interface BSellHouseInterface {
void sellBHouse(float price);
}
BRealEstateCompany
对应于真实角色RealSubject
:
/**
* B 房地产公司
*/
public class BRealEstateCompany implements BSellHouseInterface {
@Override
public void sellBHouse(float price) {
System.out.println("这是一套价值为" + price + "万的房子。");
}
}
BMediation
对应于代理角色Proxy
:
/**
* B 中介
*/
public class BMediation implements BSellHouseInterface {
// 真实的对象
private BRealEstateCompany company;
public BMediation(BRealEstateCompany company) {
this.company = company;
}
/*前置处理器*/
private void doSomethingBefore() {
System.out.println("帮您分析买房需求,找到最适合您的房子。");
}
/*后置处理器*/
private void doSomethingAfter() {
System.out.println("帮您搞定繁琐的贷款审批,让您购房无忧!");
}
@Override
public void sellBHouse(float price) {
doSomethingBefore();
company.sellBHouse(price);
doSomethingAfter();
}
}
测试代码如下:
public class HouseBuyer {
public static void main(String[] args) {
// 1, 静态代理模式
// 小明买房
ARealEstateCompany aRealEstateCompany = new ARealEstateCompany();
AMediation aMediation = new AMediation(aRealEstateCompany);
aMediation.sellAHouse(150f);
// 小花买房
BRealEstateCompany bRealEstateCompany = new BRealEstateCompany();
BMediation bMediation = new BMediation(bRealEstateCompany);
bMediation.sellBHouse(30);
}
}
/*
打印结果:
帮您分析买房需求,找到最适合您的房子。
这是一套面积为150.0平方的房子。
帮您搞定繁琐的贷款审批,让您购房无忧!
帮您分析买房需求,找到最适合您的房子。
这是一套价值为30.0万的房子。
帮您搞定繁琐的贷款审批,让您购房无忧!
*/
有同学看到这里,会想:这和小明找中介买房的代码很相似,可以使用一套代码来实现,一个中介可以对应于多个开发商嘛。这样可以实现一些代码的复用。因为如果都是一对一(一个中介对应于一个房地产公司),那么会出现很多代理对象,代码量变大,可维护性差的问题。
代码可以这样写:
ABMediation
是代理角色,它对应于 2 家房地产公司:ARealEstateCompany
和 BRealEstateCompany
/**
* A & B 中介
*/
public class ABMediation implements ASellHouseInterface, BSellHouseInterface {
// 真实的对象
private ARealEstateCompany aCompany;
private BRealEstateCompany bCompany;
public ABMediation(ARealEstateCompany aCompany, BRealEstateCompany bCompany) {
this.aCompany = aCompany;
this.bCompany = bCompany;
}
/*前置处理器*/
private void doSomethingBefore() {
System.out.println("帮您分析买房需求,找到最适合您的房子。");
}
/*后置处理器*/
private void doSomethingAfter() {
System.out.println("帮您搞定繁琐的贷款审批,让您购房无忧!");
}
@Override
public void sellAHouse(float size) {
doSomethingBefore();
aCompany.sellAHouse(size);
doSomethingAfter();
}
@Override
public void sellBHouse(float price) {
doSomethingBefore();
bCompany.sellBHouse(price);
doSomethingAfter();
}
}
下面是测试代码:
public class HouseBuyer {
public static void main(String[] args) {
// 1, 静态代理模式
// 小明买房,一个中介对应一个房地产公司(一对一)
ARealEstateCompany aRealEstateCompany = new ARealEstateCompany();
AMediation aMediation = new AMediation(aRealEstateCompany);
aMediation.sellAHouse(150f);
// 小花买房,一个中介对应一个房地产公司(一对一)
BRealEstateCompany bRealEstateCompany = new BRealEstateCompany();
BMediation bMediation = new BMediation(bRealEstateCompany);
bMediation.sellBHouse(30);
// 小明,小花买房,一个中介对应多个房地产公司(一对多)
ABMediation abMediation = new ABMediation(aRealEstateCompany, bRealEstateCompany);
abMediation.sellAHouse(180f);
abMediation.sellBHouse(40);
}
}
/*
打印结果:
帮您分析买房需求,找到最适合您的房子。
这是一套面积为150.0平方的房子。
帮您搞定繁琐的贷款审批,让您购房无忧!
帮您分析买房需求,找到最适合您的房子。
这是一套价值为30.0万的房子。
帮您搞定繁琐的贷款审批,让您购房无忧!
帮您分析买房需求,找到最适合您的房子。
这是一套面积为180.0平方的房子。
帮您搞定繁琐的贷款审批,让您购房无忧!
帮您分析买房需求,找到最适合您的房子。
这是一套价值为40.0万的房子。
帮您搞定繁琐的贷款审批,让您购房无忧!
*/
上面的方案,确实可以减少代理对象,但是却违法了开闭原则—对于扩展开放(Open for extension),对于修改关闭(Closed for modification)。
因为如果我们需要增加一家房地产公司 C,就不得不去修改 ABMediation
类;如果小明和小花的需求改变了,比如要买地段好的房子,那么就需要修改接口,这样也不得不修改 ABMediation
类。
而动态代理可以解决这些问题。
JDK 支持动态代理,需要使用java.lang.reflect
包下的 Proxy
类和 InvocationHandler
接口。
从这里开始往下写,我花了一天的工夫查看文档,在想怎么写,因为我本想采取一步一步引入的方式来介绍 JDK 中的动态代理,但是发现一天结束了我还是一头雾水,不知道从哪里开始说起(其实是我对动态代理的原理不理解)。是啊,对于 Java 了解浅薄的我,却异想天开地打算从零推出 Java 设计者们设计精巧的动态代理实现,这多么不契合实际。
我想了想,JDK 中的动态代理本身就是一种固定化的实现,我何不先照着 JDK 预定的方式实现动态代理,再去理解其中的设计呢?我希望自己能够接近 Java 设计者的思想。
所以,我打算这样分析:先直接照着文档实现动态代理;然后再进行源码方面的分析;最后,用自己的语言把动态代理的过程描述一下。
首先,定义MediationCompany
类,它是 InvocationHandler
接口的实现:
public class MediationCompany implements InvocationHandler {
// 真实的对象
private Object realEstateCompany;
public void setRealEstateCompany(Object realEstateCompany) {
this.realEstateCompany = realEstateCompany;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
doSomethingBefore();
Object result = method.invoke(realEstateCompany, args);
doSomethingAfter();
return result;
}
/*前置处理器*/
private void doSomethingBefore() {
System.out.println("帮您分析买房需求,找到最适合您的房子。");
}
/*后置处理器*/
private void doSomethingAfter() {
System.out.println("帮您搞定繁琐的贷款审批,让您购房无忧!");
}
}
MediationCompany
实现了 InvocationHandler
接口中的 invoke 方法,并且持有了真实对象的引用。在实现的 invoke
方法中,获取到 Medthod
对象,通过反射调用了真实对象上的对应的方法,并返回了调用后的结果。
这里需要说明一下,持有的真实对象的引用是 Object
类型的,这有什么好处呢?好处是,如果现在要传给 MediationCompany
另外一个真实对象(实现自不同的接口),就不需要更改 MediationCompany
的类结构了。
其次,看一下客户端的代码:
public class HouseBuyer {
public static void main(String[] args) {
// 2, 动态代理模式
MediationCompany mediationCompany = new MediationCompany();
// 小明买房
ARealEstateCompany aRealEstateCompany = new ARealEstateCompany();
mediationCompany.setRealEstateCompany(aRealEstateCompany);
ASellHouseInterface consultant1 = (ASellHouseInterface) Proxy.newProxyInstance(
aRealEstateCompany.getClass().getClassLoader(),
new Class[] {ASellHouseInterface.class},
mediationCompany);
consultant1.sellAHouse(100f);
// 小花买房
BRealEstateCompany bRealEstateCompany = new BRealEstateCompany();
mediationCompany.setRealEstateCompany(bRealEstateCompany);
BSellHouseInterface consultant2 = (BSellHouseInterface) Proxy.newProxyInstance(
bRealEstateCompany.getClass().getClassLoader(),
new Class[] {BSellHouseInterface.class},
mediationCompany);
consultant2.sellBHouse(20);
}
}
客户端需要创建动态代理的实例,这需要借助 Proxy
类的静态方法 newProxyInstance
:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
参数一:ClassLoader loader
,需要一个类加载器,通常从已经被加载的对象中获取其类加载器,传递给它就行;
参数二:Class>[] interfaces
,就是这个代理类要实现的接口列表(这里需要注意:是接口,而不是类或者抽象类);
参数三:InvocationHandler h
,这是 InvocationHandler
的一个实现。因为上面定义了 MedicationCompany
实现了 InvocationHandler
接口,所以我们传递一个 MediationCompany
对象即可。
注意到 newProxyInstance
方法的返回值是一个 Object
类型,所以我们需要把它的返回值强制类型转换为对应的公共接口,如 ASellHouseInterface
,BSellHouseInterface
。这样才能调用对应的接口方法。
我们运行一下上面的代码,打印结果如下:
帮您分析买房需求,找到最适合您的房子。
这是一套面积为100.0平方的房子。
帮您搞定繁琐的贷款审批,让您购房无忧!
帮您分析买房需求,找到最适合您的房子。
这是一套价值为20.0万的房子。
帮您搞定繁琐的贷款审批,让您购房无忧!
可以看到,动态代理的代码完全实现了静态代理的功能。
但是,我们可不能到此为止。
我们心中还有疑问:
consultant1.sellAHouse(100f);
,却是在 MediationCompany
类实现的 invoke
方法输出了结果?ASellHouseInterface consultant1
和 BSellHouseInterface consultant2
,但是为什么不能在 out 目录下找到它们对应的 .class
文件?关于问题1,我们在上面的代码中添加一下日志:
public class MediationCompany implements InvocationHandler {
// 真实的对象
private Object realEstateCompany;
public void setRealEstateCompany(Object realEstateCompany) {
this.realEstateCompany = realEstateCompany;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("invoke(): proxy = " + proxy.getClass() + ", method = " + method + ", args = " + Arrays.toString(args));
doSomethingBefore();
Object result = method.invoke(realEstateCompany, args);
doSomethingAfter();
return result;
}
/*前置处理器*/
private void doSomethingBefore() {
System.out.println("帮您分析买房需求,找到最适合您的房子。");
}
/*后置处理器*/
private void doSomethingAfter() {
System.out.println("帮您搞定繁琐的贷款审批,让您购房无忧!");
}
}
public class HouseBuyer {
public static void main(String[] args) {
// 2, 动态代理模式
MediationCompany mediationCompany = new MediationCompany();
// 小明买房
ARealEstateCompany aRealEstateCompany = new ARealEstateCompany();
mediationCompany.setRealEstateCompany(aRealEstateCompany);
ASellHouseInterface consultant1 = (ASellHouseInterface) Proxy.newProxyInstance(
aRealEstateCompany.getClass().getClassLoader(),
new Class[] {ASellHouseInterface.class},
mediationCompany);
System.out.println("consultant1 = " + consultant1.getClass());
consultant1.sellAHouse(100f);
// 小花买房
BRealEstateCompany bRealEstateCompany = new BRealEstateCompany();
mediationCompany.setRealEstateCompany(bRealEstateCompany);
BSellHouseInterface consultant2 = (BSellHouseInterface) Proxy.newProxyInstance(
bRealEstateCompany.getClass().getClassLoader(),
new Class[] {BSellHouseInterface.class},
mediationCompany);
System.out.println("consultant2 = " + consultant2.getClass());
consultant2.sellBHouse(20);
}
}
运行一下,查看打印结果:
consultant1 = class com.sun.proxy.$Proxy0
invoke(): proxy = class com.sun.proxy.$Proxy0, method = public abstract void com.java.advanced.features.proxy.ASellHouseInterface.sellAHouse(float), args = [100.0]
帮您分析买房需求,找到最适合您的房子。
这是一套面积为100.0平方的房子。
帮您搞定繁琐的贷款审批,让您购房无忧!
consultant2 = class com.sun.proxy.$Proxy1
invoke(): proxy = class com.sun.proxy.$Proxy1, method = public abstract void com.java.advanced.features.proxy.BSellHouseInterface.sellBHouse(float), args = [20.0]
帮您分析买房需求,找到最适合您的房子。
这是一套价值为20.0万的房子。
帮您搞定繁琐的贷款审批,让您购房无忧!
从上面的日志,我们可以知道在客户端动态代理实例 constultant1
调用了 consultant1.sellAHouse(100f);
,在 MediationCompany
的 invoke 方法中就接收到了 method = public abstract void com.java.advanced.features.proxy.ASellHouseInterface.sellAHouse(float), args = [100.0]
,这两者的信息是一模一样的。这说明客户端的动态代理实例调用 sellAHouse
方法,和 MediationCompnay
的 invoke
方法必然存在某种机制,把客户端的动态代理实例的方法调用转发给了 MediationCompany
(一个InvocationHandler
接口的实现类) 的 invoke
方法来处理。不过,这只是初步的观点,我们还需要查看源码来验证。
注意到,好事的我还打印了两个动态代理实例对应的 Class 信息,以及 invoke
方法的第一个参数 Object proxy
对应的 Class
信息。截取出来如下:
consultant1 = class com.sun.proxy.$Proxy0
invoke(): proxy = class com.sun.proxy.$Proxy0
consultant2 = class com.sun.proxy.$Proxy1
invoke(): proxy = class com.sun.proxy.$Proxy1
可以看到,动态代理类被 invoke
方法给接收了。另外,也是很重要的一点,不知道你看出来没有,动态代理类的名字怎么这么怪?它的路径竟然不和程序中的包名一样,名字里还有一个 $
符号。
我们或许打算在工程的编译器输出目录下找有没有 com.sun.proxy.$Proxy0.class
的存在,因为我们知道:Java 源文件(.java 文件)需要经过 javac 命令编译成 Java 字节码(.class 文件);Java 字节码(.class 文件)经过 java 命令开始执行。那么,我们有理由相信一定在某个地方存放着 com.sun.proxy.$Proxy0.class
,不然不可能有对应的动态代理对象 consultant1
。不过,在 out 目录下有 MediationCompany.class
,但是根本没有 com.sun.proxy.$Proxy0.class
,看下图:
到这里,如果不去查看源码,还有什么办法解开我们的疑问呢?同时,我们也是带着我们的疑问去查看源码,这样也更加有目标。
我们的问题目前有两个:
1,动态代理实例(它是通过 Proxy.newProxyInstance
方法创建的)上的方法调用如何转发给了 InvocationHandler
的 invoke
方法?
2,动态代理类编译好的 .class
文件在什么地方存放?
这里查看的是 JDK1.8 的源码
Prxoy.newProxyInstance()
方法从 Proxy
类的 newProxyInstance()
方法开始:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
// 省略校验代码部分
/*
* Look up or generate the designated proxy class.
*/
Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
*/
final Constructor<?> cons = cl.getConstructor(constructorParams);
// 省略了非关键的代码部分
return cons.newInstance(new Object[]{h});
// 省略了异常捕获的部分
}
这个方法接收了一个 ClassLoader
对象,一个代理要实现的接口列表,一个 InvocationHandler
实现类对象。
看一下 Class> cl = getProxyClass0(loader, intfs);
这行代码的注释:Look up or generate the designated proxy class(查找或生成指定的代理类),从里我们可以推断,这个方法就是用来获取代理类的。这个方法需要两个参数:一个 ClassLoader
对象,一个 InvocationHandler
实现类对象。
接着看 final Constructor> cons = cl.getConstructor(constructorParams);
这行代码,这是获取 Class> cl
的带有参数的 Constructor 对象,其中 constructorParams
是:
/** parameter types of a proxy class constructor */
private static final Class<?>[] constructorParams =
{ InvocationHandler.class };
最后看 return cons.newInstance(new Object[]{h});
这行代码就是创建动态代理的实例了,通过反射调用动态代理类的带有 InvocationHandler
类型参数的构造方法而创建的。
Proxy
的 getProxyClass0()
方法 private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
// If the proxy class defined by the given loader implementing
// the given interfaces exists, this will simply return the cached copy;
// otherwise, it will create the proxy class via the ProxyClassFactory
return proxyClassCache.get(loader, interfaces);
}
自然,应该看 return proxyClassCache.get(loader, interfaces);
这行代码,同样地,不要视注释而不见。我们把注释翻译一下:如果由给定的加载器定义的且实现了给定的接口的代理类存在,这里就会直接返回缓存的副本;否则,这里将通过 ProxyClassFactory
创建代理类。
划一下重点啊:创建代理类要靠 ProxyClassFactory
类。
ProxyClassFactory
是 Proxy
类的静态内部类。
ProxyClassFactory
类的 Class> apply(ClassLoader loader, Class>[] interfaces)
方法 // prefix for all proxy class names
private static final String proxyClassNamePrefix = "$Proxy";
// next number to use for generation of unique proxy class names
private static final AtomicLong nextUniqueNumber = new AtomicLong();
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
// 省略对 interfaces 进行校验的 for 循环
String proxyPkg = null; // package to define proxy class in
int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
// 省略处理 非 public 接口的 for 循环
if (proxyPkg == null) {
// if no non-public proxy interfaces, use com.sun.proxy package
// 这里 ReflectUtil.PROXY_PACKAGE 的值是"com.sun.proxy"
proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
}
/*
* Choose a name for the proxy class to generate.
*/
long num = nextUniqueNumber.getAndIncrement();
// 拼接处代理类的名字:"com.sun.proxy" + "$Proxy"+ long 型数字编号
String proxyName = proxyPkg + proxyClassNamePrefix + num;
/*
* Generate the specified proxy class.
* 生成指定的代理类
*/
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
}
defineClass0()
是一个本地方法,用来生成 Class
对象。我们无法查看 defineClass0()
的实现。但是,前面一行代码:
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
是生成一个字节数组,这就是对应的.class 文件。
这里可以回答第 2 个问题:上面明明创建了动态代理类:ASellHouseInterface consultant1
和 BSellHouseInterface consultant2
,但是为什么不能在 out 目录下找到它们对应的 .class
文件?
因为程序中默认并没有生成对应的 .class
文件在磁盘上,而是在内存中直接使用,用于创建动态代理类。
我们可以把这个字节数组写到本地来查看一下。
新建一个ProxyUtils
类:
public class ProxyUtils {
/**
* 参数一:String proxyName,动态代理类的名字;
* 参数二:Class>[] interfaces,代理类要实现的接口列表
*/
public static void generateClassFile(String proxyName, Class<?>[] interfaces) {
/*
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);*/
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces);
// 获取当前的class文件输出目录
String path = ProxyUtils.class.getResource(".").getPath();
System.out.println(path);
FileOutputStream fos = null;
try {
fos = new FileOutputStream(path + proxyName + ".class");
fos.write(proxyClassFile);
fos.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
在 HouseBuyer
类的 main
方法中添加代码:
ProxyUtils.generateClassFile(consultant1.getClass().getSimpleName(),
new Class[]{ASellHouseInterface.class});
ProxyUtils.generateClassFile(consultant2.getClass().getSimpleName(),
new Class[]{BSellHouseInterface.class});
for (Method method : aRealEstateCompany.getClass().getMethods()) {
System.out.println(method.getName());
}
运行代码,打印结果如下:
/out/production/Java_01_AdvancedFeatures/com/java/advanced/features/proxy/dynamic/
/out/production/Java_01_AdvancedFeatures/com/java/advanced/features/proxy/dynamic/
在对应的目录下,可以看到生成了两个文件 $Proxy0.class
和 $Proxy1.class
:
打开 $Proxy0.class
,它就是 consultant1
这个动态代理类对应的字节码文件:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
import com.java.advanced.features.proxy.ASellHouseInterface;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0 extends Proxy implements ASellHouseInterface {
private static Method m1;
private static Method m3;
private static Method m2;
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 sellAHouse(float var1) throws {
try {
super.h.invoke(this, m3, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
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 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"));
m3 = Class.forName("com.java.advanced.features.proxy.ASellHouseInterface").getMethod("sellAHouse", Float.TYPE);
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
网上有人通过在 main 方法开始处,加入:
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
这就打开了 ProxyGenerator
的 saveGeneratedFiles
标记为 true。
运行后,在 src 的同级目录下,会出现一个 com.sun.proxy
包,这里就是动态生成的代理类存放的地方。
看一下 $Proxy0
类:
首先,看类声明,它继承了 Proxy
类,并实现了 ASellHouseInterface
接口。这里解释了为什么在通过 Proxy.newProxyInstance()
方法获取动态实例后,进行类型强制转换为 ASellHouseInterface
。有同学觉得应该转型为 ARealEstateCompany
的,可以看到明显是不对的。
它有一个带有 InvocationHandler
参数的构造方法,这就解释了 Prxoy.newProxyInstance()
方法中对动态代理类获取有 InvocationHandler
参数的 Constructor
对象,并调用其 newInstance()
方法(需要传递一个 InvocationHandler
对象)获取动态代理实例。在构造方法内部,调用了父类 Proxy
的带有 InvocationHandler
参数的构造方法,完成了对父类 Proxy
类的 protected InvocationHandler h;
赋值操作。
再往下看成员方法,有一个 sellAHouse(float val1)
的方法:
public final void sellAHouse(float var1) throws {
try {
super.h.invoke(this, m3, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
这个方法就是我们通过 consultant1.sellAHouse(100f);
调用的。再说明一下:consultant1
是一个动态代理对象,对应的字节码文件就是 $Proxy0.class
。方法体内部,super.h.invoke(this, m3, new Object[]{var1});
调用了 h 变量上的 invoke() 方法。h
是什么呢?它是超类 Proxy
中的一个字段:
/**
* the invocation handler for this proxy instance.
* @serial
*/
protected InvocationHandler h;
而这个 InvocationHandler
对象就是实现了 InvocationHandler
接口的一个实现类对象,在程序中就是 MediationCompany
对象。我们是在 Proxy.newProxyInstance()
方法中传入的 MediationCompany
对象。
那么,super.h.invoke(this, m3, new Object[]{var1});
这行代码就是调用了 MediationCompany
类中的 invoke()
方法。第一个参数是 this
,即动态代理类$Proxy0
的实例,第二个参数是 m3
,是一个 Method
对象,它是在静态代码块中早已赋值:
m3 = Class.forName("com.java.advanced.features.proxy.ASellHouseInterface").getMethod("sellAHouse", Float.TYPE);
m3
就是 sellAHouse
方法对应的 Method
实例。第三个参数是new Object[]{var1}
是参数列表。
到这里,可以回答第 1 个问题:为什么在客户端调用动态代理对象的方法 consultant1.sellAHouse(100f);
,却是在 MediationCompany
类实现的 invoke
方法输出了结果?
在客户端通过 Proxy.newProxyInstance(ClassLoader loader, Class>[] interfaces, InvocationHandler h)
方法创建了一个动态代理实例 consultant1
,即 $Proxy0
类的实例。而在 newProxyInstance
中传入的第三个参数:InvocationHandler
对象(即 MediationCompany
对象),在通过反射调用 $Proxy0
类的带 InvocationHandler
参数 构造方法时,交给 $Proxy0
对象持有。在调用 consultant1.sellAHouse(100f);
时,就是调用了 $Proxy0
中的 sellAHouse()
方法。在方法体内,调用了 $Proxy0
持有的 InvocationHandler
对象的 invoke
方法,当然就是调用 MediationCompany
中的 invoke
方法。
在 Proxy.newProxyInstance(ClassLoader loader, Class>[] interfaces, InvocationHandler h)
方法中做了两件事情:第一,通过前两个参数生成动态代理类;第二,反射创建动态代理实例并把 InvocationHandler h
交给动态代理实例。
调用动态代理实例的方法,在方法内部,把业务处理交给自己持有的 InvocationHandler h
的 invoke
方法来处理。
动态代理的步骤:
InvocationHandler
的调用处理程序,它持有真实角色的引用;Proxy
类的 newProxyInstance
方法动态生成动态代理对象;优点 | 缺点 | |
---|---|---|
动态代理 | 1,代理类在程序运行时动态生成,减少了手动写代理类的麻烦;2,可以在原始类和接口都未知的情况下,就确定代理类的代理行为。 | 1,抽象角色只能是接口,不能是类或者抽象类;2,通过反射生成代理类并反射调用真实对象的方法,效率较低。 |
静态代理 | 1,抽象角色可以是接口,类,抽象类;2,实现方式较为简单,没有效率问题。 | 1,违反了开闭原则:接口改变,都不得不修改静态代理的代码;2,需要手动编写代理类,有些麻烦。 |
对于静态代理,可以查看Android-如何优雅的处理重复点击;
对于动态代理,可以查看 从动态代理角度看Retrofit,这才是Retrofit的精髓!。
代码地址在https://github.com/jhwsx/Java_01_AdvancedFeatures/tree/master/src/com/java/advanced/features/proxy。