SpringBoot-Term-Definition
近期,在Github
上读到了关于源码学习的仓库,并跟着学习了Spring
全家桶源码。这对设计模式的学习和程序设计是十分有帮助的,因为Spring
源码中存在大量设计模式的优秀实践。但设计模式的广泛运用也导致Spring
类之间关系复杂,源码阅读费力。
在学习过程中,发现自己对Spring
中许多概念是模糊的。这可能是因为学习Spring
时,都是从使用侧考虑,忽略了非常多底层细节。对于概念的认知,更多来自于口口相传或望文生义的猜测。例如能否严谨地阐述容器,BeanFactory
和ApplicationContext
三者间的关系。很多文章,包括笔者自己,都在不加区分地使用这三个概念。这种模糊的概念认知,对于日常使用Spring
没有任何影响,但在学习源码时,就会产生大量歧义。
比较常见的源码学习方式,例如跟踪SpringApplication.run
方法,查看一层层的调用情况,容易让读者迷失在概念的海洋当中。这样的学习模式是在用未知解释未知,很大一部分还是要靠猜,并且很难靠自己验证猜想是否准确。由于笔者对Spring
整体并没有很深刻认知,本篇笔记更多是对源码的翻译和转述,着重描述Spring
的概念。这些概念的解释全部来自于Spring
源码和注释。
源码基于SpringBoot3.1.7
版本。此版本没特别之处,只是上一个做的项目恰好使用了这个版本。通常在核心层面源码也不会发生大改动,任意一个常用的版本应该都能得到一致的概念描述。
接口、抽象类、具体类
学习Spring
源码,最大的体验就是复杂的类关系。使用了大量的抽象,定义了许多接口、抽象类。
在面向对象编程的学习中,接口实现通常用来表示“能干什么”,而类继承通常表示“是什么”。但在Spring
源码中,很多地方使用接口表示了“是什么”的概念。当然也可以玩文字游戏,接口可以表示“作为什么应该具有的功能”。例如Resource
接口表示了,作为资源应该具有的功能。但笔者认为直接使用“是什么”去解释接口更符合人们常规理解。实现了Resource
接口表示某个具体类是一种Resource
。
会产生这样的情况,很大原因是java
无法进行多继承。而spring
由于高度的抽象,功能划分地十分细致(接口隔离原则)。当一个具体类具有多个功能,多实现避免了无法多继承的限制。
但针对某个接口具体实现类中,通常包含大量相似的代码。虽然java 8
后,可以通过提供默认方法,将相似代码抽取到接口中。但许多接口方法都依赖于具体的成员变量,而接口中只能包含常量,不能包含非常量的成员变量。因此这部分具体实现类中重复的代码,通常会被抽取到一个抽象类中,将这个抽象类实现接口,并在抽象类中提供许多接口方法的默认实现。
这样的模式在Spring
中极为常见,识别这种模式能更便于理解类之间的关系。
Resource
1 |
|
Interface for a resource descriptor that abstracts from the actual type of underlying resource, such as a file or class path resource.
An InputStream can be opened for every resource if it exists in physical form, but a URL or File handle can just be returned for certain resources. The actual behavior is implementation-specific.
Resource
接口用来表示可以被读取内容的资源。通俗地理解就是某个文件,但这个文件可以使用多种方式来定位。例如可以使用文件系统路径(FileSystemResource
),也可以使用类路径(ClassPathResource
),或者使用URL/URI
来表示位于网络上的资源(UrlResource
)。不同的定位方式对应了不同的实现类。早期使用xml
文件启动Spring
,就是在启动过程中将xml
文件的路径,包装成了一个FileSystemResource
,用于读取其中定义的配置信息。
一些Resource
接口的实现类也在资源包含的内容进行了声明。例如BeanDefinitionResource
、MultipartFileResource
。
Resource
接口有一个默认实现抽象类AbstractResource
。
Resource
接口继承于InputStreamSource
接口。InputStreamSource
接口只声明了一个方法getInputStream
,用于从资源中获取流对象,用于读取资源内容。
BeanDefinition
1 |
|
A BeanDefinition describes a bean instance, which has property values, constructor argument values, and further information supplied by concrete implementations.
This is just a minimal interface: The main intention is to allow a BeanFactoryPostProcessor to introspect and modify property values and other bean metadata.
BeanDefinition
接口定义了Spring
容器中所管理的Bean
实例的配置元数据,包含了各种属性值、构造器的参数值。BeanDefinition
实例和容器中的Bean
实例一一对应。
除此外,还包括parentName
、beanClassName
、scope
、lazyInit
、dependsOn
autowireCandidate
、primary
、factoryBeanName
、factoryMethodName
、initMethodName
、destroyMethodName
、role
、description
、resolvableType
、singleton
、prototype
、abstract
、resourceDescription
、originatingBeanDefinition
等元数据的访问/修改。关于这些元数据的概念,建议参考源码,在此不赘述。
BeanDefinition
接口也有默认实现抽象类AbstractBeanDefinition
。
BeanDefinition
继承了AttributeAccessor
接口。AttributeAccessor
接口声明了查询、访问、修改对象数据的方法。对于BeanDefinition
来说,这里的数据指的就是元数据。使得对象的元数据可以在运行时动态地添加、获取和删除,从而提高了对象的灵活性和可扩展性。
BeanDefinition
继承了BeanMetadataElement
接口。BeanMetadataElement
接口声明了获取BeanDefinition
来源的信息,例如来自xml标签、注解、配置类等可能包含BeanDefinition
信息的对象。
综上,BeanDefinition
包含了bean
的元数据信息,并包含了查询/修改元数据,获取元数据来源的功能。此接口主要用于BeanFactoryPostProcessor
中,对BeanDefinition
进行动态调整。