Spring核心組件原理解析
盡管希臘哲學(xué)家赫拉克利特(Heraclitus)并不作為一名軟件開發(fā)人員而聞名,但他似乎深諳此道。他的一句話經(jīng)常被引用:“唯一不變的就是變化”,這句話抓住了軟件開發(fā)的真諦。
我們現(xiàn)在開發(fā)應(yīng)用的方式和1年前、5年前、10年前都是不同的,更別提15年前了,當(dāng)時(shí)RodJohnson的圖書 Expert One-on-One J2EE Design and Development 介紹了Spring框架的初始形態(tài)。當(dāng)時(shí),最常見的應(yīng)用形式是基于瀏覽器的Web應(yīng)用,后端由關(guān)系型數(shù)據(jù)庫作為支撐。盡管這種形式的開發(fā)依然有它的價(jià)值,Spring也為這種應(yīng)用提供了良好的支持,但是我們現(xiàn)在感興趣的還包括如何開發(fā)面向云的由微服務(wù)組成的應(yīng)用,這些應(yīng)用會(huì)將數(shù)據(jù)保存到各種類型的數(shù)據(jù)庫中。
另外一個(gè)嶄新的關(guān)注點(diǎn)是反應(yīng)式編程,它致力于通過非阻塞操作提供更好的擴(kuò)展性并提升性能。隨著軟件開發(fā)的發(fā)展,Spring框架也在不斷變化,以解決現(xiàn)代應(yīng)用開發(fā)中的問題,其中就包括微服務(wù)和反應(yīng)式編程。Spring還通過引入Spring Boot簡(jiǎn)化自己的開發(fā)模型。
Spring 的核心
任何實(shí)際的應(yīng)用程序都是由很多組件組成的,每個(gè)組件負(fù)責(zé)整個(gè)應(yīng)用功能的一部分,這些組件需要與其他的應(yīng)用元素進(jìn)行協(xié)調(diào)以完成自己的任務(wù)。當(dāng)應(yīng)用程序運(yùn)行時(shí),需要以某種方式創(chuàng)建并引入這些組件。
Spring Framework 總共有十幾個(gè)組件,但真正核心的組件只有三個(gè):Spring Core,Spring Context 和 Spring Bean,它們奠定了 Spring 的基礎(chǔ)并撐起了 Spring 的框架結(jié)構(gòu)。Spring 的其它功能特性例如 Web、AOP、JDBC 等都是在其基礎(chǔ)上發(fā)展實(shí)現(xiàn)的。
Spring之中最重要的當(dāng)屬Bean了,Spring實(shí)際上就是面向Bean的編程,Bean對(duì)于Spring的意義就好比Object對(duì)于OOP的意義一樣。那么,三個(gè)核心組件之間是如何協(xié)同工作的呢?如果把Bean比作一場(chǎng)演出中的演員,那么Context就是這場(chǎng)演出的舞臺(tái),Core就是演出的道具,至于演出的節(jié)目,就是Spring的一系列特色功能了。
我們知道Bean包裹的是Object,而Object中必然有數(shù)據(jù),Context就是給這些數(shù)據(jù)提供生存環(huán)境,發(fā)現(xiàn)每個(gè)Bean之間的關(guān)系,為他們建立并維護(hù)好這種關(guān)系。這樣來說,Context就是一個(gè)Bean關(guān)系的集合,這個(gè)關(guān)系集合就是我們所說的IOC容器。那么Core又有什么作用呢?Core就是發(fā)現(xiàn)、建立和維護(hù)每個(gè)Bean之間的關(guān)系所需的一系列工具,就是我們經(jīng)常說的Util。
Bean 組件
Bean組件在Spring的org.springframework.beans包下,主要完成了Bean的創(chuàng)建、Bean的定義以及Bean的解析三件事。
SpringBean的創(chuàng)建是典型的工廠模式,其工廠的繼承層次關(guān)系如圖所示:
Spring 使用工廠模式來管理程序中使用的對(duì)象(Bean),Bean 工廠最上層的接口為 BeanFactory,簡(jiǎn)單來看,工廠就是根據(jù)需要返回相應(yīng)的 Bean 實(shí)例。
- public interface BeanFactory {
- //...
- Object getBean(String name);
- }
在工廠模式中,在工廠的實(shí)現(xiàn)類中生成 Bean 返回給調(diào)用客戶端,這就要求客戶端提供生成自己所需類實(shí)例的工廠類,增加客戶負(fù)擔(dān)。Spring 結(jié)合控制反轉(zhuǎn)和依賴注入為客戶端提供所需的實(shí)例,簡(jiǎn)化了客戶端的操作。具體的實(shí)現(xiàn)方式大致如下。
- public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
- implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
- /** Map of bean definition objects, keyed by bean name */
- private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>;
- public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition){
- //...
- }
- }
beanDefinitionMap 作為具體的 Bean 容器,Spring 創(chuàng)建的對(duì)象實(shí)例保存其中??蛻舳诵枰獣r(shí),使用工廠的 getBean 方法去試圖得到相應(yīng)的實(shí)例,如果實(shí)例已存在,則返回該實(shí)例;如果實(shí)例不存在,則首先產(chǎn)生相應(yīng)實(shí)例并通過 registerBeanDefinition 方法將其保存在 beanDefinitionMap 中(Lazy Initialization),然后返回該實(shí)例給客戶端。
Spring Bean 工廠的繼承關(guān)系
beanDefinitionMap 并不直接保存實(shí)例本身,而是將實(shí)例封裝在 BeanDefinition 對(duì)象后進(jìn)行保存。BeanDefinition 包含了實(shí)例的所有信息,其簡(jiǎn)化版的定義如下。
- public class BeanDefinition {
- private Object bean;
- private Class<?> beanClass;
- private String beanClassName;
- // Bean 屬性字段的初始化值
- private BeanPropertyValues beanPropertyValues;
- //...
- }
Spring Bean 工廠生產(chǎn) Bean 時(shí)
- 先將實(shí)例的類型參數(shù)保存到 beanClass 和 beanClassName,將需要初始化的字段名和值保存到 beanPropertyValues 中,這個(gè)過程 Spring 通過控制反轉(zhuǎn)來實(shí)現(xiàn),本文第二小節(jié)將予以簡(jiǎn)要說明
- 生成 bean 實(shí)例,并利用反射機(jī)制將需要初始化的字段值寫入 bean 實(shí)例,將實(shí)例保存在 bean 中,完成 BeanDefinition 的構(gòu)建。
假設(shè)我們已經(jīng)完成了步驟 1) 的操作,之后的過程用代碼表述如下所示。
- public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition){
- //生成 bean 實(shí)例,并完成初始化
- Object bean = createBean(beanDefinition);
- //將 bean 實(shí)例保存在 beanDefinition 中
- beanDefinition.setBean(bean);
- //將 beanDefinition 實(shí)例保存在 Spring 容器中
- beanDefinitionMap.put(beanName, beanDefinition);
- }
- protected Object createBean(BeanDefinition beanDefinition) {
- try{
- Object bean = beanDefinition.getBeanClass().newInstance();
- try {
- setBeanPropertyValues(bean, beanDefinition);
- } catch (NoSuchFieldException | SecurityException | IllegalArgumentException e) {
- e.printStackTrace();
- }
- return bean;
- }catch(InstantiationException e){
- e.printStackTrace();
- }catch(IllegalAccessException e){
- e.printStackTrace();
- }
- return null;
- }
- protected void setBeanPropertyValues(Object bean, BeanDefinition beanDefinition) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException{
- for(PropertyValue pv : beanDefinition.getBeanPropertyValues().getBeanPropertyValues()){
- Field beanbeanFiled = bean.getClass().getDeclaredField(pv.getName());
- beanFiled.setAccessible(true);
- beanFiled.set(bean, pv.getValue());
- }
- }
Context 組件
前面說到,Context組件的作用是給Spring提供一個(gè)運(yùn)行時(shí)的環(huán)境,用以保存各個(gè)對(duì)象的狀態(tài),我們來看一下與Context相關(guān)的類結(jié)構(gòu)圖。
從圖中可以看出,Context類結(jié)構(gòu)的頂級(jí)父類是ApplicationContext,它除了能標(biāo)識(shí)一個(gè)應(yīng)用環(huán)境的基本信息以外,還繼承了5個(gè)接口,這5個(gè)接口主要是擴(kuò)展了Context的功能。ApplicationContext的子類主要包含兩個(gè)方向,圖中已作說明。再往下就是構(gòu)建Context的文件類型,接著就是訪問Context的方式。
一般地,傳統(tǒng)的程序設(shè)計(jì)中,無論是使用工廠創(chuàng)建實(shí)例,或是直接創(chuàng)建實(shí)例,實(shí)例調(diào)用者都要先主動(dòng)創(chuàng)建實(shí)例,而后才能使用??刂品崔D(zhuǎn)(Inverse of Control) 將實(shí)例的創(chuàng)建過程交由容器實(shí)現(xiàn),調(diào)用者將控制權(quán)交出,是所謂控制反轉(zhuǎn)。
依賴注入(Dependence Injection) 在控制反轉(zhuǎn)的基礎(chǔ)上更進(jìn)一步。如果沒有依賴注入,容器創(chuàng)建實(shí)例并保存后,調(diào)用者需要使用 getBean(String beanName) 才能獲取到實(shí)例。使用依賴注入時(shí),容器會(huì)將 Bean 實(shí)例自動(dòng)注入到完成相應(yīng)配置的調(diào)用者,供其進(jìn)一步使用。
Context 組件借助上述的控制反轉(zhuǎn)和依賴注入,協(xié)助實(shí)現(xiàn)了 Spring 的 Ioc 容器。下面我們以一個(gè) Service 類作為所需的 Bean 實(shí)例進(jìn)行說明。實(shí)際應(yīng)用中,我們會(huì)需要 Spring 管理很多 Bean 實(shí)例。
- public class SampleService {
- private String service;
- public String getService() {
- return service;
- }
- public void setService(String service) {
- this.service= service;
- }
- }
在程序運(yùn)行過程中,需要一個(gè) SampleService ,我們不讓調(diào)用者 new 一個(gè)實(shí)例,而是在配置文件中表明該 SampleService 的實(shí)例交由 Spring 容器進(jìn)行管理,并指定其初始化參數(shù)。配置文件即資源,其內(nèi)容如下。
- <?xml version="1.0" encoding="UTF-8"?>
- <beans>
- <bean name="sampleService " class="com.service.SampleService ">
- <property name="service" value="This is a service"></property>
- </bean>
- </beans>
Spring Core 組件提供 ResourceLoader 接口,便于讀入 xml 文件或其他資源文件。其核心功能代碼應(yīng)該提供如下方法。
- public class ResourceLoader {
- public Resource getResource(String location){
- URL resource = this.getClass().getClassLoader().getResource(location);
- return new UrlResource(resource);
- }
- }
- // UrlResource 的功能代碼
- public class UrlResource implements Resource {
- private final URL url;
- public UrlResource(URL url){
- this.url = url;
- }
- @Override
- public InputStream getInputStream() throws IOException {
- URLConnection urlurlConnection = url.openConnection();
- urlConnection.connect();
- return urlConnection.getInputStream();
- }
- }
即加載資源文件,并以數(shù)據(jù)流的形式返回。Context 根據(jù)資源中的定義,生成相應(yīng)的 bean 并保存在容器中,bean 的名字是 sampleService ,供程序進(jìn)一步使用。這樣就完成了控制反轉(zhuǎn)的工作。接下來就需要把 sampleService 注入到需要使用它的地方,亦即完成依賴注入操作。
現(xiàn)在假設(shè) SampleController 中使用 SampleService 的對(duì)象,Spring 提供三種依賴注入的方式,構(gòu)造器注入、setter 注入和注解注入。
- public class SampleController {
- /**
- * 3\. 注解注入
- **/
- /* @Autowired */
- private SampleService sampleService;
- /**
- * 1\. 構(gòu)造器注入
- **/
- public SampleController(SampleService sampleService){
- this.sampleService = sampleService;
- }
- //無參構(gòu)造函數(shù)
- public SampleController(){}
- // 類的核心功能
- public void process(){
- System.out.println(sampleService.getService());
- }
- /**
- * 2\. setter 注入
- **/
- /*public void setService(SampleService service) {
- this.service= service;
- }*/
- }
三種注入方式在配置文件中對(duì)應(yīng)不同的配置方式,在前面 xml 文件的基礎(chǔ)上,我們可以分別實(shí)現(xiàn)這三種注入方式。需要注意的是,這里 SampleController 也是使用 Spring 的 Ioc 容器生成管理的。
- <?xml version="1.0" encoding="UTF-8"?>
- <beans>
- <bean name="sampleService " class="com.service.SampleService ">
- <property name="service" value="This is a service"></property>
- </bean>
- <!-- 1\. 構(gòu)造器注入方式為SampleContorller 的 bean 注入 SampleService -->
- <bean name="sampleContorller" class="com.controller.SampleContorller">
- <!-- index 是構(gòu)造方法中相應(yīng)參數(shù)的順序 -->
- <constructor-arg index="0" ref="sampleService"></constructor-arg>
- </bean>
- <!-- 2\. setter 注入方式為SampleContorller 的 bean 注入 SampleService -->
- <!--
- <bean name="sampleContorller" class="com.controller.SampleContorller">
- <property name="sampleService " ref="sampleService"></property>
- </bean>
- -->
- <!-- 3\. 注解注入方式為SampleContorller 的 bean 注入 SampleService -->
- <!--
- <bean name="sampleContorller" class="com.controller.SampleContorller">
- <!-- 不需要配置,Spring 自動(dòng)按照類型注入相應(yīng)的 bean -->
- </bean>
- -->
- </beans>
Core組件
Core組件一個(gè)重要的組成部分就是定義了資源的訪問方式。Core組價(jià)把所有的資源都抽象成一個(gè)接口,這樣,對(duì)于資源使用者來說,不需要考慮文件的類型。對(duì)資源提供者來說,也不需要考慮如何將資源包裝起來交給別人使用(Core組件內(nèi)所有的資源都可以通過InputStream類來獲取)。另外,Core組件內(nèi)資源的加載都是由ResourceLoader接口完成的,只要實(shí)現(xiàn)這個(gè)接口就可以加載所有的資源。
那么,Context和Resource是如何建立關(guān)系的呢?通過前面Context的介紹我們知道,Context組件里面的類或者接口最終都實(shí)現(xiàn)了ResourcePatternResolver接口,ResourcePatternResolver接口的作用就是加載、解析和描述資源。這個(gè)接口相當(dāng)于Resource里面的一個(gè)接頭人,它把Resource里的資源加載、解析和定義整合到一起,便于其他組件使用。
前面介紹了三大核心組件的結(jié)構(gòu)與相互關(guān)系,那么,這三大組件是如何讓Spring完成諸如IOC和AOP等各種功能的呢?敬請(qǐng)期待下一篇文章!


































