天天看點

跟着大佬閱讀spring源碼(一)

每一個做web的同學都離不開spring,spring的設計思想博大而精深。LK在開發過程中也都是在使用spring的功能和各種配置,其實并不了解spring是如何管理bean的。最近LK在學習的springboot的時候不僅對springboot簡化的配置感到驚歎,同時也感到有一點惶恐,如此高度的封裝雖然簡化了開發配置,提高了生産效率。但隻是停留在了會用的程度,所謂知其然不知其是以然,遇到問題隻能百度解決。是以下決心從spring的源碼下手,看看其底層實作。所謂站在巨人的肩上,事辦功倍。

感謝各位大佬和前輩,spring的源碼真的太多,沒有主線,自己看真的是自尋死路。當然和spring中使用設計模式和高度封裝有關。

好了廢話不多說,來看看spring核心IOC(控制反轉)

IOC主要幹了兩件事

  1. 控制bean的生命周期。
  2. 處理對象間的關系。

    注入IOC容器中的bean各種各樣,自帶屬性也不相同,類與類之間的關系也錯綜複雜,在沒有IOC管理bean之前,我們隻能通過java的四大特性去維護這些關系。好了IOC的出現使我們從複雜的bean關系中解脫,一切交給容器自身去維護。可以說bean的吃喝拉撒都交給了IOC負責,當然我們這個大保姆也十分有耐心去照顧每一個bean兒子,從生老病死,精心呵護它們實作各自的使命。

LK在具體檢視spring源碼之前,先通過一個自己實作的的例子來看看IOC在對bean管理時都做了什麼。麻雀雖小但五髒俱全。

  1. 首先定位bean配置檔案,相信大家學習spring時都使用過,就不詳細說了。
  2. 将配置檔案轉換為IO流。
  3. 将IO流轉化為Document對象。
  4. 通過解析Document對象元素,擷取定義的bean.
  5. 使用HashMap緩存bean對象。
  6. 通過Key在HashMap中擷取指定的bean.

源碼:

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

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;

/**
 * 1.根據 xml 配置檔案加載相關 bean
 * 
 * @author yrz
 *
 */
public class SimpleIOC {
	
	private  Map<String, Object> beanMap = new HashMap<>();
	SimpleIOC(String path) throws Exception {
		loadBeans(path);
	}

	private void loadBeans(String path) throws Exception {
		// 1.加載配置檔案xml
		
		
		// 讀取xml配置檔案
		InputStream inputStream = new FileInputStream(path);
		//調用 DocumentBuilderFactory.newInstance() 方法得到建立 DOM 解析器的工廠
		DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance() ;
		// 調用工廠對象的 newDocumentBuilder方法得到 DOM 解析器對象。
		DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
		//調用 DOM 解析器對象的 parse() 方法解析 XML 文檔,得到代表整個文檔的 Document 對象,進行可以利用DOM特性對整個XML文檔進行操作了。
		org.w3c.dom.Document docurment = documentBuilder.parse(inputStream);
		//得到 XML 文檔的根節點
		Element element = docurment.getDocumentElement();
		//得到節點的子節點
		NodeList nodeList = element.getChildNodes();
		
		//周遊bean标簽
		for (int i = 0; i < nodeList.getLength(); i++) {
			Node node = nodeList.item(i);
			if(node instanceof Element) {
				Element ele = (Element)node;
				String id = ele.getAttribute("id");
				String className = ele.getAttribute("class");
				 // 加載 beanClass
				Class beanClass =  Class.forName(className);
				//建立bean
				Object object = beanClass.newInstance();
				// 周遊 <property> 标簽
				NodeList propertyNodeList = ele.getElementsByTagName("property");
				for(int j = 0 ; j < propertyNodeList.getLength() ; j++) {
					Node proNode = propertyNodeList.item(j);
					if( proNode instanceof Element) {
						  Element propertyElement = (Element) proNode;
	                        String name = propertyElement.getAttribute("name");
	                        String value = propertyElement.getAttribute("value");
	                        	
	                        // 利用反射将 bean 相關字段通路權限設為可通路
	                        Field field = object.getClass().getDeclaredField(name);
	                        field.setAccessible(true);
	                        
	                        if(value != null && value.length() > 0) {
	                        	// 将屬性值填充到相關字段中
	                        	field.set(object, value);
	                        }else {
	                        	 String ref = propertyElement.getAttribute("ref");
	                        	  if (ref == null || ref.length() == 0) {
	                                  throw new IllegalArgumentException("ref config error");
	                              }
	                        	    // 将引用填充到相關字段中
	                        	  field.set(object, getBean(ref));
	                        }
	                        // 将 bean 注冊到 bean 容器中
	                        registerBean(id, object);
					}
				}
			}
			
		}
	
	}

	private void registerBean(String id, Object object) {
		  beanMap.put(id, object);
	}

	//擷取bean
	Object getBean(String ref) {
		Object object = beanMap.get(ref);
		if (object == null) {
			throw new IllegalArgumentException("there is no bean with name " + ref);
		}
		return object;
	}
}

           

LK此時實作的例子正是之後要說的spring實作IOC的一個縮減版。

接下來LK就要來獻醜說一下IOC是如何幹活的,有些地方LK了解的也不是十厘清楚,有問題還請各位老鐵指正。

IOC幹活流程:

跟着大佬閱讀spring源碼(一)

這是IOC将配置檔案中的bean解析為自己能識别的過程,次過程叫BeanDefinition(bean的定義)

跟着LK來具體看看IOC是怎麼把一個陌生人變成自己親兒子的

  • 找到配置檔案的位置
    跟着大佬閱讀spring源碼(一)
    ApplicationContext的子類AbstractApplicationContext中的refresh()實作了配置檔案的定位。
@Override
	public void refresh() throws BeansException, IllegalStateException {
	//同步代碼快保證定位操作都是線上程安全的情況下進行的
		synchronized (this.startupShutdownMonitor) {
			// 準備重新整理上下文,設定其啟動日期和活動标志以及對屬性源執行任何初始化。
			prepareRefresh();

			// 通知子類去重新整理内部類,這個内部類是定位Resource的關鍵(後面會單獨說).
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			//配置工廠的标準上下文特征,例如上下文的類加載器和後處理器.
			prepareBeanFactory(beanFactory);

			try {
				// 允許在上下文子類中對bean工廠進行後處理.
				postProcessBeanFactory(beanFactory);

				// 調用在上下文中注冊為bean的工廠處理器.
				invokeBeanFactoryPostProcessors(beanFactory);

				// 注冊攔截bean建立的bean處理器.
				registerBeanPostProcessors(beanFactory);

				// 為此上下文初始化消息源.
				initMessageSource();

				// 為此上下文初始化事件多主機.
				initApplicationEventMulticaster();

				//初始化特定上下文子類中的其他特殊bean.
				onRefresh();

				//檢查偵聽器bean并注冊它們。
				registerListeners();

				// 執行個體化所有剩餘(非lazy init)單例.
				finishBeanFactoryInitialization(beanFactory);

				// 最後一步:釋出對應的事件。
				finishRefresh();
			}

			catch (BeansException ex) {
				logger.warn("Exception encountered during context initialization - cancelling refresh attempt", ex);

				// 銷毀已經建立的單例以避免資源懸空。
				destroyBeans();

				// 重置“活動标志”.
				cancelRefresh(ex);

				// Propagate exception to caller.
				throw ex;
			}
		}
	}

           

往下走之前我們先來看一下這個 postProcessBeanFactory(beanFactory)後處理都幹了什麼,找到它的子類AbstractRefreshablewebApplicationContext

跟着大佬閱讀spring源碼(一)
/**
	 * Register request/session scopes, a {@link ServletContextAwareProcessor}, etc.
	 */
	@Override
	protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
	//添加一個新的beanPostProcessor,它将應用于建立的bean在工廠配置期間調用,bean中執行個體化Servlet的上下文和配置項
		beanFactory.addBeanPostProcessor(new ServletContextAwareProcessor(this.servletContext, this.servletConfig));
	//忽略依賴接口
		beanFactory.ignoreDependencyInterface(ServletContextAware.class);
		beanFactory.ignoreDependencyInterface(ServletConfigAware.class);
    //注冊web作用域,環境。		
		WebApplicationContextUtils.registerWebApplicationScopes(beanFactory, this.servletContext);
		WebApplicationContextUtils.registerEnvironmentBeans(beanFactory, this.servletContext, this.servletConfig);
	}

           

總結: postProcessBeanFactory主要是在應用程式上下文标準化之後修改其内部bean工廠。此時所有bean定義都将被加載,但沒有bean将被執行個體化。

跟着主線繼續出發--------

來看到obtainFreshBeanFactory()

跟着大佬閱讀spring源碼(一)

具體實作是在它的子類AbstractRefreshableApplicationContext中實作

protected final void refreshBeanFactory() throws BeansException {
	//如果beanfactory已經存在,就銷毀和關閉這個bean工廠
		if (hasBeanFactory()) {
			destroyBeans();
			closeBeanFactory();
		}
		try {
		//建立一個新的bean工廠
			DefaultListableBeanFactory beanFactory = createBeanFactory();
			//設定序列化id
			beanFactory.setSerializationId(getId());
			//自定義bean工廠
			customizeBeanFactory(beanFactory);
			//将bean定義加載到給定的bean工廠中,運用委托設計模式将其委托給一個或多個bean定義的閱讀者
			loadBeanDefinitions(beanFactory);
			synchronized (this.beanFactoryMonitor) {
				this.beanFactory = beanFactory;
			}
		}
		catch (IOException ex) {
			throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
		}
	}
           

再來看看loadBeanDefinitions方法,具體實作來看它的子類XmlWebApplicationContext

跟着大佬閱讀spring源碼(一)
@Override
	protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
		// 給 BeanFactory 建立了一個新的 XmlBeanDefinitionReader .
		XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

		// 使用此上下文環境給bean definition 讀者配置資源和環境
		beanDefinitionReader.setEnvironment(getEnvironment());
		beanDefinitionReader.setResourceLoader(this);
		beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

		// 允許子類自定義初始化,然後繼續實際加載bean定義
		initBeanDefinitionReader(beanDefinitionReader);
		loadBeanDefinitions(beanDefinitionReader);
	}
           

接着看子類的loadBeanDefinitions

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
		String[] configLocations = getConfigLocations();
		if (configLocations != null) {
			for (String configLocation : configLocations) {
				reader.loadBeanDefinitions(configLocation);
			}
		}
	}

           

首先加載本地配置項資訊,接着

@Override
	public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
		return loadBeanDefinitions(location, null);
	}
           

此方法傳回的是加載的bean定義項的數量,接着看

public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
        //得到資源
		ResourceLoader resourceLoader = getResourceLoader();
		//指定位置沒有資源
		if (resourceLoader == null) {
			throw new BeanDefinitionStoreException(
					"Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
		}

		if (resourceLoader instanceof ResourcePatternResolver) {
			// Resource pattern matching available.
			try {
		//從指定位置加載資源	
				Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
		//得到資源數量
				int loadCount = loadBeanDefinitions(resources);
				if (actualResources != null) {
		//資源添加到集合中
					for (Resource resource : resources) {
						actualResources.add(resource);
					}
				}
				if (logger.isDebugEnabled()) {
					logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
				}
				return loadCount;
			}
			catch (IOException ex) {
				throw new BeanDefinitionStoreException(
						"Could not resolve bean definition resource pattern [" + location + "]", ex);
			}
		}
		else {
			// 隻能通過URL得到資源
			Resource resource = resourceLoader.getResource(location);
			int loadCount = loadBeanDefinitions(resource);
			if (actualResources != null) {
				actualResources.add(resource);
			}
			if (logger.isDebugEnabled()) {
				logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
			}
			return loadCount;
		}
	}
           

有沒有看到曙光,getResources()和getResource()方法就是加載資源入口。來看看他們具體比對加載資源規則,進入getResources()方法實作的子類。

跟着大佬閱讀spring源碼(一)
public Resource[] getResources(String locationPattern) throws IOException {
		Assert.notNull(locationPattern, "Location pattern must not be null");
		//從開始位置比對String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
		if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
			// 類路勁比對
			if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
				// 傳回比對到的資源路徑
				return findPathMatchingResources(locationPattern);
			}
			else {
				//擷取具有給定名稱的所有類路徑資源
				return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
			}
		}
		else {
			// 比對“ : ”後面的内容
			int prefixEnd = locationPattern.indexOf(":") + 1;
			if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
				//得到比對到的檔案路徑
				return findPathMatchingResources(locationPattern);
			}
			else {
				//一個具有給定名稱的單一資源
				return new Resource[] {getResourceLoader().getResource(locationPattern)};
			}
		}
	}
           

至此資源定位完成,也就是拿到了我們配置檔案的路勁,現在知道配置配置檔案路勁要使用classpath*:。上面的執行個體中少了此過程。

來梳理一下整個過程

跟着大佬閱讀spring源碼(一)

下一篇再來介紹xml轉換成DOm.

最後感謝大佬 田小波,I,Frankenstein等