Spring建立 BeanFactory 的方式
按照Bean
的配置方式手動建立可以分為兩種:
-
使用
XMl
配置的Bean
這種方式使用xml
配置檔案配置Bean
的資訊並且設定掃描的路徑,掃描到的包可以使用註解進行配置Bean
資訊,一般來說手動建立BeanFactory
容器的實現類為ClassPathXmlApplicationContext
和SystemFileXmlApplicationContext
,設定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
,不過在建立的過程中可能會預設初始化一些屬性,比如:allowBeanDefinitionOverriding
和allowCircularReferences
允許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
放在beanDefinitionMap
和beanDefinitionNames
集合中,最終完成了BeanDefinition
的載入過程。
現在開發基本都是使用Spring Boot
,是全註解方式,這種BeanDefinition
的載入實際上就是指定了一個包的掃描,然後掃描這些包下標記了@Configuration、@Component、@Service、@Controller
等註解的類。感興趣的可以去看下AnnotationConfigApplicationContext
這個類是如何掃描的。
這就是Spring BeanFactory
的建立過程,並且包括了BeanDefinition
的載入過程,接下來我們進行自定義標籤,讓spring
進行解析。