Spring4.x原始碼解析:IOC容器底層原理解析
引包
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.3.7.RELEASE</version>
</dependency>
IOC簡單介紹
1、作用
Spring IOC 主要負責建立物件,解決物件之間的依賴,並且管理這些物件的整個生命週期。
2、體系結構
IOC容器是由三部分組成
- BeanDefinition(Bean定義)
- BeanDefinition解析器
- Bean管理
BeanDefinition
BeanDefinition:org.springframework.beans.factory.config.BeanDefinition
XML配置檔案裡的一個標籤(這裡就以<bean>標籤為例)對應一個BeanDefinition
<bean id="" name="" class="" init-method="" scope="" lazy-init="default" destroy-method="" >
<property name= "" ref=""></property>
</bean>
1、常用的屬性
- id:在IOC容器唯一標識,如果id重複,後面id的bean會覆蓋前面id的bean。
- class:bean的class路徑
- init-method:初始化方法,建立Bean後呼叫
- scope:物件作用域,singleton(單例)、prototype(多例)。預設singleton
- lazy-init:是否懶載入,使用該Bean的使用在初始化。false(否)、true(是),預設false
- destroy-method:銷燬方法,銷燬Bean後呼叫。
- property:需要注入的屬性
- name:bean的alias;如果沒有配置id,那麼id = name;如果id存在,那麼name只是一個alias;
2、儲存方式
- xml
BeanDefinition解析器
BeanDefinitionReader:org.springframework.beans.factory.support.BeanDefinitionReader
當我們在xml定義了許多的<bean>標籤,需要一個工具去統一解析這些<bean>標籤,把這些<bean>標籤轉換成BeanDefinition,那麼這就是BeanDefinitionReader的工作
BeanDefinitionReader主要處理流程:
- 載入xml檔案
- 把xml檔案轉換成Document
- 解析Document成BeanDefinition
- 把BeanDefinition註冊到BeanDefinitionRegistry
Bean管理
當我們通過BeanDefinitionReader把所有的配置解析成一系列的BeanDefinition並且註冊到BeanDefinitionRegistry,那麼就需要一個容器,通過這些BeanDefinition去生成Bean,並且對Bean管理,那麼就是BeanFactory。
Spring我們常用的是DefaultListableBeanFactory:org.springframework.beans.factory.support.DefaultListableBeanFactory
主要作用
- Bean建立
- Bean儲存
- Bean獲取
- 自動完成依賴檢測與注入
- 獲取Bean的BeanDefinition
- 自動生成Bean
IOC簡單使用
這裡將會通過比較原始的2個Demo,去使用IOC,而不是使用ClassPathXmlApplicationContext
不使用XML配置建立Bean
public class BeanDefinitionReaderDemo {
@Test
public void testIOC(){
// 建立BeanFactory
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
// 建立BeanDefinition
RootBeanDefinition beanDefinition = new RootBeanDefinition(IOCTestBean.class);
// 設定物件域為單例
beanDefinition.setScope(BeanDefinition.SCOPE_SINGLETON);
// 把BeanDefinition註冊到BeanFactory
beanFactory.registerBeanDefinition("iocTest", beanDefinition);
// 從BeanFactory獲取Bean
IOCTestBean iocTestBean = beanFactory.getBean("iocTest",IOCTestBean.class);
iocTestBean.createBean();
}
}
class IOCTestBean{
public void createBean(){
System.out.println("IOCTestBean.createBean()");
}
}
控制檯輸出:
使用XML配置建立Bean
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="iocTest" class="com.dk.spring.IOCTestBean" />
</beans>
public class BeanDefinitionReaderDemo {
@Test
public void testIOC(){
// 建立BeanFactory
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
// 建立BeanDefinitionReader
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// 讀取xml配置檔案
beanDefinitionReader.loadBeanDefinitions("classpath:applicationContext.xml");
// 從BeanFactory獲取Bean
IOCTestBean iocTestBean = beanFactory.getBean("iocTest",IOCTestBean.class);
iocTestBean.createBean();
}
}
class IOCTestBean{
public void createBean(){
System.out.println("IOCTestBean.createBean()");
}
}
控制檯輸出:
使用XML配置建立Bean的原始碼解析
1、BeanDefinitionReader載入XML配置檔案,並且把BeanDefinition註冊到BeanFactory
// 建立BeanFactory
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
// 建立BeanDefinitionReader
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// 讀取xml配置檔案
beanDefinitionReader.loadBeanDefinitions("classpath:applicationContext.xml");
1、XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {
super(registry);
}
protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) {
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
this.registry = registry;
// Determine ResourceLoader to use.
if (this.registry instanceof ResourceLoader) {
this.resourceLoader = (ResourceLoader) this.registry;
}
else {
this.resourceLoader = new PathMatchingResourcePatternResolver();
}
// Inherit Environment if possible
if (this.registry instanceof EnvironmentCapable) {
this.environment = ((EnvironmentCapable) this.registry).getEnvironment();
}
else {
this.environment = new StandardEnvironment();
}
}
建立了兩個物件,分別為
- PathMatchingResourcePatternResolver,路徑解析器,主要用於把xml配置的路徑解析成Resource物件。
- StandardEnvironment,配置環境物件,主要用於獲取JDK環境配置、系統環境配置;
2、XmlBeanDefinitionReader.loadBeanDefinitions(“classpath:applicationContext.xml”);
AbstractBeanDefinitionReader.java
@Override
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
return loadBeanDefinitions(location, null);
}
AbstractBeanDefinitionReader.java
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 {
// Can only load single resources by absolute 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;
}
}
把路徑location通過路徑解析器解析成Resouce物件,然後繼續呼叫loadBeanDefinitions(resources)
AbstractBeanDefinitionReader.java
@Override
public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
Assert.notNull(resources, "Resource array must not be null");
int counter = 0;
for (Resource resource : resources) {
counter += loadBeanDefinitions(resource);
}
return counter;
}
XmlBeanDefinitionReader.java
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));
}
把Resource轉換成帶有編碼的EncodedResource物件
XmlBeanDefinitionReader.java
private final ThreadLocal<Set<EncodedResource>> resourcesCurrentlyBeingLoaded =
new NamedThreadLocal<Set<EncodedResource>>("XML bean definition resources currently being loaded");
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (logger.isInfoEnabled()) {
logger.info("Loading XML bean definitions from " + encodedResource.getResource());
}
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (currentResources == null) {
currentResources = new HashSet<EncodedResource>(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
try {
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
finally {
inputStream.close();
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"IOException parsing XML document from " + encodedResource.getResource(), ex);
}
finally {
currentResources.remove(encodedResource);
if (currentResources.isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
}
我們可以把上述拆分為幾個程式碼塊,然後依次分析
- 獲取當前執行緒(ThreadLocal)正在載入resource的Set集合,如果當前resource在Set集合已經存在,那麼丟擲異常,說明當前resource正在被該執行緒載入;如果不存在,那麼加入Set集合,標記當前執行緒開始載入resource;
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (currentResources == null) {
currentResources = new HashSet<EncodedResource>(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
- 獲取該配置檔案的輸入流Inputstream物件,並且把它包裝成InputSource準備進行Java Dom XML解析,然後呼叫doLoadBeanDefinitions(inputSource, encodedResource.getResource());
try {
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
finally {
inputStream.close();
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"IOException parsing XML document from " + encodedResource.getResource(), ex);
}
- 最後全部完成,把當前resource成Set集合移除
currentResources.remove(encodedResource);
if (currentResources.isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
我們繼續看doLoadBeanDefinitions(inputSource, encodedResource.getResource())
XmlBeanDefinitionReader.java
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
Document doc = doLoadDocument(inputSource, resource);
return registerBeanDefinitions(doc, resource);
}
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);
}
}
- 呼叫doLoadDocument(inputSource, resource)把inputSource通過JDK自帶的Dom技術,解析成Document文件樹 這裡為什麼使用Java自帶的Dom解析而不用Sax、Dom4J等一些開源的強大解析技術?我猜大概是使用Java自帶的,不用去解決一些相容問題,並且Spring配置檔案小的原因。
- registerBeanDefinitions(doc, resource);把Document的每個Element解析成BeanDefinition,並且把BeanDefinition注入到BeanFactory
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
int countBefore = getRegistry().getBeanDefinitionCount();
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}
- createBeanDefinitionDocumentReader(),建立一個用於解析Document的解析器
- createReaderContext(resource),把Resource包裝成XmlReaderContext物件,該物件具有XML NameSpace解析功能,並且具有XmlBeanDefinitionReader的引用。
- documentReader.registerBeanDefinitions(doc, createReaderContext(resource));解析Docuemnt
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
Element root = doc.getDocumentElement();
doRegisterBeanDefinitions(root);
}
- Element root = doc.getDocumentElement();獲取Document的根元素
- doRegisterBeanDefinitions(root);從根元素開始解析XML配置檔案,並且註冊BeanDefinition
protected void doRegisterBeanDefinitions(Element root) {
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);
if (!getReaderContext().getEnvironment().acceptsProfiles<