1、SpringCore介绍
实现过Spring框架中的DI,AOP等技术后,我对这些技术的实现过程有了浓厚的兴趣,比如:Spring怎么做到将对象创建到容器中?自动装配问题?等等。
通过自己对底层代码的实现,我明白了这些问题都是通过解析xml文件,反射机制做到的。以下就是我对底层代码的实现逻辑。(附源码)
2、实现逻辑
我们知道,xml文件中是由很多<bean></bean>
标签组成的,每一个bean标签中都包含着一个类的全限定名。通过解析XML文件,得到该全限定名的对象,然后用反射机制,得到类的全部信息,将xml文件里的各种属性,装配到对象中,并将该对象放到容器中,等待用户的使用。
我实现的SpringCore中,定义了三个类,分别是:BeanInfo,HandleBeansInfo,SpringCore。
BeanInfo类:相当于Bean对象的PO类,用于储存一个完整的Bean对象。
HandleBeansInfo类:在该类中,我定义了两个HashMap。
第一个HashMap用于存储从XML文件中解析出来的所有Bean对象的信息。
第二个HashMap用于存储容器已经创建好的对象,供外界调用。
SpringCore类:将Bean对象转化为Object对象,将该Object存入第二个HashMap中,并通过getBean返回出Object对象。
3、实现过程
先看一下XML配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<bean class="com.hugeyurt.Demo" scope="session"/>
<bean name="login" class="com.hugeyurt.Login" scope="request"/>
<bean name="check" class="com.hugeyurt.Check" scope="single">
<property name="msg" value="china"/>
<property name="dao" ref="mydao"/>
</bean>
<bean name="hello" class="com.hugeyurt.test.Hello" scope="single">
<property name="msg" value="china"/>
</bean>
<bean name="mydemo" class="com.hugeyurt.springioc.Demo"/>
<bean name = "stu" class="com.hugeyurt.springioc.Student">
<property name="demo" ref="mydemo"/>
</bean>
</beans>
我们要做的工作就是把这些beans创建为Object对象放入容器中。
BeanInfo类:
public class BeanInfo
{
private String className; // 类全限定名
private String objectName; // 对象名
private String scope; // 作用域
private List<String> properties; // 需要赋值的属性
private List<String> values; // 对应的属性值
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
public String getObjectName() {
return objectName;
}
public void setObjectName(String objectName) {
this.objectName = objectName;
}
public String getScope() {
return scope;
}
public void setScope(String scope) {
this.scope = scope;
}
public List<String> getProperties() {
return properties;
}
public void setProperties(List<String> properties) {
this.properties = properties;
}
public List<String> getValues() {
return values;
}
public void setValues(List<String> values) {
this.values = values;
}
@Override
public String toString() {
return "BeanInfo [className=" + className + ", objectName=" + objectName + ", scope=" + scope + ", properties=" + properties + ", values=" + values + "]";
}
}
重点看下面这个类,HandleBeansInfo:
public final class HandleBeansInfo
{
public HandleBeansInfo(){}
/* beansInfoMaps是来存储配置文件中列出的所有创建对象的信息;
* 每一个<bean>标签对应一个BeanInfo 对象
*/
public static HashMap<String, BeanInfo> beansInfoMaps=
new HashMap<String, BeanInfo>();
/*
* activebeansMaps是来存储容器已经创建好的对象,供外界调用;
*/
public static HashMap<String, Object> activebeansMaps=
new HashMap<String, Object>();
static {
try {
String path=HandleBeansInfo.class.getClassLoader().getResource("")
.toString().substring(6);
SAXBuilder builder = new SAXBuilder();
Document doc = builder.build(
new File(path+"com/hugeyurt/springcore/beans.xml"));
Element foo = doc.getRootElement();//得到根节点
List allChildren = foo.getChildren();
for(int i=0;i<allChildren.size();i++)
{
Element element=(Element)allChildren.get(i);
if(element.getAttribute("class")!=null)
{
BeanInfo bean=new BeanInfo();
bean.setClassName(element.getAttributeValue("class"));
if(element.getAttribute("scope")!=null)
bean.setScope(element.getAttributeValue("scope"));
else bean.setScope("single");
if(element.getAttribute("name")!=null)
bean.setObjectName(element.getAttributeValue("name"));
else
bean.setObjectName(getObjectName(bean.getClassName()));
//检测是否需要给属性装配值
List subchild=(List)element.getChildren();
if(subchild==null||subchild.size()==0)
beansInfoMaps.put(bean.getObjectName(), bean);
else
{
List<String> propertiesName=new ArrayList<String>();
List<String> propertiesValue=new ArrayList<String>();
for(int k=0;k<subchild.size();k++)
{ // 这里处理 <property>
Element element2=(Element)subchild.get(k);
String name=element2.getAttributeValue("name");
propertiesName.add(name);
String value=element2.getAttributeValue("value");
if(value!=null)
propertiesValue.add(value);
else
{
String ref=element2.getAttributeValue("ref");
propertiesValue.add("ref:"+ref);
}
}
bean.setProperties(propertiesName);
bean.setValues(propertiesValue);
beansInfoMaps.put(bean.getObjectName(), bean);
}
}
}
}catch(Exception e)
{
e.printStackTrace();
}
}
//com.hugeyurt.Demo
// return demo
public static String getObjectName(String className)
{
int index=className.lastIndexOf(".");
char result[]=className.substring(index+1).toCharArray();
if(result[0]<='Z'&&result[0]>='A')
result[0]=(char)(result[0]+32);
return new String(result);
}
@Test
public void test()
{
//String className="com.hugeyurt.hello";
// System.out.print(HandleBeansInfo.getObjectName(className));
Set<String> keys=HandleBeansInfo.beansInfoMaps.keySet();
for (String string : keys)
{
System.out.println(string);
}
System.out.println("-------------------------------------------------------");
for (String string : keys)
{
System.out.println(HandleBeansInfo.beansInfoMaps.get(string));
}
}
}
这个类的具体工作流程如下:
首先定义两个静态HashMap,分别为beansInfoMaps,activebeansMaps。
静态代码块,当程序运行时该代码块就被加载到内存中。
解析XML配置文件,得到根节点Beans的子节点Bean,将Bean中的参数写到BeanInfo对象中。
构建完一个完整的BeanInfo对象后,将该对象放入beansInfoMaps中。
循环进行3,4两条操作,当所有Bean标签被读完后,结束工作。
下面是SpringCore类,代码如下:
public class SpringCore
{
public static Object getBean(String name)
{
BeanInfo beanInfo=HandleBeansInfo.beansInfoMaps.get(name);
if(beanInfo==null) return null;
Object object=HandleBeansInfo.activebeansMaps.get(name);
//第一步:若对象已存在
if(object!=null)
{
if(beanInfo.getScope().equals("single"))
return object;
Object temp=createObject(beanInfo);
HandleBeansInfo.activebeansMaps.replace(name, temp);
return temp;
}
Object temp=createObject(beanInfo);
HandleBeansInfo.activebeansMaps.put(name, temp);
return temp;
}
private static Object createObject(BeanInfo beanInfo)
{
String className=beanInfo.getClassName();
Object object=null;
try{
Class clz=Class.forName(className);
object=clz.newInstance();
List<String> propertiesName=beanInfo.getProperties();
if(propertiesName==null) return object;
for (int i=0;i<propertiesName.size();i++)
{
String fieldName=propertiesName.get(i);
//获取字节码世界中的属性对象
Field field=clz.getDeclaredField(fieldName);
field.setAccessible(true);
String value=beanInfo.getValues().get(i);
if(value.contains("ref"))
{
//装配的是引用类型ref:mydao
String refObjectName=value.substring(4);
Object refObject=getBean(refObjectName);
field.set(object, refObject);
}
else
{
if(field.getType()==Integer.class||field.getType()==Integer.TYPE)
field.set(object, Integer.parseInt(value));
else if(field.getType()==Double.class||field.getType()==Double.TYPE)
field.set(object, Double.parseDouble(value));
else if(field.getType()==Float.class||field.getType()==Float.TYPE)
field.set(object, Float.parseFloat(value));
else if(field.getType()==Boolean.class||field.getType()==Boolean.TYPE)
field.set(object, Boolean.parseBoolean(value));
else
field.set(object, value);
}
}
}catch(Exception e)
{
e.printStackTrace();
}
return object;
}
}
首先来看第一个函数public static Object getBean(String name)
,这个函数的作用是通过传入的name,在beansInfoMaps中得到相应的BeanInfo对象,并通过
private static Object createObject(BeanInfo beanInfo)
函数将BeanInfo对象转为Object对象,将该Object对象存入activebeansMaps中,返回这个对象供外界调用。
再来看看第二个函数 private static Object createObject(BeanInfo beanInfo)
,这个函数是怎么做到BeanInfo变成Object的呢? 具体逻辑如下:
通过BeanInfo中存储的全限定名,得到字节码对象,并实例化为一个空的Object对象。
通过字节码对象clz,将object中的属性,参数类型,传入参数值等一一对应,并写到object对象中。
返回出object对象。
至此,SpringCore的工作全部结束。
4、总结
在手写这些底层代码之前,我觉得Spring的自动注入功能十分神奇。但是自己实现之后,发现逻辑其实十分简单,只是用到了xml解析,反射机制这些技术。
但是在实现这些功能时,有着很多需要注意的细节。例如:
bean标签中没有指定name时,需要通过函数,根据默认规则给BeanInfo对象写入name。
bean标签中的属性有可能是ref引用,如果出现ref引用,则需要将其递归调用,再添加到object对象中。
在判断参数的类型时,要做到全面,如果缺一种类型就会有可能报错。