深入Java-动态代理+源码分析Proxy、InvocationHandler

先来理一下概念理论

代理三要素

抽象主题角色(Subject)
具体主题角色(RealSubject)
代理主题角色(Proxy)

代理关系图

image.png

栗子

Subject:购房需求
RealSubject:小明的购房需求
Proxy: 中介A(只为小明服务)

中介可以帮助或者代理小明做一些事情,比如筛选房源、预沟通等等,这就是代理的好处,专业、高效。

但是有以下问题:
1、中介A只为小明服务,如果小红、小强都要买房,怎么办呢?
2、小明还有买车需求,也想找中介帮忙,怎么办呢? 中介A懂房但不懂车。

问题

静态代理中,此时就需要new 新的代理类,无论怎么抽象、怎么封装,一个代理类总是能力有限的。
要为每个目标Subject编写对应的代理类,随着系统庞大,工作量会剧增,并且可维护性变差。
那么思考,面对变化万千的subject,如何才能少写代理类,或者不写代理类,又能完成代理功能呢?

思考方向:
一个接口(subject) --》如何得到一个代理类proxy? 这不是自动生成类吗?
是的,反射、Class

这就是动态代理。

Java的解决方案,动态代理

Java用jdk提供的ProxyInvocationHandler结合实现动态代理功能。
先来看一段使用栗子

// 获取 买房需求 的Class
Class<?> proxyClass = Proxy.getProxyClass(BuyHouse.class.getClassLoader(), BuyHouse.class);

// 通过Class的构造器Constructor 动态创建统一的代理服务Lianjia
Constructor<?> constructor = proxyClass.getConstructor(InvocationHandler.class);
BuyHouse lianjia = (BuyHouse) constructor.newInstance(new InvocationHandler() {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 这里就可以随意添加服务了
        // 根据不同人的需求,筛选房子
        XiaomingBuyHouse xiaomingBuyHouse = new XiaomingBuyHouse();
        System.out.println("Lianjia帮每个人筛选好房子");

        // RealSubject,真实客户的看房行为
        Object result = method.invoke(xiaomingBuyHouse, args);

        // 有了这个动态代理的功能,相当于能自动动态生成符合每个人需求的独立的代理服务
        // 这样单个中介 变成了 中介公司。效率更高、服务更好了
        return result;
    }
});

lianjia.seekHouse();

// 简写
BuyHouse lianjia2 = (BuyHouse) Proxy.newProxyInstance(BuyHouse.class.getClassLoader(), new Class[]{ BuyHouse.class }, new InvocationHandler() {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 进行代理
        return null;
    }
});
lianjia2.seekHouse();

可以看到,java实现的动态代理离不开这几个核心点

1、Proxy.getProxyClass 或者 Proxy.newProxyInstance
2、InvocationHandler#invoke
3、反射

动态代理里,反射是贯穿始终的。

大家可能会奇怪, 一个 ​Proxy​ 一个​InvocationHandler​,底层到底做什么了,怎么就能代理了呢?我们也没看到调用 ​InvocationHandler​的关键方法 ​invoke​啊。

源码分析

下面简单看源码分析一下。

java.lang.reflect.Proxy#newProxyInstance
getProxyClass0(loader, intfs)

我们重点关注参数里的interfacesinvocationHandle,无论是 Proxy.​newProxyInstance()​方式 还是 ​getProxyClass()​方式,重点都落在了​getProxyClass0(loader, intfs)​

是的,这就是代理类的核心生成逻辑。

proxyClassCache.get(loader, interfaces)
java.lang.reflect.Proxy#getProxyClass0


对代理类的缓存策略,后边就能看出来,这是非常有必要的,这个缓存数据结构相当复杂,我们找到核心的点:

java.lang.reflect.WeakCache#get

我们看到proxyClassCache.get(loader, interfaces),无论如何缓存,找return就对了。

if (supplier != null) {
    // supplier might be a Factory or a CacheValue<V> instance
    V value = supplier.get();
    if (value != null) {
        return value;
    }
}

supplier.get()

然后就是 ​supplier.get()​,也就是 ​java.lang.reflect.WeakCache.Factory#get

java.lang.reflect.WeakCache.Factory#get

​ 而这里的重点是 value = Objects.requireNonNull(valueFactory.apply(key, parameter));

注:Objects.requireNonNull()返回值 还是参数本身哈,仅仅是进行非空判断,

public static <T> T requireNonNull(T obj) {
        if (obj == null)
            throw new NullPointerException();
        return obj;
    }

所以这里的重点就是 valueFactory.apply(key, parameter);
valueFactory是什么呢?

java.lang.reflect.WeakCache#WeakCache

image

是的,终于扯到关键了 ​ProxyClassFactory​

java.lang.reflect.Proxy.ProxyClassFactory#apply
java.lang.reflect.Proxy.ProxyClassFactory#apply

到这里基本理顺了,所以动态代理的核心,还是利用上面讲到的反射等技术,动态生成代理类的过程。

ProxyClassFactory#apply方法里省略里很多逻辑,大家可以展开一下,肯定会似曾相识。

比如:

  1. 在代码中可以看到JDK生成的代理类的类名是“$Proxy”+序号。
  2. 如果接口是public的,代理类默认是public final的,并且生成的代理类默认放到com.sun.proxy这个包下。
  3. 如果接口是非public的,那么代理类也是非public的,并且生成的代理类会放在对应接口所在的包下。
  4. 如果接口是非public的,并且这些接口不在同一个包下,那么就会报错。
sun.misc.ProxyGenerator.ProxyMethod#generateMethod

如果要继续深入追寻 生成的 代理类 和 ​InvocationHandler​的​invoke​的关系,继续往里看两层,就是了👇

sun.misc.ProxyGenerator#generateProxyClass(java.lang.String, java.lang.Class<?>[], int)

sun.misc.ProxyGenerator#generateClassFile


sun.misc.ProxyGenerator.ProxyMethod#generateMethod

sun.misc.ProxyGenerator.ProxyMethod#generateMethod

​是的,就是我们生成字节码的常用套路。

到这里,就完全扣上了,再回头看 java实现的动态代理离不开这几个核心点

1、Proxy
2、InvocationHandler
3、反射技术

参考
https://www.zhihu.com/question/20794107
https://www.cnblogs.com/liuyun1995/p/8157098.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,013评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,205评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,370评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,168评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,153评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,954评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,271评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,916评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,382评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,877评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,989评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,624评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,209评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,199评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,418评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,401评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,700评论 2 345

推荐阅读更多精彩内容