Spring源码学习笔记(1)——容器的基本实现
时间:2022-07-24
本文章向大家介绍Spring源码学习笔记(1)——容器的基本实现,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。
Spring源码学习笔记(1)——容器的基本实现
一. 实例代码
Resource resource = new ClassPathResource("applicationContext.xml");
XmlBeanFactory beanFactory = new XmlBeanFactory(resource);
Object bean = beanFactory.getBean("beanName");
以上是很简单的一段代码,只完成了3个Spring的基本操作:
- 读取Spring配置文件
- 根据配置文件创建BeanFactory,加载Bean
- 从BeanFactory中获取Bean的实例
上述代码完成了Spring容器的基本功能,看似简单,但是背后却封装了复杂的逻辑。下面我们结合源码看下Spring到底做了哪些处理。
一. 资源的封装——Resource接口
- Resource接口抽象了所有Spring内部使用的底层资源,如File、Classpath下的资源和ByteArray等。Resource接口继承了InputStreamSource接口,InputStreamSource接口只有一个方法:
public interface InputStreamSource {
//返回一个新的InputStream对象
InputStream getInputStream() throws IOException;
}
- Resource接口提供了一些资源基础属性的相关方法。
public interface Resource extends InputStreamSource {
boolean exists();
default boolean isReadable() {
return true;
}
default boolean isOpen() {
return false;
}
default boolean isFile() {
return false;
}
URL getURL() throws IOException;
URI getURI() throws IOException;
File getFile() throws IOException;
default ReadableByteChannel readableChannel() throws IOException {
return Channels.newChannel(getInputStream());
}
long contentLength() throws IOException;
long lastModified() throws IOException;
Resource createRelative(String relativePath) throws IOException;
@Nullable
String getFilename();
String getDescription();
}
- 对于不同来源的资源,Resource有不同类型的实现类,如FileSystemResource、ClassPathResource、UrlResource等。
- 通常,Spring会将配置文件封装成一个Resource对象,后续属性的解析和Bean的加载就交给BeanDefinitionReader去处理。
二. 资源的解析——BeanDefinitionReader
- 当Spring将配置文件封装成了Resource之后,就交给BeanDefinitionReader去对其进行加载和解析。 BeanDefinitionReader定义了解析配置文件的相关方法。
public interface BeanDefinitionReader {
BeanDefinitionRegistry getRegistry();
@Nullable
ResourceLoader getResourceLoader();
@Nullable
ClassLoader getBeanClassLoader();
BeanNameGenerator getBeanNameGenerator();
int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException;
int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException;
int loadBeanDefinitions(String location) throws BeanDefinitionStoreException;
int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException;
}
- Spring配置文件可以有不同的描述方式,如xml、properties等,根据不同的描述类型,有不同的实现类,如XmlBeanDefinitionReader、PropertiesBeanDefinitionReader等。
三. Bean的加载
Spring的Bean是何时被加载的呢?看示例代码:
new XmlBeanFactory(resource);
创建了XmlBeanFactory实例,并传入Resource对象,Resource就在这里被加载解析:
public class XmlBeanFactory extends DefaultListableBeanFactory {
//XmlBeanFactory内部维护了一个XmlBeanDefinitionReader对象,用来解析以Xml方式描述的配置文件
private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
public XmlBeanFactory(Resource resource) throws BeansException {
this(resource, null);
}
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
super(parentBeanFactory);
//构造XmlBeanFactory后,即调用XmlBeanDefinitionReader的方法解析配置文件
this.reader.loadBeanDefinitions(resource);
}
}
XmlBeanFactory调用了XmlBeanDefinitionReader的loadBeanDefinitions()方法解析配置文件,加载Bean定义。具体如下:
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException{
//对Resource进行编码包装
return loadBeanDefinitions(new EncodedResource(resource));
}
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<>(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
try {
//从Resource中获取InputStream并构造InputSource
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
//调用doLoadBeanDefinitions()进行BeanDefinition的解析
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();
}
}
}
可以看到,loadBeanDefinitions()方法只是做了一些准备工作,核心的加载逻辑是在doLoadBeanDefinitions()方法中的:
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
//将封装了Spring配置文件的Resource对象解析成Document文档对象
Document doc = doLoadDocument(inputSource, resource);
//向Spring容器中注册BeanDefinition
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);
}
}
不考虑异常处理,doLoadBeanDefinitions()方法其实只完成了两件事:
- 将资源解析成Document对象;
- 根据解析好的Document对象,向容器中注册BeanDefinition
Document是w3c规范定义的文档对象,doLoadDocument(inputSource, resource)方法的作用就是将Spring的配置文件Resource对象解析成Document对象,具体的逻辑涉及到dom解析相关处理,这里就不再展开了。
下面重点关注registerBeanDefinitions方法:
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
//创建DefaultBeanDefinitionDocumentReader实例,用来解析Document文件
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
//本次注册BeanDefinition前,容器中BeanDefinition的数量
int countBefore = getRegistry().getBeanDefinitionCount();
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
//返回本次成功注册的BeanDefinition的数量
return getRegistry().getBeanDefinitionCount() - countBefore;
}
注册BeanDefinition的核心逻辑在DefaultBeanDefinitionDocumentReader的doRegisterBeanDefinitions()方法中:
protected void doRegisterBeanDefinitions(Element root) {
//创建BeanDefinitionParserDelegate代理对象专门处理解析
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
//处理profile属性,多环境支持
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(specifiedProfiles)) {
if (logger.isInfoEnabled()) {
logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec +
"] not matching: " + getReaderContext().getResource());
}
return;
}
}
}
//解析预处理,模板方法
preProcessXml(root);
//实际处理解析
parseBeanDefinitions(root, this.delegate);
//解析后处理,模板方法
postProcessXml(root);
this.delegate = parent;
}
最后,解析和注册BeanDefinition的处理在parseBeanDefinitions()方法中
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
//解析<beans>标签
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)) {
//解析<bean>标签
parseDefaultElement(ele, delegate);
}
else {
delegate.parseCustomElement(ele);
}
}
}
}
//处理自定义标签
else {
delegate.parseCustomElement(root);
}
}
- 简单分页的性能优化
- Flash/Flex学习笔记(42):坐标旋转
- 分布式监控系统Zabbix-完整安装记录 -添加端口监控
- Flash/Flex学习笔记(40):弹性运动续--弹簧
- 表格样式—粗边框细表格线
- 分布式监控系统Zabbix--完整安装记录 -添加apache监控
- Flash/Flex学习笔记(38):缓动动画
- 把EmEditor改造成简单的Word
- centos7下FFmpeg环境部署记录
- Mac下使用rz、sz远程上传下载文件的配置记录
- 写文件
- Mac下通过VMware Fusion安装centos虚拟机操作记录
- 进程管理利器-supervisor部署记录
- python大小写转换函数
- java教程
- Java快速入门
- Java 开发环境配置
- Java基本语法
- Java 对象和类
- Java 基本数据类型
- Java 变量类型
- Java 修饰符
- Java 运算符
- Java 循环结构
- Java 分支结构
- Java Number类
- Java Character类
- Java String类
- Java StringBuffer和StringBuilder类
- Java 数组
- Java 日期时间
- Java 正则表达式
- Java 方法
- Java 流(Stream)、文件(File)和IO
- Java 异常处理
- Java 继承
- Java 重写(Override)与重载(Overload)
- Java 多态
- Java 抽象类
- Java 封装
- Java 接口
- Java 包(package)
- Java 数据结构
- Java 集合框架
- Java 泛型
- Java 序列化
- Java 网络编程
- Java 发送邮件
- Java 多线程编程
- Java Applet基础
- Java 文档注释
- AkShare-中国宏观-邮电业务基本情况
- AkShare-中国宏观-国际旅游外汇收入构成
- AkShare-中国宏观-民航客座率及载运率
- AkShare-中国宏观-航贸运价指数
- 您应该知道的11个JavaScript和TypeScript速记
- AkShare-中国宏观-央行货币当局资产负债
- AkShare-中国宏观-FR007利率互换曲线
- Github标星59.7k:用动画的形式呈现解LeetCode题目的思路
- 《爱情公寓》电影版,十年一瞬间(下)
- 实战:上亿数据如何秒查
- 《爱情公寓》电影版,十年一瞬间(上)
- Ajax爬取今日头条街拍美图
- 现在的房租有多高(杭州)?
- 英雄联盟皮肤大拼图
- Python爬虫-MongoDB