簡易版的Spring框架之IOC簡單實現

Guo_1_9發表於2019-03-01

一個簡易版的Spring框架

功能

  • 支援singleton型別的bean,包括初始化、屬性注入、以及依賴bean注入。
  • 可從xml中讀取配置。
  • 可以使用Aspectj的方式進行AOP編寫,支援介面和類代理。

說明

如果你有幸能看到

  • 1、本文簡易Spring框架參考Github,程式碼註釋參考Github.
  • 2、所有權歸原作者,在這裡自己只是臨摹,參考註釋還原過程。不懂的可以看作者的視訊。
  • 3、大家一起努力,一起學習,有興趣的也可以看下我的Github。上傳了Spring原始碼。大佬可以看看。
  • 4、看本文之前希望你有一份Spring原始碼。對照著找你想要的介面和類。加深印象。
  • 5、本文只為自己以後複習用,如果不對還請諒解。

tiny-spring是為了學習Spring的而開發的,可以認為是一個Spring的精簡版。Spring的程式碼很多,層次複雜,閱讀起來費勁。我嘗試從使用功能的角度出發,參考Spring的實現,一步一步構建,最終完成一個精簡版的Spring。 有人把程式設計師與畫家做比較,畫家有門基本功叫臨摹,tiny-spring可以算是一個程式的臨摹版本-從自己的需求出發,進行程式設計,同時對著名專案進行參考。

第一部分:IoC容器

早看到也不浪費時間了,唉。

1、時序圖-普通bean的載入過程

簡易版的Spring框架之IOC簡單實現

2、載入資源的主要相關類

簡易版的Spring框架之IOC簡單實現

3、從xml中載入bean定義的主要相關類

簡易版的Spring框架之IOC簡單實現

4、裝配bean的主要相關類

簡易版的Spring框架之IOC簡單實現

5、實現ApplicationContext介面的相關類

簡易版的Spring框架之IOC簡單實現

1.step1-最基本的容器

IoC最基本的角色有兩個:容器(BeanFactory)和Bean本身。這裡使用BeanDefinition來封裝了bean物件,這樣可以儲存一些額外的元資訊。測試程式碼:

// 1.初始化beanfactory
BeanFactory beanFactory = new BeanFactory();

// 2.注入bean
BeanDefinition beanDefinition = new BeanDefinition(new HelloWorldService());
beanFactory.registerBeanDefinition("helloWorldService", beanDefinition);

// 3.獲取bean
HelloWorldService helloWorldService = (HelloWorldService) beanFactory.getBean("helloWorldService");
helloWorldService.helloWorld();

複製程式碼

1、首先我們來看下整體程式碼結構,這裡的要點是BeanFactory介面。我們應該面向介面程式設計。

簡易版的Spring框架之IOC簡單實現

/**
 * Created by guo on 3/1/2018.
 * bean的容器,工廠
 */
public interface BeanFactory {
    Object getBean(String name) throws Exception;
}

/**
 * Created by guo on 3/1/2018.
 * 抽象bean工廠
 */
public abstract class AbstractBeanFactory implements BeanFactory {
}

``

2、大家想知道AbstractBeanFactory抽象類有什麼作用嗎?和介面有什麼區別嗎?什麼時候用介面什麼時候用抽象類?

- 1、如果你擁有一些方法想讓他們中的一些預設實現,那麼使用抽象類。
- 2、如果你想實現多重繼承,那麼你必須使用介面。由於java不支多繼承,子類不能夠繼承多個類,但可以實現多個介面
- 3、如果基本功能在不斷改變,那麼就需要使用抽象類。如果不斷改變基本功能並且使用介面 ,那麼就需要改變所有實現了該介面的類。

3、有了bean工廠你總得bean的定義吧。`BeanDefinition`來了。bean的定義

```java
/**
 * Created by guo on 3/1/2018.
 * bean的內容及後設資料,儲存在beanFactory中,包裝bean的實體。
 */
public class BeanDefinition {

  private Object bean;

  //類的class資訊
  private Class beanClass;

  //類名
  private String beanClassName;

  //儲存所有的屬性,
  private PropertyValues  propertyValues  = new PropertyValues();

  public BeanDefinition() {

  }

    public BeanDefinition() {}

    //setters and gettes 略
    public void setBeanClassName(String beanClassName) {
        this.beanClassName = beanClassName;
        try {
            //載入類,並返回class物件
            //這裡已經有類的例項來,但是沒有引用,怎麼可以獲取這個引用呢?
            this.beanClass = Class.forName(beanClassName);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}
複製程式碼

4、有了bean的定義你總得建立啊,初始化那,註冊啊,驗證啊,不然一點卵用都沒有。來瞅瞅。這時候我們來看下AbstractBeanFactory抽象類具體的實現。

/**
 * Created by guo on 3/1/2018.
 * 抽象bean工廠
 */
public abstract class AbstractBeanFactory implements BeanFactory {

    //bean工程裡維護類的字典,類名+class物件
    private Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>();

    private final List<String> beanDefinitionNames = new ArrayList<String>();

    private List<BeanPostProcessor> beanPostProcessors = new ArrayList<BeanPostProcessor>();

    /**
     * 獲取bean的時候,才建立類的例項物件,原來只是儲存類名和類的Class物件 。
     * 到這一步會根據Class物件建立類的例項
     *
     * @param name
     * @return
     * @throws Exception
     */
    @Override
    public Object getBean(String name) throws Exception {
        BeanDefinition beanDefinition = beanDefinitionMap.get(name);
        if (beanDefinition == null) {
            throw new IllegalArgumentException("no bean named " + name + "is defined");
        }
        Object bean = beanDefinition.getBean();
        if (bean == null) {
            //1、剛建立的物件,其他什麼都沒做
            bean = doCreateBean(beanDefinition);
            //2、初始化bean物件
            bean = initializeBean(bean,name);
            //3、這裡的bean是初始化之後的bean,與剛開始建立的bean不一樣。
            beanDefinition.setBean(bean);
        }
        return bean;
    }
}
複製程式碼

5、我們在來看看具體的doCreateBeaninitializeBeanregisterBeanDefinition


  /**
   * 初始化bean,BeanPostProcessor初始化前後處理器。
   */
  protected Object initializeBean(Object bean, String name) throws Exception {
      for (BeanPostProcessor beanPostProcessor : beanPostProcessors) {
          bean = beanPostProcessor.postProcessBeforeInitialization(bean, name);
      }

      for (BeanPostProcessor beanPostProcessor : beanPostProcessors) {
          bean = beanPostProcessor.postProcessAfterInitialization(bean, name);
      }
      return bean;
  }

  /**
   * 建立bean的例項
   */
  protected Object createBeanInstance(BeanDefinition beanDefinition) throws Exception {
      return beanDefinition.getBeanClass().newInstance();
  }

  /**
   * 註冊bean,即將類名和定義儲存到記憶體中(map物件)中
   */
  public void registerBeanDefinition(String name, BeanDefinition beanDefinition) throws Exception {
      beanDefinitionMap.put(name, beanDefinition);
      //儲存一份做準備
      beanDefinitionNames.add(name);
  }

  /**
   * 建立bean並設定bean的引用
   */
  protected Object doCreateBean(BeanDefinition beanDefinition) throws Exception {
      //這裡會建立bean的例項物件
      Object bean = createBeanInstance(beanDefinition);

      //將bean的例項物件設定到beandefinition中去
      beanDefinition.setBean(bean);
      //設定bean的引用的例項物件
      applyPropertyValues(bean, beanDefinition);

      return bean;
  }

複製程式碼

6、無關緊要的方法暫時就不貼出來了,描述個大概過程。帶著這些類和介面在原始碼中找。請忽略第二步和第三步。直接跳到第四步。記得是在Spring原始碼,這個簡單多了,但是基本功能有


2.step2-將bean建立放入工廠

step1中的bean是初始化好之後再set進去的,實際使用中,我們希望容器來管理bean的建立。於是我們將bean的初始化放入BeanFactory中。為了保證擴充套件性,我們使用Extract Interface的方法,將BeanFactory替換成介面,而使用AbstractBeanFactory和AutowireCapableBeanFactory作為其實現。"AutowireCapable"的意思是“可自動裝配的”,為我們後面注入屬性做準備。

// 1.初始化beanfactory
BeanFactory beanFactory = new AutowireCapableBeanFactory();

// 2.注入bean
BeanDefinition beanDefinition = new BeanDefinition();
beanDefinition.setBeanClassName("us.codecraft.tinyioc.HelloWorldService");
beanFactory.registerBeanDefinition("helloWorldService", beanDefinition);

// 3.獲取bean
HelloWorldService helloWorldService = (HelloWorldService) beanFactory.getBean("helloWorldService");
helloWorldService.helloWorld();
複製程式碼

3.step3-為bean注入屬性

這一步,我們想要為bean注入屬性。我們選擇將屬性注入資訊儲存成PropertyValue物件,並且儲存到BeanDefinition中。這樣在初始化bean的時候,我們就可以根據PropertyValue來進行bean屬性的注入。Spring本身使用了setter來進行注入,這裡為了程式碼簡潔,我們使用Field的形式來注入。

// 1.初始化beanfactory
BeanFactory beanFactory = new AutowireCapableBeanFactory();

// 2.bean定義
BeanDefinition beanDefinition = new BeanDefinition();
beanDefinition.setBeanClassName("us.codecraft.tinyioc.HelloWorldService");

// 3.設定屬性
PropertyValues propertyValues = new PropertyValues();
propertyValues.addPropertyValue(new PropertyValue("text", "Hello World!"));
beanDefinition.setPropertyValues(propertyValues);

// 4.生成bean
beanFactory.registerBeanDefinition("helloWorldService", beanDefinition);

// 5.獲取bean
HelloWorldService helloWorldService = (HelloWorldService) beanFactory.getBean("helloWorldService");
helloWorldService.helloWorld();
複製程式碼

step4-讀取xml配置來初始化bean

1、看到讀取你總得有IO流吧 ,還有有資源啊(Xxx.xml),還有一個讀取器。讓我們看下重要的介面和實現類。

/**
 * Resource是Spring內部定位資源介面
 */
public interface Resource {
    InputStream getInputStream() throws Exception;
}
-----------------載入資源------------------------------
public class ResourceLoader {

    //獲取資源
    public Resource getResource(String location){
        URL resource = this.getClass().getClassLoader().getResource(location);
        return new UrlResource(resource);
    }
}
--------------------------------------------------
*/
public class UrlResource implements Resource {

   private final URL url;

   public UrlResource(URL url) {
       this.url = url;
   }

   @Override
   //根據URL載入輸入流
   public InputStream getInputStream() throws IOException{
       URLConnection urlConnection = url.openConnection();
       urlConnection.connect();
       return urlConnection.getInputStream();
   }
}

--------------------測試--------------------------------
public class ResourceLoaderTest {

	@Test
	public void test() throws IOException {
		ResourceLoader resourceLoader = new ResourceLoader();
        Resource resource = resourceLoader.getResource("tinyioc.xml");
        InputStream inputStream = resource.getInputStream();
        Assert.assertNotNull(inputStream);
    }
}
複製程式碼

2、接下來就讓我們看看更為重要的介面和實現類

public interface BeanDefinitionReader {

    void loadBeanDefinitions(String location) throws Exception;
}

----------------------重要實現----------------------------------
/**
 * Created by guo on 3/1/2018.
 * 從配置檔案中讀取BeanDifinition 抽象類
 */
public abstract class AbstractBeanDefinitionReader implements BeanDefinitionReader {

    //bean集合
    private Map<String,BeanDefinition> registry;

    //資源載入器
    private ResourceLoader resourceLoader;

    protected AbstractBeanDefinitionReader(ResourceLoader resourceLoader) {
        this.registry = new HashMap<String, BeanDefinition>();
        this.resourceLoader = resourceLoader;
    }
      //setter。getter
}
複製程式碼

3、最終的實現來了


public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {

    public XmlBeanDefinitionReader(ResourceLoader resourceLoader) {
        super(resourceLoader);
    }

    @Override
    public void loadBeanDefinitions(String location) throws Exception {
        InputStream inputStream = getResourceLoader().getResource(location).getInputStream();
        doLoadBeanDefinitions(inputStream);

    }

複製程式碼

4、為了便於理解 ,我把方法抽出來了。這裡主要是解析和註冊

protected void doLoadBeanDefinitions(InputStream inputStream) throws Exception {
    //xml解析
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    DocumentBuilder docBuilder = factory.newDocumentBuilder();
    Document doc = docBuilder.parse(inputStream);
    // 解析bean
    registerBeanDefinitions(doc);
    inputStream.close();
}

public void registerBeanDefinitions(Document doc) {
    Element root = doc.getDocumentElement();
    parseBeanDefinitions(root);
}

複製程式碼

5、真正的解析在這裡。



protected void parseBeanDefinitions(Element root) {
    NodeList nl = root.getChildNodes();
    for (int i = 0; i < nl.getLength(); i++) {
        Node node = nl.item(i);
        if (node instanceof Element) {
            Element ele = (Element) node;
            processBeanDefinition(ele);
        }
    }
}

protected void processBeanDefinition(Element ele) {
    //獲取id和classname
    String name = ele.getAttribute("id");
    String className = ele.getAttribute("class");
    BeanDefinition beanDefinition = new BeanDefinition();
    //處理屬性
    processProperty(ele, beanDefinition);
    //註冊Class
    beanDefinition.setBeanClassName(className);
    getRegistry().put(name, beanDefinition);
}

//新增bean的屬性,和ref引用
private void processProperty(Element ele, BeanDefinition beanDefinition) {
    NodeList propertyNode = ele.getElementsByTagName("property");
    for (int i = 0; i < propertyNode.getLength(); i++) {
        Node node = propertyNode.item(i);
        if (node instanceof Element) {
            Element propertyEle = (Element) node;
            String name = propertyEle.getAttribute("name");
            String value = propertyEle.getAttribute("value");
            if (value != null && value.length() > 0) {
                beanDefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, value));
            } else {
                String ref = propertyEle.getAttribute("ref");
                if (ref == null || ref.length() == 0) {
                    throw new IllegalArgumentException("Configuration problem: <property> element for property '"
                            + name + "' must specify a ref or value");
                }
                //bean對其他物件的引用,直接放到自己的屬性裡面
                BeanReference beanReference = new BeanReference(ref);
                beanDefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, beanReference));
            }
        }
    }
}
}
複製程式碼

6、這裡是測試程式碼

@Test
public void test() throws Exception {
    XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(new ResourceLoader());
    xmlBeanDefinitionReader.loadBeanDefinitions("tinyioc.xml");
    Map<String, BeanDefinition> registry = xmlBeanDefinitionReader.getRegistry();
    Assert.assertTrue(registry.size() > 0);
}
複製程式碼

這麼大一坨初始化程式碼讓人心煩。這裡的BeanDefinition只是一些配置,我們還是用xml來初始化吧。我們定義了BeanDefinitionReader初始化bean,它有一個實現是XmlBeanDefinitionReader。

// 1.讀取配置
XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(new ResourceLoader());
xmlBeanDefinitionReader.loadBeanDefinitions("tinyioc.xml");

// 2.初始化BeanFactory並註冊bean
BeanFactory beanFactory = new AutowireCapableBeanFactory();
for (Map.Entry<String, BeanDefinition> beanDefinitionEntry : xmlBeanDefinitionReader.getRegistry().entrySet()) {
        beanFactory.registerBeanDefinition(beanDefinitionEntry.getKey(), beanDefinitionEntry.getValue());
}

// 3.獲取bean
HelloWorldService helloWorldService = (HelloWorldService) beanFactory.getBean("helloWorldService");
helloWorldService.helloWorld();
複製程式碼

5.step5-為bean注入bean

使用xml配置之後,似乎裡我們熟知的Spring更近了一步!但是現在有一個大問題沒有解決:我們無法處理bean之間的依賴,無法將bean注入到bean中,所以它無法稱之為完整的IoC容器!如何實現呢?我們定義一個BeanReference,來表示這個屬性是對另一個bean的引用。這個在讀取xml的時候初始化,並在初始化bean的時候,進行解析和真實bean的注入。

for (PropertyValue propertyValue : mbd.getPropertyValues().getPropertyValues()) {
    Field declaredField = bean.getClass().getDeclaredField(propertyValue.getName());
    declaredField.setAccessible(true);
    Object value = propertyValue.getValue();
    if (value instanceof BeanReference) {
        BeanReference beanReference = (BeanReference) value;
        value = getBean(beanReference.getName());
    }
    declaredField.set(bean, value);
}
複製程式碼

同時為了解決迴圈依賴的問題,我們使用lazy-init的方式,將createBean的事情放到getBean的時候才執行,是不是一下子方便很多?這樣在注入bean的時候,如果該屬性對應的bean找不到,那麼就先建立!因為總是先建立後注入,所以不會存在兩個迴圈依賴的bean建立死鎖的問題。

// 1.讀取配置
XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(new ResourceLoader());
xmlBeanDefinitionReader.loadBeanDefinitions("tinyioc.xml");

// 2.初始化BeanFactory並註冊bean
AbstractBeanFactory beanFactory = new AutowireCapableBeanFactory();
for (Map.Entry<String, BeanDefinition> beanDefinitionEntry : xmlBeanDefinitionReader.getRegistry().entrySet()) {
    beanFactory.registerBeanDefinition(beanDefinitionEntry.getKey(), beanDefinitionEntry.getValue());
}

// 3.初始化bean
beanFactory.preInstantiateSingletons();

// 4.獲取bean
HelloWorldService helloWorldService = (HelloWorldService) beanFactory.getBean("helloWorldService");
helloWorldService.helloWorld();
複製程式碼

6.step6-ApplicationContext登場

不管三七二十一,我們先看重要的介面以及重要實現

/**
 * 繼承beanFactory,繼承了factory所有的遺產
 */
public interface ApplicationContext extends BeanFactory {
}
-----------------------------------------------------------------
public abstract class AbstractApplicationContext implements ApplicationContext {
	protected AbstractBeanFactory beanFactory;

	public AbstractApplicationContext(AbstractBeanFactory beanFactory) {
		this.beanFactory = beanFactory;
	}

	public void refresh() throws Exception {
		//載入bean
		loadBeanDefinitions(beanFactory);
		//註冊之前,乾點什麼事情
		registerBeanPostProcessors(beanFactory);
		onRefresh();
	}

	//呼叫beanfactory工廠獲取bean的例項物件
	@Override
	public Object getBean(String name) throws Exception {
		return beanFactory.getBean(name);
	}
}
複製程式碼

2、為了方便,方法放這裡

protected abstract void loadBeanDefinitions(AbstractBeanFactory beanFactory) throws Exception;

protected void registerBeanPostProcessors(AbstractBeanFactory beanFactory) throws Exception {
  List beanPostProcessors = beanFactory.getBeansForType(BeanPostProcessor.class);
  for (Object beanPostProcessor : beanPostProcessors) {
    beanFactory.addBeanPostProcessor((BeanPostProcessor) beanPostProcessor);
  }
}

protected void onRefresh() throws Exception{
      beanFactory.preInstantiateSingletons();
  }
複製程式碼

3、熟悉的東東出場了

public class ClassPathXmlApplicationContext extends AbstractApplicationContext {

	private String configLocation;

	public ClassPathXmlApplicationContext(String configLocation) throws Exception {
		this(configLocation, new AutowireCapableBeanFactory());     //可自動裝配內容的BeanFactory
	}

	public ClassPathXmlApplicationContext(String configLocation, AbstractBeanFactory beanFactory) throws Exception {
		super(beanFactory);
		this.configLocation = configLocation;
		//直接全部初始化
		refresh();
	}

	@Override
	protected void loadBeanDefinitions(AbstractBeanFactory beanFactory) throws Exception {
		//定位bean,然後載入bean
		XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(new ResourceLoader());
		xmlBeanDefinitionReader.loadBeanDefinitions(configLocation);
		//註冊bean,這裡bean已經載入到虛擬機器中,但還沒有例項化物件,先不急嘛。
		for (Map.Entry<String, BeanDefinition> beanDefinitionEntry : xmlBeanDefinitionReader.getRegistry().entrySet()) {
			beanFactory.registerBeanDefinition(beanDefinitionEntry.getKey(), beanDefinitionEntry.getValue());
		}
	}

複製程式碼

3、測試程式碼

@Test
public void test() throws Exception {
    //就是把beanfactory封裝一下,使呼叫更加方便。註冊,全部初始化。
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("tinyioc.xml");
    HelloWorldService helloWorldService = (com.guo.codecraft.tinyioc.HelloWorldService) applicationContext.getBean("helloWorldService");
    helloWorldService.helloWorld();
}

@Test
public void testPostBeanProcessor() throws Exception {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("tinyioc-postbeanprocessor.xml");
    HelloWorldService helloWorldService = (com.guo.codecraft.tinyioc.HelloWorldService) applicationContext.getBean("helloWorldService");
    helloWorldService.helloWorld();
}
複製程式碼

現在BeanFactory的功能齊全了,但是使用起來有點麻煩。於是我們引入熟悉的ApplicationContext介面,並在AbstractApplicationContext的refresh()方法中進行bean的初始化工作。

ApplicationContext applicationContext = new ClassPathXmlApplicationContext("tinyioc.xml");
HelloWorldService helloWorldService = (HelloWorldService) applicationContext.getBean("helloWorldService");
helloWorldService.helloWorld();
複製程式碼

是不是非常熟悉?至此為止,我們的tiny-spring的IoC部分可說完工了。這部分的類、方法命名和作用,都是對應Spring中相應的元件。雖然程式碼量只有400多行,但是已經有了基本的IoC功能!

相關文章