ClassPathXmlApplicationContext源码分析

master
xuchengsheng 2023-11-23 17:50:38 +08:00
parent 0eb8e4e310
commit ab20e17e64
12 changed files with 305 additions and 65 deletions

View File

@ -28,6 +28,7 @@
<module>spring-resources</module>
<module>spring-metadata</module>
<module>spring-beans</module>
<module>spring-context</module>
</modules>
<dependencies>

View File

@ -21,7 +21,6 @@
<module>spring-bean-groovyBeanDefinitionReader</module>
<module>spring-bean-annotatedBeanDefinitionReader</module>
<module>spring-bean-classPathBeanDefinitionScanner</module>
<module>spring-bean-importBeanDefinitionRegistrar</module>
</modules>
</project>

View File

@ -1,18 +0,0 @@
package com.xcs.spring;
import com.xcs.spring.bean.MyBean;
import com.xcs.spring.config.MyConfiguration;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* @author xcs
* @date 20231117 1448
**/
public class ImportBeanDefinitionRegistrarDemo {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class);
MyBean bean = context.getBean(MyBean.class);
System.out.println("bean = " + bean);
}
}

View File

@ -1,8 +0,0 @@
package com.xcs.spring.bean;
/**
* @author xcs
* @date 20231117 1453
**/
public class MyBean {
}

View File

@ -1,13 +0,0 @@
package com.xcs.spring.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/**
* @author xcs
* @date 20231117 1452
**/
@Configuration
@Import(MyImportBeanDefinitionRegistrar.class)
public class MyConfiguration {
}

View File

@ -1,23 +0,0 @@
package com.xcs.spring.config;
import com.xcs.spring.bean.MyBean;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
/**
* @author xcs
* @date 20231117 1452
**/
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 注册一个名为 "myBean" 的简单Bean
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MyBean.class);
GenericBeanDefinition definition = (GenericBeanDefinition) builder.getBeanDefinition();
registry.registerBeanDefinition("myBean", definition);
}
}

18
spring-context/pom.xml Normal file
View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-reading</artifactId>
<groupId>com.xcs.spring</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-context</artifactId>
<packaging>pom</packaging>
<modules>
<module>spring-context-classPathXmlApplicationContext</module>
</modules>
</project>

View File

@ -0,0 +1,240 @@
## AbstractApplicationContext
### 一、基本信息
✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址** - [github](https://github.com/xuchengsheng/spring-reading)
### 二、知识储备
1. **XmlBeanDefinitionReader**
+ [XmlBeanDefinitionReader](https://github.com/xuchengsheng/spring-reading/blob/master/spring-beans/spring-bean-xmlBeanDefinitionReader/README.md)是Spring Framework中的一个类用于加载和解析XML格式的Bean定义配置文件将配置文件中定义的Bean元数据信息提取为Spring容器内部的Bean定义对象进而实现IOC容器的构建和管理。这类负责读取XML配置文件解析Bean的定义信息包括ID、类名、属性、依赖等并将这些定义注册到Spring应用程序上下文使我们能够方便地配置和管理应用程序中的各种Bean组件。
### 三、基本描述
`ClassPathXmlApplicationContext` 是 Spring 框架中用于从类路径classpath加载 XML 配置文件并初始化 Spring 容器的一种方式。它是 `ApplicationContext` 接口的实现类之一,负责读取配置文件,解析配置信息,然后创建和管理 Spring 容器中的 bean 实例。
### 四、主要功能
1. **加载配置文件**
+ 主要功能是加载指定的 XML 配置文件该配置文件包含了应用程序中各个组件bean的定义、依赖关系、配置信息等。
2. **容器初始化**
+ `ClassPathXmlApplicationContext` 在被实例化时,会读取并解析配置文件,然后初始化 Spring 容器。这个过程包括创建和管理 bean 实例、解决 bean 之间的依赖关系等。
3. **获取 bean 实例**
+ 通过容器的 `getBean` 方法,可以从容器中获取在配置文件中定义的 bean 实例。
4. **IoC控制反转**
+ `ClassPathXmlApplicationContext` 是 IoC 容器的一种实现,它负责管理和控制组件的生命周期。在容器初始化时,会根据配置文件中的信息实例化和装配 bean而不是由应用程序代码直接创建对象。
5. **依赖注入**
+ 容器通过读取配置文件中的信息,自动解决 bean 之间的依赖关系。这意味着在配置文件中声明的 bean 可以通过属性注入或构造函数注入的方式获取其依赖的其他 bean。
6. **AOP面向切面编程**
+ `ClassPathXmlApplicationContext` 支持通过配置文件定义切面和通知,实现横切关注点的分离,使得应用程序的关注点更加清晰和模块化。
7. **事件传播**
+ Spring 容器支持事件机制,`ClassPathXmlApplicationContext` 可以发布应用程序中发生的事件,以便其他组件能够监听并作出相应的响应。
### 五、最佳实践
通过 `ClassPathXmlApplicationContext` 构造方法创建 Spring 容器的实例,加载类路径下的 "beans.xml" 配置文件。使用容器的 `getBean` 方法,通过指定 bean 的类型(`MyBean.class`)获取在配置文件中定义的 bean 实例,并将其打印出来。
```java
public class ClassPathXmlApplicationContextDemo {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:beans.xml");
System.out.println("MyBean = " + context.getBean(MyBean.class));
}
}
```
加载类路径下名为 "`classpath:beans.xml`" 的资源文件的内容。在我们的示例配置文件中,这个资源文件定义了一个名为 "`myBean`" 的 Spring Bean该 Bean 具有一个属性 "message",其值设置为 "Hello World"。
```xml
<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="myBean" class="com.xcs.spring.bean.MyBean">
<property name="message" value="Hello World"/>
</bean>
</beans>
```
`MyBean` 的Java类代表了一个简单的Java Bean。
```java
public class MyBean {
private String message;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
```
### 六、时序图
~~~mermaid
sequenceDiagram
Title: @ComponentScan Annotation Sequence Diagram
ClassPathXmlApplicationContextDemo->>ClassPathXmlApplicationContext:ClassPathXmlApplicationContext(configLocation)
Note over ClassPathXmlApplicationContextDemo,ClassPathXmlApplicationContext: 创建ClassPathXmlApplicationContext实例
ClassPathXmlApplicationContext->>ClassPathXmlApplicationContext:ClassPathXmlApplicationContext(configLocations,refresh,parent)
Note over ClassPathXmlApplicationContext: 通过多个配置文件路径、刷新标志和父上下文构造实例
ClassPathXmlApplicationContext->>AbstractApplicationContext:refresh()
Note over ClassPathXmlApplicationContext,AbstractApplicationContext: 刷新应用程序上下文
AbstractApplicationContext->>AbstractApplicationContext:obtainFreshBeanFactory()
Note over AbstractApplicationContext: 获取刷新过的bean工厂
AbstractApplicationContext->>AbstractRefreshableApplicationContext:refreshBeanFactory()
Note over AbstractApplicationContext,AbstractRefreshableApplicationContext: 调用子类的refreshBeanFactory()
AbstractRefreshableApplicationContext->>AbstractXmlApplicationContext:loadBeanDefinitions(beanFactory)
Note over AbstractRefreshableApplicationContext,AbstractXmlApplicationContext: 调用子类的loadBeanDefinitions方法
AbstractXmlApplicationContext->>XmlBeanDefinitionReader:new XmlBeanDefinitionReader(beanFactory)
Note over AbstractXmlApplicationContext,XmlBeanDefinitionReader: 创建XmlBeanDefinitionReader实例
XmlBeanDefinitionReader->>AbstractXmlApplicationContext:返回Bean定义读取器
Note over XmlBeanDefinitionReader,AbstractXmlApplicationContext: 返回Bean定义读取器
AbstractXmlApplicationContext->>AbstractXmlApplicationContext:loadBeanDefinitions(reader)
Note over AbstractXmlApplicationContext: 调用XmlBeanDefinitionReader的loadBeanDefinitions方法
AbstractXmlApplicationContext->>XmlBeanDefinitionReader:reader.loadBeanDefinitions(configLocations)
Note over AbstractXmlApplicationContext,XmlBeanDefinitionReader: 加载配置文件中的Bean定义
AbstractApplicationContext->>AbstractRefreshableApplicationContext:getBeanFactory()
Note over AbstractApplicationContext,AbstractRefreshableApplicationContext: 获取Bean工厂
AbstractRefreshableApplicationContext->>AbstractApplicationContext:返回Bean工厂
Note over AbstractApplicationContext: 返回Bean工厂
~~~
### 七、源码分析
在`org.springframework.context.support.ClassPathXmlApplicationContext#ClassPathXmlApplicationContext(configLocation)`方法中,又调用了另外一个构造方法。
```java
public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
this(new String[] {configLocation}, true, null);
}
```
在`org.springframework.context.support.ClassPathXmlApplicationContext#ClassPathXmlApplicationContext(configLocations, refresh,parent)`方法中,首先设置了父应用程序上下文和配置文件位置,然后根据是否需要刷新,决定是否立即执行应用程序上下文的刷新操作。
```java
public ClassPathXmlApplicationContext(
String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
```
在`org.springframework.context.support.AbstractApplicationContext#refresh`方法中,调用 `obtainFreshBeanFactory` 方法来刷新内部的 bean 工厂,从而触发容器的初始化过程。
```java
@Override
public void refresh() throws BeansException, IllegalStateException {
// ... [代码部分省略以简化]
// 该方法由子类实现,用于刷新内部的 bean 工厂。
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// ... [代码部分省略以简化]
}
```
在`org.springframework.context.support.AbstractApplicationContext#obtainFreshBeanFactory`方法中,首先调用 `refreshBeanFactory` 方法来刷新内部的 bean 工厂,并最终返回这个刷新过的 bean 工厂。
```java
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
refreshBeanFactory();
return getBeanFactory();
}
```
在`org.springframework.context.support.AbstractRefreshableApplicationContext#refreshBeanFactory`方法中,首先检查是否已存在 bean 工厂,如果存在则先销毁已有的 bean 并关闭 bean 工厂。接着,创建一个新的 `DefaultListableBeanFactory` 实例,设置其序列化 ID 为容器的 ID然后调用 `customizeBeanFactory` 定制 bean 工厂。随后,通过 `loadBeanDefinitions` 加载 bean 的定义,包括读取配置文件、解析 bean 的定义等。
```java
@Override
protected final void refreshBeanFactory() throws BeansException {
// 如果已经存在 bean 工厂,则销毁已有的 bean 并关闭 bean 工厂
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
// 创建一个新的 DefaultListableBeanFactory 实例
DefaultListableBeanFactory beanFactory = createBeanFactory();
// 设置 bean 工厂的序列化 ID 为容器的 ID
beanFactory.setSerializationId(getId());
// 定制 bean 工厂,由子类实现
customizeBeanFactory(beanFactory);
// 加载 bean 的定义,包括读取配置文件、解析 bean 的定义等
loadBeanDefinitions(beanFactory);
// 将创建好的 bean 工厂赋值给容器
this.beanFactory = beanFactory;
} catch (IOException ex) {
// 如果在解析 bean 定义时发生 I/O 错误,则抛出 ApplicationContextException 异常
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
```
在`org.springframework.context.support.AbstractXmlApplicationContext#loadBeanDefinitions(beanFactory)`方法中,首先创建了一个 `XmlBeanDefinitionReader` 对象,用于读取 XML 格式的 bean 定义。然后,配置了 bean 定义阅读器的环境、资源加载器和实体解析器等属性。接着,允许子类通过 `initBeanDefinitionReader` 提供自定义的初始化操作。最后,调用阅读器的加载方法,实际上读取配置文件,解析 bean 定义,将它们注册到 bean 工厂中。
```java
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// 为给定的 BeanFactory 创建一个新的 XmlBeanDefinitionReader
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// 使用该上下文的资源加载环境配置 bean 定义阅读器
beanDefinitionReader.setEnvironment(this.getEnvironment());
// 将资源加载器设置为当前应用程序上下文
beanDefinitionReader.setResourceLoader(this);
// 设置实体解析器为 ResourceEntityResolver用于解析 bean 定义中的实体引用
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
// 允许子类提供对阅读器的自定义初始化,然后继续实际加载 bean 定义
initBeanDefinitionReader(beanDefinitionReader);
// 调用阅读器的加载方法,实际上会读取配置文件,解析 bean 定义,将它们注册到 bean 工厂中
loadBeanDefinitions(beanDefinitionReader);
}
```
在`org.springframework.context.support.AbstractXmlApplicationContext#loadBeanDefinitions(reader)`方法中,首先获取配置资源(`Resource`)数组,这些资源通过类路径方式加载的配置文件。如果配置资源不为空,则通过 `XmlBeanDefinitionReader` 对象加载这些资源中的 bean 定义。接着,获取配置文件路径数组,这些路径通过字符串形式指定的配置文件路径。如果配置文件路径不为空,则同样通过`XmlBeanDefinitionReader`来加载这些路径中的 bean 定义。
> **关于`reader.loadBeanDefinitions`方法的源码分析已经在另外一篇关于[XmlBeanDefinitionReader](https://github.com/xuchengsheng/spring-reading/blob/master/spring-beans/spring-bean-xmlBeanDefinitionReader/README.md)类的博客中详细分析了。**
```java
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
// 获取配置资源Resource数组通常表示通过类路径或其他方式加载的配置文件
Resource[] configResources = getConfigResources();
// 如果配置资源不为空,则通过阅读器加载这些资源中的 bean 定义
if (configResources != null) {
reader.loadBeanDefinitions(configResources);
}
// 获取配置文件路径数组,通常表示通过字符串形式指定的配置文件路径
String[] configLocations = getConfigLocations();
// 如果配置文件路径不为空,则通过阅读器加载这些路径中的 bean 定义
if (configLocations != null) {
reader.loadBeanDefinitions(configLocations);
}
}
```

View File

@ -3,12 +3,13 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-beans</artifactId>
<artifactId>spring-context</artifactId>
<groupId>com.xcs.spring</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-bean-importBeanDefinitionRegistrar</artifactId>
<artifactId>spring-context-classPathXmlApplicationContext</artifactId>
</project>

View File

@ -0,0 +1,16 @@
package com.xcs.spring;
import com.xcs.spring.bean.MyBean;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author xcs
* @date 20231123 1627
**/
public class ClassPathXmlApplicationContextDemo {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:beans.xml");
System.out.println("MyBean = " + context.getBean(MyBean.class));
}
}

View File

@ -0,0 +1,18 @@
package com.xcs.spring.bean;
/**
* @author xcs
* @date 20231123 1628
**/
public class MyBean {
private String message;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}

View File

@ -0,0 +1,9 @@
<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="myBean" class="com.xcs.spring.bean.MyBean">
<property name="message" value="Hello World"/>
</bean>
</beans>