把反射用到出神入化

网站建设3年前发布
49 0 0

为什么,读不懂框架源码?,我们都知道作为一个程序员,如果想学习到更深层次的技术,就需要阅读大量的框架源码,学习这些框架源码中的开发套路和设计思想,从而提升自己的编程能力。,事大家都清楚,但在实操上,很多码农根本没法阅读框架源码。首先一个非常大的问题是,面对如此庞大的框架源码,不知道从哪下手。与平常的业务需求开发相比,框架源码中运用了大量的设计原则和设计模式对系统功能进行解耦和实现,也使用了不少如反射、代理、字节码等相关技术。,当你还以为是平常的业务需求中的实例化对象调用方法,去找寻源码中的流程时,可能根本就找不到它是何时发起调用的、怎么进行传参、在哪处理赋值的等一连串的问题,都把一个好码农劝退在开始学习的路上。,不知道大家在学习《手写 Mybatis》的过程中,是否有对照 Mybatis 源码一起学习,如果你有对照源码,那么大概率会发现我们在实现数据源池化时,对于属性信息的获取,采用的是硬编码的方式。如图 8-1 所示,图 8-1 数据源池化配置获取,如果说我们需要对一个对象的所提供的属性进行统一的设置和获取值的操作,那么就需要把当前这个被处理的对象进行解耦,提取出它所有的属性和方法,并按照不同的类型进行反射处理,从而包装成一个工具包。如图 8-2 所示,图 8-2 对象属性反射处理,元对象反射工具类,处理对象的属性设置和获取操作核心类,如图 8-3 所示:,图 8-3 所示 元对象反射工具类,处理对象的属性设置和获取操作核心类,关于对象类中的属性值获取和设置可以分为 Field 字段的 get/set 还有普通的 Method 的调用,为了减少使用方的过多的处理,这里可以把集中调用者的实现包装成调用策略,统一接口不同策略不同的实现类。,定义接口,无论任何类型的反射调用,都离不开对象和入参,只要我们把这两个字段和返回结果定义的通用,就可以包住不同策略的实现类了。,源码详见:cn.bugstack.mybatis.reflection.invoker.MethodInvoker,提供方法反射调用处理,构造函数会传入对应的方法类型。,源码详见:cn.bugstack.mybatis.reflection.invoker.GetFieldInvoker,getter 方法的调用者处理,因为get是有返回值的,所以直接对 Field 字段操作完后直接返回结果。,源码详见:cn.bugstack.mybatis.reflection.invoker.SetFieldInvoker,setter 方法的调用者处理,因为set只是设置值,所以这里就只返回一个 null 就可以了。,Reflector 反射器专门用于解耦对象信息的,只有把一个对象信息所含带的属性、方法以及关联的类都以此解析出来,才能满足后续对属性值的设置和获取。,源码详见:cn.bugstack.mybatis.reflection.Reflector,Reflector 反射器类中提供了各类属性、方法、类型以及构造函数的保存操作,当调用反射器时会通过构造函数的处理,逐步从对象类中拆解出这些属性信息,便于后续反射使用。,读者在对这部分源码学习时,可以参考对应的类和这里的处理方法,这些方法都是一些对反射的操作,获取出基本的类型、方法信息,并进行整理存放。,Reflector 反射器类提供的是最基础的核心功能,很多方法也都是私有的,为了更加方便的使用,还需要做一层元类的包装。在元类 MetaClass 提供必要的创建反射器以及使用反射器获取 get/set 的 Invoker 反射方法。,源码详见:cn.bugstack.mybatis.reflection.MetaClass,MetaClass 元类相当于是对我们需要处理对象的包装,解耦一个原对象,包装出一个元类。而这些元类、对象包装器以及对象工厂等,再组合出一个元对象。相当于说这些元类和元对象都是对我们需要操作的原对象解耦后的封装。有了这样的操作,就可以让我们处理每一个属性或者方法了。,对象包装器相当于是更加进一步反射调用包装处理,同时也为不同的对象类型提供不同的包装策略。框架源码都喜欢使用设计模式,从来不是一行行ifelse的代码,在对象包装器接口中定义了更加明确的需要使用的方法,包括定义出了 get/set 标准的通用方法、获取get\set属性名称和属性类型,以及添加属性等操作。,对象包装器接口,后续所有实现了对象包装器接口的实现类,都需要提供这些方法实现,基本有了这些方法,也就能非常容易的处理一个对象的反射操作了。,无论你是设置属性、获取属性、拿到对应的字段列表还是类型都是可以满足的。,在有了反射器、元类、对象包装器以后,在使用对象工厂和包装工厂,就可以组合出一个完整的元对象操作类了。因为所有的不同方式的使用,包括:包装器策略、包装工程、统一的方法处理,这些都需要一个统一的处理方,也就是我们的元对象进行管理。,源码详见:cn.bugstack.mybatis.reflection.MetaObject,MetaObject 元对象算是整个服务的包装,在构造函数中提供各类对象的包装器类型的创建。之后提供了一些基本的操作封装,这回封装后就更贴近实际的使用了。,包括这里提供的 getValue(String name) 、setValue(String name, Object value) 等,其中当一些对象的中的属性信息不是一个层次,是 班级[0].学生.成绩 还需要被拆解后才能获取到对应的对象和属性值。,当所有的这些内容提供完成以后,就可以使用 SystemMetaObject#forObject 提供元对象的获取了。,好了,现在有了我们实现的属性反射操作工具包,那么对于数据源中属性信息的设置,就可以更加优雅的操作了。,源码详见:cn.bugstack.mybatis.datasource.unpooled.UnpooledDataSourceFactory,在之前我们对于数据源中属性信息的获取都是采用的硬编码,那么这回在 setProperties 方法中则可以使用 SystemMetaObject.forObject(dataSource) 获取 DataSource 的元对象了,也就是通过反射就能把我们需要的属性值设置进去。,这样在数据源 UnpooledDataSource、PooledDataSource 中就可以拿到对应的属性值信息了,而不是我们那种在2个数据源的实现中硬编码操作。,本章节的测试会分为2部分,一部分是我们这个章节实现的反射器工具类的测试,另外一方面是我们把反射器工具类接入到数据源的使用中,验证使用是否顺利。,创建一个数据库名称为 mybatis 并在库中创建表 user 以及添加测试数据,如下:,通过 mybatis-config-datasource.xml 配置数据源信息,包括:driver、url、username、password,在这里 dataSource 测试验证 UNPOOLED 和 POOLED,因为这2个都属于被反射工具类处理,这部分暂时不需要调整,目前还只是一个入参的类型的参数,后续我们全部完善这部分内容以后,则再提供更多的其他参数进行验证。,这是一组比较常见的用于测试 Mybatis 源码中 MetaObject 的测试类,我们把这个单元测试用到我们自己实现的反射工具类上,看看是否可以正常运行。,测试结果,好了,那么这个测试中可以看到,我们拿到了对应的属性信息,并可以设置以及修改属性值,无论是单个属性还是对象属性,都可以操作。,这块的调用我们手写框架的测试类到不需要什么改变,只要数据源配置上使用 type="POOLED/UNPOOLED" 即可,这样就能测试我们自己开发的使用了反射器设置属性的数据源类了。,测试结果,图 8-4 使用MetaObject 设置属性值,根据单元测试和调试的截图,可以看到属性值通过反射的方式设置到对象中,也满足了我们在创建数据源时候的使用。这样就可以顺利的调用数据源完成数据的查询操作了。,本章节关于反射工具类的实现中,使用了大量的 JDK 所提供的关于反射一些处理操作,也包括可以获取一个 Class 类中的属性、字段、方法的信息。那么再有了这些信息以后就可以按照功能流程进行解耦,把属性、反射、包装,都依次拆分出来,并按照设计原则,逐步包装让外接更少的知道内部的处理。,这里的反射也算是小天花板的使用级别了,封装的工具类方式,如果在我们也有类似的场景中,就可以直接拿来使用。因为整个工具类并没有太多的额外关联,直接拿来封装成一个工具包进行使用,处理平常的业务逻辑中组件化的部分,也是非常不错的。技术迁移、学以致用、升职加薪,由于整个工具包中涉及的类还是比较多的,大家在学习的过程中尽可能的验证和调试,以及对某个不清楚的方法进行单独开发和测试,这样才能滤清整个结构是如何实现的。当你把这块的内容全部拿下,以后再遇到反射就是小意思了

© 版权声明

相关文章