Java 高级特性——反射及动态代理模式

反射(Reflect)

反射之中包含了一个「反」字,所以了解反射我们先从「正」开始。

一般情况下,我们使用某个类时必定知道它是什么类,是用来做什么的。于是我们直接对这个类进行实例化,之后使用这个类对象进行操作。

反射则是一开始并不知道我要初始化的类对象是什么,自然也无法使用 new 关键字来创建对象了。这时候,我们使用 JDK 提供的反射 API 进行反射调用。反射就是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法。

Reflection(反射)是Java被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的內部信息,并能直接操作任意对象的内部属性及方法。

Java反射机制主要提供了以下功能:

在运行时构造任意一个类的对象

在运行时获取任意一个类所具有的成员变量和方法

在运行时调用任意一个对象的方法(属性)

Java 是一门面向对象的语言。在面向对象的世界里,万事万物皆对象,既然万事万物皆对象,那么我们的类是不是对象呢?我们写的每一个类都可以看成一个对象,是 java.lang.Class 类的对象。每一个类对应的Class放在哪里呢?当我们写完一个类的Java文件,编译成class文件的时候,编译器都会将这个类的对应的class对象放在class文件的末尾。里面都保存了些什么?大家可以理解保存了类的元数据信息,一个类的元数据信息包括什么?有哪些属性,方法,构造器,实现了哪些接口等等,那么这些信息在Java里都有对应的类来表示。

Class类

Class是一个类,封装了当前对象所对应的类的信息

一个类中有属性,方法,构造器等,比如说有一个Person类,一个Order类,一个Book类,这些都是不同的类,现在需要一个类,用来描述类,这就是Class,它应该有类名,属性,方法,构造器等。Class是用来描述类的类。

Class类是一个对象照镜子的结果,对象可以看到自己有哪些属性,方法,构造器,实现了哪些接口等等

对于每个类而言,JRE 都为其保留一个不变的 Class 类型的对象。一个 Class 对象包含了特定某个类的有关信息。

对象只能由系统建立对象,一个类(而不是一个对象)在 JVM 中只会有一个Class实例

获取Class对象的三种方式:

    1.通过类名获取      类名.class   

    2.通过对象获取     对象名.getClass()

 3.通过全类名获取   Class.forName(全类名)

Class类的常用方法

Java 高级特性——反射及动态代理模式_第1张图片

类加载器、构造器、Method、Field

参见包cn.enjoyedu.refle.more下对应的类,评论问我要

Java 高级特性——反射及动态代理模式_第2张图片

动态代理

代理模式和静态代理

代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗的来讲代理模式就是我们生活中常见的中介。

举个例子来说明:张三想买某种用品,虽然他可以自己去找,但是这确实太浪费时间和精力了,或者不好意思去买。于是张三就通过中介Mark来买,Mark来帮张三,张三只是负责选择自己喜欢的的size,然后付钱就可以了。

目的:(1)通过引入代理对象的方式来间接访问目标对象,防止直接访问目标对象给系统带来的不必要复杂性; (2)通过代理对象对原有的业务增强;

代理模式一般会有三个角色:

Java 高级特性——反射及动态代理模式_第3张图片

抽象角色:指代理角色和真实角色对外提供的公共方法,一般为一个接口

真实角色:需要实现抽象角色接口,定义了真实角色所要实现的业务逻辑,以便供代理角色调用。也就是真正的业务逻辑在此。

代理角色:需要实现抽象角色接口,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。将统一的流程控制都放到代理角色中处理!

而访问者不再访问真实角色,而是去访问代理角色。

静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类。一般来说,被代理对象和代理对象是一对一的关系,当然一个代理对象对应多个被代理对象也是可以的。

静态代理,一对一则会出现时静态代理对象量多、代码量大,从而导致代码复杂,可维护性差的问题,一对多则代理对象会出现扩展能力差的问题。

动态代理

是指在使用时再创建代理类和实例

优点

只需要1个动态代理类就可以解决创建多个静态代理的问题,避免重复、多余代码

更强的灵活性

缺点

效率低,相比静态代理中 直接调用目标对象方法,动态代理则需要先通过Java反射机制 从而 间接调用目标对象方法

应用场景局限,因为 Java 的单继承特性(每个代理类都继承了 Proxy 类),即只能针对接口 创建 代理类,不能针对类创建代理类。

在java的动态代理机制中,有两个重要的类或接口,一个是InvocationHandler接口、另一个则是 Proxy类,这个类和接口是实现我们动态代理所必须用到的。

InvocationHandler接口是给动态代理类实现的,负责处理被代理对象的操作的,而Proxy是用来创建动态代理类实例对象的,因为只有得到了这个对象我们才能调用那些需要代理的方法。

具体使用,参见包cn.enjoyedu.proxy.dynamic下MarkCompany类(评论找我要)

动态代理实现原理

通过调试模式我们发现,动态代理里,代理类的类名是这样的:

这个代理类为何是这个名字?它是如何执行被代理对象的相关方法呢?我们在java文件编译后的目录里其实找不到这个名为$Proxy0的class文件的。

观察Proxy.newProxyInstance方法,与创建对象有关的代码主要有:

获得代理类的class对象:

获得代理类的构造器:

创建代理类的实例

看来其中的关键点就是如何获得代理类的class对象,我们进入getProxyClass0方法,进而进入proxyClassCache.get方法,通过这个这个方法所在的类名,我们可以推测,JDK内部使用了某种机制缓存了我们的代理类的class对象,同时get方法接受的参数是被代理类的类加载器和类实现的的接口。

在这个get方法中,除去和缓存相关的操作,同时用到了被代理类的类加载器和类实现的的接口这两个参数的是

我们再进入这个方法的实现,

Java 高级特性——反射及动态代理模式_第4张图片
Java 高级特性——反射及动态代理模式_第5张图片

而最终生成代理类的class对象是defineClass0方法,但是这个方法是个native方法,所以我们不去也无法深究它,但是通过这个方法的参数我们可以明显看到它接收了上面所生成的byte数组。

而我们通过ProxyUtils,这个自己写的工具类,将这个byte数组写入文件,我们并反编译,我们将会看到

Java 高级特性——反射及动态代理模式_第6张图片

同时我们还会看到其中实现了业务接口的方法

Java 高级特性——反射及动态代理模式_第7张图片

而h则来自派生类Proxy中

Java 高级特性——反射及动态代理模式_第8张图片

这个h的实例来自哪里?不就是我们在创建代理类的实例时传入的吗?

Java 高级特性——反射及动态代理模式_第9张图片

使用了动态代理的`Retrofit

Retrofit简单的说就是一个网络请求的适配器,它将一个基本的Java接口通过动态代理的方式翻译成一个HTTP请求,并通过OkHttp去发送请求。此外它还具有强大的可扩展性,支持各种格式转换以及RxJava。我们基于Retrofit2解析。

先定义一个名为X的java接口,当然里面有各种注解。

@FormUrlEncoded注解表示from表单,另外还有@Multipart等注解。@POST表示post请求,此外还可以使用@GET请求

Java 高级特性——反射及动态代理模式_第10张图片

然后如何使用的呢?

首先将域名传入构造一个Retrofit,然后通过retrofit中的create方法传入一个Java接口并得到一个x(当然x这个对象是经过处理了的)调用getPersonalListInfo(12)然后返回一个Call,最后这个Call调用了enqueue方法去异步请求http,这就是一个基本的Retrofit的网络请求。Retrofit2中Call接口的默认实现是OkHttpCall,它默认使用OkHttp3作为底层http请求client。

Java 高级特性——反射及动态代理模式_第11张图片

我们只定义了一个接口X,并没有实现这个接口,那么它是如何工作的呢?我们看看create方法的实现。

create()方法是个泛型方法,调用它时会返回一个范型T的对象,我们这里类型是X接口,在内部实现上,很明显了使用了动态代理返回了一个X的代理类。当调用X内部方法的时候,会调用invoke方法。invoke方法内则通过内部一系列的封装最后返回一个Call对象。

Java 高级特性——反射及动态代理模式_第12张图片

将一段Java代码编译成Class文件,然后再用字节码反编译工具进行反编译后,将会发现泛型都不见了,程序又变回了Java泛型出现之前的写法,泛型类型都变回了原生类型

Java 高级特性——反射及动态代理模式_第13张图片

上面这段代码是不能被编译的,因为参数List<Integer>和List<String>编译之后都被擦除了,变成了一样的原生类型List<E>,擦除动作导致这两种方法的特征签名变得一模一样。

由于Java泛型的引入,各种场景(虚拟机解析、反射等)下的方法调用都可能对原有的基础产生影响和新的需求,如在泛型类中如何获取传入的参数化类型等。因此,JCP组织对虚拟机规范做出了相应的修改,引入了诸如Signature、LocalVariableTypeTable等新的属性用于解决伴随泛型而来的参数类型的识别问题,Signature是其中最重要的一项属性,它的作用就是存储一个方法在字节码层面的特征签名[3],这个属性中保存的参数类型并不是原生类型,而是包括了参数化类型的信息。修改后的虚拟机规范要求所有能识别49.0以上版本的Class文件的虚拟机都要能正确地识别Signature参数。

另外,从Signature属性的出现我们还可以得出结论,擦除法所谓的擦除,仅仅是对方法的Code属性中的字节码进行擦除,实际上元数据中还是保留了泛型信息,这也是我们能通过反射手段取得参数化类型的根本依据。


补充动态代理实现

首先定义一个接口

然后写他的实现

Java 高级特性——反射及动态代理模式_第14张图片

然后新建一个类实现InvocationHandler接口


Java 高级特性——反射及动态代理模式_第15张图片

最后调用

Java 高级特性——反射及动态代理模式_第16张图片

你可能感兴趣的:(Java 高级特性——反射及动态代理模式)