您好,登录后才能下订单哦!
这篇文章主要介绍“服务器中反射是什么”,在日常操作中,相信很多人在服务器中反射是什么问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”服务器中反射是什么”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!
假设我们希望开发一套通用型的软件框架,这个框架允许用户自定义大量不同的情况下的回调函数(方法),用来实现丰富多彩的业务逻辑功能,例如一个游戏脚本引擎,那么,其中一个实现方式,就是使用观察者模式,以事件的方式来驱动整个框架。用户通过定义各个事件的响应函数,来组织和实现业务逻辑。而框架也提供了自定义事件及其响应函数的入口。在一些实现代码中,我们可能会发现有大量的“注册事件”的代码,或者是使用一个巨大的switch…case…对事件函数进行分发调用。譬如我们想做一个服务器端的基本进程框架,这个框架让用户只需要填写一些回调函数,就能成为一个稳定持续运行的后台服务进程。其中一个部分,就是需要定义程序启动事件,以便用户自定义程序启动要做的事情。那么我们可以定义一个”Init”的字符串来代表这个事件,在一个事件响应函数的回调哈希表里面,记录上”Init”pfunInit()。又或者是用一个常量宏INIT=12来表示此事件,在程序的主循环处,利用switch…case…来检查代表每个事件的类型编码,如果发现是和INIT宏相等的,就调用case INIT下面的代码(往往是一个单独的函数,如pfunINit())
维护长长的“注册事件”代码和长长的switch…case…都一样的让人昏昏欲睡,同时容易让人错漏百出。这些代码往往还带有大量的“常量”,因为用来作为回调函数的key的数据,往往都是一些自定义的常量。这些常量的同步维护,也往往让人筋疲力尽。这些长长的代码清单,经常还都需要由多个开发者一起来使用,自然就很容易发生你错改了我的,我覆盖了你的这一类问题。这些问题非常的“低级”,但是要找起来却一点都不容易。
[游戏的按键控制代码/JS]
难道我们的框架代码中,就一定会充斥着长长的字符串常量,或者整数常量吗?答案是否定的,因为很多编程语言,都提供能反射的功能。在编译型语言如C/C++里面,也可以利用代码生成技术,模拟出类似反射的能力。
要想知道什么是反射,我们可以先来看一个观察者模式的例子。假设我们在编写一个GUI的程序:在一个窗体上安放了一个按钮,此按钮的名字叫“ButtonA”,当这个按钮按下的时候,我们希望有一个我们自己写的函数被调用。根据观察者模式的设计,这个按钮被用户按下后,程序底层应该能监测到这个事情,然后在进程内部产生一个“事件”,这个“事件”对象往往会带有这个信息:被按下的按钮名字。如果我们用以前的注册事件的方法来编码,我们必须要在按钮被按下之前,比如程序初始化的时候,就向观察者对象注册这样一个回调函数:RegisterEvent(“ButtonA”, ONCLICK, myOnClick) —— ButtonA
被按下的事件—myOnClick()
。这里的函数myOnClick()
就是我们想处理ButtonA被按下的事件的响应函数。但是,我们可以用另外一个更省事的方法来解决:我们把myOnClick()函数的名字改成ButtonA_OnClick()
,然后观察者在发生“ButtonA”被按下的事件后,自动去找有没有叫“ButtonA_OnClick
”这个名字的函数,如果找到的话,就调用这个函数。——显然这种做法无需预先手工去注册回调函数,而是仅仅根据函数名字的约定,简单的来决定要调用什么函数。一般来说,我们认为程序运行的过程中,这些函数名字、类名字、属性名字都不起什么重要的作用,以至于我们还会用一些“混淆器”软件来处理源代码,把这些自定义的名字都弄的乱七八糟,也不影响程序的运行。然而,如果我们使用反射的技术,程序就可以在运行时,实时的用一些常量,来检索并且获得源代码中,函数、类、属性名字所对应的实体,并且还能调用这些东西。
[Spring通过XML来配置对象的关系]
从代码维护的角度来看,类、成员、方法的名字,被程序以外的一些“配置文件”所管理和知道,是有一定风险的。因为我们常常不把配置文件看成是源代码那么重要的东西,错漏也没有编译器或者IDE协助,所以一些难以调试的BUG往往是从这些位置产生的。不过作为一种大大节省框架代码的技术,还是受到广泛欢迎。而上文所说的问题,现在渐渐由另外一种技术“元数据”(或者叫注解、特性),把配置文件和源代码合并起来,这样就能大大改善上述的问题。
我们在编写通信功能的程序时,传统的思路是要定义协议,也就是定义协议头部,协议包长度,协议包字段等等。在一个比较复杂的网络服务程序中,这样的协议很容易就有几十上百个。维护代码的程序员想要搞明白别人定义的如此众多的协议,实际上是不太容易的。我们很容易想到,能不能使用对象模型来代替通信协议的定义呢?答案是可以的。但是,使用对象模型又有一个新的问题:对象是一个在运行时的内存结构,如何把对象中的数据,通过网络接收和发送呢?最简单的做法,就是使用memcpy(),Linux提供了这个功能强大的API,可以让任何内存中的数据变成一段字节数组,然后我们就能直接通过网络发送了。但是,如果我们的对象不是一个简单的结构体(事实上简单的结构体也有问题),而是一个对象,这个对象里面可能存在指针类型的成员,这样的拷贝就不可能顾及到这些指针指向的数据了。而且,如果收发两端的程序,并不是同一种语言(操作系统、平台),这样的内存结构数据可能毫无意义,比如把一个C++的对象内存直接拷贝给JAVA程序,肯定无法直接使用。所以,我们想要用对象结构来定义通信协议,我们需要一个把对象转换成通用的字节数组的方法,这就是“序列化/反序列化”的能力。在这里我不打算说太多关于序列化的内容,我只想说,当这些对象具备序列化能力后,就能成为通信数据的载体。问题是,如果我们收到了一段对象序列化的数据,如何构建出对应数据的对象呢?答案就是使用反射,反射机能能从数据中获得对象类的名字,然后通过这个名字构造出对象来,然后从数据中继续获得余下成员的数据,一一复制到这个对象身上。由此看,只要我们有反射功能,我们可以让使用者,简单的构造一个对象,然后整个把这个对象发送给网络的另外一端,对方也能直接收到一个对象,这样在编写通信程序的时候,只要按照业务需求定义对象即可。对于阅读代码的程序员来说,不用在脑子装一根叫“编码、解码”的弦,只要“无脑”的定义、处理对象即可。
在通信程序中,有种叫命令模式的设计模式非常常见,它脱胎于传统的基于命令字的网络处理方式:解析出命令字通过switch…case调用对应的处理函数。命令模式下的通信程序往往很简单,就是定义一个类型,这个类型的成员属性(通信协议)是可以随便定义的,只要再定一个Process()方法即可——这个方法的内容,就是收到此类型对象,应该如何处理的容器。由于我们利用反射可以在网络另外一段重建这个对象,所以我们也可以调用这个预定义的Process()方法,这个方法由于和协议对象类定义在一起,所以它是知道所有的成员定义的,这样这个处理方法,就无需好像以前的程序那样,费劲的通过强制类型转换,来得到具体的数据内容。在命令模式的通信程序实现过程里,反射是至关重要的一环,因为当我们收到一个数据包时,必须要从数据包中得到其对应的对象的类名,然后建立这个类所对应的对象。一旦这个对象建立后,我们可以调用其反序列化函数,让对象的内容和数据包中一致,最后调用其Process()方法,就大功告成了。这种设计,可以用不同的语言,定义同结构的类对象,用来在不同的语言平台程序之间通讯,而无需定义很复杂的协议定义规范。一些强大的对象数据工具,比如Google Protocol Buffer和Apache Thrift,直接可以用一个通用的IDL语言,生成各种语言的类定义源代码,就更方便了。
[delphi上用界面设置ADO数据库控件的属性]
在JAVA中,JavaBean就是一个著名的利用反射来使用的“对象约定”:只要你编写的JAVA类型,其成员是类似setXXX()
或者getXXX()
的,很多框架都会自动识别和处理这些成员函数,从而实现诸如自动更新成员数据,自动关联界面内容等功能。另外一个类似的例子是JMX,这个JAVA的通用监控标准接口,可以把你定义的类对象解析出来,成员属性的值可以变成统计图线、可修改的表格项,方法变成按钮。在游戏开发领域,反射还广泛的用于,把图形美术资源和程序代码结合的目的:比如Flash Builder就可以通过反射,把一个Flash动画对象,绑定到一个MovieClip类型上,从而获得一个既具备美术效果,又能让用户自定义行为的对象。Unity3D在绑定了3D的游戏对象和脚本组件后,对于脚本中的Start()/Update()函数调用,也是通过反射进行的,这样开发者就不必要把脚本的类型,死死的和某个基类绑定到一块,而且这些反射调用的函数,还是可以有不同的返回值(不同的函数原型),从而实现协程或者非协程的调用。
[在flash编辑器里,对一个动画指定关联的自定义类]
反射由于可以把源代码中的信息提取出来,和其他的数据结合,让源代码的能力大大的提升,所以在开发工具方面,具有非常重要的地位。我们不再需要通过写代码,一遍遍的把源代码的数据和外部结构做对接,而是简单的开发一个反射能力框架,就能让我们实现某种源代码的“约定”,从而实现各种丰富的快捷开发能力。
在反射的使用过程中,我们往往会发现,源代码直接作为数据,还是会有一些问题。譬如我们的源代码可能会根据一些非业务因数做修改,改名、改参数类型是在重构的时候非常常见的。所以我们往往还是离不开配置文件,把源代码里的名字写到配置里面,然后框架再根据配置来运行。一个比较典型的例子就是Hibernate,这一款著名的ORM框架,能让你的源代码类型和数据库、表结构关联起来。按理说利用反射,我们可以直接建立一些和数据库表、字段名字同名的对象,就能直接关联了,但是我们的源代码如果需要修改这些名字,再去改数据库的内容,就显得太麻烦了。所以我们要编写很多配置文件,来关联什么表对应什么类,什么字段对应哪个属性……这些配置文件往往和使用数据库的表数量一样多,任何的修改都还要记得对应这些配置的修改,我们被迫同时维护:数据库结构、配置文件、源代码这三个东西。然而,如果我们的平台是支持“元数据”的话,问题就很好解决了。因为我们可以在源代码里面直接写配置文件项目。我们在源代码的类名前面,用类似注释的方式,标注这个类对应数据库的哪个表;在属性名前面,用注释标注对应的字段、默认值等等。这样我们只需要维护两个东西:数据库结构、源代码。这大大的减轻的项目的复杂程度。
我接触的最早最著名的元数据,是用来同步修改API文档的JavaDoc技术,这个技术让更新文档不再成为一个苦力活。由于可以在源代码的注释里面编写文档,所以在修改代码的同时也可以同时更新文档。更重要的是,javadoc标记自然的把源代码中的“名字表”和相关注释自动对应起来了,要知道,这种对应如果人工来做,可是要费相当大的功夫。在javadoc的教育下,我对于java的注解、C#的attribute(特性)都觉得非常亲切。以前那些需要登记大量类名、方法名的配置,统统都可以直接记录在源代码里面了。而一些和美术资源关联的客户端代码,也可以通过源代码的特殊标记,连接上正确的图形资源。
能让这些源代码里面的“元数据”生效的重要技术,其实就是反射。由于我们的元数据处理程序,一般都需要和源代码里面的类、方法名字对应起来,所以都要使用反射的方法。而这种反射,又为我们任意增加“元数据”提供了强大的机制。
我们曾经相信:数据结构+算法=程序。但是从今天的软件产业来看,固然还是有很多专事计算的软件在被开发着,然而我们接触到更多的软件,都是所谓“信息管理系统”类的软件。这类软件要处理的并非是复杂的计算任务,而是对各种各样现实世界中的信息,增删查改是这些信息处理最通俗的描述。我们在处理这些信息的时候,如果还是把程序的载体源代码,仅仅看成是编译过程中不可缺少的一环而已,那么我们就必须额外处理大量的数据形式:数据库、配置文件、IDE配置……然而,在面向对象的风潮之下,源代码完全可以作为一种“树状”的数据承载方式。面向对象定义的类、成员、方法,就是一个个现实世界中的实体映像,他们所包含的结构和常量,往往直接可以成为系统中的数据源头。在MUD文字游戏中,几乎整个游戏世界,都是以源代码常量的形式编写的,这不但没有成为维护的难题,反而让真个游戏的开发变得更轻松,因为程序员还是最习惯于面对源代码去工作。
反射这种特性,能把源代码中的所有数据,包括“名字符号表”,都提供给开发者去使用,让软件开发过程,从单纯的算法实现过程,变成一个综合的信息管理的过程。这个做法看起来似乎不够专业,但是在编程已经不算“高科技”的年代,这种技术能帮助大量的开发者,以某种“约定”的方式去编写源代码,从而自动获得框架的强大支持。——制造这种允许“约定”方式运行源代码的框架,正式新的框架应该拥有的特点,因为人类的创造时间,不应该被浪费在大量的重复而类似的工作之上啊!
到此,关于“服务器中反射是什么”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注亿速云网站,小编会继续努力为大家带来更多实用的文章!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。