Spring读取xml配置文件的原理与实现

时间:2022-09-03 14:53:47

本篇博文的目录:

一:前言

二: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三种情况(这里只为了说明问题,我们只定义两个实现类,正式的话是三个实现类),这里采用的是状态设计模式,设计一个总接口,然后对不同的情况,我们定义相关的实现类,是那种情况,就返回具体的类。如图展示的是接口和具体的实现类,接下来我们将会按照这样的方式去讲解每一个接口对应的实现类。

Spring读取xml配置文件的原理与实现

四:获取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,有更多的东西值得我们去思考,加深自己的理解。好了,本次讲解就到这里,我们下篇再见,谢谢。