倚楼听风雨
淡看江湖路

浅析Java反射系列相关进阶知识(下)之JDK动态代理及反射泛型

通过上一篇《浅析Java反射系列相关基础知识(上)之类的加载以及反射的基本应用》文章我们知道每当要加载一个 class 的时候,JVM(Java Virtual Machine,Java虚拟机)就为其创建一个 Class 实例并关联起来,通过 Class 实例获取 class 信息的方法我们叫做「反射」。那么反射的目的也就是当我们获得某个 Object 实例的时候,我们可以利用反射这种方式获取该 Object 的 class 信息。 那么利用 JVM 动态加载 class 的特性,我们就可以做很多事情,我们可以在运行期间根据条件加载不同的实现类,可以通过反射生成 JDK(Java Development Kit,Java 语言的软件开发工具包) 动态代理等,这也是本文的重点内容。

动态代理的概念以及Java中动态代理的实现

所谓的「动态代理」指的就是在程序运行的时候,我们通过反射机制动态的创建代理类,通过代理类实现原有类的程序执行的同时,我们可以在这个基础上扩展我们自己业务,从而在不影响原有业务基础之上增加新的特性和功能,实现主副业务的解耦,代码的解耦等。

在 Java 中,java.lang.reflect 包下面有一个 Proxy 类和 InvocationHandler 接口,我们就是使用他俩来生成 JDK 动态代理类或者动态代理对象的。两个类部分重要的源码如下:

举一个简单的日志业务场景实例说一下动态代理的作用。比如老四做了一款12306刷票辅助软件,帮助用户刷到票提交订单的前后都需要在软件 GUI(Graphical User Interface,又称图形用户接口)界面日志控制台打印乔朴提交订单的相关信息。比如,占票成功前后需要打印「开始为您锁定有座座位」、「占票成功,准备开始提交订单」;提交订单之后需要提示用户「下单成功,请尽快前往12306官网付款」。这样的信息在实际的抢票流程中是不需要的,也就是说抢票模块只需要负责老老实实的给我刷票就好,至于这些日志提醒信息如果我们生硬的嵌套在抢票业务代码中不仅是业务耦合,维护起来也是相当麻烦,接下来我们以此为例看看如何通过反射生成 JDK 动态代理解决这样的问题。

首先,有一个非常简单的 Ticket 接口,声明了一个提交订单方法。

然后 TicketImpl 类实现 Ticket 接口,这里只输出了一句话。

创建 TicketLogger 业务类,这里面的两个方法要分前后切入到 submitOrder() 方法中。

写一个主方法类,通过动态代理进行日志切入。

Spring AOP底层实现原理值Java的JDK动态代理实现无痕切入解决业务耦合代码冗余问题 第1张

从以上代码我们看到,说白点就是通过动态代理的对象来代替 target 对象,然后代理对象通过 invoke 方法实现了我们想要的日志切入功能。不难发现当我们采用动态代理这种方式可以灵活地实现解耦,这其实就是我们经常在 Spring 中经常提及到的 AOP(Aspect Oriented Programming,面向切面编程),Spring 中的 AOP 代理包含了目标对象的全部方法,从而实现在目标方法前后可以定义一些通用方法帮助我们实现业务解耦,业务无痕切入等功能。

反射与泛型

关于泛型,老四之前在文章《浅析Java中的泛型以及泛型中的PECS(Producer Extends,Consumer Super)原则》中有所浅析,当然,在反射中也是支持泛型的,Class 这个类就是支持泛型的,形如 Class<String> ,如果类型未知需要使用 Class<?> 代替。在没说泛型反射之前,你可能经常看到形如 String str = (String)Class.forname(clazzName).newInstance(); 这样的代码,这样的代码是经常会在不小心的情况下抛出 ClassCastException 异常的,所以我们应该经常使用反射泛型以此来约束我们获取到的反射类型,甚至是参数类型,这样在实例化的反射类的时候不需要进行类型强制转换。

在说反射泛型之前,先简单说一下 Java8 新增的方法参数反射,这里面包含了可以获取带泛型的形参参数的特性。

Java8 中新增了一个 Executable 抽象类和一个 Parameter 类,Constructor 和 Method 都是 Executable 的子类,顾名思义,Constructor 就是提供了关于反射类的构造器形参相关信息获取的一些方法,Method 提供了获取方法形参的一些信息的方法。我们主要介绍一下获取形参信息的一些主要方法:

  1. int getParameterCount():获取构造器或者方法的形参个数
  2. Parameter[] getParameters():获取构造器或者方法的所有形参
  3. String getName():获取形参名称
  4. Type getparameterizedType():获取带泛型的形参类型,比如 Map<String, Integer>
  5. Class<?> getType(): 获取形参类型
  6. boolean isVarArgs():判断形参是否是可变形参

简单的代码示例如下:

上面代码有一个需要注意的地方,如图所示,形参名称打印出来不正确,这是因为这个方法要求运行的时候需要我们制定 javac 指定 -parameters 选项,在程序中可以通过 parameter.isNamePresent() 方法来判断是否返回了方法的形参信息。

浅析Java反射系列相关进阶知识之JDK动态代理及反射泛型-高老四博客 第2张

通过上面的我们知道 java8 新增了可以获取方法中带有泛型形参的反射,那么在此之前我们主要是使用反射来获取成员变量的泛型信息,其实代码相通,直接看一下简单的示例代码好了。

使用反射来获取泛型信息 第3张

可以看到,getType() 方法只对普通类型的成员变量有效,如果成员变量是带有泛型的类型,并且想获取准确的泛型类型,我们需要使用 getGenericType() 方法。转换为 ParameterizedType 对象之后,可以通过其提供的 getRawType() 和 getActualTypeArguments() 方法获取相应的参数类型,前者返回没有泛型信息的原始类型,后者返回泛型参数的类型。

更博不易,如果觉得文章对你有帮助并且有能力的老铁烦请捐赠盒烟钱,点我去赞助。或者扫描文章下面的微信/支付宝二维码打赏任意金额(点击「给你买杜蕾斯」),也可扫描小站放的支付宝领红包二维码,线下支付享受优惠的同时老四也可以获得对应赏金,老四这里抱拳谢谢诸位了。捐赠时请备注姓名或者昵称,因为您的署名会出现在赞赏列表页面,您的捐赠钱财也会被用于小站的服务器运维上面,再次抱拳感谢。

赞(19) 给你买杜蕾斯
本站原创文章受自媒体平台原创保护,未经允许不得转载高老四博客 » 浅析Java反射系列相关进阶知识(下)之JDK动态代理及反射泛型

开始你的表演 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下老四,鼓励我更好的创作

支付宝扫一扫打赏

微信扫一扫打赏