在 Java中,其反射和动态代理机制极其强大,我们可以通过其反射机制在运行时获取信息。而代理是一种基本的设计模式,它是一种为了提供额外的或不同的操作而插入到真实对象中的某个对象。而 Java的动态代理在代理上更进一步,既能动态的创建代理对象,又能动态的调用代理方法。 Java的反射和动态代理机制,使 Java变得更加强大。
Spring框架这几年风头正劲,虽然使用者众多,但真正了解其内部实现原理的朋友却并不是很多。其实,了解它的内部实现机制和设计思想是很有必要的大家都知道, Spring框架的 IOC和 AOP部分功能强大,很值得我们学习。那么让我们在这两篇文章中分别详细的学习 IOC和 AOP的实现吧。
在本文中,主要讲述的是用 Java的反射机制实现 IOC。下面,让我们开始 IOC之旅吧!
一. Java 反射机制概述与初探
Java的反射机制是 Java语言的一个重要特性, Java具有的比较突出的动态机制就是反射( reflection)。通过它,我们可以获取如下信息:
1) 在运行时判断任意一个对象所属的类;
2) 在运行时获取类的对象;
3) 在运行时获得类所具有的成员变量和方法等。
下面让我们通过调用一个 Java Reflection API的演示实例来见识一下反射机制的强大。
首先在 IDE中建立名为 reflection_proxy的 Java工程,并建立存放源文件的目录 src,并在 src目录下分别建立 org.amigo. reflection目录和 org.amigo.proxy目录来分别存放代理和反射的实例。我们在 reflection目录下建立 ReflectionTest.java文件,在该文件中编写代码来演示 Java Reflection API的使用。该类的代码如下所示:
import java.awt.Button;
import java.lang.reflect.Method;
import java.util.Hashtable;
/**
*初探Java的反射机制.
* @author <a href="mailto:xiexingxing1121@126.com">AmigoXie</a>
*Creationdate:2007-10-2-上午10:13:48
*/
publicclass ReflectionTest {
/**
*@paramargs
*/
publicstaticvoid main(String[] args) throws Exception {
ReflectionTest reflection = new ReflectionTest();
reflection.getNameTest();
System.out.println( "" );
reflection.getMethodTest();
}
/**
*Class的getName()方法测试.
*@throwsException
*/
publicvoid getNameTest() throws Exception {
System.out.println( " ===========begin getNameTest============ " );
String name = " 阿蜜果 " ;
Class cls = name.getClass();
System.out.println( " String类名: " + cls.getName());
Button btn = new Button();
Class btnClass = btn.getClass();
System.out.println( " Button类名: " + btnClass.getName());
Class superBtnClass = btnClass.getSuperclass();
System.out.println( " Button的父类名: " + superBtnClass.getName());
Class clsTest = Class.forName( " java.awt.Button " );
System.out.println( " clsTest name: " + clsTest.getName());
System.out.println( " ===========end getNameTest============ " );
}
/**
*Class的getMethod()方法测试.
*@throwsException
*/
publicvoid getMethodTest() throws Exception {
System.out.println( " ===========begin getMethodTest========== " );
Class cls = Class.forName( " org.amigo.reflection.ReflectionTest " );
Class ptypes[] = new Class[ 2 ];
ptypes[ 0 ] = Class.forName( " java.lang.String " );
ptypes[ 1 ] = Class.forName( " java.util.Hashtable " );
Method method = cls.getMethod( " testMethod " , ptypes);
Object args[] = new Object[ 2 ];
args[ 0 ] = " hello, my dear! " ;
Hashtable < String, String > ht = new Hashtable < String, String > ();
ht.put( " name " , " 阿蜜果 " );
args[ 1 ] = ht;
String returnStr = (String) method.invoke( new ReflectionTest(), args);
System.out.println( " returnStr= " + returnStr);
System.out.println( " ===========end getMethodTest========== " );
}
public String testMethod(String str, Hashtable ht) throws Exception {
String returnStr = " 返回值 " ;
System.out.println( " 测试testMethod()方法调用 " );
System.out.println( " str= " + str);
System.out.println( " 名字= " + (String) ht.get( " name " ));
System.out.println( " 结束testMethod()方法调用 " );
return returnStr;
}
}
运行该例,可在控制台看到如下内容:
===========begin getNameTest============
String 类名 : java.lang.String
Button 类名 : java.awt.Button
Button 的父类名 : java.awt.Component
clsTest name: java.awt.Button
===========end getNameTest============
===========begin getMethodTest==========
测试 testMethod() 方法调用
str= hello, my dear!
名字 = 阿蜜果
结束 testMethod() 方法调用
returnStr= 返回值
===========end getMethodTest==========
分析运行结果,我们可以发现, Java 的反射机制使得我们在运行时能够判断一个对象所属的类,获取对象的方法并得其进行调用,并获取方法的返回结果等功能。
二. IOC 使用的背景
在我们日常的设计中,类与类之间存在着千丝万缕的关系,如果两个类存在着强耦合关系,那么在维护时,一个类的修改很可能会牵动另一个类的关联修改,从而使得我们的维护工作步履维艰。下面让我们来看这样的一个强耦合反面例子。
首先我们建立一个 Chinese.java类,该类的 sayHelloWorld(String name)方法,用中文对名为 name的人问好,其内容如下:
/**
*中国人类.
* @author <a href="mailto:xiexingxing1121@126.com">AmigoXie</a>
*Creationdate:2007-10-2-上午10:37:17
*/
publicclass Chinese {
/**
*用中文对某人问好.
*@paramname姓名
*/
publicvoid sayHelloWorld(String name) {
String helloWorld = " 你好, " + name;
System.out.println(helloWorld);
}
}
下面我们接着建立一个 American.java 类, 该类的 sayHelloWorld(String name)方法,用英文对名为 name的人问好,其内容如下:
/**
*美国人类.
* @author <a href="mailto:xiexingxing1121@126.com">AmigoXie</a>
*@version1.0
*Creationdate:2007-10-2-上午10:41:27
*/
publicclass American {
/**
*用英文对某人问好.
*@paramname姓名
*/
publicvoid sayHelloWorld(String name) {
String helloWorld = " Hello, " + name;
System.out.println(helloWorld);
}
}
最后我们编写一个测试类对这两个类的 sayHelloWorld(String name) 方法进行测试,下面是该类的内容:
/**
*HelloWorld测试.
* @author <a href="mailto:xiexingxing1121@126.com">AmigoXie</a>
*Creationdate:2007-10-2-上午10:45:13
*/
publicclass HelloWorldTest {
/**
*测试Chinese和American的sayHelloWorld()方法.
*@paramargs
* @author <a href="mailto:xiexingxing1121@126.com">AmigoXie</a>
*Creationdate:2007-10-2-上午10:43:51
*/
publicstaticvoid main(String[] args) {
Chinese chinese = new Chinese();
chinese.sayHelloWorld( " 阿蜜果 " );
American american = new American();
american.sayHelloWorld( " Amigo " );
}
}
观察 HelloWorldTest 我们可以很清楚的看到,该类与 Chinese.java 类和 American.java 类都存在强耦合关系。
上面的例子让我们想到的是在 N 年以前,当我们需要某个东西时,我们一般是自己制造。但是当发展到了一定的阶段后,工厂出现了,我们可以工厂中购买我们需要的东西,这极大的方便了我们。在上例中,我们都是通过 new 来创建新的对象,在开发中,这种强耦合关系是我们所不提倡的,那么我们应该如何来实现这个例子的解耦呢?我们接着想到了使用工厂模式,我们需要新建一个工厂类来完成对象的创建,并采用依赖接口的方式,此时需要对代码进行如下修改:
首先建立接口类 Human.java ,其内容如下:
/**
* 人类接口类.
* @author <a href="mailto:xiexingxing1121@126.com">AmigoXie</a>
* Creation date: 2007-10-2 - 上午11:04:56
*/
public interface Human {
/**
* 对某人问好.
* @param name 姓名
*/
public void sayHelloWorld(String name);
}
并将 American.java类和 Chinese.java类改为实现该接口,即类头分别改成: public class American implements Human和 public class Chinese implements Human。
接着编写 HumanFactory.java工厂类,其内容为 :
/**
* 工厂类.
* @author <a href="mailto:xiexingxing1121@126.com">AmigoXie</a>
* Creation date: 2007-10-2 - 上午11:09:30
*/
public class HumanFactory {
/**
* 通过类型字段获取人的相应实例
* @param type 类型
* @return 返回相应实例
*/
public Human getHuman(String type) {
if ( " chinese " .equals(type)) {
return new Chinese();
} else {
return new American();
}
}
}
最后我们还需要修改测试类 HelloWorld.java类,修改后的内容如下:
/**
* HelloWorld测试.
* @author <a href="mailto:xiexingxing1121@126.com">AmigoXie</a>
* Creation date: 2007-10-2 - 上午10:45:13
*/
public class HelloWorldTest {
/**
* 测试sayHelloWorld()方法.
* @param args
* @author <a href="mailto:xiexingxing1121@126.com">AmigoXie</a>
* Creation date: 2007-10-2 - 上午10:43:51
*/
public static void main(String[] args) {
HumanFactory factory = new HumanFactory();
Human human1 = factory.getHuman( " chinese " );
human1.sayHelloWorld( " 阿蜜果 " );
Human human2 = factory.getHuman( " american " );
human2.sayHelloWorld( " Amigo " );
}
}
观察此例我们可以看到,该类不再与具体的实现类 Chinese和 American存在耦合关系,而只是与它们的接口类 Human存在耦合关系,具体对象的获取只是通过传入字符串来获取,很大程度上降低了类与类之间的耦合性。
但是我们还是不太满足,因为还需要通过 chinese和 american在类中获取实例,那么当我们需要修改时实现时,我们还需要在类中修改这些字符串,那么还有没有更好的办法呢?让我们在下节中进行继续探讨。
三. IOC 粉墨登场
IOC( Inverse of Control )可翻译为“控制反转”,但大多数人都习惯将它称为“依赖注入”。在 Spring中,通过 IOC可以将实现类、参数信息等配置在其对应的配置文件中,那么当需要更改实现类或参数信息时,只需要修改配置文件即可,这种方法在上例的基础上更进一步的降低了类与类之间的耦合。我们还可以对某对象所需要的其它对象进行注入,这种注入都是在配置文件中做的, Spring的 IOC的实现原理利用的就是 Java的反射机制, Spring还充当了工厂的角色,我们不需要自己建立工厂类。 Spring的工厂类会帮我们完成配置文件的读取、利用反射机制注入对象等工作,我们可以通过 bean的名称获取对应的对象。
下面让我们看看如下的模拟 Spring的 bean工厂类:
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
/**
* bean工厂类.
* @author <a href="mailto:xiexingxing1121@126.com">AmigoXie</a>
* Creation date: 2007-10-6 - 上午10:04:41
*/
public class BeanFactory {
private Map < String, Object > beanMap = new HashMap < String, Object > ();
/**
* bean工厂的初始化.
* @param xml xml配置文件
*/
public void init(String xml) {
try {
// 读取指定的配置文件
SAXReader reader = new SAXReader();
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// 从class目录下获取指定的xml文件
InputStream ins = classLoader.getResourceAsStream(xml);
Document doc = reader.read(ins);
Element root = doc.getRootElement();
Element foo;
// 遍历bean
for (Iterator i = root.elementIterator( " bean " ); i.hasNext();) {
foo = (Element) i.next();
// 获取bean的属性id和class
Attribute id = foo.attribute( " id " );
Attribute cls = foo.attribute( " class " );
// 利用Java反射机制,通过class的名称获取Class对象
Class bean = Class.forName(cls.getText());
// 获取对应class的信息
java.beans.BeanInfo info = java.beans.Introspector.getBeanInfo(bean);
// 获取其属性描述
java.beans.PropertyDescriptor pd[] = info.getPropertyDescriptors();
// 设置值的方法
Method mSet = null ;
// 创建一个对象
Object obj = bean.newInstance();
// 遍历该bean的property属性
for (Iterator ite = foo.elementIterator( " property " ); ite.hasNext();) {
Element foo2 = (Element) ite.next();
// 获取该property的name属性
Attribute name = foo2.attribute( " name " );
String value = null ;
// 获取该property的子元素value的值
for (Iterator ite1 = foo2.elementIterator( " value " ); ite1.hasNext();) {
Element node = (Element) ite1.next(); value = node.getText();
break ;
}
for ( int k = 0 ; k < pd.length; k ++ ) {
if (pd[k].getName().equalsIgnoreCase(name.getText())) { mSet = pd[k].getWriteMethod();
// 利用Java的反射极致调用对象的某个set方法,并将值设置进去 mSet.invoke(obj, value);
}
}
}
// 将对象放入beanMap中,其中key为id值,value为对象
beanMap.put(id.getText(), obj);
}
} catch (Exception e) {
System.out.println(e.toString());
}
}
/**
* 通过bean的id获取bean的对象.
* @param beanName bean的id
* @return 返回对应对象
*/
public Object getBean(String beanName) {
Object obj = beanMap.get(beanName);
return obj;
}
/**
* 测试方法.
* @param args
* @author <a href="mailto:xiexingxing1121@126.com">AmigoXie</a>
* Creation date: 2007-10-6 - 上午11:21:14
*/
public static void main(String[] args) {
BeanFactory factory = new BeanFactory();
factory.init( " config.xml " );
JavaBean javaBean = (JavaBean) factory.getBean( " javaBean " );
System.out.println( " userName= " + javaBean.getUserName());
System.out.println( " password= " + javaBean.getPassword());
}
}
该类的 init(xml)方法,通过指定的 xml来给对象注入属性,为了对该类进行测试,我还需要新建一个 JavaBean和在 src目录下新建一个名为 config.xml的配置文件。 JavaBean的内容如下:
/**
*
* 简单的bean,用于测试
* @author <a href="mailto:xiexingxing1121@126.com">AmigoXie</a>
* Creation date: 2007-10-6 - 上午11:24:30
*/
public class JavaBean {
private String userName;
private String password;
public String getPassword() {
return password;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this .userName = userName;
}
public void setPassword(String password) {
this .password = password;
}
}
这个简单 bean对象中有两个属性,分别为 userName和 password,下面我们在配置文件 config.xml中对其属性注入对应的属性值。配置文件内容如下:
< beans >
< bean id ="javaBean" class ="org.amigo.reflection.JavaBean" >
< property name ="userName" >
< value > 阿蜜果 </ value >
</ property >
< property name ="password" >
< value > 12345678 </ value >
</ property >
</ bean >
</ beans >
类与配置文件都完成后,可以运行 BeanFactory.java文件,控制台显示内容为:
userName= 阿蜜果
password=12345678
可以看到,虽然在 main()方法中没有对属性赋值,但属性值已经被注入,在 BeanFactory类中的 Class bean = Class.forName(cls.getText());通过类名来获取对应的类, mSet.invoke(obj, value);通过 invoke方法来调用特定对象的特定方法,实现的原理都是基于 Java的反射机制,在此我们有一次见证了 Java反射机制的强大。
当然,这只是对 IOC的一个简单演示,在 Spring中,情况要复杂得多,例如,可以一个 bean引用另一个 bean,还可以有多个配置文件、通过多种方式载入配置文件等等。不过原理还是采用 Java的反射机制来实现 IOC的。
四. 总结
在本文中,笔者通过讲述 Java反射机制概述与初探、 IOC使用的背景、 IOC粉墨登场等内容,演示了 Java反射机制 API的强大功能,并通过编写自己的简单的 IOC框架,让读者更好的理解了 IOC的实现原理。
本文通过 IOC的一个简要实现实例,模拟了 Spring中 IOC的实现,虽然只是完成了 Spring中依赖注入的一小部分工作,但是很好的展现了 Java反射机制在 Spring中的应用,能使我们能更好的从原理上了解 IOC的实现,也能为我们实现自己的准 Spring框架提供方案,有兴趣的朋友可以通过 Spring的源码进行 IOC的进一步的学习。