每一個做web的同學都離不開spring,spring的設計思想博大而精深。LK在開發過程中也都是在使用spring的功能和各種配置,其實并不了解spring是如何管理bean的。最近LK在學習的springboot的時候不僅對springboot簡化的配置感到驚歎,同時也感到有一點惶恐,如此高度的封裝雖然簡化了開發配置,提高了生産效率。但隻是停留在了會用的程度,所謂知其然不知其是以然,遇到問題隻能百度解決。是以下決心從spring的源碼下手,看看其底層實作。所謂站在巨人的肩上,事辦功倍。
感謝各位大佬和前輩,spring的源碼真的太多,沒有主線,自己看真的是自尋死路。當然和spring中使用設計模式和高度封裝有關。
好了廢話不多說,來看看spring核心IOC(控制反轉)
IOC主要幹了兩件事
- 控制bean的生命周期。
-
處理對象間的關系。
注入IOC容器中的bean各種各樣,自帶屬性也不相同,類與類之間的關系也錯綜複雜,在沒有IOC管理bean之前,我們隻能通過java的四大特性去維護這些關系。好了IOC的出現使我們從複雜的bean關系中解脫,一切交給容器自身去維護。可以說bean的吃喝拉撒都交給了IOC負責,當然我們這個大保姆也十分有耐心去照顧每一個bean兒子,從生老病死,精心呵護它們實作各自的使命。
LK在具體檢視spring源碼之前,先通過一個自己實作的的例子來看看IOC在對bean管理時都做了什麼。麻雀雖小但五髒俱全。
- 首先定位bean配置檔案,相信大家學習spring時都使用過,就不詳細說了。
- 将配置檔案轉換為IO流。
- 将IO流轉化為Document對象。
- 通過解析Document對象元素,擷取定義的bean.
- 使用HashMap緩存bean對象。
- 通過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幹活流程:
這是IOC将配置檔案中的bean解析為自己能識别的過程,次過程叫BeanDefinition(bean的定義)
跟着LK來具體看看IOC是怎麼把一個陌生人變成自己親兒子的
- 找到配置檔案的位置 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
/**
* 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()
具體實作是在它的子類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
@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()方法實作的子類。
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*:。上面的執行個體中少了此過程。
來梳理一下整個過程
下一篇再來介紹xml轉換成DOm.
最後感謝大佬 田小波,I,Frankenstein等