Spring IOC,SpringIOC
在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的使用。該類的代碼如下所示:
package org.amigo.reflection;
import java.awt.Button;
import java.lang.reflect.Method;
import java.util.Hashtable;
/**
*初探Java的反射機制.
*@author<a href="mailto:
[email protected]">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 = "xxxxx";
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", "xxxxx");
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!
名字= xxxxxx
結束testMethod()方法調用
returnStr= 返回值
===========end getMethodTest==========
分析運行結果,我們可以發現,Java的反射機制使得我們在運行時能夠判斷一個對象所屬的類,獲取對象的方法並得其進行調用,並獲取方法的返回結果等功能。
二. IOC使用的背景
在我們日常的設計中,類與類之間存在著千絲萬縷的關系,如果兩個類存在著強耦合關系,那麼在維護時,一個類的修改很可能會牽動另一個類的關聯修改,從而使得我們的維護工作步履維艱。下面讓我們來看這樣的一個強耦合反面例子。
首先我們建立一個Chinese.java類,該類的sayHelloWorld(String name)方法,用中文對名為name的人問好,其內容如下:
package org.amigo.reflection;
/**
*中國人類.
*@author<a href="mailto:
[email protected]">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的人問好,其內容如下:
package org.amigo.reflection;
/**
*美國人類.
*@author<a href="mailto:
[email protected]">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)方法進行測試,下面是該類的內容:
package org.amigo.reflection;
/**
*HelloWorld測試.
*@author<a href="mailto:
[email protected]">AmigoXie</a>
*Creationdate:2007-10-2-上午10:45:13
*/
publicclass HelloWorldTest {
/**
*測試Chinese和American的sayHelloWorld()方法.
*@paramargs
*@author<a href="mailto:
[email protected]">AmigoXie</a>
*Creationdate:2007-10-2-上午10:43:51
*/
publicstaticvoid main(String[] args) {
Chinese chinese = new Chinese();
chinese.sayHelloWorld("xxxxxx");
American american = new American();
american.sayHelloWorld("Amigo");
}
}
觀察HelloWorldTest我們可以很清楚的看到,該類與Chinese.java類和American.java類都存在強耦合關系。
上面的例子讓我們想到的是在N年以前,當我們需要某個東西時,我們一般是自己制造。但是當發展到了一定的階段後,工廠出現了,我們可以工廠中購買我們需要的東西,這極大的方便了我們。在上例中,我們都是通過new來創建新的對象,在開發中,這種強耦合關系是我們所不提倡的,那麼我們應該如何來實現這個例子的解耦呢?我們接著想到了使用工廠模式,我們需要新建一個工廠類來完成對象的創建,並采用依賴接口的方式,此時需要對代碼進行如下修改:
首先建立接口類Human.java,其內容如下:
package org.amigo.reflection;
/**
* 人類接口類.
* @author <a href="mailto:
[email protected]">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工廠類,其內容為:
package org.amigo.reflection;
/**
* 工廠類.
* @author <a href="mailto:
[email protected]">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類,修改後的內容如下:
package org.amigo.reflection;
/**
* HelloWorld測試.
* @author <a href="mailto:
[email protected]">AmigoXie</a>
* Creation date: 2007-10-2 - 上午10:45:13
*/
public class HelloWorldTest {
/**
* 測試sayHelloWorld()方法.
* @param args
* @author <a href="mailto:
[email protected]">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("xxxxx");
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工廠類:
package org.amigo.reflection;
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:
[email protected]">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:
[email protected]">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的內容如下:
package org.amigo.reflection;
/**
*
* 簡單的bean,用於測試
* @author <a href="mailto:
[email protected]">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中對其屬性注入對應的屬性值。配置文件內容如下:
xml version="1.0" encoding="UTF-8"?>
<beans>
<bean id="javaBean" class="org.amigo.reflection.JavaBean">
<property name="userName">
<value>xxxx</value>
</property>
<property name="password">
<value>12345678</value>
</property>
</bean>
</beans>
類與配置文件都完成後,可以運行BeanFactory.java文件,控制台顯示內容為:
userName=xxxxx
password=12345678
可以看到,雖然在main()方法中沒有對屬性賦值,但屬性值已經被注入,在BeanFactory類中的Class bean = Class.forName(cls.getText());通過類名來獲取對應的類,mSet.invoke(obj, value);通過invoke方法來調用特定對象的特定方法,實現的原理都是基於Java的反射機制,在此我們有一次見證了Java反射機制的強大。
當然,這只是對IOC的一個簡單演示,在Spring中,情況要復雜得多,例如,可以一個bean引用另一個bean,還可以有多個配置文件、通過多種方式載入配置文件等等。不過原理還是采用Java的反射機制來實現IOC的。