反射机制与系统耦合实例详解 反映射技术(以下简称:反射)的概念是由Smith在1982年首次提出的,主要是指程序可以访问、检测和修改它本身状态或行为的一种能力。这一概念的提出很快引发了计算机科学领域关于应用反射性的研究。它首先被程序语言的设计领域所采用。最近,反射机制也被应用到了视窗系统、操作系统和文件系统中。
我--陈宇,作为
Softworks对日软件人才培训中心的资深教员对于该技术有比较深入的研究,也正好借此与各位读者深入探讨一下。
在如今程序语言的设计领域中,几乎每种OO语言都专门设计了支持反射技术的API,不管是Microsoft公司的.Net框架还是SUN公司的Java语言都是如此,本技术帖就以Java为例来进行探讨。
在Java编程语言中,反射是一种强大的工具。它使您能够创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代码链接。这样一来整个系统的耦合性就会降低并可以大大增加系统的灵活度。反射机制被大量运用在系统架构的设计层次上,并且在编写公共类和系统基盘的时候也起到了举足轻重的作用,甚至有人提出这门技术是一个程序员转型成为系统架构师的必经之路。
反射机制是 Java 被视为动态(或准动态)语言的关键,允许程序于执行期取得任何已知名称之 class 的內部信息,包括 包、父类、接口、内部类、属性、结构体、方法,並可于执行期生成实体、变更 字段內容或唤起 方法。
有了反射机制,我们可以:
1.判断某个对象所属的类型(Class)。
2.取得类型(Class)的属性,方法,构造体和父类的相关信息。
3.找出接口中的常量和方法定义。
4.为一个执行期才得知名称的类产生对象。
Java类反射中的主要方法:
对于类而言构造函数、字段和方法是最为重要的内容java.lang.Class 提供四种独立的反射调用,以不同的方式来获得信息。调用都遵循一种标准格式。
以下是用于查找构造函数的一组反射调用:
方法
说明
Constructor getConstructor(Class[] params)
获得使用特殊的参数类型的
公共构造函数
Constructor[] getConstructors()
获得类的所有公共构造函数
Constructor getDeclaredConstructor(Class[] params)
获得使用特定参数类型的构造函数(与访问级别无关)
Constructor[] getDeclaredConstructors()
获得类的所有构造函数(与访问级别无关)
获得字段信息的Class 反射调用不同于那些用于接入构造函数的调用,在参数类型数组中使用了字段名:
方法
说明
Field getField(String name)
获得指定的公共字段
Field[] getFields()
获得类的所有公共字段
Field getDeclaredField(String name)
获得指定的字段
Field[] getDeclaredFields()
获得类声明的所有字段
用于获得方法信息函数:
方法
说明
Method getMethod(String name, Class[] params)
使用特定的参数类型,获得命名的公共方法
Method[] getMethods()
获得类的所有公共方法
Method getDeclaredMethod(String name, Class[] params)
使用特定的参数类型,获得类声明的命名的方法
Method[] getDeclaredMethods()
获得类声明的所有方法
开始使用反射机制:
用于反射机制的类,如 Method,可以在 java.lang.relfect 包中找到。使用这些类的时候必须要遵循三个步骤:第一步是获得你想操作的类的java.lang.Class 对象。在运行中的 Java 程序中,用 java.lang.Class 类来描述类和接口等。
下面就是获得一个 Class 对象的方法之一:
Class c = Class.forName ("java.lang.Integer");
这条语句得到一个 String 类的类对象。还有另一种方法,如下面的语句:
Class c = Integer.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 中定义的第一个方法的原型。
反射经常由框架型代码使用,由于这一点,我们可能希望框架能够全面接入代码,无需考虑常规的接入限制。例如当代码在不值得信任的代码共享的环境中运行时。
假设有以下这个类的声明:
class DemoReflection
{
private String name = null;
private void doPrint() {
System.out.println("print.....");
}
};
可以肯定的是,这个类中的属性name和方法doPrint都是无法对外展示的,但是使用了反射以后就可以办到。
package cn.softworks.demo;
import java.lang.reflect.*;
publicclass TestReflection {
publicstaticvoid main(String args[]) {
try {
// 通过反映射技术得到DemoReflection的类型
Class cls = Class.forName("DemoReflection");
// 动态创建DemoReflection类的实力
Object instance = cls.newInstance();
// 通过反映射技术得到DemoReflection的非公有方法doPrint
Method m = cls.getDeclaredMethod("doPrint",
new Class[] { String.class });
// 表示可以随意访问该类中的方法
m.setAccessible(true);
// 调用doPrint方法
m.invoke(instance, new Object[] { "Softworks" });
} catch (Exception ex) {
System.out.println(ex);
}
}
};
在该代码中,读者可能看到了一个比较陌生的方法setAccessible,这个方法非常重要,如果它不被设置成true那么所有非公有方法仍然无法调用,所以在调用非公有方法的时候需要注意这点。
Private属性的访问方式和方法的访问方式类似。
反射的实际应用
在之前的介绍中,我们已经了解了反射机制的重要性,也已经了解到了反射经常被运用到系统框架设计和系统解耦中,现在就以一个真实的项目开发案例来探讨一下反射机制的重要性。
1.什么叫系统耦合:
或许很少有读者会在电影结束后做在电影院中观看电影的幕后工作者,但是可以肯定的是,如果没有那么多幕后工作者,那么就不会有诸如《指环王》等大片的出现了。但是如果真的要拍摄出像《指环王》这样的大片,光依靠强大的拍摄团队还不够,团队中的每个成员都必须要相互合作和交流,那么这种行为就被称为团队人员和团队人员的耦合关系。
软件系统就如同刚才的电影拍摄团队,是有大量的类(工作人员)所组成的,那么这些类之间如果没有交互的话,整个软件系统就不可能正确合理的工作,因此软件系统中的耦合就来自于类与类之间的通讯,例如以下代码:
Cleaner clearner = new Cleaner(“Chen.yu”);
Broom broom = new Broom();
cleaner.clear(broom);
通过这段代码,我们可以看出,Cleaner类和Broom类之间有相互交互的关系,也就是说这两个类之间产生了耦合关系。请设想,代码如果有一个人来开发的话,耦合关系是不会影响到系统开发的。因为一个开发员可以理所当然的先开发Broom类再开发Clear类。可以软件工程是一个复杂的过程,一个人是不可能完成所有开发任务的,现在假设我们系统中的Cleaner类和Broom类一定要有两个人分别开发的话,那么问题就暴露出来了。因为根据代码可知,要想开发Cleaner类就必须先开发Broom类,Cleaner类中的方法clear需要使用Broom类的信息。那么究竟应该如何解决这个问题呢?
2.利用工厂模式解决耦合关系:
现在我把代码改写成以下的状态:
package cn.softworks.test;
import cn.softworks.demo.BeanFactory;
import cn.softworks.demo.Cleaner;
import cn.softworks.demo.IClearEquipment;
/**
* 反射机制的测试类
*
* @version 1.0
*
* @author Chen.yu
*
* 上海Softworks对日软件人才培训中心版权所有
*/
public class TestClient {
public static void main(String args[]) {
//通过工厂创建指定的清洁工具类
IClearEquipment eq =
(IClearEquipment) BeanFactory.newInstance().creator("ClearEquipment");
if(eq == null)
return;
//创建清洁工对象
Cleaner cleaner = new Cleaner();
//清洁工人开始清洁
cleaner.clear(eq);
}
}
代码虽然多了,但是可以肯定的是在代码中我们只使用了Cleaner类,并没有使用到Broom类,那么Broom类上哪里去了呢?我们看到了IClearEquipment接口,Broom类正好实现了该接口,并且我们利用工厂模式BeanFactory完全隐藏了Broom类的创建细节,以此来解决Broom类与Cleaner类之间的耦合关系,现在Broom类和Cleaner类就可以交给2个开发员并行开发了。
以下是IClearEquipment接口的代码片断: package cn.softworks.demo;
/**
*
* 该接口是的作用是用来定义清洁设备的标准<br>
* 换句话说,如果要想成为清洁设备,那么就必须要具有清洁能力
*
* @version 1.0
*
* @author Chen.yu
*
* 上海Softworks对日软件人才培训中心版权所有
*/
public interface IClearEquipment {
/**
* 清洁设备的清洁方法<br>
* 不同的清洁设备有不同的清洁方法
*
*/
public void clear();
}
以下是BeanFactory类的代码片断: package cn.softworks.demo;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
/**
* 该类的作用是从配置文件中读取类名,并依靠反射将指定类的实体<br>
* 返回,以此达到“清洁工”类和“清洁设备”类之间的解耦<br>
*
* 该类被设置成了单例模式,并在创建指定类的时候加入了同步锁,<br>
* 以此保证线程安全。
*
* @version 1.0
*
* @author Chen.yu
*
* 上海Softworks对日软件人才培训中心版权所有
*/
public class BeanFactory {
/**
* 单例工厂实体
*/
private static BeanFactory instance = null;
/**
* 用于保存softworks.cfg.properties配置文件的实体。
*/
private static Properties config = null;
/**
* 默认配置文件路径
*/
private static final String CONFIG_PATH = "softworks.cfg.properties";
/**
* 使用了单例模式的工厂类默认构造函数
*
*/
private BeanFactory() {
//得到配置文件的路径
InputStream stream = Thread.currentThread().getContextClassLoader()
.getResourceAsStream(CONFIG_PATH);
config = new Properties();
try {
//将配置文件信息加载到config对象中
config.load(stream);
} catch (IOException e) {
instance = null;
}
}
/**
* 创建BeanFactory实体的静态方法
*
* @return BeanFactory的单例实体
*/
public synchronized static BeanFactory newInstance() {
//判断BeanFactory的实体是否已经存在
if (instance != null)
return instance;
//如果BeanFactory实体不存在那么立刻创建
instance = new BeanFactory();
return instance;
}
/**
* 工厂了的创建方法,该方法可以用于创建配置文件中指定key名的类<br>
* 现在配置文件中有如下信息:<br>
* ClearEquipment=cn.softworks.demo.DustCollector<br>
* 那么当参数被设置成ClearEquipment的时候,通过该方法就会创建<br>
* cn.softworks.demo.DustCollector类的实体。
*
* @param key 配置文件中类所对应的Key名
*
* @return 被加载类的实体
*/
public synchronized Object creator(String key) {
if(config == null)
return null;
//得到配置文件中的类名
String className = config.getProperty(key);
try {
//通过反射机制创建类实体
return Class.forName(className).newInstance();
} catch (Exception e) {
return null;
}
}
}
以下是Broom类的代码片断: package cn.softworks.demo;
/**
* 该类是用来描述扫帚这个清洁工具的<br>
* 它实现了清洁工具接口,所以必须实现清洁方法
* @version 1.0
* @author Chen.yu
* 上海Softworks对日软件人才培训中心版权所有
*/
public class Broom implements IClearEquipment {
/**
* 扫帚的清洁方法
*/
public void clear() {
System.out.println("The Cleaner Use Broom");
}
}
以下是DustCollector类的代码片断:
package cn.softworks.demo;
/**
* 该类是用来描述吸尘器这个清洁工具的<br>
* 它实现了清洁工具接口,所以必须实现清洁方法
* @version 1.0
* @author Chen.yu
* 上海Softworks对日软件人才培训中心版权所有
*/
public class DustCollector implements IClearEquipment {
/**
* 扫帚的清洁方法
*/
public void clear() {
System.out.println("The Cleaner Use Dust Collector");
}
}
以下是Cleaner类的代码片断: package cn.softworks.demo;
/**
* 该类是用来描述一个清洁工人的<br>
* 清洁工人会使用清洁设备来进行清洁工作的
*
* @version 1.0
*
* @author Chen.yu
*
* 上海Softworks对日软件人才培训中心版权所有
*/
public class Cleaner {
/**
* 这个方法的作用是定义清洁工人的清洁行为<br>
* 可以肯定的是,清洁工人必须借助清洁设备才能清洁
*
* @param eq 使用的清洁设备
*/
public void clear(IClearEquipment eq) {
//清洁工人使用清洁设备进行清洁
eq.clear();
}
}
softworks.cfg.properties配置文件:
ClearEquipment=cn.softworks.demo.DustCollector
代码工作原理:
通过以上代码可以知道,现在有一个清洁工类(Cleaner),两个清洁设备类(DustCollector和Broom他们都实现了清洁设备接口(IClearEquipment)),清洁工人的清洁方法(clear)明确要求指明,清洁工人究竟使用哪个清洁设备来开展工作,为了增加系统的灵活性,我们把清洁工人所需要使用的清洁设备在“softworks.cfg.properties”配置文件中做出了定义,并且利用BeanFactory从配置文件中读出了清洁设备的名字,利用反射机制将该清洁设备传入到了Cleaner类的clear方法中。这样一来系统的灵活读就上升了。如果系统需要更改清洁设备,那么我们只需要更改配置文件中的类名就可以了,整个系统的代码都不需要变更,因此反射机制不但解决了系统耦合,而且还大大增加了系统的灵活性。
但是读者可能要问这样,那么Cleaner和BeanFactory之间的耦合性问题又显现出来了,这个问题又如何解决呢?
以上这个问题我们可以利用Spring框架中的IoC(依赖注射)来解决。待续......
上海Softworks软件人才培训中心电话:021-53086530 53086531
地址:上海市北京东路668号科技京城东楼9楼E座
网址:
www.softworks.cn