本篇博文的目錄:
一:前言
二:spring的配置文件
三:依賴的第三方庫、使用技術、代碼布局
四:Document實現
五:獲取Element的實現
六:解析Element元素
七:Bean創造器
八:Ioc容器的創建
九:總結
一:前言:
Spring作為Bean的管理容器,在我們的項目構建中發揮了舉足輕重的作用,尤其是控制反轉(IOC)和依賴(DI)注入的特性,將對象的創建完全交給它來實現,當我們把與其他框架進行整合時,比如與Mybatis整合,可以把sqlMapClientTemplate、數據源等Bean交給它來管理,這樣在我們程序需要的時候,只需要調用它的getBean(String id)方法就可以獲取它的一個實例。這一點我們都知道它是利用反射的原理,取得class然后獲取constructor-arg配置的參數,然后調用newInstance()方法進行實例化的,那么我們在spring配置文件中配置的Bean的屬性,比如Lazy-int、AutoWire屬性Spring是如何解析的呢?這背后又是怎樣的原理呢。這就是本篇博文將要探討的問題,我們將深入到代碼層面,看一看Spring解析Xml的具體原理
二:Spring的配置文件
我們先來看一個簡單的Spring配置文件,由配置文件我們看到了Spring配置文件的一系列屬性,配置Bean的id,class屬性,還有Lazy-int、AutoWire、SingleTon等屬性,配置了這些東西,那么Spring就會按照我們配置的東西進行解析,從而得到我們需要的值。
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:p="http://www.springframework.org/schema/p" xmlns:util="http://www.springframework.org/schema/util" xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:cache="http://www.springframework.org/schema/cache" xsi:schemaLocation=" http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.1.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-3.1.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"> <bean id="pad1" class="com.wyq.Bean.Pad" scope="singleton"> <constructor-arg> <value type="java.lang.double">1999.9</value> </constructor-arg> </bean> <!-- 懶加載 --> <bean id="pad2" class="com.wyq.Bean.Pad" lazy-init="true" autowire="no"></bean> <bean id="person" class="com.wyq.Bean.Person" autowire="byName"> <property name="name" value="Yrion"></property> </bean> </beans>
三:依賴的第三方庫、使用技術、代碼布局
spring解析xml配置的第三方庫需要的是dom4j,使用的技術是java,代碼布局會按照Document、Element、BeanCreator的方式進行,首先定義相關的接口,然后定義子類去實例化,我們來展示一下Spring解析配置文件接口的思維導圖,從中可以看出我們定義了一系列的讀取屬性的接口,比如AutoWire屬性,因為有兩種情況ByName和no、byType三種情況(這里只為了說明問題,我們只定義兩個實現類,正式的話是三個實現類),這里采用的是狀態設計模式,設計一個總接口,然后對不同的情況,我們定義相關的實現類,是那種情況,就返回具體的類。如圖展示的是接口和具體的實現類,接下來我們將會按照這樣的方式去講解每一個接口對應的實現類。
四:獲取Document實現
按照從大到小的思維,我們先來實現DocumenHoler接口,可以看出這個接口我們只定義了一個方法,根據路徑返回具體的Document。然后我們來寫具體的實現子類,有了這樣的類,我們只需要傳入一個路徑,那么就會返回一個模擬的Document對象
import org.dom4j.Document; public interface DocumentHolder { Document getDocument(String filePath); }
import java.io.File; import java.util.HashMap; import java.util.Map; import org.dom4j.Document; import org.dom4j.io.SAXReader; public class XMLDocumentHolder implements DocumentHolder{ //建立一個HashMap用來存放字符串和文檔 private Map<String, Document> docs = new HashMap<String, Document>(); @Override public Document getDocument(String filePath) { Document doc=this.docs.get(filePath);//用HashMap先根據路徑獲取文檔 if (doc==null) { this.docs.put(filePath, readDocument(filePath)); //如果為空,把路徑和文檔放進去 } return this.docs.get(filePath); } /** * 根據路徑讀Document * @param filePath * @return */ private Document readDocument(String filePath) { Document doc =null; try { SAXReader reader = new SAXReader(true);//借用dom4j的解析器 reader.setEntityResolver(new IocEntityResolver()); File xmlFile = new File(filePath); //根據路徑創建文件 doc = reader.read(xmlFile);//用dom4j自帶的reader讀取去讀返回一個Document } catch (Exception e) { e.printStackTrace(); } return doc; } }
五:獲取Element的接口實現
有了Document,按照從大到小的邏輯,我們就需要解析Element元素了,也就是具體的元素加載器,首先我們先來定義一個接口,然后再定義具體的類去實現這個接口。中間主要定義了三個方法添加元素和獲取元素,和獲取所有的元素,在子類中定義了一個HashMap用於鍵用來存儲屬性名,值用來存儲具體的Element元素。
import java.util.Collection; import org.dom4j.Document; import org.dom4j.Element; /** * 加載Element元素 * @author Yiron * */ public interface ElementLoader { void addElements(Document doc);//添加元素 Element getElement(String id);//獲取元素 Collection<Element> getElements();//獲取所有的元素 }
import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import org.dom4j.Document; import org.dom4j.Element; public class ElementLoaderImpl implements ElementLoader{ private Map<String, Element> elements=new HashMap<String, Element>(); public void addElements(Document doc) { @SuppressWarnings("unchecked") List<Element> eles = doc.getRootElement().elements(); for (Element e : eles) { String id=e.attributeValue("id"); elements.put(id, e); } } public Element getElement(String id) { return elements.get(id); } public Collection<Element> getElements() { return this.elements.values(); } }
六:解析Element元素
在第五步中,我們主要是獲取了Element元素,獲取到了之后,我們就要對其進行解析了。我們首先來定義一個解析Element的接口,接口里面的方法主要是對xml文件配置的元素作出解析,比如boolean isLazy(Element element)就是對其是否進行懶加載進行判斷,然后實現該接口:
import java.util.List; import org.dom4j.Element; /** * 解析Element元素 * @author Yiron * */ public interface ElementReader { boolean isLazy(Element element); List<Element> getConstructorElements(Element element); String getAttribute(Element element,String name); boolean isSingleTon(Element element); List<Element> getPropertyElements(Element element); AutoWire getAutoWire(Element element); List<DataElement> getConstructorValue(Element element); List<PropertyElement> getPropertyValue(Element element); }
package com.wyq.ResolveElement; import java.util.ArrayList; import java.util.List; import org.dom4j.Element; import org.omg.PortableServer.ID_ASSIGNMENT_POLICY_ID; public class ElementReaderImpl implements ElementReader{ /** * 判斷是否延遲加載 */ @Override public boolean isLazy(Element element) { String lazy = getAttribute(element, "lazy-int");//得到是否懶加載這個元素 Element parent = element.getParent(); Boolean parentLazy = new Boolean(getAttribute(parent, "default-lazy-int")); if (parentLazy) { if ("false".equals(lazy)) return false; return true; }else { if ("true".equals(lazy)) return true; return false; } } /** * 獲取constructor-arg節點 */ @Override public List<Element> getConstructorElements(Element element) { List<Element> childrens = element.elements();//得到bean節點下的所有節點 List<Element> reslut=new ArrayList<Element>();//存放節點的鏈表 for (Element e : childrens) {//遍歷 if ("constructor-arg".equals(e.getName())) {//如果是constructor-arg節點 reslut.add(e);//放入到預設的鏈表中 } } return reslut; //返回這個鏈表 } /** * 根據元素的name獲取元素的值 */ public String getAttribute(Element element, String name) { String value = element.attributeValue(name); return value; } /** * 判斷是不是單例模式 */ public boolean isSingleTon(Element element) { Boolean singleTon = new Boolean(getAttribute(element, "singleTon")); return singleTon; } /** * 獲得自動注入 */ @Override public AutoWire getAutoWire(Element element) { String value = this.getAttribute(element, "autoWire"); String parentValue=this.getAttribute(element.getParent(),"default-autowire"); if ("no".equals(parentValue)) { if ("byName".equals(parentValue)) { return new ByNameAutoWire(value); }else { return new NoAutoWire(value); } }else if ("byName".equals(parentValue)) { if("no".equals(value)) return new NoAutoWire(value); return new ByNameAutoWire(value); } return new NoAutoWire(value); } @Override public List<DataElement> getConstructorValue(Element element) { List<Element> cons=getConstructorElements(element); List<DataElement> result = new ArrayList<DataElement>(); for (Element e : cons) { List<Element> els = e.elements(); DataElement dataElement = getDataElement(els.get(0)); result.add(dataElement); } return result; } @Override public List<PropertyElement> getPropertyValue(Element element) { List<Element> properties=getPropertyElements(element); List<PropertyElement> reslut=new ArrayList<PropertyElement>(); for (Element e : properties) { List<Element> els=e.elements(); DataElement dataElement = getDataElement(els.get(0)); String value = getAttribute(e, "name"); PropertyElement pe = new PropertyElement(value, dataElement); reslut.add(pe); } return reslut; } private DataElement getDataElement(Element element){ String name=element.getName(); if ("value".equals(name)) { String classTypeName=element.attributeValue("type"); String data = element.getText(); return new ValueElement(getValue(classTypeName,data)); }else if ("ref".equals(name)) { return new RefElement(this.getAttribute(element, "bean")); } return null; } private Object getValue(String className,String data){ if (isType(className,"Integer")) { return Integer.parseInt(data); }else { return data; } } private boolean isType(String className, String type) { if (className.indexOf(type)!=-1) { return true; }else { return false; } } @Override public List<Element> getPropertyElements(Element element) { List<Element> elements = element.elements(); return elements; } }
6.2:解析Element的時候,我們定義了幾個接口,我們來看看自動注入的源代碼,自動注入返回三種情況,我們來模擬實現其中兩種ByNameAutoWire與NoAutoWire。還有DataElement,其子類分別是:RefElement和ValueElement分別表示引用元素和值元素。
public interface AutoWire { //自動注入 String getValue(); }
public class ByNameAutoWire implements AutoWire{ private String value; public ByNameAutoWire(String value) { this.value = value; } @Override public String getValue() { return value; } }
public class NoAutoWire implements AutoWire{ String value; public NoAutoWire(String value) { this.value = value; } public String getValue() { return value; } }
public interface DataElement { String getType(); Object getValue(); }
public class RefElement implements DataElement{ private Object value; public RefElement(Object value) { this.value=value; } @Override public String getType() { return "ref"; } @Override public Object getValue() { return this.value; } }
public class ValueElement implements DataElement { private Object value; public ValueElement(Object value) { this.value = value; } @Override public String getType() { return "value"; } @Override public Object getValue() { return this.value; } }
七:Bean創造器
主要創造Bean不使用默認構造器和使用定義的construction-arg參數,當我們配置構造參數的時候,就會拿到這些信息,然后進行反射來調用構建對象,其中還包括獲取Setter方法。其代碼如下:
import java.lang.reflect.Method; import java.util.List; import java.util.Map; public interface BeanCreator { Object createBeanUseDefaultConstruct(String className);//使用空構造器 Object createBeanUseDefineConstruce(String className,List<Object> args);//使用定義的構造器 Map<String, Method> getSetterMethodsMap(Object obj); void executeMethod(Object object,Object argBean,Method method); }
package com.wyq.Bean; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public class BeanCreatorImpl implements BeanCreator{ @Override public Object createBeanUseDefaultConstruct(String className) { Object object=null; try { Class clazz = Class.forName(className); object= clazz.newInstance(); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } return object; } /** * className:類的名字 * args:配置的構造參數 */ @Override public Object createBeanUseDefineConstruct(String className, List<Object> args) { Class[] argsClass=getArgsClasses(args); try { Class clazz = Class.forName(className); Constructor constructor=findConstructor(clazz,argsClass); } catch (Exception e) { // TODO: handle exception } return null; } /** * 根據類型和參數查找構造器 * @param clazz * @param argsClass * @return */ private Constructor findConstructor(Class clazz, Class[] argsClass) throws NoSuchMethodException{ Constructor constructor= getConstructor(clazz,argsClass); if (constructor==null) { Constructor[] constructors = clazz.getConstructors(); for (Constructor c : constructors) { Class[] constructorArgsClass = c.getParameterTypes(); if (constructorArgsClass.length==argsClass.length) { if (isSameArgs(argsClass,constructorArgsClass)) { return c; } } } } return null; } private boolean isSameArgs(Class[] argsClass, Class[] constructorArgsClass) { for (int i = 0; i < argsClass.length; i++) { try{ argsClass[i].asSubclass(constructorArgsClass[i]); if (i==(argsClass.length-1)) { return true; }}catch (Exception e) { e.printStackTrace(); break; } } return false; } private Constructor getConstructor(Class clazz, Class[] argsClass) throws SecurityException, NoSuchMethodException { try { Constructor constructor = clazz.getConstructor(argsClass); return constructor; } catch (Exception e) { return null; } } private Class[] getArgsClasses(List<Object> args) { //裝有class類的list集合 List<Class> reslut =new ArrayList<Class>(); for (Object arg : args) { reslut.add(getClass(arg)); } Class[] a = new Class[reslut.size()]; return reslut.toArray(a); } private Class getClass(Object obj) { if (obj instanceof Integer) { return Integer.TYPE; }else if (obj instanceof Double) { return Double.TYPE; }else if (obj instanceof Long) { return Long.TYPE; }else if (obj instanceof Float) { return Float.TYPE; }else if (obj instanceof Character) { return Character.TYPE; }else if (obj instanceof Byte) { return Byte.TYPE; } return obj.getClass(); } @Override public void executeMethod(Object object, Object argBean, Method method) { try { Class[] paramterTypes = method.getParameterTypes(); if (paramterTypes.length==1) { if (isMethodArgs(method,paramterTypes[0])) { method.invoke(object, argBean); } } } catch (Exception e) { try { throw new BeanCreateException("autoWire exception"+e.getMessage()); } catch (BeanCreateException e1) { e1.printStackTrace(); } } } private boolean isMethodArgs(Method method, Class class1) { Class[] c = method.getParameterTypes(); if (c.length==1) { try { class1.asSubclass(c[0]); return true; } catch (Exception e) { e.printStackTrace(); return false; } } return false; } @Override public Map<String, Method> getSetterMethodsMap(Object obj) { List<Method> methods=getSetterMethodsList(obj); Map<String, Method> result=new HashMap<String ,Method>(); for (Method method : methods) { String propertyName=getMethodNameWithOutSet(method.getName()); } return null; } /** * 還原setter方法 * @param methodname * @return */ private String getMethodNameWithOutSet(String methodname) { String propertyName=methodname.replace("set", ""); String firstWord=propertyName.substring(0,1); String lowerFirstWord = firstWord.toLowerCase(); return propertyName.replaceFirst(firstWord, lowerFirstWord); } private List<Method> getSetterMethodsList(Object obj) { Class clazz = obj.getClass(); List<Method> result=new ArrayList<Method>(); Method[] methods = clazz.getMethods(); for (Method method : methods) { if (method.getName().startsWith("Set")) { result.add(method); } } return result; } }
八:定義自己的IoC容器:
首先我們來定義自己的ApplicationContext接口,其中有getBean(String id)方法,通過這個方法就可以獲取具體的對象實例,也是我們使用Spring框架中用的最多的一個方法。然后來定義具體的實現子類
public interface ApplicationContext { Object getBean(String id); boolean containsBean(String id); boolean isSingleton(String id);
package com.wyq.Ioc; import java.io.UnsupportedEncodingException; import java.lang.reflect.Method; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import org.dom4j.Document; import org.dom4j.Element; import com.wyq.Bean.BeanCreateException; import com.wyq.Bean.BeanCreator; import com.wyq.Bean.BeanCreatorImpl; import com.wyq.Element.ElementLoader; import com.wyq.Element.ElementLoaderImpl; import com.wyq.ReadXml.DocumentHolder; import com.wyq.ReadXml.XMLDocumentHolder; import com.wyq.ResolveElement.AutoWire; import com.wyq.ResolveElement.ByNameAutoWire; import com.wyq.ResolveElement.DataElement; import com.wyq.ResolveElement.ElementReader; import com.wyq.ResolveElement.ElementReaderImpl; import com.wyq.ResolveElement.NoAutoWire; import com.wyq.ResolveElement.PropertyElement; import com.wyq.ResolveElement.RefElement; import com.wyq.ResolveElement.ValueElement; import com.wyq.SetInput.PropertyHandler; import com.wyq.SetInput.PropertyHandlerImpl; public class AbstractApplicationContext implements ApplicationContext { protected ElementLoader elementLoader = new ElementLoaderImpl(); protected DocumentHolder documentHolder = new XMLDocumentHolder(); protected Map<String, Object> beans = new HashMap<String, Object>(); protected BeanCreator beanCreator = new BeanCreatorImpl(); protected ElementReader elementReader = new ElementReaderImpl(); protected PropertyHandler propertyHandler = new PropertyHandlerImpl(); protected void setUpElements(String[] xmlPaths){ URL classPathUrl = AbstractApplicationContext.class.getClassLoader().getResource("."); String classpath; try { classpath = java.net.URLDecoder.decode(classPathUrl.getPath(), "utf-8"); for (String path : xmlPaths) { Document doc = documentHolder.getDocument(classpath + path); elementLoader.addElements(doc); } } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } @Override public Object getBean(String id) { Object bean = this.beans.get(id); if (bean == null) { bean = handleSingleton(id); } return bean; } private Object handleSingleton(String id) { Object bean = createBean(id); if (isSingleton(id)) { this.beans.put(id, bean); } return bean; } private Object createBean(String id) { Element e = elementLoader.getElement(id); if (e == null) { try { throw new BeanCreateException("element not found" + id); } catch (BeanCreateException e1) { e1.printStackTrace(); } } Object result = instance(e); System.out.println("創建bean" + id); System.out.println("該bean的對象是" + result); AutoWire autoWire = elementReader.getAutoWire(e); if (autoWire instanceof ByNameAutoWire) { // 使用名稱自動裝配 autowireByName(result); } else if (autoWire instanceof NoAutoWire) { setterInject(result, e); } return null; } protected void createBeans(){ Collection<Element> elements = elementLoader.getElements(); for (Element element : elements) { boolean lazy = elementReader.isLazy(element); if (!lazy) { String id = element.attributeValue("id"); Object bean = this.getBean(id); if (bean==null) { handleSingleton(id); } } } } private void setterInject(Object obj, Element e) { List<PropertyElement> properties = elementReader.getPropertyValue(e); Map<String, Object> propertiesMap = getPropertyArgs(properties); propertyHandler.setProperties(obj, propertiesMap); } private Map<String, Object> getPropertyArgs(List<PropertyElement> properties) { Map<String, Object> result = new HashMap<String, Object>(); for (PropertyElement p : properties) { DataElement de = p.getDataElement(); if (de instanceof RefElement) { result.put(p.getName(), this.getBean((String) de.getValue())); } else if (de instanceof ValueElement) { result.put(p.getName(), de.getValue()); } } return result; } private void autowireByName(Object obj) { Map<String, Method> methods = propertyHandler.getSetterMethodsMap(obj); for (String s : methods.keySet()) { Element e = elementLoader.getElement(s); if (e == null) continue; Object bean = this.getBean(s); Method method = methods.get(s); propertyHandler.executeMethod(obj, bean, method); } } private Object instance(Element e) { String className = elementReader.getAttribute(e, "class"); List<Element> constructorElements = elementReader.getConstructorElements(e); if (constructorElements.size() == 0) { return beanCreator.createBeanUseDefaultConstruct(className); } else { List<Object> args = getConstructArgs(e); return beanCreator.createBeanUseDefineConstruct(className, args); } } private List<Object> getConstructArgs(Element e) { List<DataElement> datas = elementReader.getConstructorValue(e); List<Object> result = new ArrayList<Object>(); for (DataElement d : datas) { if (d instanceof ValueElement) { d = (ValueElement) d; result.add(d.getValue()); } else if (d instanceof RefElement) { d = (RefElement) d; String refid = (String) d.getValue(); result.add(this.getBean(refid)); } } return result; } @Override public boolean containsBean(String id) { Element element = elementLoader.getElement(id); return element == null ? false : true; } @Override public boolean isSingleton(String id) { Element e = elementLoader.getElement(id); return elementReader.isSingleTon(e); } }
}
九:總結
我們經過上面的代碼就完整實現了一個Ioc容器,其中從Document的創造,再到Element的創建,再到解析Element,然后設置我們的Bean創造器,再實現AppliacionContext,這一過程在編碼中是不可逆的,因為只有有了Document,才有Element,再然后才有解析,我們的方法進行下一步的解析都需要上層作為參數,只有這樣才能完整解析Spring配置文件。我詳細闡述了這一流程,其中參閱了不少資料,耗時一星期,希望大家細細體會其中的方法,尤其是方法之間的串聯與解析的思路。從中過我們看出Spring解析的原理,我們進一步深入理解Spring框架起到了很大的的作用。同時本篇博文只是起到一個拋磚引玉的作用,示范了一些特性。按照這樣的邏輯,我們可以舉一反三,像mybaits、Hibernate的配置文件,我們大概就可以知道是如何解析的了。比如mybatis的配置文件,里面的sql配置,如何去拼接sql,無非就是解析文檔,把標簽里面的內容拿出來,拼接成String,交給jdbc去運行,這就是編程中的舉一反三,所以這篇博文不僅僅局限於Spring,有更多的東西值得我們去思考,加深自己的理解。好了,本次講解就到這里,我們下篇再見,謝謝。