緊接上一篇文章《 輕松了解Spring中的控制反轉和依賴注入
》講解了SpringIOC和DI的基本概念,這篇文章我們模拟一下SpringIOC的工作機制,使我們更加深刻的了解其中的工作。
類之間的結構圖如下
以下是代碼
BeanFactor接口:在Spring源碼中的定義是:持有對一定數量的Bean的定義,同時每個Bean都被唯一辨別的對象(類),需要實作這個接口。根據對Bean的定義,該工廠将會傳回一個包含Bean定義的對象的獨立執行個體(原型設計模式),或者單例共享(一個不錯的單例設計模式,)範圍是整個工廠的範圍(也可以了解成是整個容器下),傳回哪種類型的執行個體依賴于Bean工廠的配置:API是相同的。因為Spring2.0中擴大了依賴範圍,可以根據具體應用上下問(如在Web環境中的請求和會話),BeanFactory是應用程式元件的中央注冊中心和集中配置。簡單的來說該接口定義了擷取Bean的方法,由子類去實作。
package ioc.factory;
/**
* Created by zzf on 2016/10/26.
*/
public interface BeanFactory {
/**
* 根據對象的ID辨別擷取對象執行個體
* @param name
* @return
*/
Object getBean(String name);
}
BeanInfo類:使用Hash Map進行存儲Bean的資訊,注意主要是存儲了Bean定義的Class類路徑,這樣才能通過取得type進而利用反射執行個體化所定義的Bean。
package ioc.info;
import java.lang.Object;
import java.util.HashMap;
import java.util.Map;
/**
* Created by zzf on 2016/10/26.
*/
public class BeanInfo {
private String id;
private String type;
private Map<String,Object> properties=new HashMap<String,Object>();
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public Map<String, Object> getProperties() {
return properties;
}
public void setProperties(Map<String, Object> properties) {
this.properties = properties;
}
public void addProperty(String name, Object object)
{
this.properties.put(name, object);
}
}
Person:xml檔案中定義的Bean
package ioc.bean;
/**
* Created by zzf on 2016/10/26.
*/
public class Person {
private String username;
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
AbstractBeanFactory接口:是實作BeanFactory接口的抽象基類。實作擷取Bean定義的方法。是實作邏輯的最關鍵一個類,注冊、讀取、分析、注入都是在這個類上的方法調用的,具體可以根據方法查找。是最頂層的IOC,實作該類負責從注冊器中取出注冊對象,和實作從對象描述資訊轉換為對象執行個體的過程實作根據名稱擷取對象的方法。
抽象方法setReader:由子類決定如果使用什麼樣的注冊讀取器,這裡使用了模闆方法,父類定義抽象方法,但交給子類自由去設計方法内容,即交由子類自己去選擇SourceReader的實作類。
parseBead方法:解析生成并生成對象執行個體,主要通過反射完成,根據類名,加載指定類,并取得該類的Class對象使用Class對象執行個體化該類,擷取一個對象。逐個設定對象字段的值,這裡采用反射調用setter Method方式
loadBeans方法:在SourceReader的實作類中所實作,主要作用是加載Xml中所定義的Bean内容,并将其屬性資訊存入BeanInfo類中。
parseBean(beanInfo):分析在loadBeans方法所存入BeanInfo,并通過反射調用注入到Person類中。
package ioc.factory;
import ioc.info.BeanInfo;
import ioc.reader.SourceReader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
/**
* Created by zzf on 2016/10/26.
*/
public abstract class AbstractBeanFactory implements BeanFactory {
private String filePath;
private Map<String,BeanInfo> container;
protected SourceReader reader;
public AbstractBeanFactory(String filePath){
this.filePath=filePath;
}
/**
*
* @param reader
*/
protected abstract void setReader(SourceReader reader);
//注冊bean
public void registerBeans(){
this.container=this.reader.loadBeans(filePath);
}
@Override
public Object getBean(String name) {
BeanInfo beanInfo=this.container.get(name);
if(beanInfo==null){
return null;
}else {
return this.parseBean(beanInfo);
}
}
/**
*
* @param beanInfo 指定對象的描述資訊
* @return
*/
protected Object parseBean(BeanInfo beanInfo){
Class clazz;
try {
//加載Bean的執行個體
clazz=Class.forName(beanInfo.getType());
Object bean=clazz.newInstance();
//擷取該對象下的所有方法,包括私有
Method[] methods=clazz.getDeclaredMethods();
for (String property:beanInfo.getProperties().keySet()){
String setter="set"+ firstCharToUp(property);
for(Method method:methods){
String methodName=method.getName();
if(methodName.equals(setter)){
Object value=beanInfo.getProperties().get(property);
//使用反射調用set方法
method.invoke(bean,value);
}
}
}
return bean;
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
private String firstCharToUp(String property) {
System.out.println(property);
char [] c=property.toCharArray();
String first=String.valueOf(c[0]).toUpperCase();
c[0]=first.charAt(0);
System.out.println(String.valueOf(c));
return String.valueOf(c);
}
}
SourceReader:注冊讀取器接口,設計頂層讀取器的抽象方法,負責讀取使用者注冊的對象,繼承該接口的類可以實作多種讀取方式,如從配置檔案中讀取,根據标注讀取,從網絡中讀取等等
package ioc.reader;
import ioc.info.BeanInfo;
import java.util.Map;
/**
* Created by zzf on 2016/10/26.
*
*/
public interface SourceReader {
Map<String,BeanInfo> loadBeans(String filePath);
}
XMLContext:XML的上下文,繼承了AbstractBeanFactory,裡面比較重要的方法是setReader(),在父類中該方法是抽象方法, 這樣的做的意義是交由子類實作自己所想要的讀取方式。該方法中指明注冊讀取器(這裡采用的XML,讀者可以根據興趣去實作另外的方式如注解)并在構造該方法時一次性加載注冊的對象。
package ioc.context;
import ioc.factory.AbstractBeanFactory;
import ioc.reader.SourceReader;
import ioc.reader.XMLSourceReader;
/**
* Created by zzf on 2016/10/26.
* 上下文的構造方法
*
*/
public class XMLContext extends AbstractBeanFactory{
public XMLContext(String filePath){
super(filePath);
this.setReader(new XMLSourceReader());
super.registerBeans();
}
@Override
protected void setReader(SourceReader reader) {
super.reader=reader;
}
}
XmlContext類:繼承了AbstractBeanFactory抽象類,進行Bean的注冊和注冊XML的讀取器,注意傳回的是Map<String, BeanInfo>
。
package ioc.reader;
import ioc.info.BeanInfo;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.InputStream;
import java.io.Reader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
* Created by zzf on 2016/10/26.
* 使用 dom4j進行Xml的讀取操作
*/
public class XMLSourceReader implements SourceReader {
@Override
public Map<String, BeanInfo> loadBeans(String filePath) {
//讀取指定的配置檔案
SAXReader reader = new SAXReader();
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
//從class目錄下擷取指定的xml檔案
InputStream ins = classLoader.getResourceAsStream(filePath);
Document doc = null;
try {
doc = reader.read(ins);
} catch (DocumentException e) {
e.printStackTrace();
}
//獲得根節點
Element root = doc.getRootElement();
Map<String,BeanInfo>beanInfoMap=new HashMap<String, BeanInfo>();
//周遊bean
for (Iterator i = root.elementIterator("bean"); i.hasNext();){
Element element = (Element) i.next();
//擷取bean的屬性id和class
Attribute id = element.attribute("id");
Attribute cls = element.attribute("class");
try {
//利用Java反射機制,通過class的名稱擷取Class對象
Class bean=Class.forName(cls.getText());
//擷取對應class的資訊
java.beans.BeanInfo info= Introspector.getBeanInfo(bean);
//擷取其屬性描述
PropertyDescriptor [] propertyDescriptors=info.getPropertyDescriptors();
Method method;
Object object=bean.newInstance();
BeanInfo beanInfo=new BeanInfo();
for(Iterator iterator=element.elementIterator("property");iterator.hasNext();){
Element foo2= (Element) iterator.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;
}
System.out.println("name:"+name.getText()+"value"+value);
for (int j=0;j<propertyDescriptors.length;j++){
if(propertyDescriptors[j].getName().equalsIgnoreCase(name.getText())){
method=propertyDescriptors[j].getWriteMethod();
//利用Java的反射極緻調用對象的某個set方法,并将值設定進去
method.invoke(object,value);
//将擷取的對象屬性資訊存入我們自定義的BeanInfo當中
beanInfo.addProperty(name.getText(),value);
}
}
beanInfo.setId(id.getText());
beanInfo.setType(cls.getText());
beanInfoMap.put(id.getText(),beanInfo);
}
return beanInfoMap;
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IntrospectionException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
return null;
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<bean id="person" class="ioc.bean.Person">
<property name="username">
<value>zzf</value>
</property>
<property name="password">
<value>12345678</value>
</property>
</bean>
</beans>
package ioc;
import ioc.bean.Person;
import ioc.context.XMLContext;
import ioc.factory.BeanFactory;
/**
* Created by zzf on 2016/10/26.
*/
public class test {
public static void main(String[] args) {
BeanFactory factory=new XMLContext("configuration/config.xml");
Person person= (Person) factory.getBean("person");
System.out.println(person.getPassword());
System.out.println(person.getUsername());
}
}
執行main方法後,控制台成功輸出xml所定義的屬性。
整體的實作過程主要分以下幾步,1.讀取Xml檔案,怎麼讀取?(看代碼) 2.讀取完之後呢?應該有個容器臨時存放一下,進而實作友善傳遞,這裡使用的HashMap,從中也可以看到為什麼要用BeanInfo類來存放類的資訊,3.分析Map<String,BeanInfo>容器,提取裡面的Bean屬性,4.提取之後,通過反射方法注入到Person類中,進而便實作了username和password屬性的注入了。
再結合我第一篇所講的SpringIOC概念講解,相信讀者應該能夠清晰的認識的IOC的作用和大概的實作過程。