反映射技术(以下简称:反射)的概念是由Smith在1982年首次提出的,主要是指程序可以访问、检测和修改它本身状态或行为的一种能力。这一概念的提出很快引发了计算机科学领域关于
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.*; public class TestReflection { public static void 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; /** *反射机制的测试类 * *@version1.0 * *@authorChen.yu * *上海Softworks对日软件人才培训中心版权所有 */ publicclass TestClient { publicstaticvoid 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; /** * *该接口是的作用是用来定义清洁设备的标准 *换句话说,如果要想成为清洁设备,那么就必须要具有清洁能力 * *@version1.0 * *@authorChen.yu * *上海Softworks对日软件人才培训中心版权所有 */ publicinterface IClearEquipment { /** *清洁设备的清洁方法 *不同的清洁设备有不同的清洁方法 * */ publicvoid clear(); }以下是BeanFactory类的代码片断:
package cn.softworks.demo; import java.io.IOException; import java.io.InputStream; import java.util.Properties; /** *该类的作用是从配置文件中读取类名,并依靠反射将指定类的实体 *返回,以此达到“清洁工”类和“清洁设备”类之间的解耦 * *该类被设置成了单例模式,并在创建指定类的时候加入了同步锁, *以此保证线程安全。 * *@version1.0 * *@authorChen.yu * *上海Softworks对日软件人才培训中心版权所有 */ publicclass BeanFactory { /** *单例工厂实体 */ privatestatic BeanFactory instance = null; /** *用于保存softworks.cfg.properties配置文件的实体。 */ privatestatic Properties config = null; /** *默认配置文件路径 */ privatestaticfinal 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实体的静态方法 * *@returnBeanFactory的单例实体 */ publicsynchronizedstatic BeanFactory newInstance() { //判断BeanFactory的实体是否已经存在 if (instance != null) returninstance; //如果BeanFactory实体不存在那么立刻创建 instance = new BeanFactory(); returninstance; }
/** *工厂了的创建方法,该方法可以用于创建配置文件中指定key名的类 *现在配置文件中有如下信息: *ClearEquipment=cn.softworks.demo.DustCollector *那么当参数被设置成ClearEquipment的时候,通过该方法就会创建 *cn.softworks.demo.DustCollector类的实体。 * *@paramkey配置文件中类所对应的Key名 * *@return 被加载类的实体 */ publicsynchronized Object creator(String key) { if(config == null) returnnull; //得到配置文件中的类名 String className = config.getProperty(key); try { //通过反射机制创建类实体 return Class.forName(className).newInstance(); } catch (Exception e) { returnnull; } } }
以下是Broom类的代码片断:
package cn.softworks.demo; /** *该类是用来描述扫帚这个清洁工具的 *它实现了清洁工具接口,所以必须实现清洁方法 *@version1.0 *@authorChen.yu *上海Softworks对日软件人才培训中心版权所有 */ publicclass Broom implements IClearEquipment { /** *扫帚的清洁方法 */ publicvoid clear() { System.out.println("The Cleaner Use Broom"); } } 以下是 DustCollector 类的代码片断:package cn.softworks.demo; /** *该类是用来描述吸尘器这个清洁工具的 *它实现了清洁工具接口,所以必须实现清洁方法 *@version1.0 *@authorChen.yu *上海Softworks对日软件人才培训中心版权所有 */ publicclass DustCollector implements IClearEquipment { /** *扫帚的清洁方法 */ publicvoid clear() { System.out.println("The Cleaner Use Dust Collector"); } }
以下是Cleaner类的代码片断:
package cn.softworks.demo; /** *该类是用来描述一个清洁工人的 *清洁工人会使用清洁设备来进行清洁工作的 * *@version1.0 * *@authorChen.yu * *上海Softworks对日软件人才培训中心版权所有 */ publicclass Cleaner { /** *这个方法的作用是定义清洁工人的清洁行为 *可以肯定的是,清洁工人必须借助清洁设备才能清洁 * *@parameq使用的清洁设备 */ publicvoid 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( 依赖注射 )来解决 。待续......