天天看點

Spring AutoConfiguration解密[譯]

本文是<code>http://sivalabs.in/2016/03/how-springboot-autoconfiguration-magic/</code>的翻譯。

我們知道springboot的應用可以以非常簡潔的代碼去做很多的事情, 可以自動幫你注入資料庫的bean,消息隊列的bean等等等等,那麼springboot是怎麼做到的呢?

但是在探索springboot的神秘之前,我們先了解一下spring的<code>@conditional</code>注解,這是springboot的<code>autoconfiguration</code>的神奇之處所依賴的底層機制。

我們開發spring應用的時候有時候會碰到要<code>根據外界條件注冊不同bean</code>的情況。

比如如果你的應用是跑在本地機器的話,你會想要讓你的<code>datasource</code> bean指向一個本地的資料庫,而如果是跑線上上機器的話,你會想讓<code>datasource</code> bean指向一個生産的資料庫。

你可以把這些資料庫連接配接資訊抽取到幾個不同的配置檔案裡面去,然後在不同的環境下使用不同的配置檔案。但是當你的應用被部署到新的環境下的時候,你還是要添加新的配置檔案,并且重新打包。(譯者注:這裡其實隻要把配置檔案抽取到代碼之外不就好了麼?不知道原作者在想什麼)

為了解決這個問題,spring 3.1引入了<code>profiles</code>的概念。你可以注冊同一個類型bean的不同執行個體,然後把這些不同的執行個體綁定到不同的<code>profile</code>, 當你運作這個spring應用的時候,你可以指定你要激活的<code>profile</code>, 這樣隻有跟這些被激活的<code>profile</code>相關聯的bean才會被注冊:

然後你可以通過系統屬性指定激活的profile:

這種方式對于你要基于profile來決定是否注冊一個bean的情況工作得很好。但是如果你要基于一些條件性的判斷邏輯來決定是否注冊一個bean的話,那麼光靠profile是不行的。

為了給條件性地注冊bean提供更高的靈活性,spring 4提供了<code>@conditional</code>的概念。通過使用<code>@conditional</code>你可以基于任何條件來決定是否注冊一個bean。

你的<code>條件</code>可能是這樣的:

classpath裡面有一個特定的class

applicationcontext裡面有一個特定類型的bean

在指定的位置有指定的檔案

配置檔案裡面有指定的配置項

系統屬性裡面配置了指定的屬性

這些隻是我能想到的一些,實際上你可以基于<code>任何</code>條件。下面讓我們來看看spring的<code>@conditional</code>到底是如何工作的。我們先設定一個場景:

我們有一個<code>userdao</code>接口用來從資料庫裡面擷取資料。這個接口我們有兩個實作: <code>jdbcuserdao</code>從<code>mysql</code>資料庫裡面擷取資料;<code>mongouserdao</code>從<code>mongodb</code>裡面擷取資料。

我們想通過一個名為<code>dbtype</code>的系統屬性來決定到底使用<code>jdbcuserdao</code>還是<code>mongouserdao</code>。期望的效果是,如果通過<code>java -jar myapp.jar -ddbtype=mysql</code>那麼使用的是<code>jdbcuserdao</code>, 如果通過<code>java -jar myapp.jar -ddbtype=mongo</code>啟動,這使用<code>mongouserdao</code>。幾個類的實作是這樣的:

我們可以實作這樣的一個<code>mysqldatabasetypecondition</code>來檢測系統屬性<code>dbtype</code>是否是<code>mysql</code>:

類似的<code>mongodbdatabasetypecondition</code>:

現在我們就可以通過<code>@conditional</code>來判斷使用<code>jdbcuserdao</code>還是<code>mongouserdao</code>:

類似地我們可以通過判斷classpath裡面是否有<code>com.mongodb.server</code>這個driver類來決定是使用<code>mongouserdao</code>還是<code>jdbcuserdao</code>:

如果我們隻在容器裡面沒有任何類型的<code>userdao</code>的bean的時候才注冊<code>mongouserdao</code>。我們通過建立一個condition來檢測是否存在一個指定類型的bean:

如果想通過配置檔案裡面的配置來決定dao類型呢?

我們已經試了通過各種不同條件來實作<code>condition</code>。但是其實有更優雅的、通過注解來實作condition的方式。我們不再為<code>mysql</code>和<code>mongodb</code>實作各自<code>condition</code>, 我們可以實作下面這樣一個<code>databasetype</code>注解:

然後我們實作<code>databasetypecondition</code>來使用這個注解來判斷dao的類型了:

現在我們就可以在我們的bean上使用<code>databasetype</code>注解了:

這裡我們從<code>databasetype</code>注解裡面擷取中繼資料,并且把擷取的值跟系統屬性裡面的<code>dbtype</code>進行對比來決定是否激活bean。

我們已經看了很多例子來看<code>@conditional</code>注解的作用。springboot大量的使用<code>@conditional</code>來實作基于條件的注冊bean。你可以在<code>spring-boot-autoconfigure-{version}.jar</code> 的 <code>org.springframework.boot.autoconfigure</code> 包裡面看到springboot使用的大量的<code>condition</code>的實作。

那麼我們已經知道了springboot使用<code>@conditional</code>來決定是否初始化一個bean, 但是是什麼機制觸發了<code>auto-configuration</code>機制呢? 我們下一節來聊聊這個事情:

springboot的自動配置的關鍵在于<code>@enableautoconfiguration</code>這個注解。一般來說我們把我們程式的入口類加上<code>@springbootapplication</code>注解,或者如果我們想要更加細緻的定制這些預設值的話:

<code>@enableautoconfiguration</code>注解通過掃描classpath裡面所有的元件,然後基于條件來決定是否注冊bean來使得spring的applicationcontext自動配置。

springboot在<code>spring-boot-autoconfigure-{version}.jar</code>裡面提供了很多<code>autoconfiguration</code>的類來負責注冊各種不同的元件。

一般來說<code>autoconfiguration</code>類上面會标上<code>@configuration</code>注解來标明它是一個spring的配置類,标上<code>@enableconfigurationproperties</code>來綁定自定義的配置值,并且會在一到多個方法上标上<code>@conditional</code>注解來标記注冊方法。

比如我們來看看<code>org.springframework.boot.autoconfigure.jdbc.datasourceautoconfiguration</code>:

這裡<code>datasourceautoconfiguration</code>上标記了一個<code>@conditionalonclass({ datasource.class,embeddeddatabasetype.class })</code>, 這樣隻有當classpath上有<code>datasource.class</code>和<code>embeddeddatabasetype.class</code>的時候,<code>datasourceautoconfiguration</code> 才會生效。

這個類還被<code>@enableconfigurationproperties(datasourceproperties.class)</code>标記了,這樣<code>application.properties</code>裡面相關的配置值會自動綁定到<code>datasourceproperties</code>上面。

有了這個配置屬性類,所有<code>spring.datasource.*</code>的配置都會自動綁定到<code>datasourceproperties</code>:

你還能看到一些内部類以及bean的定義方法被springboot的<code>@conditionalonmissingbean</code>, <code>@conditiononclass</code>, <code>@conditionalonproperty</code>等等标記。

這些bean的定義隻有在那些條件滿足的時候才會注冊。

在<code>spring-boot-autoconfigure-{version}.jar</code>裡面你還能看到其它的<code>autoconfiguration</code>類:

<code>org.springframework.boot.autoconfigure.web.dispatcherservletautoconfiguration</code>

<code>org.springframework.boot.autoconfigure.orm.jpa.hibernatejpaautoconfiguration</code>

<code>org.springframework.boot.autoconfigure.data.jpa.jparepositoriesautoconfiguration</code>

<code>org.springframework.boot.autoconfigure.jackson.jacksonautoconfiguration</code>

等等。我希望通過今天文章的介紹大家已經了解springboot是怎麼做到自動配置bean的了。