Spring源碼-IOC容器(一)-構建簡單IOC容器

Spring IOC容器 源碼解析系列,建議你們按順序閱讀,歡迎討論java

(spring源碼均爲4.1.6.RELEASE版本)node

  1. Spring源碼-IOC容器(一)-構建簡單IOC容器
  2. Spring源碼-IOC容器(二)-Bean的定位解析註冊
  3. Spring源碼-IOC容器(三)-GetBean
  4. Spring源碼-IOC容器(四)-FactoryBean
  5. Spring源碼-IOC容器(五)-Bean的初始化
  6. Spring源碼-IOC容器(六)-bean的循環依賴
  7. Spring源碼-IOC容器(七)-ApplicationContext
  8. Spring源碼-IOC容器(八)-NamespaceHandler與自定義xml
  9. Spring源碼-IOC容器(九)-Component-Scan源碼解析
  10. Spring源碼-IOC容器(十)-@Autowired解析

起點

我第一次認真地看spring的源碼,應該算是在2015年。那時我已經看了一些spring的框架原理之類的博客文章,自我感受已經從宏觀上了解spring的概要設計。因而下了一本spring源碼深度解析的PDF(原諒一個剛工做的人的囊中羞澀),跟着做者的思路開始擼起Spring的源碼,通過兩個星期左右的廢寢忘食(有時候坐在馬桶上也捧着pad看),終於看完了第一部分,也就是Spring的核心部分,IOC容器和AOP。spring

大家覺得我這麼牛逼嗎,兩個星期就把java最經典的開源項目的核心理解了?其實我真的只是把書看完了,努力理解做者貼出來的大段大段的代碼,尤爲是spring的一些精妙的設計,在當時的我看來簡直就是一會跳到這,一會又跳到那,什麼玩意兒。但我仍是壓制住了心中的洪荒之力,兢兢業業地看完了好幾章的源碼剖析。等我拖着疲憊的精神終於到達終點時,驀然回首,尼瑪,連個鬼影都沒有,只模模糊糊的記得解析了xml文件,實例化了bean,組裝了一些bean的依賴屬性,而後呢,哪有什麼而後,我忽然發現好像領悟到張無忌學太極劍的感受,整個腦殼裏空空蕩蕩,惟有江月與清風。緩存

咳咳,好像扯的有點多了,言歸正傳。我後來又屢次嘗試跟斷點去追spring的源碼,看解析spring的書,每次老是會以爲又懂了那麼一點點,心中仍是有那麼絲絲的竊喜,又進步了一點哈哈。直到有一天看到一篇博客,叫1000行代碼實現一個基本的IoC容器(很是建議你們看下,還有一篇1000行實現AOP的)。我想我擦,我反反覆覆看了這麼長時間的源碼,1000行就能搞定了?等我仔細看我這篇博客,讓我震驚的不是1000行的代碼,而是我一直以來看spring ioc源碼存在的誤區:我歷來沒有認真思考過爲何咱們要用spring ioc,spring IOC究竟實現了什麼,IOC的核心又是什麼?數據結構

我想對我spring源碼的學習過程有個總結,因此纔有這一篇以及以後的spring源碼分析的博客。但我不想在第一篇就貼上BeanDefinition和BeanFactory的大段代碼,列舉出他們的實現接口以及類(固然後面仍是會貼的,呵呵),我會在前輩大神的思路上,構建一個簡單的ioc容器,讓你們對spring有個基本的認識和理解,這樣能夠在以後源碼解析的海洋裏矗立一座燈塔,不會被波濤洶涌的代碼所淹沒。app

什麼是ioc容器

想要知道IOC容器的核心是什麼,首先得了解什麼是ioc容器。而IOC容器,它得先是個容器,那什麼是容器呢?容器是許多對象的運行環境,它管理着對象的生命週期,支持對象的查找和依賴決策,以及配置管理等其餘功能,最終讓對象井井有理地運轉着。框架

那IOC是什麼,控制反轉(Inversion Of Control),它也等同於依賴注入(DI Dependence Injection),什麼意思呢? 一個對象依賴另外一個對象,當咱們使用這個對象時,須要主動去建立或查找這個依賴對象,而IOC是容器在對象初始化時不等這個對象被使用就主動將依賴傳遞給它。dom

舉個例子吧,我在公司作個項目,須要其餘部門的人提供一個API,通常都是我主動找那個部門的人溝通讓他們提供。假若有個項目經理,我給他一份API需求文檔,等我須要這個API時,發現已經提供好了能夠直接使用了。從這個例子能夠發現,個人工做變簡單了,而這個項目經理的角色就至關於IOC容器,它讓開發變得簡單。ide

具體怎麼變簡單的呢,就是你按照格式提供一份對象的組件及依賴的配置信息,IOC容器就能產生出配置完整可執行的應用系統。以下圖:函數

image

構建IOC容器

理論都說的差很少了,如今來寫一個IOC容器。提供一份配置信息,它要完成的目標以下:

  1. 建立對象
  2. 組裝對象中的屬性和依賴

如今咱們以xml文件爲配置信息載體,構建一個公司的Demo.公司(Company)開業有名字(name),也要有員工(Employee)幹活。

Company

package com.lntea.test.bean;

public class Company {

    private String name;
    
    private Employee employee;
    
    public void open(){
        System.out.println("Company " + name + " is open.");
        employee.work();
    }
}

Employee

package com.lntea.test.bean;

public class Employee {

    private String name;
    
    public void work(){
        System.out.println("Employee " + name + " is working");
    }
}

company-ioc.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
		"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <bean id="company" class="com.lntea.test.bean.Company">
    	<property name="name" value="Apple"></property>
    	<property name="employee" ref="employee"></property>
    </bean>
    <bean id="employee" class="com.lntea.test.bean.Employee">
    	<property name="name" value="Jack"></property>
    </bean>
</beans>

1.定位

首先咱們要對xml資源文件進行讀取,通常對資源的操做都是經過流來作的,定義一個Resource資源接口

package com.lntea.ioc.resource;

import java.io.IOException;
import java.io.InputStream;

public interface Resource {
	InputStream getInputStream() throws IOException;
}

一般資源文件都會放在classpath路徑下,實現一個ClassPathResource類能夠讀取classpath的文件

package com.lntea.ioc.resource;

import java.io.IOException;
import java.io.InputStream;

public class ClassPathResource implements Resource{
    
    private String path;
    
    private ClassLoader classLoader;
    
    public ClassPathResource(String path) {
        this(path, (ClassLoader)null);
    }
    
    public ClassPathResource(String path, ClassLoader classLoader) {
        this.path = path;
        this.classLoader = (classLoader != null ? classLoader : Thread.currentThread().getContextClassLoader());
    }

    @Override
    public InputStream getInputStream() throws IOException {
        return classLoader.getResourceAsStream(path);
    }

}

可是資源也能夠經過其餘方式定義,好比絕對路徑或者URL的方式,於是須要一個Resource的處理器ResourceLoader。當前咱們就只處理classpath方式的。

package com.lntea.ioc.resource;

public class ResourceLoader {
    
    final String CLASSPATH_URL_PREFIX = "classpath:";
	
	public Resource getResource(String location){
	    if (location.startsWith(CLASSPATH_URL_PREFIX)) {
            return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()));
        }
        else {
            return new ClassPathResource(location);
        }
	}
}

這樣就能夠經過ResourceLoader來得到company-ioc.xml文件的輸入流

InputStream inputStream = new ResourceLoader().getResource("company-ioc.xml").getInputStream;

2.解析

有了company-ioc.xml文件的輸入流,就能夠進行bean及bean關係的解析,在解析以前,咱們須要一個數據結構存儲解析後的數據。爲何不直接存儲對象?由於spring中能夠設置lazy-init=true,從而在真正須要bean對象時才建立;另外一個是spring中能夠設置bean的scope爲prototype,也就是bean不是單例的,每次獲取的都是一個新的bean對象。這樣就要求咱們只能存儲bean的數據結構,而不能直接存儲bean對象。

bean的數據結構至少要包含:

  1. bean名稱
  2. bean的class包路徑或class對象
  3. 依賴屬性

能夠定義bean的數據結構BeanDefinition

package com.lntea.ioc.beans;

public class BeanDefinition {
    // bean名稱
	private String beanName;
	// bean的class對象
	private Class beanClass;
	// bean的class的包路徑
	private String beanClassName;
	// bean依賴屬性
	private PropertyValues propertyValues = new PropertyValues();
	
	public String getBeanName() {
		return beanName;
	}
	public void setBeanName(String beanName) {
		this.beanName = beanName;
	}
	
	public Class getBeanClass() {
		return beanClass;
	}

	public void setBeanClass(Class beanClass) {
		this.beanClass = beanClass;
	}

	public String getBeanClassName() {
		return beanClassName;
	}

	public void setBeanClassName(String beanClassName) {
		this.beanClassName = beanClassName;
	}

	public PropertyValues getPropertyValues() {
		return propertyValues;
	}

	public void setPropertyValues(PropertyValues propertyValues) {
		this.propertyValues = propertyValues;
	}
	
}

其中PropertyValues用來存儲依賴屬性

package com.lntea.ioc.beans;

import java.util.ArrayList;
import java.util.List;

public class PropertyValues {
	private List<PropertyValue> propertyValues = new ArrayList<PropertyValue>();
	
	public void addPropertyValue(PropertyValue propertyValue){
		propertyValues.add(propertyValue);
	}
	
	public List<PropertyValue> getPropertyValues(){
		return this.propertyValues;
	}
}

PropertyValue經過鍵值對記錄依賴屬性的名稱和值或引用

package com.lntea.ioc.beans;

public class PropertyValue {
	private final String name;
	private final Object value;
	
	public PropertyValue(String name, Object value) {
		this.name = name;
		this.value = value;
	}
	public String getName() {
		return name;
	}
	public Object getValue() {
		return value;
	}
}

定義BeanDefinition讀取接口

package com.lntea.ioc.xml;

public interface BeanDefinitionReader {
	void loadBeanDefinitions(String location) throws Exception;
}

實現xml文件的BeanDefinition讀取類XmlBeanDefinitionReader

package com.lntea.ioc.xml;

import java.io.InputStream;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.lntea.ioc.beans.BeanDefinition;
import com.lntea.ioc.beans.BeanDefinitionRegistry;
import com.lntea.ioc.beans.BeanReference;
import com.lntea.ioc.beans.PropertyValue;
import com.lntea.ioc.resource.ResourceLoader;

public class XmlBeanDefinitionReader implements BeanDefinitionReader{
    // BeanDefinition註冊到BeanFactory接口
    private BeanDefinitionRegistry registry;
    // 資源載入類
    private ResourceLoader resourceLoader;
    
    public XmlBeanDefinitionReader(BeanDefinitionRegistry registry, ResourceLoader resourceLoader) {
        this.registry = registry;
        this.resourceLoader = resourceLoader;
    }
    
    @Override
    public void loadBeanDefinitions(String location) throws Exception{
    	InputStream is = getResourceLoader().getResource(location).getInputStream();
    	doLoadBeanDefinitions(is);
    }
    
    public BeanDefinitionRegistry getRegistry(){
        return registry;
    }
    
    public ResourceLoader getResourceLoader(){
        return resourceLoader;
    }
}

XmlBeanDefinitionReader的構造參數傳入資源文件的載入類和BeanDefinition註冊接口實現類,用來定位xml文件和註冊BeanDefinition,因此在XmlBeanDefinitionReader中實現了Bean的定位,解析和以後的註冊。

在loadBeanDefinitions方法中,經過ResourceLoader獲取到xml文件的InputStream,完成了定位操做,解析操做轉到doLoadBeanDefinitions方法中。

protected void doLoadBeanDefinitions(InputStream is) throws Exception {
	DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
	DocumentBuilder builder = factory.newDocumentBuilder();
	Document document = builder.parse(is);
	registerBeanDefinitions(document);
	is.close();
}

protected void registerBeanDefinitions(Document document) {
	Element root = document.getDocumentElement();
	parseBeanDefinitions(root);
}

DOM方式解析xml,拿到Document對象,再拿到根節點,轉到parseBeanDefinitions解析具體的 <beans> 信息

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

protected void processBeanDefinition(Element ele) {
	String name = ele.getAttribute("id");
	String className = ele.getAttribute("class");
	if(className==null || className.length()==0){
		throw new IllegalArgumentException("Configuration exception: <bean> element must has class attribute.");
	}
	if(name==null||name.length()==0){
		name = className;
	}
	BeanDefinition beanDefinition = new BeanDefinition();
	beanDefinition.setBeanClassName(className);
	processBeanProperty(ele,beanDefinition);
	getRegistry().registerBeanDefinition(name, beanDefinition);
}

遍歷<beans>下的每個<bean>節點,獲取屬性id和class的值,分別對應BeanDefinition的beanName和BeanClassName。而後解析依賴屬性<property>標籤:

protected void processBeanProperty(Element ele, BeanDefinition beanDefinition) {
		NodeList childs = ele.getElementsByTagName("property");
		for (int i = 0; i < childs.getLength(); i++) {
			Node node = childs.item(i);
			if(node instanceof Element){
				Element property = (Element)node;
				String name = property.getAttribute("name");
				String value = property.getAttribute("value");
				if(value!=null && value.length()>0){
					beanDefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, value));
				}else{
					String ref = property.getAttribute("ref");
					if(ref==null || ref.length()==0){
						throw new IllegalArgumentException("Configuration problem: <property> element for "+
								name+" must specify a value or ref.");
					}
					BeanReference reference = new BeanReference(ref);
					beanDefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, reference));
				}
			}
		}
	}

若是<property>中存在value屬性,則做爲String類型存儲,若是是ref屬性,則建立BeanReference對象存儲。

package com.lntea.ioc.beans;

public class BeanReference {
	private String ref;

	public BeanReference(String ref) {
		this.ref = ref;
	}

	public String getRef() {
		return ref;
	}
	
}

最後註冊BeanDefinition到存儲BeanDefinition的地方,咱們能夠猜想確定有一個Map<String,BeanDefinition>。

getRegistry().registerBeanDefinition(name, beanDefinition);

3. 註冊

上面的getRegistry()得到的就是XmlBeanDefinitionReader構造時傳入的BeanDefinitionRegistry實現類。首先BeanDefinitionRegistry是個接口。

package com.lntea.ioc.beans;

public interface BeanDefinitionRegistry {

    void registerBeanDefinition(String beanName, BeanDefinition beanDefinition);
}

它只定義了一個方法,就是註冊BeanDefinition。它的實現類就是要實現這個方法並將BeanDefinition註冊到Map<String,BeanDefinition>中。在這裏這個實現類是DefaultListableBeanFactory。

public class DefaultListableBeanFactory extends AbstractBeanFactory implements ConfigurableListableBeanFactory,BeanDefinitionRegistry{

    private Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>();
    private List<String> beanDefinitionNames = new ArrayList<String>();
    
    public void registerBeanDefinition(String beanName,BeanDefinition beanDefinition){
        beanDefinitionMap.put(beanName, beanDefinition);
        beanDefinitionNames.add(beanName);
    }
}

能夠看出它包含一個beanDefinitionMap 和 beanDefinitionNames,一個記錄beanName和BeanDefinition的關係,一個記錄全部BeanName。而registerBeanDefinition方法就是將BeanDefinition添加到beanDefinitionMap和beanDefinitionNames。

4.獲取Bean

DefaultListableBeanFactory實現了ConfigurableListableBeanFactory接口,而ConfigurableListableBeanFactory繼承了BeanFactory接口。BeanFactory是一個頂級接口。

package com.lntea.ioc.factory;

public interface BeanFactory {
    
	Object getBean(String beanName);
}

它定義了獲取bean對象的接口方法,實現這個方法的是AbstractBeanFactory抽象類。

public abstract class AbstractBeanFactory implements ConfigurableListableBeanFactory {
	private Map<String, Object> singleObjects = new ConcurrentHashMap<String, Object>();
	
	@Override
    public Object getBean(String beanName){
	    Object singleBean = this.singleObjects.get(beanName);
	    if(singleBean != null){
	        return singleBean;
	    }
	    
        BeanDefinition beanDefinition  = getBeanDefinitionByName(beanName);
        if(beanDefinition==null){
            throw new RuntimeException("bean for name '"+beanName+"' not register.");
        }
        
        singleBean = doCreateBean(beanDefinition);
        this.singleObjects.put(beanName, singleBean);
        return singleBean;
    }
    
    protected abstract BeanDefinition getBeanDefinitionByName(String beanName);
}

AbstractBeanFactory中定義了單實例(Demo默認都是單實例的bean)的beanName和bean對象關係的singleObjects的Map。getBean方法傳入beanName,先查詢singleObjects有沒有beanName的緩存,若是存在,直接返回;若是不存在則調用getBeanDefinitionByName獲取BeanDefinition,getBeanDefinitionByName由AbstractBeanFactory的子類實現,也就是DefaultListableBeanFactory。

@Override
protected BeanDefinition getBeanDefinitionByName(String beanName) {
    return beanDefinitionMap.get(beanName);
}

拿到BeanDefinition後,進入真正建立bean的方法doCreateBean。

protected Object doCreateBean(BeanDefinition beanDefinition){
	Object bean = createInstance(beanDefinition);
	applyPropertyValues(bean,beanDefinition);
	return bean;
}

它作了兩個操做,一個是建立bean對象,另外一個是注入依賴屬性。

protected Object createInstance(BeanDefinition beanDefinition) {
    try {
	    if(beanDefinition.getBeanClass() != null){
            return beanDefinition.getBeanClass().newInstance();
	    }else if(beanDefinition.getBeanClassName() != null){
	        try {
                Class clazz = Class.forName(beanDefinition.getBeanClassName());
                beanDefinition.setBeanClass(clazz);
                return clazz.newInstance();
            } catch (ClassNotFoundException e) {
                throw new RuntimeException("bean Class " + beanDefinition.getBeanClassName() + " not found");
            }
	    }
    } catch (Exception e) {
        throw new RuntimeException("create bean " + beanDefinition.getBeanName() + " failed");
    } 
    throw new RuntimeException("bean name for " + beanDefinition.getBeanName() + " not define bean class");
}

經過Class.forName加載bean的Class對象,再經過Class的newInstance方法生成bean對象。

protected void applyPropertyValues(Object bean, BeanDefinition beanDefinition){
    for(PropertyValue propertyValue : beanDefinition.getPropertyValues().getPropertyValues()){
        String name = propertyValue.getName();
        Object value = propertyValue.getValue();
        if(value instanceof BeanReference){
            BeanReference reference = (BeanReference) value;
            value = getBean(reference.getRef());
        }
        try {
            Method method = bean.getClass().getDeclaredMethod("set"+name.substring(0, 1).toUpperCase()+
                    name.substring(1), value.getClass());
            method.setAccessible(true);
            method.invoke(bean, value);
        } catch(Exception e){
            try {
                Field field = bean.getClass().getDeclaredField(name);
                field.setAccessible(true);
                field.set(bean, value);
            } catch (Exception e1) {
                throw new RuntimeException("inject bean property " + name + " failed");
            } 
        }
    }
}

若是依賴屬性是對象的話,經過getBean獲取或生成依賴對象,並經過反射注入到bean對象中,這也就是依賴注入。

至此,Bean的建立和獲取就完成了。

5. ApplicationContext

在咱們平常使用spring時,通常不會直接用BeanFactory,而是經過一個更加高級的接口ApplicationContext來初始化容器以及提供其餘企業級的功能。在咱們的簡易IOC容器中,經過ApplicationContext來實例化全部的bean。

package com.lntea.ioc.context;

import com.lntea.ioc.factory.BeanFactory;

public interface ApplicationContext extends BeanFactory{
	
}

ApplicationContext沒有提供其餘功能,只是繼承了BeanFactory。

來看ApplicationContext的一個最終實現類ClasspathXmlApplicationContext。

package com.lntea.ioc.context;

import com.lntea.ioc.beans.BeanDefinitionRegistry;
import com.lntea.ioc.resource.ResourceLoader;
import com.lntea.ioc.xml.XmlBeanDefinitionReader;

public class ClasspathXmlApplicationContext extends AbstractApplicationContext {
	private String location;
	
	public ClasspathXmlApplicationContext(String location) {
		this.location = location;
		try {
			refresh();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

ClasspathXmlApplicationContext的構造函數裏接收傳入xml資源文件的路徑location,再調用refresh方法。refresh方法在AbstractApplicationContext抽象類中實現。

package com.lntea.ioc.context;

import com.lntea.ioc.beans.BeanDefinitionRegistry;
import com.lntea.ioc.factory.ConfigurableListableBeanFactory;
import com.lntea.ioc.factory.DefaultListableBeanFactory;

public abstract class AbstractApplicationContext implements ApplicationContext{
	private ConfigurableListableBeanFactory beanFactory;
	
	@Override
    public Object getBean(String beanName) {
        return beanFactory.getBean(beanName);
    }
	
	public void refresh() throws Exception{
	    DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
		loadBeanDefinitions(beanFactory);
		this.beanFactory = beanFactory;
		onRefresh();
	}
	
	protected void onRefresh() throws Exception {
		beanFactory.preInstantiateSingletons();
	}

	protected abstract void loadBeanDefinitions(BeanDefinitionRegistry registry) throws Exception;
}

refresh方法先建立一個DefaultListableBeanFactory,而後經過loadBeanDefinitions解析並註冊bean,真正的實如今ClasspathXmlApplicationContext中。

@Override
	protected void loadBeanDefinitions(BeanDefinitionRegistry registry) throws Exception {
		XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(registry, new ResourceLoader());
		reader.loadBeanDefinitions(location);
	}

就是經過XmlBeanDefinitionReader來處理,這在上面已經詳細的講過了,就再也不贅述了。

refresh方法中最後調用onRefresh方法,初始化全部bean實例。

protected void onRefresh() throws Exception {
	beanFactory.preInstantiateSingletons();
}

實際調用的是DefaultListableBeanFactory,方法定義在ConfigurableListableBeanFactory接口中。

package com.lntea.ioc.factory;

public interface ConfigurableListableBeanFactory extends BeanFactory{

    void preInstantiateSingletons() throws Exception;
}

DefaultListableBeanFactory的實現

public void preInstantiateSingletons() throws Exception{
    for(String beanName : beanDefinitionNames){
        getBean(beanName);
    }
}

這樣在咱們須要bean對象時,就能夠直接拿到實例化好的對象。還記得以前定義的Company和Employee以及company-ioc.xml嗎,咱們寫個測試:

package com.lntea.test;

import com.lntea.ioc.context.ApplicationContext;
import com.lntea.ioc.context.ClasspathXmlApplicationContext;
import com.lntea.test.bean.Company;

public class CompanyApplicationContext {

    public static void main(String[] args) {
        ApplicationContext context = new ClasspathXmlApplicationContext("company-ioc.xml");
        Company company = (Company) context.getBean("company");
        company.open();
    }
}

能夠看出與真正的spring的使用方式一致,執行結果以下。

Company Apple is open.
Employee Jack is working.

6.總結

以上咱們構建了一個很是簡單的IOC容器,可是實現的功能確是IOC容器最核心的DI(依賴注入)。在此基礎上,spring創建起完善的企業級的IOC容器以及一樣重要的AOP功能。老子《道德經》有云:「合抱之木,生於毫末;九層之臺,起於壘土;千里之行,始於足下。」。當咱們理解了spring的基礎,再去學習spring的源碼,天然要事半功倍。後面的文章咱們將真正學習spring的源碼,領略spring源碼的美妙。

相關文章
相關標籤/搜索