上文,我們看了IOC設計要點和設計結構;緊接著這篇,我們可以看下原始碼的實現了:Spring如何實現將資源配置(以xml配置為例)通過載入,解析,生成BeanDefination並註冊到IoC容器中的。@pdai
- Spring框架系列(7) - Spring IOC實現原理詳解之IOC初始化流程
- 引入
- 如何將Bean從XML配置中解析後放到IoC容器中的?
- 初始化的入口
- 設定資源解析器和環境
- 設定配置路徑
- 初始化的主體流程
- 初始化BeanFactory之obtainFreshBeanFactory
- 初始化BeanFactory之loadBeanDefinitions
- AbstractBeanDefinitionReader讀取Bean定義資源
- XmlBeanDefinitionReader載入Bean定義資源
- DocumentLoader將Bean定義資源轉換為Document物件
- XmlBeanDefinitionReader解析載入的Bean定義資原始檔
- DefaultBeanDefinitionDocumentReader對Bean定義的Document物件解析
- BeanDefinitionParserDelegate解析Bean定義資原始檔生成BeanDefinition
- 解析過後的BeanDefinition在IoC容器中的註冊
- DefaultListableBeanFactory向IoC容器註冊解析後的BeanDefinition
- 總結
- 參考文章
- 更多文章
引入
上文,我們看了IOC設計要點和設計結構;緊接著這篇,我們可以看下原始碼的實現了:Spring如何實現將資源配置(以xml配置為例)通過載入,解析,生成BeanDefination並註冊到IoC容器中的(就是我們圈出來的部分)
如何將Bean從XML配置中解析後放到IoC容器中的?
本文的目標就是分析Spring如何實現將資源配置(以xml配置為例)通過載入,解析,生成BeanDefination並註冊到IoC容器中的。
初始化的入口
對於xml配置的Spring應用,在main()方法中例項化ClasspathXmlApplicationContext即可建立一個IoC容器。我們可以從這個構造方法開始,探究一下IoC容器的初始化過程。
// create and configure beans
ApplicationContext context = new ClassPathXmlApplicationContext("aspects.xml", "daos.xml", "services.xml");
public ClassPathXmlApplicationContext(String... configLocations) throws BeansException {
this(configLocations, true, (ApplicationContext)null);
}
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException {
// 設定Bean資源載入器
super(parent);
// 設定配置路徑
this.setConfigLocations(configLocations);
// 初始化容器
if (refresh) {
this.refresh();
}
}
設定資源解析器和環境
呼叫父類容器AbstractApplicationContext的構造方法(super(parent)
方法)為容器設定好Bean資源載入器
public AbstractApplicationContext(@Nullable ApplicationContext parent) {
// 預設建構函式初始化容器id, name, 狀態 以及 資源解析器
this();
// 將父容器的Environment合併到當前容器
this.setParent(parent);
}
通過AbstractApplicationContext預設建構函式初始化容器id, name, 狀態 以及 資源解析器
public AbstractApplicationContext() {
this.logger = LogFactory.getLog(this.getClass());
this.id = ObjectUtils.identityToString(this);
this.displayName = ObjectUtils.identityToString(this);
this.beanFactoryPostProcessors = new ArrayList();
this.active = new AtomicBoolean();
this.closed = new AtomicBoolean();
this.startupShutdownMonitor = new Object();
this.applicationStartup = ApplicationStartup.DEFAULT;
this.applicationListeners = new LinkedHashSet();
this.resourcePatternResolver = this.getResourcePatternResolver();
}
// Spring資源載入器
protected ResourcePatternResolver getResourcePatternResolver() {
return new PathMatchingResourcePatternResolver(this);
}
通過AbstractApplicationContext的setParent(parent)
方法將父容器的Environment合併到當前容器
public void setParent(@Nullable ApplicationContext parent) {
this.parent = parent;
if (parent != null) {
Environment parentEnvironment = parent.getEnvironment();
if (parentEnvironment instanceof ConfigurableEnvironment) {
this.getEnvironment().merge((ConfigurableEnvironment)parentEnvironment);
}
}
}
設定配置路徑
在設定容器的資源載入器之後,接下來FileSystemXmlApplicationContet執行setConfigLocations方法通過呼叫其父類AbstractRefreshableConfigApplicationContext的方法進行對Bean定義資原始檔的定位
public void setConfigLocations(@Nullable String... locations) {
if (locations != null) {
Assert.noNullElements(locations, "Config locations must not be null");
this.configLocations = new String[locations.length];
for(int i = 0; i < locations.length; ++i) {
// 解析配置路徑
this.configLocations[i] = this.resolvePath(locations[i]).trim();
}
} else {
this.configLocations = null;
}
}
protected String resolvePath(String path) {
// 從上一步Environment中解析
return this.getEnvironment().resolveRequiredPlaceholders(path);
}
初始化的主體流程
Spring IoC容器對Bean定義資源的載入是從refresh()函式開始的,refresh()是一個模板方法,refresh()方法的作用是:在建立IoC容器前,如果已經有容器存在,則需要把已有的容器銷燬和關閉,以保證在refresh之後使用的是新建立起來的IoC容器。refresh的作用類似於對IoC容器的重啟,在新建立好的容器中對容器進行初始化,對Bean定義資源進行載入。
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
beanPostProcess.end();
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
contextRefresh.end();
}
}
}
這裡的設計上是一個非常典型的資源類載入處理型的思路,頭腦中需要形成如下圖的頂層思路(而不是隻停留在流水式的方法上面):
- 模板方法設計模式,模板方法中使用典型的鉤子方法
- 將具體的初始化載入方法插入到鉤子方法之間
- 將初始化的階段封裝,用來記錄當前初始化到什麼階段;常見的設計是xxxPhase/xxxStage;
- 資源載入初始化有失敗等處理,必然是try/catch/finally...
初始化BeanFactory之obtainFreshBeanFactory
AbstractApplicationContext的obtainFreshBeanFactory()方法呼叫子類容器的refreshBeanFactory()方法,啟動容器載入Bean定義資原始檔的過程,程式碼如下:
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
// 這裡使用了委派設計模式,父類定義了抽象的refreshBeanFactory()方法,具體實現呼叫子類容器的refreshBeanFactory()方法
refreshBeanFactory();
return getBeanFactory();
}
AbstractApplicationContext類中只抽象定義了refreshBeanFactory()方法,容器真正呼叫的是其子類AbstractRefreshableApplicationContext實現的refreshBeanFactory()方法;
在建立IoC容器前,如果已經有容器存在,則需要把已有的容器銷燬和關閉,以保證在refresh之後使用的是新建立起來的IoC容器。方法的原始碼如下:
protected final void refreshBeanFactory() throws BeansException {
// 如果已經有容器存在,則需要把已有的容器銷燬和關閉,以保證在refresh之後使用的是新建立起來的IoC容器
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
// 建立DefaultListableBeanFactory,並呼叫loadBeanDefinitions(beanFactory)裝載bean定義
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory); // 對IoC容器進行定製化,如設定啟動引數,開啟註解的自動裝配等
loadBeanDefinitions(beanFactory); // 呼叫載入Bean定義的方法,主要這裡又使用了一個委派模式,在當前類中只定義了抽象的loadBeanDefinitions方法,具體的實現呼叫子類容器
this.beanFactory = beanFactory;
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
初始化BeanFactory之loadBeanDefinitions
AbstractRefreshableApplicationContext中只定義了抽象的loadBeanDefinitions方法,容器真正呼叫的是其子類AbstractXmlApplicationContext對該方法的實現,AbstractXmlApplicationContext的主要原始碼如下:
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// 建立XmlBeanDefinitionReader,即建立Bean讀取器,並通過回撥設定到容器中去,容器使用該讀取器讀取Bean定義資源
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// 配置上下文的環境,資源載入器、解析器
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this)); // 為Bean讀取器設定SAX xml解析器
// 允許子類自行初始化(比如校驗機制),並提供真正的載入方法
initBeanDefinitionReader(beanDefinitionReader); // 當Bean讀取器讀取Bean定義的Xml資原始檔時,啟用Xml的校驗機制
loadBeanDefinitions(beanDefinitionReader);
}
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
// 載入XML配置方式裡的Bean定義的資源
Resource[] configResources = getConfigResources();
if (configResources != null) {
reader.loadBeanDefinitions(configResources);
}
// 載入建構函式裡配置的Bean配置檔案,即{"aspects.xml", "daos.xml", "services.xml"}
String[] configLocations = getConfigLocations();
if (configLocations != null) {
reader.loadBeanDefinitions(configLocations);
}
}
Xml Bean讀取器(XmlBeanDefinitionReader)呼叫其父類AbstractBeanDefinitionReader的 reader.loadBeanDefinitions方法讀取Bean定義資源。
由於我們使用ClassPathXmlApplicationContext作為例子分析,因此getConfigResources的返回值為null,因此程式執行reader.loadBeanDefinitions(configLocations)分支。
AbstractBeanDefinitionReader讀取Bean定義資源
AbstractBeanDefinitionReader的loadBeanDefinitions方法原始碼如下:
@Override
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
return loadBeanDefinitions(location, null);
}
public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot load bean definitions from location [" + location + "]: no ResourceLoader available");
}
// 模式匹配型別的解析器,這種方式是載入多個滿足匹配條件的資源
if (resourceLoader instanceof ResourcePatternResolver) {
try {
// 獲取到要載入的資源
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
int count = loadBeanDefinitions(resources); // 委派呼叫其子類XmlBeanDefinitionReader的方法,實現載入功能
if (actualResources != null) {
Collections.addAll(actualResources, resources);
}
if (logger.isTraceEnabled()) {
logger.trace("Loaded " + count + " bean definitions from location pattern [" + location + "]");
}
return count;
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"Could not resolve bean definition resource pattern [" + location + "]", ex);
}
}
else {
// 只能通過絕對路徑URL載入單個資源.
Resource resource = resourceLoader.getResource(location);
int count = loadBeanDefinitions(resource);
if (actualResources != null) {
actualResources.add(resource);
}
if (logger.isTraceEnabled()) {
logger.trace("Loaded " + count + " bean definitions from location [" + location + "]");
}
return count;
}
}
從對AbstractBeanDefinitionReader的loadBeanDefinitions方法原始碼分析可以看出該方法做了以下兩件事:
- 首先,呼叫資源載入器的獲取資源方法resourceLoader.getResource(location),獲取到要載入的資源。
- 其次,真正執行載入功能是其子類XmlBeanDefinitionReader的loadBeanDefinitions方法。
XmlBeanDefinitionReader載入Bean定義資源
繼續看子類XmlBeanDefinitionReader的loadBeanDefinitions(Resource …)方法看到代表bean檔案的資源定義以後的載入過程。
/**
* 本質上是載入XML配置的Bean。
* @param inputSource the SAX InputSource to read from
* @param resource the resource descriptor for the XML file
*/
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
Document doc = doLoadDocument(inputSource, resource); // 將Bean定義資源轉換成Document物件
int count = registerBeanDefinitions(doc, resource);
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + count + " bean definitions from " + resource);
}
return count;
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (SAXParseException ex) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(),
"Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
}
catch (SAXException ex) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(),
"XML document from " + resource + " is invalid", ex);
}
catch (ParserConfigurationException ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"Parser configuration exception parsing XML from " + resource, ex);
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"IOException parsing XML document from " + resource, ex);
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"Unexpected exception parsing XML document from " + resource, ex);
}
}
// 使用配置的DocumentLoader載入XML定義檔案為Document.
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
getValidationModeForResource(resource), isNamespaceAware());
}
通過原始碼分析,載入Bean定義資原始檔的最後一步是將Bean定義資源轉換為Document物件,該過程由documentLoader實現
DocumentLoader將Bean定義資源轉換為Document物件
DocumentLoader將Bean定義資源轉換成Document物件的原始碼如下:
// 使用標準的JAXP將載入的Bean定義資源轉換成document物件
@Override
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
// 建立檔案解析器工廠
DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
if (logger.isTraceEnabled()) {
logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
}
// 建立文件解析器
DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
return builder.parse(inputSource); // 解析
}
protected DocumentBuilderFactory createDocumentBuilderFactory(int validationMode, boolean namespaceAware)
throws ParserConfigurationException {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(namespaceAware);
// 設定解析XML的校驗
if (validationMode != XmlValidationModeDetector.VALIDATION_NONE) {
factory.setValidating(true);
if (validationMode == XmlValidationModeDetector.VALIDATION_XSD) {
// Enforce namespace aware for XSD...
factory.setNamespaceAware(true);
try {
factory.setAttribute(SCHEMA_LANGUAGE_ATTRIBUTE, XSD_SCHEMA_LANGUAGE);
}
catch (IllegalArgumentException ex) {
ParserConfigurationException pcex = new ParserConfigurationException(
"Unable to validate using XSD: Your JAXP provider [" + factory +
"] does not support XML Schema. Are you running on Java 1.4 with Apache Crimson? " +
"Upgrade to Apache Xerces (or Java 1.5) for full XSD support.");
pcex.initCause(ex);
throw pcex;
}
}
}
return factory;
}
該解析過程呼叫JavaEE標準的JAXP標準進行處理。
至此Spring IoC容器根據定位的Bean定義資原始檔,將其載入讀入並轉換成為Document物件過程完成。
接下來我們要繼續分析Spring IoC容器將載入的Bean定義資原始檔轉換為Document物件之後,是如何將其解析為Spring IoC管理的Bean物件並將其註冊到容器中的。
XmlBeanDefinitionReader解析載入的Bean定義資原始檔
XmlBeanDefinitionReader類中的doLoadBeanDefinitions方法是從特定XML檔案中實際載入Bean定義資源的方法,該方法在載入Bean定義資源之後將其轉換為Document物件,接下來呼叫registerBeanDefinitions啟動Spring IoC容器對Bean定義的解析過程,registerBeanDefinitions方法原始碼如下:
// 按照Spring的Bean語義要求將Bean定義資源解析並轉換為容器內部資料結構
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
int countBefore = getRegistry().getBeanDefinitionCount();
// 解析過程入口,這裡使用了委派模式,具體的解析實現過程有實現類DefaultBeanDefinitionDocumentReader完成
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore; // 返回此次解析了多少個物件
}
// 建立BeanDefinitionDocumentReader物件,解析Document物件
protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() {
return BeanUtils.instantiateClass(this.documentReaderClass);
}
/**
* Create the {@link XmlReaderContext} to pass over to the document reader.
*/
public XmlReaderContext createReaderContext(Resource resource) {
return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
this.sourceExtractor, this, getNamespaceHandlerResolver());
}
Bean定義資源的載入解析分為以下兩個過程:
- 首先,通過呼叫XML解析器將Bean定義資原始檔轉換得到Document物件,但是這些Document物件並沒有按照Spring的Bean規則進行解析。這一步是載入的過程
- 其次,在完成通用的XML解析之後,按照Spring的Bean規則對Document物件進行解析。
按照Spring的Bean規則對Document物件解析的過程是在介面BeanDefinitionDocumentReader的實現類DefaultBeanDefinitionDocumentReader中實現的。
DefaultBeanDefinitionDocumentReader對Bean定義的Document物件解析
BeanDefinitionDocumentReader介面通過registerBeanDefinitions方法呼叫其實現類DefaultBeanDefinitionDocumentReader對Document物件進行解析,解析的程式碼如下:
@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
doRegisterBeanDefinitions(doc.getDocumentElement());
}
// 註冊<beans/>配置的Beans
@SuppressWarnings("deprecation") // for Environment.acceptsProfiles(String...)
protected void doRegisterBeanDefinitions(Element root) {
// Any nested <beans> elements will cause recursion in this method. In
// order to propagate and preserve <beans> default-* attributes correctly,
// keep track of the current (parent) delegate, which may be null. Create
// the new (child) delegate with a reference to the parent for fallback purposes,
// then ultimately reset this.delegate back to its original (parent) reference.
// this behavior emulates a stack of delegates without actually necessitating one.
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
if (this.delegate.isDefaultNamespace(root)) {
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
// We cannot use Profiles.of(...) since profile expressions are not supported
// in XML config. See SPR-12458 for details.
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
if (logger.isDebugEnabled()) {
logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +
"] not matching: " + getReaderContext().getResource());
}
return;
}
}
}
preProcessXml(root);
parseBeanDefinitions(root, this.delegate); // 從Document的根元素開始進行Bean定義的Document物件
postProcessXml(root);
this.delegate = parent;
}
BeanDefinitionParserDelegate解析Bean定義資原始檔生成BeanDefinition
/**
* Parse the elements at the root level in the document:
* "import", "alias", "bean".
* @param root the DOM root element of the document
*/
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(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;
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}
else {
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
// 如果元素節點是<Import>匯入元素,進行匯入解析
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
importBeanDefinitionResource(ele);
}
// 如果元素節點是<Alias>別名元素,進行別名解析
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
processAliasRegistration(ele);
}
// 如果元素節點<Bean>元素, 按照Spring的Bean規則解析元素
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
processBeanDefinition(ele, delegate);
}
// 如果元素節點<Beans>元素,即它是巢狀型別的
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// 遞迴解析
doRegisterBeanDefinitions(ele);
}
}
解析Bean生成BeanDefinitionHolder的方法
/**
* Process the given bean element, parsing the bean definition
* and registering it with the registry.
*/
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// 註冊最終的裝飾例項
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to register bean definition with name '" +
bdHolder.getBeanName() + "'", ele, ex);
}
// Send registration event.
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}
(這裡就不展開了,無非就是解析XML各種元素,來生成BeanDefinition)
解析過後的BeanDefinition在IoC容器中的註冊
Document物件的解析後得到封裝BeanDefinition的BeanDefinitionHold物件,然後呼叫BeanDefinitionReaderUtils的registerBeanDefinition方法向IoC容器註冊解析的Bean,BeanDefinitionReaderUtils的註冊的原始碼如下:
// 通過BeanDefinitionRegistry將BeanDefinitionHolder註冊到BeanFactory
public static void registerBeanDefinition(
BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException {
// Register bean definition under primary name.
String beanName = definitionHolder.getBeanName();
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
// Register aliases for bean name, if any.
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
for (String alias : aliases) {
registry.registerAlias(beanName, alias);
}
}
}
當呼叫BeanDefinitionReaderUtils向IoC容器註冊解析的BeanDefinition時,真正完成註冊功能的是DefaultListableBeanFactory。
DefaultListableBeanFactory向IoC容器註冊解析後的BeanDefinition
IOC容器本質上就是一個beanDefinitionMap, 註冊即將BeanDefinition put到map中
/** Map of bean definition objects, keyed by bean name. */
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
/** Map from bean name to merged BeanDefinitionHolder. */
private final Map<String, BeanDefinitionHolder> mergedBeanDefinitionHolders = new ConcurrentHashMap<>(256);
@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {
Assert.hasText(beanName, "Bean name must not be empty");
Assert.notNull(beanDefinition, "BeanDefinition must not be null");
if (beanDefinition instanceof AbstractBeanDefinition) {
try {
((AbstractBeanDefinition) beanDefinition).validate();
}
catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Validation of bean definition failed", ex);
}
}
BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
// 如果已經註冊
if (existingDefinition != null) {
// 檢查是否可以覆蓋
if (!isAllowBeanDefinitionOverriding()) {
throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
}
else if (existingDefinition.getRole() < beanDefinition.getRole()) {
// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
if (logger.isInfoEnabled()) {
logger.info("Overriding user-defined bean definition for bean '" + beanName +
"' with a framework-generated bean definition: replacing [" +
existingDefinition + "] with [" + beanDefinition + "]");
}
}
else if (!beanDefinition.equals(existingDefinition)) {
if (logger.isDebugEnabled()) {
logger.debug("Overriding bean definition for bean '" + beanName +
"' with a different definition: replacing [" + existingDefinition +
"] with [" + beanDefinition + "]");
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace("Overriding bean definition for bean '" + beanName +
"' with an equivalent definition: replacing [" + existingDefinition +
"] with [" + beanDefinition + "]");
}
}
// 覆蓋
this.beanDefinitionMap.put(beanName, beanDefinition);
}
else {
if (hasBeanCreationStarted()) {
// Cannot modify startup-time collection elements anymore (for stable iteration)
synchronized (this.beanDefinitionMap) {
this.beanDefinitionMap.put(beanName, beanDefinition);
List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
updatedDefinitions.addAll(this.beanDefinitionNames);
updatedDefinitions.add(beanName);
this.beanDefinitionNames = updatedDefinitions;
removeManualSingletonName(beanName);
}
}
else {
// Still in startup registration phase
this.beanDefinitionMap.put(beanName, beanDefinition);
this.beanDefinitionNames.add(beanName);
removeManualSingletonName(beanName);
}
//重置所有已經註冊過的BeanDefinition的快取
this.frozenBeanDefinitionNames = null;
}
if (existingDefinition != null || containsSingleton(beanName)) {
resetBeanDefinition(beanName);
}
else if (isConfigurationFrozen()) {
clearByTypeCache();
}
}
至此,Bean定義資原始檔中配置的Bean被解析過後,已經註冊到IoC容器中,被容器管理起來,真正完成了IoC容器初始化所做的全部工作。現 在IoC容器中已經建立了整個Bean的配置資訊,這些BeanDefinition資訊已經可以使用,並且可以被檢索,IoC容器的作用就是對這些註冊的Bean定義資訊進行處理和維護。這些的註冊的Bean定義資訊是IoC容器控制反轉的基礎,正是有了這些註冊的資料,容器才可以進行依賴注入。
總結
現在通過上面的程式碼,總結一下IOC容器初始化的基本步驟:
-
初始化的入口在容器實現中的 refresh()呼叫來完成
-
對 bean 定義載入 IOC 容器使用的方法是 loadBeanDefinition,其中的大致過程如下:
- 通過 ResourceLoader 來完成資原始檔位置的定位,DefaultResourceLoader 是預設的實現,同時上下文字身就給出了 ResourceLoader 的實現,可以從類路徑,檔案系統, URL 等方式來定為資源位置。如果是 XmlBeanFactory作為 IOC 容器,那麼需要為它指定 bean 定義的資源,也就是說 bean 定義檔案時通過抽象成 Resource 來被 IOC 容器處理的
- 通過 BeanDefinitionReader來完成定義資訊的解析和 Bean 資訊的註冊, 往往使用的是XmlBeanDefinitionReader 來解析 bean 的 xml 定義檔案 - 實際的處理過程是委託給 BeanDefinitionParserDelegate 來完成的,從而得到 bean 的定義資訊,這些資訊在 Spring 中使用 BeanDefinition 物件來表示 - 這個名字可以讓我們想到loadBeanDefinition,RegisterBeanDefinition 這些相關的方法 - 他們都是為處理 BeanDefinitin 服務的
- 容器解析得到 BeanDefinition 以後,需要把它在 IOC 容器中註冊,這由 IOC 實現 BeanDefinitionRegistry 介面來實現。註冊過程就是在 IOC 容器內部維護的一個HashMap 來儲存得到的 BeanDefinition 的過程。這個 HashMap 是 IoC 容器持有 bean 資訊的場所,以後對 bean 的操作都是圍繞這個HashMap 來實現的.
-
然後我們就可以通過 BeanFactory 和 ApplicationContext 來享受到 Spring IOC 的服務了,在使用 IOC 容器的時候,我們注意到除了少量粘合程式碼,絕大多數以正確 IoC 風格編寫的應用程式程式碼完全不用關心如何到達工廠,因為容器將把這些物件與容器管理的其他物件鉤在一起。基本的策略是把工廠放到已知的地方,最好是放在對預期使用的上下文有意義的地方,以及程式碼將實際需要訪問工廠的地方。 Spring 本身提供了對宣告式載入 web 應用程式用法的應用程式上下文,並將其儲存在ServletContext 中的框架實現。
參考文章
https://blog.csdn.net/qq_36212439/article/details/82749963
https://juejin.cn/post/6973884466171215908
https://juejin.cn/post/6844903838743265294
https://blog.csdn.net/hjing123/article/details/104867343
https://www.cnblogs.com/wl20200316/p/12522993.html
更多文章
首先, 從Spring框架的整體架構和組成對整體框架有個認知。
- Spring基礎 - Spring和Spring框架組成
- Spring是什麼?它是怎麼誕生的?有哪些主要的元件和核心功能呢? 本文通過這幾個問題幫助你構築Spring和Spring Framework的整體認知。
其次,通過案例引出Spring的核心(IoC和AOP),同時對IoC和AOP進行案例使用分析。
- Spring基礎 - Spring簡單例子引入Spring的核心
- 上文中我們簡單介紹了Spring和Spring Framework的元件,那麼這些Spring Framework元件是如何配合工作的呢?本文主要承接上文,向你展示Spring Framework元件的典型應用場景和基於這個場景設計出的簡單案例,並以此引出Spring的核心要點,比如IOC和AOP等;在此基礎上還引入了不同的配置方式, 如XML,Java配置和註解方式的差異。
- Spring基礎 - Spring核心之控制反轉(IOC)
- 在Spring基礎 - Spring簡單例子引入Spring的核心中向你展示了IoC的基礎含義,同時以此發散了一些IoC相關知識點; 本節將在此基礎上進一步解讀IOC的含義以及IOC的使用方式
- Spring基礎 - Spring核心之面向切面程式設計(AOP)
- 在Spring基礎 - Spring簡單例子引入Spring的核心中向你展示了AOP的基礎含義,同時以此發散了一些AOP相關知識點; 本節將在此基礎上進一步解讀AOP的含義以及AOP的使用方式。
基於Spring框架和IOC,AOP的基礎,為構建上層web應用,需要進一步學習SpringMVC。
- Spring基礎 - SpringMVC請求流程和案例
- 前文我們介紹了Spring框架和Spring框架中最為重要的兩個技術點(IOC和AOP),那我們如何更好的構建上層的應用呢(比如web 應用),這便是SpringMVC;Spring MVC是Spring在Spring Container Core和AOP等技術基礎上,遵循上述Web MVC的規範推出的web開發框架,目的是為了簡化Java棧的web開發。 本文主要介紹SpringMVC的請求流程和基礎案例的編寫和執行。
Spring進階 - IoC,AOP以及SpringMVC的原始碼分析
- Spring進階 - Spring IOC實現原理詳解之IOC體系結構設計
- 在對IoC有了初步的認知後,我們開始對IOC的實現原理進行深入理解。本文將幫助你站在設計者的角度去看IOC最頂層的結構設計
- Spring進階 - Spring IOC實現原理詳解之IOC初始化流程
- 上文,我們看了IOC設計要點和設計結構;緊接著這篇,我們可以看下原始碼的實現了:Spring如何實現將資源配置(以xml配置為例)通過載入,解析,生成BeanDefination並註冊到IoC容器中的
- Spring進階 - Spring IOC實現原理詳解之Bean例項化(生命週期,迴圈依賴等)
- 上文,我們看了IOC設計要點和設計結構;以及Spring如何實現將資源配置(以xml配置為例)通過載入,解析,生成BeanDefination並註冊到IoC容器中的;容器中存放的是Bean的定義即BeanDefinition放到beanDefinitionMap中,本質上是一個
ConcurrentHashMap<String, Object>
;並且BeanDefinition介面中包含了這個類的Class資訊以及是否是單例等。那麼如何從BeanDefinition中例項化Bean物件呢,這是本文主要研究的內容?
- 上文,我們看了IOC設計要點和設計結構;以及Spring如何實現將資源配置(以xml配置為例)通過載入,解析,生成BeanDefination並註冊到IoC容器中的;容器中存放的是Bean的定義即BeanDefinition放到beanDefinitionMap中,本質上是一個
- Spring進階 - Spring AOP實現原理詳解之切面實現
- 前文,我們分析了Spring IOC的初始化過程和Bean的生命週期等,而Spring AOP也是基於IOC的Bean載入來實現的。本文主要介紹Spring AOP原理解析的切面實現過程(將切面類的所有切面方法根據使用的註解生成對應Advice,並將Advice連同切入點匹配器和切面類等資訊一併封裝到Advisor,為後續交給代理增強實現做準備的過程)。
- Spring進階 - Spring AOP實現原理詳解之AOP代理
- 上文我們介紹了Spring AOP原理解析的切面實現過程(將切面類的所有切面方法根據使用的註解生成對應Advice,並將Advice連同切入點匹配器和切面類等資訊一併封裝到Advisor)。本文在此基礎上繼續介紹,代理(cglib代理和JDK代理)的實現過程。
- Spring進階 - Spring AOP實現原理詳解之Cglib代理實現
- 我們在前文中已經介紹了SpringAOP的切面實現和建立動態代理的過程,那麼動態代理是如何工作的呢?本文主要介紹Cglib動態代理的案例和SpringAOP實現的原理。
- Spring進階 - Spring AOP實現原理詳解之JDK代理實現
- 上文我們學習了SpringAOP Cglib動態代理的實現,本文主要是SpringAOP JDK動態代理的案例和實現部分。
- Spring進階 - SpringMVC實現原理之DispatcherServlet初始化的過程
- 前文我們有了IOC的原始碼基礎以及SpringMVC的基礎,我們便可以進一步深入理解SpringMVC主要實現原理,包含DispatcherServlet的初始化過程和DispatcherServlet處理請求的過程的原始碼解析。本文是第一篇:DispatcherServlet的初始化過程的原始碼解析。
- Spring進階 - SpringMVC實現原理之DispatcherServlet處理請求的過程
- 前文我們有了IOC的原始碼基礎以及SpringMVC的基礎,我們便可以進一步深入理解SpringMVC主要實現原理,包含DispatcherServlet的初始化過程和DispatcherServlet處理請求的過程的原始碼解析。本文是第二篇:DispatcherServlet處理請求的過程的原始碼解析。