精灵王
- 注册日期2010-12-08
- 发帖数640
- QQ
- 火币1103枚
- 粉丝120
- 关注75
|
阅读:5381回复:0
Java中的类反射机制-JSP教程,J2EE/EJB/服务器
楼主#
更多
发布于:2011-01-08 20:00
| | | | 一、反射的概念 :反射的概念是由smith在1982年首次提出的,主要是指程式能访问、检测和修改他本身状态或行为的一种能力。这一概念的提出非常快引发了计算机科学领域关于应用反射性的研究。他首先被程式语言的设计领域所采用,并在lisp和面向对象方面取得了成绩。其中lead/lead++ 、openc++ 、metaxa和openjava等就是基于反射机制的语言。最近,反射机制也被应用到了视窗系统、操作系统和文件系统中。 反射本身并不是个新概念,他可能会使我们联想到光学中的反射概念,尽管计算机科学赋予了反射概念新的含义,不过,从现象上来说,他们确实有某些相通之处,这些有助于我们的理解。在计算机科学领域,反射是指一类应用,他们能够自描述和自控制。也就是说,这类应用通过采用某种机制来实现对自己行为的描述(self-representation)和监测(examination),并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。能看出,同一般的反射概念相比,计算机科学领域的反射不单单指反射本身,还包括对反射结果所采取的措施。所有采用反射机制的系统(即反射系统)都希望使系统的实现更开放。能说,实现了反射机制的系统都具有开放性,但具有开放性的系统并不一定采用了反射机制,开放性是反射系统的必要条件。一般来说,反射系统除了满足开放性条件外还必须满足原因连接(causally-connected)。所谓原因连接是指对反射系统自描述的改动能够即时反映到系统底层的实际状态和行为上的情况,反之亦然。开放性和原因连接是反射系统的两大基本要素。13700863760 java中,反射是一种强大的工具。他使你能够创建灵活的代码,这些代码能在运行时装配,无需在组件之间进行源代表链接。反射允许我们在编写和执行时,使我们的程式代码能够接入装载到jvm中的类的内部信息,而不是原始码中选定的类协作的代码。这使反射成为构建灵活的应用的主要工具。但需注意的是:如果使用不当,反射的成本非常高。 二、java中的类反射:reflection 是 java 程式研发语言的特征之一,他允许运行中的 java 程式对自身进行检查,或说“自审”,并能直接操作程式的内部属性。java 的这一能力在实际应用中也许用得不是非常多,不过在其他的程式设计语言中根本就不存在这一特性。例如,pascal、c 或 c++ 中就没有办法在程式中获得函数定义相关的信息。 1.检测类: 1.1 reflection的工作机制考虑下面这个简单的例子,让我们看看 reflection 是怎么工作的。 import java.lang.reflect.*; public class dumpmethods { public static void main(string args[]) { try { class c = class.forname(args[0]); method m[] = c.getdeclaredmethods(); for (int i = 0; i < m.length; i++) system.out.println(m.tostring()); } catch (throwable e) { system.err.println(e); } } } 按如下语句执行: java dumpmethods java.util.stack 他的结果输出为: public java.lang.object java.util.stack.push(java.lang.object) public synchronized java.lang.object java.util.stack.pop() public synchronized java.lang.object java.util.stack.peek() public boolean java.util.stack.empty() public synchronized int java.util.stack.search(java.lang.object) 这样就列出了java.util.stack 类的各方法名及他们的限制符和返回类型。这个程式使用 class.forname 载入指定的类,然后调用 getdeclaredmethods 来获取这个类中定义了的方法列表。java.lang.reflect.methods 是用来描述某个类中单个方法的一个类。 1.2 java类反射中的主要方法对于以下三类组件中的所有一类来说 -- 构造函数、字段和方法 -- java.lang.class 提供四种独立的反射调用,以不同的方式来获得信息。调用都遵循一种标准格式。以下是用于查找构造函数的一组反射调用: l constructor getconstructor(class[] params) -- 获得使用特别的参数类型的公共构造函数, l constructor[] getconstructors() -- 获得类的所有公共构造函数 l constructor getdeclaredconstructor(class[] params) -- 获得使用特定参数类型的构造函数(和接入级别无关) l constructor[] getdeclaredconstructors() -- 获得类的所有构造函数(和接入级别无关) 获得字段信息的class 反射调用不同于那些用于接入构造函数的调用,在参数类型数组中使用了字段名: l field getfield(string name) -- 获得命名的公共字段 l field[] getfields() -- 获得类的所有公共字段 l field getdeclaredfield(string name) -- 获得类声明的命名的字段 l field[] getdeclaredfields() -- 获得类声明的所有字段 用于获得方法信息函数: l method getmethod(string name, class[] params) -- 使用特定的参数类型,获得命名的公共方法 l method[] getmethods() -- 获得类的所有公共方法 l method getdeclaredmethod(string name, class[] params) -- 使用特写的参数类型,获得类声明的命名的方法 l method[] getdeclaredmethods() -- 获得类声明的所有方法 1.3开始使用 reflection:用于 reflection 的类,如 method,能在 java.lang.relfect 包中找到。使用这些类的时候必须要遵循三个步骤:第一步是获得你想操作的类的 java.lang.class 对象。在运行中的 java 程式中,用 java.lang.class 类来描述类和接口等。下面就是获得一个 class 对象的方法之一: class c = class.forname("java.lang.string"); 这条语句得到一个 string 类的类对象。更有另一种方法,如下面的语句: class c = int.class; 或 class c = integer.type; 他们可获得基本类型的类信息。其中后一种方法中访问的是基本类型的封装类 (如 integer) 中预先定义好的 type 字段。第二步是调用诸如 getdeclaredmethods 的方法,以取得该类中定义的所有方法的列表。一旦取得这个信息,就能进行第三步了??使用 reflection api 来操作这些信息,如下面这段代码: class c = class.forname("java.lang.string"); method m[] = c.getdeclaredmethods(); system.out.println(m[0].tostring()); 他将以文本方式打印出 string 中定义的第一个方法的原型。 2.处理对象:如果要作一个研发工具像debugger之类的,你必须能发现filed values,以下是三个步骤: a.创建一个class对象 b.通过getfield 创建一个field对象 c.调用field.getxxx(object)方法(xxx是int,float等,如果是对象就省略;object是指实例). 例如: import java.lang.reflect.*; import java.awt.*; class sampleget { public static void main(string[] args) { rectangle r = new rectangle(100, 325); printheight(r); } static void printheight(rectangle r) { field heightfield; integer heightvalue; class c = r.getclass(); try { heightfield = c.getfield("height"); heightvalue = (integer) heightfield.get(r); system.out.println("height: " + heightvalue.tostring()); } catch (nosuchfieldexception e) { system.out.println(e); } catch (securityexception e) { system.out.println(e); } catch (illegalaccessexception e) { system.out.println(e); } } } 三、安全性和反射:在处理反射时安全性是个较复杂的问题。反射经常由框架型代码使用,由于这一点,我们可能希望框架能够全方面接入代码,无需考虑常规的接入限制。不过,在其他情况下,不受控制的接入会带来严重的安全性风险,例如当代码在不值得信任的代码共享的环境中运行时。由于这些互相矛盾的需求,java编程语言定义一种多级别方法来处理反射的安全性。基本模式是对反射实施和应用于原始码接入相同的限制: n 从任意位置到类公共组件的接入 n 类自身外部无所有到私有组件的接入 n 受保护和打包(缺省接入)组件的有限接入 不过至少有些时候,围绕这些限制更有一种简单的方法。我们能在我们所写的类中,扩展一个普通的基本类java.lang.reflect.accessibleobject 类。这个类定义了一种setaccessible方法,使我们能够启动或关闭对这些类中其中一个类的实例的接入检测。唯一的问题在于如果使用了安全性管理器,他将检测正在关闭接入检测的代码是否许可了这样做。如果未许可,安全性管理器抛出一个例外。下面是一段程式,在twostring 类的一个实例上使用反射来显示安全性正在运行: public class reflectsecurity { public static void main(string[] args) { try { twostring ts = new twostring("a", "b"); field field = clas.getdeclaredfield("m_s1"); // field.setaccessible(true); system.out.println("retrieved value is " + field.get(inst)); } catch (exception ex) { ex.printstacktrace(system.out); } } } 如果我们编译这一程式时,不使用所有特定参数直接从命令行运行,他将在field .get(inst)调用中抛出一个illegalaccessexception异常。如果我们不注释field.setaccessible(true)代码行,那么重新编译并重新运行该代码,他将编译成功。最后,如果我们在命令行添加了jvm参数-djava.security.manager以实现安全性管理器,他仍然将不能通过编译,除非我们定义了reflectsecurity类的许可权限。 四、反射性能:反射是一种强大的工具,但也存在一些不足。一个主要的缺点是对性能有影响。使用反射基本上是一种解释操作,我们能告诉jvm,我们希望做什么并且他满足我们的需求。这类操作总是慢于只直接执行相同的操作。下面的程式是字段接入性能测试的一个例子,包括基本的测试方法。每种方法测试字段接入的一种形式 -- accesssame 和同一对象的成员字段协作,accessother 使用可直接接入的另一对象的字段,accessreflection 使用可通过反射接入的另一对象的字段。在每种情况下,方法执行相同的计算 -- 循环中简单的加/乘顺序。程式如下: public int accesssame(int loops) { m_value = 0; for (int index = 0; index < loops; index++) { m_value = (m_value + additive_value) * multiplier_value; } return m_value; } public int accessreference(int loops) { timingclass timing = new timingclass(); for (int index = 0; index < loops; index++) { timing.m_value = (timing.m_value + additive_value) * multiplier_value; } return timing.m_value; } public int accessreflection(int loops) throws exception { timingclass timing = new timingclass(); try { field field = timingclass.class. getdeclaredfield("m_value"); for (int index = 0; index < loops; index++) { int value = (field.getint(timing) + additive_value) * multiplier_value; field.setint(timing, value); } return timing.m_value; } catch (exception ex) { system.out.println("error using reflection"); throw ex; } } 在上面的例子中,测试程式重复调用每种方法,使用一个大循环数,从而平均多次调用的时间衡量结果。平均值中不包括每种方法第一次调用的时间,因此初始化时间不是结果中的一个因素。下面的图清晰的向我们展示了每种方法字段接入的时间:图 1:字段接入时间 : 我们能看出:在前两副图中(sun jvm),使用反射的执行时间超过使用直接接入的1000倍以上。通过比较,ibm jvm可能稍好一些,但反射方法仍旧需要比其他方法长700倍以上的时间。所有jvm上其他两种方法之间时间方面无所有显著差异,但ibm jvm几乎比sun jvm快一倍。最有可能的是这种差异反映了sun hot spot jvm的专业优化,他在简单基准方面表现得非常糟糕。反射性能是sun研发1.4 jvm时关注的一个方面,他在反射方法调用结果中显示。在这类操作的性能方面,sun 1.4.1 jvm显示了比1.3.1版本非常大的改进。如果为为创建使用反射的对象编写了类似的计时测试程式,我们会发现这种情况下的差异不象字段和方法调用情况下那么显著。使用newinstance()调用创建一个简单的java.lang.object实例耗用的时间大约是在sun 1.3.1 jvm上使用new object()的12倍,是在ibm 1.4.0 jvm的四倍,只是sun 1.4.1 jvm上的两部。使用array.newinstance(type, size)创建一个数组耗用的时间是所有测试的jvm上使用new type[size]的两倍,随着数组大小的增加,差异逐步缩小。 结束语:java语言反射提供一种动态链接程式组件的多功能方法。他允许程式创建和控制所有类的对象(根据安全性限制),无需提前硬编码目标类。这些特性使得反射特别适用于创建以非常普通的方式和对象协作的库。例如,反射经常在持续存储对象为数据库、xml或其他外部格式的框架中使用。java reflection 非常有用,他使类和数据结构能按名称动态检索相关信息,并允许在运行着的程式中操作这些信息。java 的这一特性非常强大,并且是其他一些常用语言,如 c、c++、fortran 或 pascal 等都不具有的。但反射有两个缺点。第一个是性能问题。用于字段和方法接入时反射要远慢于直接代码。性能问题的程度取决于程式中是怎么使用反射的。如果他作为程式运行中相对非常少涉及的部分,缓慢的性能将不会是个问题。即使测试中最坏情况下的计时图显示的反射操作只耗用几微秒。仅反射在性能关键的应用的核心逻辑中使用时性能问题才变得至关重要。许多应用中更严重的一个缺点是使用反射会模糊程式内部实际要发生的事情。程式人员希望在原始码中看到程式的逻辑,反射等绕过了原始码的技术会带来维护问题。反射代码比相应的直接代码更复杂,正如性能比较的代码实例中看到的相同。解决这些问题的最佳方案是保守地使用反射??仅在他能真正增加灵活性的地方??记录其在目标类中的使用。 利用反射实现类的动态加载
bromon原创 请尊重版权 最近在成都写一个移动增值项目,俺负责后台server端。功能非常简单,手机用户通过gprs打开socket和服务器连接,我则根据用户传过来的数据做出响应。做过类似项目的兄弟一定都知道,首先需要定义一个类似于msnp的通讯协议,不过今天的话题是怎么把这个系统设计得具有高度的扩展性。由于这个项目本身没有进行过较为完善的客户沟通和需求分析,所以以后肯定会有非常多功能上的扩展,通讯协议肯定会越来越庞大,而我作为一个不那么勤快的人,当然不想以后再去修改写好的程式,所以这个项目是实践面向对象设计的好机会。 首先定义一个接口来隔离类: package org.bromon.reflect; public interface operator { public java.util.list act(java.util.list params) } 根据设计模式的原理,我们能为不同的功能编写不同的类,每个类都继承operator接口,客户端只需要针对operator接口编程就能避免非常多麻烦。比如这个类: package org.bromon.reflect.*; public class success implements operator { public java.util.list act(java.util.list params) { list result=new arraylist(); result.add(new string(“操作成功”)); return result; } } 我们还能写其他非常多类,不过有个问题,接口是无法实例化的,我们必须手动控制具体实例化哪个类,这非常不爽,如果能够向应用程式传递一个参数,让自己去选择实例化一个类,执行他的act方法,那我们的工作就轻松多了。 非常幸运,我使用的是java,只有java才提供这样的反射机制,或说内省机制,能实现我们的无理需求。编写一个设置文件emp.properties: #成功响应 1000=success #向客户发送普通文本消息 2000=load #客户向服务器发送普通文本消息 3000=store 文件中的键名是客户将发给我的消息头,客户发送1000给我,那么我就执行success类的act方法,类似的如果发送2000给我,那就执行load类的act方法,这样一来系统就完全符合开闭原则了,如果要添加新的功能,完全不必修改已有代码,只需要在设置文件中添加对应规则,然后编写新的类,实现act方法就ok,即使我弃这个项目而去,他将来也能非常好的扩展。这样的系统具有了非常良好的扩展性和可插入性。 下面这个例子体现了动态加载的功能,程式在执行过程中才知道应该实例化哪个类: package org.bromon.reflect.*; import java.lang.reflect.*; public class testreflect { //加载设置文件,查询消息头对应的类名 private string loadprotocal(string header) { string result=null; try { properties prop=new properties(); fileinputstream fis=new fileinputstream("emp.properties"); prop.load(fis); result=prop.getproperty(header); fis.close(); }catch(exception e) { system.out.println(e); } return result; } //针对消息作出响应,利用反射导入对应的类 public string response(string header,string content) { string result=null; string s=null; try { /* * 导入属性文件emp.properties,查询header所对应的类的名字 * 通过反射机制动态加载匹配的类,所有的类都被operator接口隔离 * 能通过修改属性文件、添加新的类(继承msgoperator接口)来扩展协议 */ s="org.bromon.reflect."+this.loadprotocal(header); //加载类 class c=class.forname(s); //创建类的事例 operator mo=(operator)c.newinstance(); //构造参数列表 class params[]=new class[1]; params[0]=class.forname("java.util.list"); //查询act方法 method m=c.getmethod("act",params); object args[]=new object[1]; args[0]=content; //调用方法并且获得返回 object returnobject=m.invoke(mo,args); }catch(exception e) { system.out.println("handler-response:"+e); } return result; } public static void main(string args[]) { testreflect tr=new testreflect(); tr.response(args[0],”消息内容”); } } 测试一下:java testreflect 1000 这个程式是针对operator编程的,所以无需做所有修改,直接提供load和store类,就能支持2000、3000做参数的调用。 有了这样的内省机制,能把接口的作用发挥到极至,设计模式也更能体现出威力,而不仅仅供我们饭后闲聊。 更多黑客技术 黑客软件 计算机技术 编程技术 网站技术 qq技术 IT新闻 黑客基地 请访问 灯火安全联盟 灯火黑客 www.hack8888.com/bbs
| | | | |
|