内省的概念
反射与内省应该放到一起来说,这样也便于两者之间的比较。
内省是对象自己分析自己的能力,其中方法可以返回一个对象中的变量,方法等。这样似乎是在说反射,与反射不同的是强调动动态改变,加载的反射可以称为内省,比如动态创建一个类。
反射的概念
反射的概念是1982年Smith首次提出的,指的是程序可以访问,检测和修改它本身状态或行为的一种能力。它的提出很快就引发了计算机科学领域关于应用反射性的研究。
反射本身并不是什么新的概念,一说到反射,马上就能让人想到的是在每天我们照镜子,那就是反射,只不过计算机科学的反射包含的不止是"照镜子"那么简单,它还包括了我们"照镜子"之后的举动,比如,看到发型有些乱,要用梳子梳理一下,当然了,这是比较通俗的说法,在计算机科学领域,反射是一类应用,它能自我描述和自我控制。也就是说比如说某个对象,可以通过反射机制对自身的行为进行描述和监测,并能更具自身行为的状态和结果,调整或者修改应用所描述行为的状态和相关的语意。它不单指反射本身,还包括对反射结果所采取的措施。反射所带来的最明显的结果就是实现了反射机制的系统,都具有了开放性,当然,反射系统除了满足开放性条件外,还必须满足原因连接。原因连接是指对反射系统自描述的改变能够立即反映到系统底层的实际状态和行为上的情况,反之亦然。毕竟照着镜子梳头,就是为了梳理好,并且能看到自己梳好了嘛。开放性和原因连接是反射系统的两大基本要素。关于反射的应用,将在后面引用一篇文章来表现其在实际应用种的灵活性。
Java中的反射机制
Java中的反射,是反射概念在Java这种语言的一种体现,它是Java非常强大的工具,能创建灵活的代码,这些代码可以在运行的时候再去装配,而不需要组件之间进行源代码链接。试想,有时候我们需要在运行的时候才决定我们所需要加载的类,如果没有反射机制,那做这种工作的时候,将是非常麻烦的。反射允许我们再编写和执行的时候,使我们的程序代码能够装载到JVM种的类的内部信息,而不是源代码种选定的类协作代码。
java.lang.reflection包中包含了Java为反射机制所写的API,它允许Java程序对自身进行检查,并能直接操作程序的内部属性。
一个类一般包括了三类组件,构造器,字段和方法,下面将会通过具体代码阐述Java中如何反射调用。
首先是如何捕获自身,要获得对象,有三个方法
Class c = Class.forName(String str);
这条语句通过一个字符串捕获该字符串所代表的类对象。
Class c = int.class;
这里的int可以被任何一个类,或者八种基本类型的关键字替换。
Class c = Integer.TYPE;
这里的类要注意的是,它只能在基本类型的类中有,而像String就没有TYPE属性。
这样就可以通过c对动态加载的类进行操作了。
首先是获得关键字
Field f[] = c.getDeclaredFields();
for(int i = 0;i<f.length;i++){
System.out.println(f[i].toString());
}
获得构造函数
Constructor con[] = c.getDeclaredConstructors();
for(int j = 0;j<con.length;j++){
System.out.println(con[j].toString());
}
获得方法
Method m[] = c.getDeclaredMethods();
for(int k = 0;k<m.length;k++){
System.out.println(m[k].toString());
}
Class的方法有很多,具体的可以参看API,这里使用的方法是getDeclaredXXX(),当然,它们还有getXXX()方法,两者不同之处是前者能返回所有定义过的字段构造器和方法,而后者只能返回公用字段,下面是完整代码:
import java.lang.reflect.*;
public class DumpMethods{
public static void main(String[] args){
try{
Class c = Class.forName(args[0]);
Field f[] = c.getDeclaredFields();
for(int i = 0;i<f.length;i++){
System.out.println(f[i].toString());
}
System.out.println("****************************");
Constructor con[] = c.getDeclaredConstructors();
for(int j = 0;j<con.length;j++){
System.out.println(con[j].toString());
}
System.out.println("****************************");
Method m[] = c.getDeclaredMethods();
for(int k = 0;k<m.length;k++){
System.out.println(m[k].toString());
}
System.out.println("****************************");
}catch(Exception e){
System.err.println(e);
}
}
}
只要在命令行里输入某个类的类名就可以得到它的所有字段构造器和方法了,当然可以得到它的注释,同样的就是Annotation,使用方法是一样的。这段代码志在展示反射的概念,对于这种机制的实际应用,将在下面展示。
反射常由框架型代码使用,由于这点,有时候它会带来很大的风险。从结构上看,Field,Constructor,Method都是从AccessibleObject继承而来,而在这个父类中定义了一个叫做setAccessible的方法,就好像它的名字一样,这个方法能够使用单一安全性检查,为一组对象设置accessible标志,但是这样也不能保证它就是安全的,最终它是否是安全的,还是要看设计者如何决策他的设计。
反射机制响应的也会带来一些性能上的影响,这是肯定的,就好像Java实现了跨平台,同时牺牲的是他在平台上的性能,就好像也有专署平台的Java,性能很高,但是也牺牲了Java的跨平台的性能,反射也一样,在方便快捷的基础上,同样也带来了性能上的下降。
下面就是一个反射机制的实际应用,是转的别人的文章(作者blog:http://blog.csdn.net/bromon/),不过,很形象的说明了反射机制的灵活性。
利用反射实现类的动态加载(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做参数的调用。
有了这样的内省机制,可以把接口的作用发挥到极至,设计模式也更能体现出威力,而不仅仅供我们饭后闲聊。