书籍《Spring源码深度解析》
官方文档
之前的一篇
推荐一个博主的系列博客,手写IOC,AOP
源码Spring5.x
文章目录
1. 整体架构
从Spring的架构图从下往上看,下层是上层的依据基础。
-
Core Container
其中的Core和Beans模块是框架的基础部分,提供IoC(转控制)和依赖注入特性。这里的基础概念是BeanFactory,它提供对Factory模式的经典实现来消除对程序性单例模式的需要,并真正地允许你从程序逻辑中分离出依赖关系和配置。
但Context模块构建于Core和Beans模块基础之上,提供了一种类似于JNDI注册器的框架式的对象访问方法。
Expression Language模块提供了一个强大的表达式语言用于在运行时查询和操纵对象。 -
Data Access/Integration
- JDBC模块提供了一个JDBC抽象层,它可以消除冗长的JDBC编码和解析数据库厂商特有的错误代码。这个模块包含了Spring对JDBC数据访问进行封装的所有类。
- RM模块为流行的对象-关系映射API,如JPA、JDO、Hibernate、iBatis等,提供了一个交互层。利用ORM封装包,可以混合使用所有Spring提供的特性进行O/R映射。如前边提到的简单声明性事物管理。
- Web
Web上下文模块建立在应用程序上下文模块之上,为基于Web的应用程序提供了上下文。 AOP
AOP模块提供了一个符合AOP联盟标准的面向切面编程的实现,它让你可以定义例如方法拦截器和切点,从而将逻辑代码分开,降低它们之间的耦合性。AOP采用Aspects模块提供了对AspectJ的集成支持。
1.2 环境搭建pom
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
</dependencies>
2. Spring流程分析
使用过Spring的都应该了解,Spring的工作流程一般是读取配置文件(扫描配置),实例化Bean,调用Bean。
那么对于源码的学习也就从以上的三个角度来探讨。
3. 核心类
3.1 DefaultlistableBeanFactory
XmlBeanFactory 继承 DefaultListableBeanFactory ,而DefaultListableBeanFactroy 是整个 bean加载的核心部分,是 Spring 注册及加载 Bean 的默认实现,而对于 XmlBeanFactory与DefaultListableBeanFactory 不同的地方其实是在 XmlBeanFactory 中使用了自定义的 XML 读取器XmlBeanDefinitionReader ,实现了个性化的 BeanDefinitionReader 读取, DefaultListableBeanFactory
继承了 AbstractAutowireCapableBeanFactory 并实现了 ConfigurableListableBeanFactory以及BeanDefinitionRegistry 接口。
如果想了解其父类以及接口的作用,可以查看源码的注释。
3.2 XmlBeanDefinitionReader
XML 置文件的读取是 spring 重要的功能 ,因为 Spring 的大部分功能都是以配置作为切入点的。
其中:
- BeanDefinitionReader :主要用于定义资源文件读取并转换为BeanDefinition的功能。
- AbstractBeanDefinitionReader:对 EnvironmentCapab BeanDefinitionReader 类定义的功能进行实现。
- EnvironmntCapable :定义获取 Environment 方法。
其次,在5.x注解明显还是用得多些,那么就存在AnnotatedBeanDefinitionReader
用于编程注册注释bean类。是一个适配器。这是的另一种应用注释的解析方式,但仅用于显式注册的类。是非常常用的注解模式下的配置读取类。
XML 文件读取的大致流程:
- 通过继 AbstractBeanDefinitionReader 中的方法,来使用 ResoureLoader 将资源文件路径转换为对应的 Resource 文件。
- 通过 DocumentLoaderResource 文件进行转换,将 Resource 文件转换为 Document文件。
- 通过实现接口 BeanDefinitionDocumentReader 的DefaultBeanDefinitionDocumentReader对Document 进行解析,并使用 BeanDefinitionParserDelegate对 Element 进行解析。
ResoureLoader - > Resource - > Document
3.3 XmlBeanFactory
这还是一个很传统的配置文件的解析方式。
public class XmlBeanFactory extends DefaultListableBeanFactory {
private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
/**
使用给定的资源创建一个新的XmlBeanFactory,
*必须使用DOM解析。
* @param 从其中加载bean定义的XML资源
* @throws 加载或解析错误时的BeansException异常
*/
public XmlBeanFactory(Resource resource) throws BeansException {
this(resource, null);
}
/**
使用给定的输入流创建一个新的XmlBeanFactory,
*必须使用DOM解析。
* @param 从其中加载bean定义的XML资源
* @param 从它加载由bean定义的XML资源
* @throws 加载或解析错误时的BeansException异常
*/
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
super(parentBeanFactory);
this.reader.loadBeanDefinitions(resource);
}
}
Spring 的配置文件读取是通过 ClassPathResource 进行封装的。
在Java 中,将不同来源的资游抽象成 URL ,通过注册不同的 handler ( URLStreamHandler ) 来处理不同来源的资源的读取逻辑,一般 handler 类型使用不同前缀(协议, Protocool )来识别,如“file :”“ http :” jar :”等,然而 URL 没有默认定义相对 Classpath 或者ServletContext 等资源的 handler ,虽然可以注册自己的 URLStreamHandler 来解析指定得 URL 前缀(协议 ), 比如Classpath ,然而这需要了解 URL的实现机制,而URL 也也没有提供基本 方法,如检查当前资源是否存在、检查当前资源是否可读 等方法。 因而Spring 内部使用到的资源实现了自己的抽象结构 :Resource 接口封装底层资源。
public interface Resource extends InputStreamSource {
/**
*确定该资源是否以物理形式实际存在。
该方法执行确定的存在性检查,而
*存在一个{@code Resource}句柄只能保证有效
*描述符句柄。
*/
boolean exists();
/**
*指示是否可以通过读取该资源的内容
* {@link #getInputStream()}.
* <p>对于典型的资源描述符,将为{@code true};
*注意,实际的内容阅读仍然可能失败时,尝试。
但是,值{@code false}是一个明确的指示
*无法读取资源内容。
* @see #getInputStream()
*/
default boolean isReadable() {
return true;
}
/**
指示该资源是否表示一个打开流的句柄。
*如果{@code true}, InputStream不能被多次读取,
*必须读取和关闭,以避免资源泄漏。
* <p对于典型的资源描述符>将是{@code false}。
*/
default boolean isOpen() {
return false;
}
/**
确定该资源是否表示文件系统中的文件。
强烈建议(但不保证)值为{@code true}
*一个{@link #getFile()}调用将成功。
* <p>默认情况下,这是{@code false}。
* @since 5.0
* @see #getFile()
*/
default boolean isFile() {
return false;
}
/**
*返回此资源的URL句柄。
* @throw IOException如果资源不能被解析为URL,
*例如,如果资源不能作为描述符使用
*/
URL getURL() throws IOException;
/**
*返回该资源的URI句柄。
* @抛出IOException,如果资源不能被解析为URI,
*例如,如果资源不能作为描述符使用
*/
URI getURI() throws IOException;
/**
*返回该资源的文件句柄。
* @throws . io .如果资源不能被解析为
*绝对文件路径,即,如果资源在文件系统中不可用
*在一般的解析/读取失败时,@抛出IOException
* @see # getInputStream ()
*/
File getFile() throws IOException;
/**
*返回{@link ReadableByteChannel}。
* p>期望每个调用创建一个fresh通道。
*
默认实现返回{@link Channels#newChannel(InputStream)}
*,结果为{@link #getInputStream()}。
* @返回底层资源的字节通道(不能是{@code null})
* @throws . io .如果底层资源不存在,FileNotFoundException异常
* @抛出IOException,如果内容通道无法打开
* @see #getInputStream()
*/
default ReadableByteChannel readableChannel() throws IOException {
return Channels.newChannel(getInputStream());
}
/**
*确定该资源的内容长度。
* @抛出IOException,如果资源不能被解析
*(在文件系统或其他已知的物理资源类型中)
*/
long contentLength() throws IOException;
/**
*确定该资源的最后修改时间戳。
* @抛出IOException,如果资源不能被解析
*(在文件系统或其他已知的物理资源类型中)
*/
long lastModified() throws IOException;
/**
创建一个相对于该资源的资源。
* @param relativePath相对路径(相对于此资源)
返回相对资源的资源句柄
* @抛出IOException,如果相关资源无法确定
*/
Resource createRelative(String relativePath) throws IOException;
/**
*确定这个资源的文件名,通常是最后一个
路径的一部分:例如,“myfile.txt”。
*
返回{@code null},如果该类型的资源没有返回
*有一个文件名。
*/
@Nullable
String getFilename();
/**
*返回该资源的描述,
*用于处理资源时的错误输出。
*
实现也被鼓励返回这个值
*从他们的{@code toString}方法。
* @see Object#toString()
*/
String getDescription();
}
由以上代码可见Resource主要定义了当前资源状态的方法:存在性,可读性,是否处于打开状态。其次还提供了URL,URI,file类型的转换。
public interface InputStreamSource {
/**
*返回一个{@link InputStream}作为底层资源的内容。
期望每个调用都创建一个fresh流。
当您考虑这样的API时,这个要求是特别重要的
*作为JavaMail,其中需要能够多次读取流时
创建邮件附件。对于这样的用例,它是required
*每个{@code getInputStream()}调用都会返回一个新的流。
* @返回底层资源的输入流(不能是{@code null})
* @throws . io .如果底层资源不存在,FileNotFoundException异常
如果无法打开内容流,@抛出IOException
*/
InputStream getInputStream() throws IOException;
}
InputStreamSource封装能返回InputStream的类。可以说他提供了一个对外的同一接口,而底层可以是任意的文件来源。
其继承类图如下:
//ClassPathResource 可以是任意的文件来源采用对于的xxxResource类。
Resource resource=new ClassPathResource("beanFactoryTest.xml”),
InputStream inputStream=resource.getinputStream();
那么接下来可以稍微的了解一下XmlBeanFactory的初始化过程。
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
super(parentBeanFactory);
this.reader.loadBeanDefinitions(resource);
}
上面函数中的代码 this.reader.loadBeanDefinitions(resource)才是资源加载的真正实现。
其父类的最终调用方法为:
public AbstractAutowireCapableBeanFactory() {
super();
ignoreDependencyInterface(BeanNameAware.class);
ignoreDependencyInterface(BeanFactoryAware.class);
ignoreDependencyInterface(BeanClassLoaderAware.class);
}
ignoreDependencyInterface的主要功能是忽略给定接口的向动装配功能,主要功能是为了解决属性依赖问题,当 A中有属性B,那么 Spring 在获取 Bean 的时候如果其属性,B还没有初始化,那么Spring会自动初始化 ,这也是 Spring提供的一个重要特性。
但是,某些情况下,B不会被初始化,其中的一种情况就是B实现了 BeanNameAware 接口。 Spring 中是这样介绍的:自动装配时忽略给定的依赖接口,典型应用是边过其他方式解析 Application上下文注册依赖,类似于 BeanFactory通过 BeanFactoryAware 进行注入或者 ApplicationContext 通过ApplicationContextAware 进行注入。
下面来XmlBeanFactory来加载Bean。
在 XmlBeanFactory 构造函数中调用了 XmlBeanDefinitionReader 类型的 reader属性提供的方法 this.reader.loadBeanDefinitions(resource);
其整个的准备工作流程如下:
- 封装资源文件,当进入XmlBeanDefinitionReader使用以下代码:
return loadBeanDefinitions(new EncodedResource(resource));对resource进行封装。
EncodedResource的作用主要用于对资源文件的编码进行处理。
- 获取输入流, InputStream inputStream = encodedResource.getResource().getInputStream();
从Resource获取对应的输入流,并构造InputSource。
InputSource inputSource = new InputSource(inputStream); - 通过构造的 InputSource 实例和 Resource 实例继续调用函数 doLoadBeanDefinitions。其核心代码如下:
try {
Document doc = doLoadDocument(inputSource, resource);
int count = registerBeanDefinitions(doc, resource);
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + count + " bean definitions from " + resource);
}
return count;
}
再来看看这个方法内的registerBeanDefinitions:
org.springframework.beans.factory.xml.XmlBeanDefinitionReader#registerBeanDefinitions
/**
注册包含在给定DOM文档中的bean定义。
*由{@code loadBeanDefinitions}调用。
*
创建解析器类的新实例并调用
* {@code registerBeanDefinitions}。
* @param doc DOM文档
* @param resource资源描述符(用于上下文信息)
* @return 找到的bean定义的数量
* @throws 分析错误时的BeanDefinitionStoreException异常
*/
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
//实例化一个BeanDefinitionDocumentReader
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
//记录统计前的BeanDefinition加载数
int countBefore = getRegistry().getBeanDefinitionCount();
//加载注册Bean
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
//返回本次加载个数
return getRegistry().getBeanDefinitionCount() - countBefore;
}
其中BeanDefinitionDocumentReader 是一个接口。
public interface BeanDefinitionDocumentReader {
/**
*从给定的DOM文档中读取bean定义
*在给定的读取器上下文中注册它们。
* @param doc the DOM document
* @param 阅读器的当前上下文
* (包括目标注册表和正在解析的资源)
* @throws 分析错误时的BeanDefinitionStoreException异常
*/
void registerBeanDefinitions(Document doc, XmlReaderContext readerContext)
throws BeanDefinitionStoreException;
}
其具体的实现只有DefaultBeanDefinitionDocumentReader:
public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocumentReader {
@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
Element root = doc.getDocumentElement();
doRegisterBeanDefinitions(root);
}
}
然后就到了关键的doRegisterBeanDefinitions方法:
org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#doRegisterBeanDefinitions
/**
*在给定根元素{@code <beans/>}中注册每个bean定义。
*/
protected void doRegisterBeanDefinitions(Element root) {
//在这个方法中,任何嵌套的<beans>元素都会导致递归。在
//正确传播和保存<beans> default-*属性的顺序,
//跟踪当前(父)委托,该委托可能为空。创建
//新的(子)委托带有一个用于回退的对父委托的引用,
//然后最终将this.delegate重置为它的原始(父)引用。
//此行为模拟委托堆栈,而不实际需要一个委托。
//专门处理解析
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
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.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;
}
这个profile的使用就是可以方便我们区分使用测试环境或者开发环境的配置文件。
接着parseBeanDefinitions其实就是对XML的读取。这一部分就看到这里了。
5.x时代使用xml配置Spring的已经很少了,但是了解XML配置对注解配置的理解有辅助作用。毕竟都是处于Spring扫描部分的内容。