Spring 原始碼(3)Spring BeanFactory 是怎麼建立的?

玲丶蹊發表於2022-04-19

Spring建立 BeanFactory 的方式

按照Bean的配置方式手動建立可以分為兩種:

  • 使用XMl配置的Bean
    這種方式使用xml配置檔案配置Bean的資訊並且設定掃描的路徑,掃描到的包可以使用註解進行配置Bean資訊,一般來說手動建立BeanFactory容器的實現類為ClassPathXmlApplicationContextSystemFileXmlApplicationContext,設定xml的路徑即可建立出IOC容器。

    例如:

    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring-test.xml");
    User user = context.getBean(User.class);
    
  • 使用註解配置的Bean

    這種方式不使用xml配置檔案,全部基於註解方式配置Bean的資訊,比如使用@Component@Configuration進行Bean的配置,實現類為AnnotationConfigApplicationContext 設定掃描的包,然後呼叫refresh方法進行IOC容器的建立。

    例如:

    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    context.scan("com.redwinter.test");
    context.refresh();
    

但是一般來說開發中都是使用web容器進行IOC容器的建立的,比如tomcat容器、jetty容器、undertow容器、netty容器,在Spring中有一個BeanFactory的實現類:GenericApplicationContext,他的子類有一個叫GenericWebApplicationContext,在Spring Boot中,就是通過實現這個類完成Web容器的建立+IOC容器的建立的。在Spring Boot中有個類叫ServletWebServerApplicationContext就是繼承了GenericWebApplicationContext這個類,然後ServletWebServerApplicationContext中有個屬性叫webServer,這個是一個介面,這個介面對應的實現就是Web容器的實現:

public class ServletWebServerApplicationContext extends GenericWebApplicationContext
		implements ConfigurableWebServerApplicationContext {
	public static final String DISPATCHER_SERVLET_NAME = "dispatcherServlet";
	// web 容器,實現類有TomcatWebServer、JettyWebServer、NettyWebServer、UndertowWebServer
	private volatile WebServer webServer;
	// .... 去掉其他程式碼
	}

本文介紹使用XML配置檔案手動建立IOC容器的方式

Spring 使用Xml啟動IOC容器

根據上一篇文章 https://www.cnblogs.com/redwinter/p/16151489.htmlSpring Bean IOC 的建立流程種的第一個方法AbstractApplicationContext#prepareRefresh前戲準備工作繼續解讀AbstractApplicationContext#refresh方法中的第二方法 AbstractApplicationContext#obtainFreshBeanFactory獲取BeanFactory,這個方法會建立一個DefaultListableBeanFactory 預設的可列出Bean的工廠。

AbstractApplicationContext#obtainFreshBeanFactory中主要是重新整理BeanFactory,原始碼如下:

@Override
protected final void refreshBeanFactory() throws BeansException {
  // 如果有BeanFactory 就銷燬掉並關閉
  if (hasBeanFactory()) {
    destroyBeans();
    closeBeanFactory();
  }
  try {
    // 直接new一個BeanFactory 實現出來 DefaultListableBeanFactory
    DefaultListableBeanFactory beanFactory = createBeanFactory();
    // 根據上一步建立BeanFactory建立的Id進行獲取
    beanFactory.setSerializationId(getId());
    // 定製化BanFactory ,比如設定allowBeanDefinitionOverriding 和allowCircularReferences 的屬性
    customizeBeanFactory(beanFactory);
    // 載入BeanDefinitions 從xml 和註解定義的Bean
    // 從configLocations -> String[] -> String -> Resource[] -> Resource -> InputStream -> Document -> 解析成一個一個的BeanDefinition 物件
    loadBeanDefinitions(beanFactory);
    this.beanFactory = beanFactory;
  }
  catch (IOException ex) {
    throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
  }
}
  • 首先判斷是否已經有BeanFactory了,如果有就銷燬掉並且關閉工廠
  • 直接建立一個BeanFactory,預設就是使用new DefaultListableBeanFactory,不過在建立的過程中可能會預設初始化一些屬性,比如:allowBeanDefinitionOverridingallowCircularReferences 允許Bean覆蓋和解決迴圈依賴的問題,還有就是BeanFactory的序列化id等屬性。
  • 設定序列化id
  • 定製BeanFactory,這裡是一個擴充套件點,你可以對BeanFactory進行定製
  • 載入BeanDefinition,這裡從XML配置檔案中去載入,這裡面的邏輯非常的複雜繁瑣
  • 將建立的BeanFactory設定出去

定製個性化的BeanFactory

customizeBeanFactory(beanFactory);這個方法中,spring設定了兩個屬性,一個是設定是否可以覆蓋Bean,一個是否允許迴圈依賴,原始碼如下:

protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
  // 可以定製設定是否允許Bean覆蓋
  if (this.allowBeanDefinitionOverriding != null) {
    beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
  }
  // 可以定製設定是否允許迴圈依賴
  if (this.allowCircularReferences != null) {
    beanFactory.setAllowCircularReferences(this.allowCircularReferences);
  }
}

spring提供了這個擴充套件點,那麼我們就可以定製BeanFactory,比如我們新建一個類繼承ClassPathXmlApplicationContext,然後重寫customizeBeanFactory這個方法:

/**
 * @author <a href="https://www.cnblogs.com/redwinter/">redwinter</a>
 * @since 1.0
 **/
public class MyClassPathXmlApplicationContext extends ClassPathXmlApplicationContext {

	public MyClassPathXmlApplicationContext(String... configLocation) throws BeansException {
		super(configLocation);
	}

	@Override
	protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
		// 擴充套件點 設定不去處理迴圈依賴或者beanDefinition覆蓋
		super.setAllowBeanDefinitionOverriding(true);
         // 設定不允許迴圈依賴
		super.setAllowCircularReferences(false);
        // 呼叫父類的方法
		super.customizeBeanFactory(beanFactory);
	}

}

建立兩個類,並且設定為迴圈依賴:

/**
 * @author <a href="https://www.cnblogs.com/redwinter/">redwinter</a>
 * @since 1.0
 **/
@Service
public class PersonService {

	@Autowired
	private UserService userService;

	public void test() {
		System.out.println(userService);
	}
}

/**
 * @author <a href="https://www.cnblogs.com/redwinter/">redwinter</a>
 * @since 1.0
 **/
@Service
public class UserService {
	@Autowired
	private PersonService personService;

	public void test(){
		System.out.println(personService);
	}
}

建立之後然後使用自定義的MyClassPathXmlApplicationContext類進行啟動:

/**
 * @author <a href="https://www.cnblogs.com/redwinter/">redwinter</a>
 * @since 1.0
 **/
public class BeanCreate {

  @Test
  public void classPathXml() {
    //		ClassPathXmlApplicationContext context = new     ClassPathXmlApplicationContext("classpath:spring-test.xml");
    ClassPathXmlApplicationContext context = new                       MyClassPathXmlApplicationContext("classpath:spring-test.xml");
    UserService userService = context.getBean(UserService.class);
	userService.test();
  }
}

啟動之後發現報錯了:

四月 19, 2022 1:26:55 下午 org.springframework.context.support.AbstractApplicationContext refresh
警告: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'personService': Unsatisfied dependency expressed through field 'userService'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userService': Unsatisfied dependency expressed through field 'personService'; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'personService': Requested bean is currently in creation: Is there an unresolvable circular reference?

如果設定為true,那麼啟動不會報錯了並且輸出了:

com.redwinter.test.service.PersonService@6fc6f14e

BeanDefinition 的載入

在重新整理BeanFactory的方法中,有個方法叫loadBeanDefinitions,這個方法就是進行BeanDefinition的載入的,他的大致流程是這樣的:

BeanDefinition載入的過程中,有個關鍵點可以讓我們自定義標籤進行BeanDefinition的載入和解析,在設定解析器的時候,Spring是這樣設定解析器的:

public DelegatingEntityResolver(@Nullable ClassLoader classLoader) {
		// 建立dtd解析器
		this.dtdResolver = new BeansDtdResolver();
		// 建立schema 解析器
		// 在Debug的時候,這裡會呼叫toString方法,然後去呼叫getSchemaMappings 方法,將schemaMappings 設定屬性進去
		this.schemaResolver = new PluggableSchemaResolver(classLoader);
	}

Spring中一般解析XML檔案的時候都是從網上下載對應的標籤解析,比如Spring配置檔案中的https://www.springframework.org/schema/beans/spring-beans-3.1.xsd ,但是一般來說都是不需要進行下載的,Spring提供了本地檔案的xsd檔案,這些xsd檔案就配置在META-INF/spring.schemas檔案中進行配置,由於檔案中內容比較多我就不復製出來了。

Spring進行xml解析之前會建立一個namespace的處理器的解析器:

public NamespaceHandlerResolver getNamespaceHandlerResolver() {
  if (this.namespaceHandlerResolver == null) {
    // 建立預設的namespace處理器解析器,載入spring.handlers中配置的處理器
    this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver();
  }
  return this.namespaceHandlerResolver;
}

這裡建立的namespace處理器就是放在META-INF/spring.handlers檔案中,比如util標籤、context標籤的都是在這個檔案中配置的處理器,對於util標籤的namespace處理器如下:

public class UtilNamespaceHandler extends NamespaceHandlerSupport {

	private static final String SCOPE_ATTRIBUTE = "scope";

	@Override
	public void init() {
		// 註冊constant標籤的解析器
		registerBeanDefinitionParser("constant", new ConstantBeanDefinitionParser());
		// 註冊property-path標籤的解析器
		registerBeanDefinitionParser("property-path", new PropertyPathBeanDefinitionParser());
		// 註冊list標籤的解析器
		registerBeanDefinitionParser("list", new ListBeanDefinitionParser());
		// 註冊set標籤的解析器
		registerBeanDefinitionParser("set", new SetBeanDefinitionParser());
		// 註冊map標籤的解析器
		registerBeanDefinitionParser("map", new MapBeanDefinitionParser());
		// 註冊properties標籤的解析器
		registerBeanDefinitionParser("properties", new PropertiesBeanDefinitionParser());
	}
  // ....省略其他程式碼
}

這些處理器載入完之後就會進行BeanDefinition的解析:

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 {
          // 定製的namespace標籤
          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節點
  else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
    processBeanDefinition(ele, delegate);
  }
  // 解析beans節點
  else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
    // recurse
    doRegisterBeanDefinitions(ele);
  }
}

解析完之後就會呼叫註冊,將解析到的BeanDefinition放在beanDefinitionMapbeanDefinitionNames集合中,最終完成了BeanDefinition的載入過程。

現在開發基本都是使用Spring Boot,是全註解方式,這種BeanDefinition的載入實際上就是指定了一個包的掃描,然後掃描這些包下標記了@Configuration、@Component、@Service、@Controller等註解的類。感興趣的可以去看下AnnotationConfigApplicationContext這個類是如何掃描的。

這就是Spring BeanFactory的建立過程,並且包括了BeanDefinition的載入過程,接下來我們進行自定義標籤,讓spring進行解析。

相關文章