首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >彻底搞懂Java动态代理

彻底搞懂Java动态代理

作者头像
普通程序员
发布2019-10-23 14:19:38
1.7K0
发布2019-10-23 14:19:38
举报

现在spring大行其道,经常使用的AOP功能就是动态代理机制的实现。动态代理到底是怎么回事呢?

一、静态代理

描述动态代理之前,先看一看静态代理。

定义一个程序员的接口,只干两件事情(程序员太忙,别的做不了)

Java程序员长这个样,他会开发Java代码,会调试Java代码

有个很牛逼的叫 Farmerbrag 的程序员,他在开发之前,会祈祷一下,这样他开发的代码就不会有bug。

我们这么来描述Farmerbrag(代理类)

如果Farmerbrag只是一个普通的Java程序员,那么他的开发结果是

Farmerbrag is coding java.

Farmerbrag is debugging java.

真正的Farmerbrag(Farmerbrag代理类)是这样的

Farmerbrag is praying for the code!

Farmerbrag is coding java.

Farmerbrag's code is bug-free and does not require debugging

静态代理优点不说了,说一下缺点

1、增加了代码维护复杂度。代理类和实现类具有相同的接口,代理类通过实现类执行具体的方法。这样就出现了大量的代码重复。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。

2、代理对象只服务于一种类型的对象。如果要服务多类型的对象,要为每一种对象都进行代理,静态代理在程序规模稍大时就无法胜任。

二、动态代理的例子

接着静态代理的例子,Farmerbrag会祈祷这个功能是他特有的,其他程序员不会。我们可以定义一个拥有这项特技的程序员(第一部分静态代理就是这么做的),但其他程序员可能具有别的特技,根本定义不完!(有没有感觉到某些业务需求很想这个场景)

其实我们不需要去定义他,这个技能可以后天习得。看看怎么做?

这个动态代理的程序员执行结果如下

Farmerbrag is praying for the code!

Farmerbrag is coding java.

Farmerbrag's code is bug-free and does not require debugging

farmerbragProxy的类型是Developer接口,不是一个实现类。farmerbrag在被代理后生成的对象,并不属于Developer接口的任何一个实现类,是基于Developer接口和farmerbrag的类加载代理出来的。(mybatis定义的mapper接口怎么就能被调用执行呢)

看一下newProxyInstance()接口的定义

包括三个参数

loader和interfaces决定这个类到底是个怎么样的类。而h是InvocationHandler,决定这个代理类到底是多了什么功能。所以动态代理的内容重点就是这个InvocationHandler。

动态代理的例子采用了lambda表达式,主要代码是对InvocationHandler的实现。

三、代码分析

从写代码的角度,前面2节已经足够了,下边对原理进行分析。

看看源码其中关键的地方。在newProxyInstance()方法中有这样几段

1、克隆接口

2、查找或生成指定的代理类

3、通过反射,拿到代理类的构造函数

4、通过构造函数new一个对象,并关联InvocationHandler

看到这里有些人可能会更蒙,InvocationHandler到底是做什么的?反射(reflect)又是怎么回事?代码到底是怎么就被串改了呢?

四、原理剖析

1、class文件及其加载(反射)

编译器编译Java文件,产生.class 文件存放在磁盘中,文件内容是只有JVM虚拟机能够识别的机器码。JVM虚拟机读取字节码文件,取出二进制数据,加载到内存中,解析.class 文件内的信息,生成对应 Class对象

通过一段代码演示手动加载class文件字节码到系统内,转换成class对象,再实例化的过程

被加载的类我们复用前面的JavaDeveloper.class

自定义一个类加载器

执行代码得到如下结果

net.fengyu.proxy.JavaDeveloper

Farmerbrag is coding java.

以上代码演示了,通过字节码加载成class对象的过程

2、运行期生成二进制字节码

JVM通过字节码的二进制信息加载类,如果我们在运行期的系统中,遵循Java编译系统组织.class文件的格式和结构,生成相应的二进制数据,然后把这个二进制数据加载转换成对应的类,这样,就完成了在代码中,动态创建一个类的能力。第二节动态代理例子显然是这个能力的一个子集。

有一些开源框架支持运行期生成二进制字节码这个功能,如ASM,Javassist

ASM 是一个 Java 字节码操控框架。能够以二进制形式修改已有类或者动态生成类。ASM可以直接产生二进制 class 文件,也可以在类被加载入Java虚拟机之前动态改变类行为。Spring使用的CGLIB也采用ASM框架作为其字节码操作的工具。

下边一段代码生成一个跟前面JavaDeveloper几乎一样的类ASMDeveloper,使用上一小节的LoadClass类运行有相同的输出

这个例子说明,在代码里生成字节码,并动态地加载成class对象,创建实例是完全可以实现的。动态修改某个类当然也能做到(动态代理就做这事)。

至此,原理层面基本已经说清楚了。

3、为什么是InvocationHandler

我们已经具有能力动态修改一个类的代码,使用ASM哪怕生成一个非常简单的类,代码量也是又多又复杂。仔细思考代理模式中的代理Proxy角色。Proxy角色在执行代理业务的时候,无非是在调用真正业务之前或者之后做一些“额外”业务。

代理类处理的逻辑很简单,在调用某个方法前及方法后做一些额外的业务。换一种思路就是,在触发(invoke)真实角色的方法之前或者之后做一些额外的业务。为了构造出具有通用、简单的代理类,可以将所有的触发真实角色动作交给一个触发的管理器。这种管理器就是InvocationHandler。

在这种模式之中,代理Proxy和RealSubject需要实现相同的功能(函数方法)。

面向对象的编程之中,想要约定Proxy和RealSubject实现相同的功能(函数方法)有两种方式

a、定义一个功能接口,Proxy 和RealSubject都实现这个接口。

b、通过继承,Proxy继承自RealSubject,这样Proxy则拥有了RealSubject的功能,

JDK中提供的创建动态代理的机制采用a思路;而cglib采用b思路(spring两者都使用了)。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-10-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 普通程序员 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
腾讯云代码分析
腾讯云代码分析(内部代号CodeDog)是集众多代码分析工具的云原生、分布式、高性能的代码综合分析跟踪管理平台,其主要功能是持续跟踪分析代码,观测项目代码质量,支撑团队传承代码文化。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档