书籍《Spring源码深度解析》
官方文档
之前的一篇
推荐一个博主的系列博客,手写IOC,AOP
源码Spring5.x
文章目录
- 1. 整体架构
- 1.2 环境搭建pom
- 2. Spring流程分析
- 3. 核心类
- 3.1 DefaultlistableBeanFactory
- 3.2 XmlBeanDefinitionReader
- 3.3 XmlBeanFactory
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},如果该类型的资源没有返回*有一个文件名。*/@NullableString 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提供的一个重要特性。