springCore


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 
&#123;
    private String className; // 类全限定名
    private String objectName; // 对象名
    private String scope;  // 作用域
    private List<String> properties; // 需要赋值的属性
    private List<String> values;  // 对应的属性值

    public String getClassName() &#123;
        return className;
    &#125;
    public void setClassName(String className) &#123;
        this.className = className;
    &#125;
    public String getObjectName() &#123;
        return objectName;
    &#125;
    public void setObjectName(String objectName) &#123;
        this.objectName = objectName;
    &#125;
    public String getScope() &#123;
        return scope;
    &#125;
    public void setScope(String scope) &#123;
        this.scope = scope;
    &#125;
    public List<String> getProperties() &#123;
        return properties;
    &#125;
    public void setProperties(List<String> properties) &#123;
        this.properties = properties;
    &#125;
    public List<String> getValues() &#123;
        return values;
    &#125;
    public void setValues(List<String> values) &#123;
        this.values = values;
    &#125;
    @Override
    public String toString() &#123;
        return "BeanInfo [className=" + className + ", objectName=" + objectName + ", scope=" + scope + ", properties=" + properties + ", values=" + values + "]";
    &#125;    
&#125;

重点看下面这个类,HandleBeansInfo:

public final class HandleBeansInfo
&#123;
   public HandleBeansInfo()&#123;&#125;

    /* 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 &#123;
         try &#123; 
             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++) 
              &#123;  
                 Element  element=(Element)allChildren.get(i);

                if(element.getAttribute("class")!=null)
                 &#123;
                     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
                   &#123;
                       List<String> propertiesName=new ArrayList<String>();
                       List<String> propertiesValue=new ArrayList<String>();
                       for(int k=0;k<subchild.size();k++)
                       &#123;     // 这里处理 <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
                           &#123;
                               String ref=element2.getAttributeValue("ref");
                               propertiesValue.add("ref:"+ref);
                           &#125;
                        &#125;
                       bean.setProperties(propertiesName);
                       bean.setValues(propertiesValue);
                       beansInfoMaps.put(bean.getObjectName(), bean);
                   &#125;
                 &#125;
              &#125;

          &#125;catch(Exception e)
         &#123;
              e.printStackTrace();
         &#125;
  &#125;

   //com.hugeyurt.Demo
   // return demo
  public static String  getObjectName(String className)
  &#123;
      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);
  &#125;

  @Test
  public void test()
  &#123;
      //String className="com.hugeyurt.hello";
    //  System.out.print(HandleBeansInfo.getObjectName(className));
      Set<String> keys=HandleBeansInfo.beansInfoMaps.keySet();

      for (String string : keys)
      &#123;
        System.out.println(string);
      &#125;

      System.out.println("-------------------------------------------------------");
      for (String string : keys)
      &#123;
        System.out.println(HandleBeansInfo.beansInfoMaps.get(string));
      &#125;

    &#125;

&#125;

​ 这个类的具体工作流程如下:

  1. 首先定义两个静态HashMap,分别为beansInfoMaps,activebeansMaps。

  2. 静态代码块,当程序运行时该代码块就被加载到内存中。

  3. 解析XML配置文件,得到根节点Beans的子节点Bean,将Bean中的参数写到BeanInfo对象中。

  4. 构建完一个完整的BeanInfo对象后,将该对象放入beansInfoMaps中。

  5. 循环进行3,4两条操作,当所有Bean标签被读完后,结束工作。

    下面是SpringCore类,代码如下:

public class SpringCore 
&#123;

   public  static Object getBean(String name)
   &#123;
       BeanInfo beanInfo=HandleBeansInfo.beansInfoMaps.get(name);
       if(beanInfo==null) return null;

       Object object=HandleBeansInfo.activebeansMaps.get(name);
       //第一步:若对象已存在
       if(object!=null)
       &#123;  
           if(beanInfo.getScope().equals("single"))
             return object;

           Object temp=createObject(beanInfo);

           HandleBeansInfo.activebeansMaps.replace(name, temp);
           return temp;
       &#125;
       Object temp=createObject(beanInfo);
       HandleBeansInfo.activebeansMaps.put(name, temp);
       return temp;
   &#125;

   private static Object createObject(BeanInfo beanInfo)
   &#123;
       String className=beanInfo.getClassName();
       Object object=null;
       try&#123;
          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++)
          &#123;
             String fieldName=propertiesName.get(i);
             //获取字节码世界中的属性对象
             Field field=clz.getDeclaredField(fieldName);
             field.setAccessible(true);
             String value=beanInfo.getValues().get(i);
             if(value.contains("ref"))
             &#123;
                 //装配的是引用类型ref:mydao
                 String refObjectName=value.substring(4);
                 Object refObject=getBean(refObjectName);
                 field.set(object, refObject);
             &#125;
             else
             &#123;
                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);

             &#125;
           &#125;

       &#125;catch(Exception e)
       &#123;
           e.printStackTrace();
       &#125;
       return object;
   &#125;
&#125;

​ 首先来看第一个函数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的呢? 具体逻辑如下:

  1. 通过BeanInfo中存储的全限定名,得到字节码对象,并实例化为一个空的Object对象。

  2. 通过字节码对象clz,将object中的属性,参数类型,传入参数值等一一对应,并写到object对象中。

  3. 返回出object对象。

​ 至此,SpringCore的工作全部结束。

4、总结

​ 在手写这些底层代码之前,我觉得Spring的自动注入功能十分神奇。但是自己实现之后,发现逻辑其实十分简单,只是用到了xml解析,反射机制这些技术。

​ 但是在实现这些功能时,有着很多需要注意的细节。例如:

  1. bean标签中没有指定name时,需要通过函数,根据默认规则给BeanInfo对象写入name。

  2. bean标签中的属性有可能是ref引用,如果出现ref引用,则需要将其递归调用,再添加到object对象中。

  3. 在判断参数的类型时,要做到全面,如果缺一种类型就会有可能报错。


文章作者: kilig
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 kilig !
 上一篇
spring事务管理 spring事务管理
1、什么是Spring事务管理?​ 在实际开发中,操作数据库时都会涉及到事务管理问题,为此Spring提供了专门用于事务处理的API。Spring的事务管理简化了传统的事务管理流程,并且在一定程度上减少了开发者的工作量。 2、
2020-07-10
下一篇 
MySQL索引 MySQL索引
# 1、索引的语法 ​ 创建索引: 建表的时候添加索引 建表之后添加索引 tips: 索引需要占用磁盘空间,所以在创建索引时要考虑磁盘空间是否足够。 创建索引时要对表加锁,所以实际操作中需要在业务空闲期进行。
2020-06-15
  目录