书籍:《Spring源码深度解析 第2版 郝佳》
Spring源码版本5.3.21
源码地址:spring-projects/spring-framework: Spring Framework (github.com)
建议在早上、安静的地方阅读
文章目录
一、Spring的整体架构
Spring框架是一个分层架构,它包含一系列的功能要素,并被分为大约20个模块。
这些模块被总结为以下几部分:
- Core Container
- Data Access/Integration
- Web
- AOP
1.Core Cotainer
核心容器。Core 和 Bean 是框架的基础部分,提供 IoC(控制反转)和依赖注入特性。
- Core:包含 spring 框架的基本的核心工具类,其他组件的基本核心。
- Bean:包含访问配置文件、创建和管理 bean 以及 IoC/DI 操作相关的所有类。
- Context:于 Core 和 Bean 上建立。提供了一种类似于 JNDI 注册器的框架式的对象访问方法,继承了 Beans 的特性,为 spring 提供了大量的扩展。ApplicationContext 接口是 Context 模块的关键。
- Expression Language:提供了强大的表达式语言,在运行时查询和操纵对象。支持 设置、获取属性的值、属性的分配、方法的调用、访问数组上下文等。
2.Data Access/Integration
数据库访问相关
- JDBC:为不同的数据库连接访问提供抽象类。
- ORM:为流行的对象——映射API(JPA\JDO\Hibernate等)提供了一个交互层。
- OXM:提供了一个对Object/XML映射实现的抽象层。
- JMS:包含一些制造和雄安飞消息的特性。
- Transaction:支持编程和声明性的事务管理,这些事务必须实现特定的接口,对所有POJO都适用。
3.Web
建立在应用上下文之上
- Web:提供了基础的面向Web的集成特性。如多文件上传。
- Web-Servlet:web.servlet.jar 包含了Spring的MVC的实现。
- Web-Status:提供了对Status的支持,使得类在Spring应用中能够与一个典型的Status Web 层集成在一起。
- Web-Porlet:提供了用于Porlet环境和Web-Senlet模块的MVC的实现。
4.AOP
Spring AOP 直接将面向切面编程功能集成到了spring框架中。
- Aspects:提供了对Aspect的集成支持
- Instrumentation:提供了class instrumentation 支持和class loader 实现,使得可以在特点的应用服务器上实现
二、容器的基本实现
- 读取配置文件 beanFactoryTest.xml
- 根据 beanFactoryTest.xml 中的配置找到对应的类的配置,并实例化
- 调用实例化后的实例
@Test
public void testSimpleLoad(){
BeanFactory bf = new XmlBeanFactory(new ClassPathResource("beanFactoryTest.xml"));
MyTestBean bean = (MyTestBean) bf.getBean("myTestBean");
assertEquals("testStr",bean.getTestStr());
}
=============================beanFactoryTest.xml=======================================
<bean id="myTestBean" class="bean.MyTestBean"/>
先主要来看这行代码:
BeanFactory bf = new XmlBeanFactory(new ClassPathResource("beanFactoryTest.xml"));
XmlBeanFactory 继承于 DefaultListableBeanFactory,所以接下来先学习DefaultListableBeanFactory。
beans包下的两个核心类
- DefaultListableBeanFactory
- XmlBeanDefinitionReader
1.核心类-DefaultListableBeanFactory
如果根据书上的定义和自己的结构图来看一个一个对应太慢了,所以做了一张图来看起来方便点。根据自己不理解、想了解的知识有根据的查看源码。
而我们创建获取bean配置的XmlBeanFactory 就继承了DefaultListableBeanFactory
在XmlBeanFactory 的源码中,我们可以看到XmlBeanDefinitionReader实例对象被创建了。
官方定义:XmlBeanDefinitionReader是用于XML Bean定义的Bean定义读取器。
接下来学习XmlBeanDefinitionReader
2.核心类-XmlBeanDefinitionReader
3.容器的基础XmlBeanFactory
接下类深入分析以下功能的代码实现:
BeanFactory bf = new XmlBeanFactory(new ClassPathResource("beanFactoryTest.xml"));
ClassPathResource()
new ClassPathResource(“beanFactoryTest.xml”)
InputStreamSource 封装任何能返回InputStream的类
public interface InputStreamSource {
InputStream getInputStream() throws IOException;
}
Resource
- 对不同的资源文件都有响应的Resource实现:文件、ClassPath资源、URL资源、InputStream资源等
- 定义了三个判断当前资源状态的方法
- 提供了不同资源URL、URI、File类型的转换
- 获取资源属性的方法(getFileName() 获取不带路径的文件名)
- …
public interface Resource extends InputStreamSource {
boolean exists();
default boolean isReadable() {
return exists();
}
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();
}
ClassPathResource 中的实现方法是通过class或者classloader提供的底层方法进行调用的
public class ClassPathResource extends AbstractFileResolvingResource {
private final String path;
@Nullable
private ClassLoader classLoader;
@Nullable
private Class<?> clazz;
public ClassPathResource(String path) {
this(path, (ClassLoader) null);
}
public ClassPathResource(String path, @Nullable ClassLoader classLoader) {
Assert.notNull(path, "Path must not be null");
String pathToUse = StringUtils.cleanPath(path);
if (pathToUse.startsWith("/")) {
pathToUse = pathToUse.substring(1);
}
this.path = pathToUse;
this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
}
public ClassPathResource(String path, @Nullable Class<?> clazz) {
Assert.notNull(path, "Path must not be null");
this.path = StringUtils.cleanPath(path);
this.clazz = clazz;
}
@Deprecated
protected ClassPathResource(String path, @Nullable ClassLoader classLoader, @Nullable Class<?> clazz) {
this.path = StringUtils.cleanPath(path);
this.classLoader = classLoader;
this.clazz = clazz;
}
public final String getPath() {
return this.path;
}
@Nullable
public final ClassLoader getClassLoader() {
return (this.clazz != null ? this.clazz.getClassLoader() : this.classLoader);
}
@Override
public boolean exists() {
return (resolveURL() != null);
}
@Override
public boolean isReadable() {
URL url = resolveURL();
return (url != null && checkReadable(url));
}
@Nullable
protected URL resolveURL() {
try {
if (this.clazz != null) {
return this.clazz.getResource(this.path);
}
else if (this.classLoader != null) {
return this.classLoader.getResource(this.path);
}
else {
return ClassLoader.getSystemResource(this.path);
}
}
catch (IllegalArgumentException ex) {
return null;
}
}
@Override
public InputStream getInputStream() throws IOException {
InputStream is;
if (this.clazz != null) {
is = this.clazz.getResourceAsStream(this.path);
}
else if (this.classLoader != null) {
is = this.classLoader.getResourceAsStream(this.path);
}
else {
is = ClassLoader.getSystemResourceAsStream(this.path);
}
if (is == null) {
throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
}
return is;
}
@Override
public URL getURL() throws IOException {
URL url = resolveURL();
if (url == null) {
throw new FileNotFoundException(getDescription() + " cannot be resolved to URL because it does not exist");
}
return url;
}
@Override
public Resource createRelative(String relativePath) {
String pathToUse = StringUtils.applyRelativePath(this.path, relativePath);
return (this.clazz != null ? new ClassPathResource(pathToUse, this.clazz) :
new ClassPathResource(pathToUse, this.classLoader));
}
@Override
@Nullable
public String getFilename() {
return StringUtils.getFilename(this.path);
}
@Override
public String getDescription() {
StringBuilder builder = new StringBuilder("class path resource [");
String pathToUse = this.path;
if (this.clazz != null && !pathToUse.startsWith("/")) {
builder.append(ClassUtils.classPackageAsResourcePath(this.clazz));
builder.append('/');
}
if (pathToUse.startsWith("/")) {
pathToUse = pathToUse.substring(1);
}
builder.append(pathToUse);
builder.append(']');
return builder.toString();
}
@Override
public boolean equals(@Nullable Object other) {
if (this == other) {
return true;
}
if (!(other instanceof ClassPathResource)) {
return false;
}
ClassPathResource otherRes = (ClassPathResource) other;
return (this.path.equals(otherRes.path) &&
ObjectUtils.nullSafeEquals(this.classLoader, otherRes.classLoader) &&
ObjectUtils.nullSafeEquals(this.clazz, otherRes.clazz));
}
@Override
public int hashCode() {
return this.path.hashCode();
}
}
ClassPathResource 的 getInputStream() 方法是利用 ClassLoader 的 getResourceAsStream(path) 得到 InputStream 进而转化为 Resource
4.XmlBeanFactory详细
再来看XmlBeanFactory是如何处理接收到的Resource资源的
4.1.XmlBeanFactory的构造方法
4.1.1.调用父类的构造方法
在资源加载的真正实现(this.reader.loadBeanDefinitions(resource);)之前,调用了父类构造函数的初始化
跟踪代码到父类AbstractAutowireCapableBeanFactory的构造函数中。
/**
* Create a new AbstractAutowireCapableBeanFactory.
*/
public AbstractAutowireCapableBeanFactory() {
super();
// 忽略给定接口的自动转配,Bean、BeanFactory、BeanClassloader可以通过实现下面3个接口的时候进行相应Bean的注入
//当其他Bean的属性包含上面情况的Bean的时候,上面情况的Bean不会因为依赖注入被自动初始化
ignoreDependencyInterface(BeanNameAware.class);
ignoreDependencyInterface(BeanFactoryAware.class);
ignoreDependencyInterface(BeanClassLoaderAware.class);
if (NativeDetector.inNativeImage()) {
this.instantiationStrategy = new SimpleInstantiationStrategy();
}
else {
this.instantiationStrategy = new CglibSubclassingInstantiationStrategy();
}
}
4.1.2 资源加载的真正实现
资源加载的真正实现调用了 XmlBeanDefinitionReader 类型的reader属性提供的方法 :this.reader.loadBeanDefinitions(resource);
在进入XmlBeanDefinitionReader 后的执行流程:
- 封装资源文件。对参数Resource使用EncodedResource类进行封装
- 获取输入流。从Resource中获取对应的InputStream并构造InputSource
- 通过构造的InputSource实例和Resource实例调用函数doLoadBeanDefinitions
1.使用EncodedResource类进行封装
考虑到Resource可能存在编码要求的情况,首先对参数Resource使用EncodedResource类进行封装
@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));
}
EncodedResource这个类主要是对资源文件的编码进行处理的。主要体现在getReader()中,当设置了编码属性的时候spring会使用相应的编码作为输入流的编码。
public Reader getReader() throws IOException {
if (this.charset != null) {
return new InputStreamReader(this.resource.getInputStream(), this.charset);
}
else if (this.encoding != null) {
return new InputStreamReader(this.resource.getInputStream(), this.encoding);
}
else {
return new InputStreamReader(this.resource.getInputStream());
}
}
2.获取输入流
上面代码构造了一个有编码(encoding)的InputStreamReader。当构造好encodedResource对象后,再次转入可复用方法,真正的数据准备阶段开始了
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (logger.isTraceEnabled()) {
logger.trace("Loading XML bean definitions from " + encodedResource);
}
//通过属性来记录已经加载的资源
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
//从encodedResource中获取已经封装的Resource对象并再次从Resource中获取其中的InputStream
try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
//InputSource这个类并不来自于spring,它的全路径是org.xml.sax.InputSource
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
//获取输入流InputSource后,封装和准备工作完成
//真正进入了逻辑核心部分
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"IOException parsing XML document from " + encodedResource.getResource(), ex);
}
finally {
currentResources.remove(encodedResource);
if (currentResources.isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
}
3.调用函数doLoadBeanDefinitions
封装资源文件和获取输入流 之后就可以调用函数doLoadBeanDefinitions
在spring5.3.21版本中 将获取对XML文件的验证模式的代码放到了doLoadDocument里,这样做也确实让代码分工显得更加明确
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
//doLoadDocument做了两件事,下面有doLoadDocument的源码
//1.获取对XML文件的验证模式
//2.加载XML文件,并得到对应的Document
Document doc = doLoadDocument(inputSource, resource);
//3.返回Document注册Bean的信息
int count = registerBeanDefinitions(doc, resource);
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + count + " bean definitions from " + resource);
}
return count;
}
//冗长的异常类代码
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);
}
}
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
//getValidationModeForResource(resource)获取对XML文件的验证模式
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
getValidationModeForResource(resource), isNamespaceAware());
}
doLoadBeanDefinitions(inputSource,encodedResource.getResource)做了三件事:
- 1.获取对XML文件的验证模式
- 2.加载XML文件,并得到对应的Document
- 3.返回Document注册Bean的信息
这三个步骤支撑这整个Spring容器部分的实现,尤其是第三步对配置文件的解析,逻辑非常复杂,我们先看1.获取XML文件的验证模式。
5.获取XML的验证模式
先来了解上什么是XML文件的验证模式
XML文件的验证模式保证了XML文件的正确性,而比较常用的验证模式有:DTD和XSD
5.1.DTD 与 XSD
简单了解一下DTD 与 XSD
- DTD(Document Type Definition)文档类型定义
- XSD(XML Schemas Definition)文档结构定义
5.1.1.DTD
- DTD是一种XML约束模式语言,是XML文件的验证机制,属于XML文件组成的一部分
- 可以通过比较XML文档和DTD文件来看文档是否符合规范,元素、标签是否使用正确
- 一个DTD文档包含:元素的定义规则,元素间关系的定义规则,元素可使用的属性,可使用的实体或符号规则
要使用DTD验证模式的时候需要在XML文件的头部声明:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//Spring//DTD BEAN 2.0//EN" "http:/www.Springframework.org/dtd/ Spring-beans-2.0.dtd">
<beans>
... ...
</beans>
5.1.2.XSD
XML Schema 语言就是XSD
-
XML Schema 描述了XML文档的结构。XML Schema 本身就是XML文档,它符合 XML 语法结构
-
可以用一个指定的XML Schema 来验证某个 XML 文档,以检查XML文档是否符合要求
-
可以通过XML Schema 指定 XML 文档所允许的结构和内容,并可据此检查 XML 文档是否是有效的
在使用XML Schema 文档对XML实例文档进行检验时,除了要声明名称空间(xmlns=“http://www.springframework.org/schema/beans”)外,还必须指定该名称空间所对应的XML Schema 文档的存储位置,通过schemaLocation来指定(xsi:schemaLocation=“http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-3.1.xsd”)
<?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 https://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
<!-- intentionally empty: only needed so that the ContextLoader can find this file -->
</beans>
5.2.验证模式的读取
了解了DTD与XSD,现在我们回归正题
之前我们说到获取XML文件的验证模式 是在 doLoadDocument 的 getValidationModeForResource(resource) 方法实现的
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
//getValidationModeForResource(resource)获取对XML文件的验证模式
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
getValidationModeForResource(resource), isNamespaceAware());
}
getValidationModeForResource(resource)
protected int getValidationModeForResource(Resource resource) {
int validationModeToUse = getValidationMode();
//已指定验证模式
if (validationModeToUse != VALIDATION_AUTO) {
return validationModeToUse;
}
//没有得到指定的声明,默认用XSD
int detectedMode = detectValidationMode(resource);
if (detectedMode != VALIDATION_AUTO) {
return detectedMode;
}
// Hmm, we didn't get a clear indication... Let's assume XSD,
// since apparently no DTD declaration has been found up until
// detection stopped (before finding the document's root tag).
return VALIDATION_XSD;
}
在detectValidationMode(resource)方法中把自动检测验证模式的工作委托给了XmlValidationModeDetector,调用了XmlValidationModeDetector的validationModeDetector方法。
public int detectValidationMode(InputStream inputStream) throws IOException {
this.inComment = false;
// Peek into the file to look for DOCTYPE.
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
boolean isDtdValidated = false;
String content;
while ((content = reader.readLine()) != null) {
content = consumeCommentTokens(content);
if (!StringUtils.hasText(content)) {
continue;
}
//判断是否包含Doctype,包含就是DTD 否则XSD
if (hasDoctype(content)) {
isDtdValidated = true;
break;
}
if (hasOpeningTag(content)) {
// End of meaningful data...
break;
}
}
return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD);
}
catch (CharConversionException ex) {
// Choked on some character encoding...
// Leave the decision up to the caller.
return VALIDATION_AUTO;
}
}
spring用来检测验证模式的方法就是判断是否包含Doctype,包含就是DTD 否则XSD
6.获取Document
对于Document的加载,XmlBeanFactoryReader 同样没有亲力亲为,而是委托给了DocumentLoader去执行,DocumentLoader是一个接口,真正调用的是DefaultDocumentLoader
- 创建DocumentBuilderFactory(用到了之前上一步获取的验证模式)
- 通过DocumentBuilderFactory创建DocumentBuilder
- 解析inputSource来返回Document对象
@Override
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
if (logger.isTraceEnabled()) {
logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
}
DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
return builder.parse(inputSource);
}
loaderDocument 方法中涉及了一个参数 EntityResolver ,EntityResolver 的作用是项目本身就可以提供一个如何寻找DTD声明的方法
这里不再多作阐述
7.解析及注册BeanDefinitions
历经了千辛万苦,终于来到了这一步。
将文件转换为Document后,接下类的提取及注册bean就是我们的重头戏。
再来回顾一下资源加载的过程
我们走到了registerBeanDefinitions(doc, resource) 这一步
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
//在实例化BeanDefinitionDocumentReader 时会将 BeanDefinitionRegistry 传入,默认使用继承自己的DefaultListableBeanFactory 的子类
//1.记录统计前BeanDefinition的加载个数
int countBefore = getRegistry().getBeanDefinitionCount();
//2.加载和注册bean
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
//3.记录本次加载的BeanDefinition个数
return getRegistry().getBeanDefinitionCount() - countBefore;
}
着重来看第二步,不过我们先来研究一下BeanDefinitionDocumentReader
BeanDefinitionDocumentReader其实是一个接口,而实例化的操作是在createBeanDefinitionDocumentReader()中完成的,而通过此方法BeanDefinitionDocumentReader的真正类型其实已经是DefaultBeanDefinitionDocumentReader了
进入DefaultBeanDefinitionDocumentReader
这个方法在通过 doc.getDocumentElement() 获取到root后,就继续执行BeanDefinitions的注册
@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
doRegisterBeanDefinitions(doc.getDocumentElement());
}
doRegisterBeanDefinitions(root)
//任何嵌套的 <beans> 元素都将导致此方法中的递归
protected void doRegisterBeanDefinitions(Element root) {
//专门处理解析
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
//确定给定的 URI 是否指示默认命名空间及默认的spring配置
if (this.delegate.isDefaultNamespace(root)) {
//处理Profile属性
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.isDebugEnabled()) {
logger.debug("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;
}
这里着重来看parseBeanDefinitions(root, this.delegate);不过在这之前,我们先了解一下profile属性
7.1 profile属性的使用
有了这个属性,我们可以同时配置两套环境:生产环境、开发环境。方便切换开发、部署环境、更换不同的数据库
<beans profile="test">
<jdbc:embedded-database id="dataSource">
<jdbc:script location="classpath:com/bank/config/sql/schema.sql"/>
</jdbc:embedded-database>
</beans>
<beans profile="production">
<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
</beans>
7.2 解析并注册BeanDefinition
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;
//对bean处理
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}
else {
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
很清晰的代码,但是也是因为这是对默认Spring配置的解析
Spring的XML配置中又两种声明bean的配置
一种是默认的:
<bean id="test" class="test.TestBean"/>
一种是自定义的:
<tx:annotation-driven/>
使用自定义的话,就需要用户实现一些接口及配置
判断是否使用默认的命名空间配置是在 doRegisterBeanDefinitions(root) 中,处理profile属性之前就会进行判断
总结
就此容器的基本实现已经完成,对于最后的默认、自定义标签的解析 都分了两大章来阅读。会在后续更新。
第一次阅读源码,中间放弃了好多次,终于在今天早上完成了,spring5.3.21版本的源码和书上有些许出入,但是不影响阅读。
整个早上下来,给我的经验就是阅读源码最好在早上进行,就和初高中时候的早读一样,早上的人的心思没有受到一天中接下来事情的影响,是最适合记忆和探索的时间,一日之计在于晨!