SpringBoot的幕後推手...

阿豪聊乾貨發表於2018-06-01

一、背景

​  這兩年隨著微服務的盛行,SpringBoot框架水到渠成的得到了高曝光,作為程式猿的我們,現在要是不知道一點SpringBoot相關的東西,貌似出去找工作都會被深深地鄙視,不過在我們開始SpringBoot之旅前,我們先回顧和探索一下Spring框架的本質,然後一點點的很自然的過渡到SpringBoot,這樣對於我們深刻理解SpringBoot也是非常有幫助的。

二、Spring框架的起源

​  在“黑暗”的EJB1的時代,開發人員非常痛快,這時候解放開發人員的高效能Spring框架千呼萬喚始出來。那是一個J2EE規範統治的時代,基於各種容器和J2EE規範的軟體解決方案是唯一的“正道”,臃腫的生態和沉重的開發模式讓當時的每一個開發都痛不欲生。這時候技術大牛Rod Johnson在自己的經典鉅作《Expert One-on-One J2EE Design and Development》中描繪了輕量級框架的研發理念,抨擊了原有的笨重的規範,然後基於這本書中的研發理念開發出了最初版的Spring框架,然後火的一塌糊塗,一直延續至今,已10多年之久仍舊沒有衰敗之勢。

​   Spring框架是構建高效能Java研發體系的最佳實踐之一,通過一系列簡潔而統一的設計,為廣大Java開發人員劈開了一條光明之路。

​   Spring對Java開發中常用的技術做了合理的封裝和設計,包括我們所熟知的Spring IoC和AOP等,可以讓Java開發者避免往日因為API和系統設計不合適而出現的錯誤,還能高效高質量的完成相應問題領域中的開發工作,此乃Java開發必備神器也~

三、沒想到Spring IoC 竟如此簡單

​  我相信很多Java開發者對IoC(Inversion Of Control) 和DI(Dependency Injection)的概念都傻傻分不清楚,認為這兩者是同一個東西。其實它倆是包含和被包含關係,IoC有兩種方式:DI和DL(Dependency Lookup 依賴查詢),DI是當前軟體實體被動接受它所依賴的其他元件被IoC容器注入,而DL是當前實體主動去某個服務註冊中心去查詢其依賴的那些元件,概念之間的關係如下圖:

  

​  我們經常說的Spring IoC其實是指Spring框架給我們提供的IoC容器實現(IoC Container)。下面我們看一個Spring IoC容器使用的一個經典案例:

public class Demo {
    public static void main(String[] args) {
        ApplicationContext context = new FileSystemXmlApplicationContext("config-file-path")
        DemoService service = context.getBean(DemoService.class);
        service.doSomething();
    }
}

我相信每一個使用Spring框架構建的獨立的Java應用,通常都會存在類似context.getBean(...)的程式碼,其實這行程式碼做的事情就是DL,而且構建每一種IoC容器背後發生的事情,更多的是DI的過程,當前也可能會有部分DL的邏輯用來對接舊的遺留系統。

Spring的IoC容器的依賴注入工作分為兩步走:

階段一、收集和註冊Bean

這個階段中,開發者通過XML或者Java程式碼的方式來定義bean,然後以手動組裝或讓容器基於特定的機制自動掃描的形式,將這些定義好的bean收集到IoC容器中。

假如我們以XML配置的方式來收集和註冊如下一個單一bean,一般來說形式如下:

<bean id="DemoService" class="x.x.DemoService">
    ...
</bean>

隨著我們專案中bean越來越多,這樣逐個手動配置比較麻煩,我們還可以使用如下方式配置來批量掃描並採集和註冊一批bean:

<context:component-scan base-package="x.x"/>

階段二、分析和組裝

  第一個階段完成以後,我們可以先暫時認為IoC容器中儲存著一個個相互獨立的bean,它們之間還沒有任何關係,但是實際專案中它們之間是有著不可或缺的關係的,所以呢,Ioc容器第二個階段需要做的工作就是分析這些已經在IoC容器中bean,根據它們的依賴關係先後按順序組裝它們,工作原來是這樣的:IoC發現一個bean依賴另外一個bean,那麼它會將另一個bean注入給依賴它的那個bean,一直到所有的bean的依賴都完成注入。這個時候容器中所有的bean都已經準備好待使用,也就標誌著整個IoC容器的工作完成。

  那麼IoC容器分析和組裝的依據是啥呢?Spring框架其實最早的時候只能通過XML配置檔案來描述bean和bean之間的關係,但是隨著Java生態研發技術以及理念的轉變,又出現了基於Java程式碼和Annotation元資訊的描述方式(比如@AutoWired和@Inject)。但是呢,無論使用哪一種配置方式,目的都是為了簡化繫結邏輯描述的各種表象,最終也都是為本階段的最終目的來服務。

四、JavaConfig是個什麼鬼?

​  Java 5的出世,加上當時基於純Java Annatation的依賴注入框架Guice的出現,就使得Spring框架和社群不得不順應民意,出版並持續完善了基於Java程式碼和Annotation元資訊的依賴關係繫結描述方式,就是JavaConfig專案。

基於JavaConfig方式的依賴關係描述基本對映了早期基於XML方式的配置,比如:

1. 表達形式

XML配置方式如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://www.springframework.org/schema/beans"         
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
         http://www.springframework.org/schema/beans/spring-beans-4.0.xsd 
         http://www.springframework.org/schema/context 
         http://www.springframework.org/schema/context/spring-context.xsd">
    // bean定義
</beans>

JavaConfig的配置方式如下:

@Configuration
public class DemoConfiguration {
    // bean定義
}

也就是說任何一個被標註了@Configuration註解的Java類都是一個JavaConfig配置類。

2. 註冊bean定義

XML配置方式:

<bean id="demoService" class="x.x.DemoServiceImpl">
    ...
</bean>

JavaConfig配置方式:

@Configuration
public class DemoConfiguration {
    @Bean
    public DemoService demoService() {
        return new DemoServiceImpl();
    }
}

也就是說任何一個標註了@Bean註解的方法,它的返回值就將作為一個bean定義註冊到Spring的IoC容器,方法名將預設成為該bean在容器中的id.

3. 表達依賴注入關係

XML配置形式:

<bean id="aService" class="x.x.AServiceImpl">
<bean id="bService" class="x.x.BServiceImpl">
    <property name="dependencyService" ref="aService"/>
</bean>

JavaConfig形式:

@Configuration
public class DemoConfiguration {
    @Bean
    public AService aService() {
        return new AServiceImpl();
    }
    @Bean
    public BService bService() {
        return new BServiceImpl(aService());
    }
}

也就是說如果一個bean定義了依賴其他bean,就直接呼叫其對應JavaConfig類中依賴bean的建立方法即可。

從以上種種表象我們可以看出,以前Spring IoC容器中具有的特性在JavaConfig中都可以進行表述,只是換了另外的一種形式而已。並且通過宣告相應的Java Annotation反而“內聚”一起了,變得更加簡潔了。

五、那些常用的Annotation

1. @ComponentScan

​  該註解對應的是XML配置中的<context:component-scan>元素,用於配合一些元資訊Java Annotation,比如@Component@Repository等,將標註了這些註解的bean定義類批量採集到Spring的IoC容器中。

​  我們可以通過basePackage等屬性來細粒度的定製該註解自動掃描的範圍,如果沒有指定的話,則預設Spring框架會從宣告@ComponentScan註解所在的類的package進行掃描

​  這裡還要說的@ComponentScan是SpringBoot框架得以實現的一個重要元件,我們以後還能在碰到它,也會做深入講解。

2. @PropertySource@PropertySources

​  註解@PropertySource用來從指定的地方來載入.properties配置檔案,並且將其中的屬性載入到IoC容器中,以便我們能用來填充一些bean定義的屬性佔位符(placeholder),當然它的實現需要PropertySourcesPlaceHolderConfigurer的配合。

  若我們使用Java8或者更高的版本,那麼我們可以並行宣告多個@PropertySource,如:

@Configuration
@PropertySource("classpath:1.properties")
@PropertySource("classpath:2.properties")
@PropertySource("...")
public class XConfiguration {
    ...
}

  若我們使用低於Java8版本的JDK進行開發Spring應用,我們就必須藉助@PropertySources註解來實現宣告多個@PropertySource了,如下:

@Configuration
@PropertySources({
    @PropertySource("classpath:1.properties"),
    @PropertySource("classpath:2.properties"),
    ...
})
public class XConfiguration {
    ...
}

3. @Import@ImportSource

  在以前XML配置方式中,我們可以通過<import resource="xxx.xml"/>來將多個分開的容器配置合併到一個配置中,在JavaConfig形式的配置中,我們可以使用@Import這個註解完成同樣的目的:

@Configuration
@Import(DemoConfiguration.class)
public class XConfiguration {
    ...
}

  註解@Import只能將以JavaConfig形式定義的配置引入到IoC容器,而若我們有一些以前遺留的配置或者遺留的系統需要以XML形式來配置(如Dubbo框架),我們就需要使用@ImportSource註解來將它們一起合併到以JavaConfig配置形式配置的容器中:

@Configuration
@Import
@ImportSource(...)
public class XConfiguration {
    ...
}

六、總結

​  通過本文,我們就回顧了Spring框架以及IoC的概念剖析,還了解到了SpringBoot的實現基石:JavaConfig。然後還介紹了在SpringBoot中最常用幾個註解。為我們以後學習和快速上手SpringBoot打下了良好的基礎。加油,筒子們…

相關文章