自定義springboot-starter打造通用數(shù)據(jù)庫基礎(chǔ)配置思路及實現(xiàn)

在編寫分布式微服務(wù)架構(gòu)項目的時候,我們一般在一個idea的project里創(chuàng)建多個獨立的module员辩,有時候我們多個服務(wù)的數(shù)據(jù)庫频轿、連接池的偷仿、mybatis等框架的依賴和配置大部分都可能相同,比如以下依賴和配置:

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
        </dependency>
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/hehehe?serverTimezone=GMT%2B8
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: root
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      max-active: 20
      min-idle: 8
mybatis:
  mapper-locations: classpath:mapper/*.xml
  configuration:
    map-underscore-to-camel-case: true
pagehelper:
  reasonable: true
  helper-dialect: mysql
...

雖然springboot的自動配置已經(jīng)極大的為我們避免了xml地獄衣屏,我們已經(jīng)少寫了很多東西躏升,但如果在每一個module的application.yml中都再寫一遍依然很麻煩,維護起來也不方便狼忱。

如果能把這些都集中起來做成一個starter膨疏,所有需要數(shù)據(jù)庫功能的服務(wù)就可以依賴這個starter實現(xiàn)自動配置,并且如果某個服務(wù)的數(shù)據(jù)庫信息和自動配置的基礎(chǔ)信息不一致藕赞,比如username是root2成肘,starter中的基礎(chǔ)配置是root,那么就只需要在自己的yml中設(shè)置username即可覆蓋starter的基礎(chǔ)配置中的username斧蜕。

本例中我使用的是Druid双霍,其實無論使用哪種連接池,最后應(yīng)該都是通過其自動配置將自己的DataSource實現(xiàn)放入到spring容器中批销,所以spring.datasource下的信息應(yīng)該是在Druid的autoConfigure類中讀取到洒闸,并設(shè)置到Druid的DataSource中,查看DruidDataSourceAutoConfigure源碼如下:

@Configuration
@ConditionalOnClass({DruidDataSource.class})
@AutoConfigureBefore({DataSourceAutoConfiguration.class})
@EnableConfigurationProperties({DruidStatProperties.class, DataSourceProperties.class})
@Import({DruidSpringAopConfiguration.class, DruidStatViewServletConfiguration.class, DruidWebStatFilterConfiguration.class, DruidFilterConfiguration.class})
public class DruidDataSourceAutoConfigure {
    private static final Logger LOGGER = LoggerFactory.getLogger(DruidDataSourceAutoConfigure.class);

    public DruidDataSourceAutoConfigure() {
    }

    @Bean(
        initMethod = "init"
    )
    @ConditionalOnMissingBean
    public DataSource dataSource() {
        LOGGER.info("Init DruidDataSource");
        return new DruidDataSourceWrapper();
    }
}

可以看到DruidDataSourceAutoConfigure向spring容器中添加了Druid的DataSource實現(xiàn)均芽, 而且還加了@EnableConfigurationProperties({DruidStatProperties.class, DataSourceProperties.class})注解丘逸,這時候DataSourceProperties就已經(jīng)被加載到spring容器中,這個類中就保存著spring.datasource下的信息掀宋,其源碼如下:

@ConfigurationProperties(
    prefix = "spring.datasource"
)
public class DataSourceProperties implements BeanClassLoaderAware, InitializingBean {
    private ClassLoader classLoader;
    private String name;
    private boolean generateUniqueName;
...

那么實現(xiàn)思路就簡單了深纲,只需要在我們自己的starter中創(chuàng)建一個自動配置類,讓其被加載順序優(yōu)先于Druid的DruidDataSourceAutoConfigure劲妙,并在我們的配置類中搶先一步加載DataSourceProperties湃鹊,判斷下我們starter自己的基礎(chǔ)配置中的信息在DataSourceProperties里是否是空值。
如果是空值镣奋,就代表當前依賴此starter項目的application.yml中沒有填寫此值币呵,我們就可以把starter中的信息set進DataSourceProperties,等到DruidDataSourceAutoConfigure被加載時侨颈,它創(chuàng)建的DataSource就是用的我們搶先修改過的DataSourceProperties余赢,這樣就實現(xiàn)了項目中可以一句配置都不用寫芯义,只要引入了我們的starter依賴就會為其自動配置,而如果項目中寫了配置就又可以覆蓋starter的配置妻柒。
具體實現(xiàn)如下:

@Configuration
@ConditionalOnClass({DruidDataSource.class})
@AutoConfigureBefore({DruidDataSourceAutoConfigure.class})
@EnableConfigurationProperties({DataSourceProperties.class, DaoProperties.class})
public class DaoAutoConfigure {

    public DaoAutoConfigure(DataSourceProperties dataSourceProperties, DaoProperties daoProperties) {
        if (Strings.isBlank(dataSourceProperties.getUrl())) {
            dataSourceProperties.setUrl(daoProperties.getJdbc().getUrl());
        }
        if (Strings.isBlank(dataSourceProperties.getDriverClassName())) {
            dataSourceProperties.setDriverClassName(daoProperties.getJdbc().getDriverClassName());
        }
        if (Strings.isBlank(dataSourceProperties.getUsername())) {
            dataSourceProperties.setUsername(daoProperties.getJdbc().getUsername());
        }
        if (Strings.isBlank(dataSourceProperties.getPassword())) {
            dataSourceProperties.setPassword(daoProperties.getJdbc().getPassword());
        }
        if (dataSourceProperties.getType() == null) {
            dataSourceProperties.setType(daoProperties.getJdbc().getType());
        }
         ...
    }
}

@AutoConfigureBefore({DruidDataSourceAutoConfigure.class})注解代表在DruidDataSourceAutoConfigure之前加載扛拨。

但是實際只這么寫是有問題的,當項目依賴此starter后啟動時會報以下錯誤:

2019-01-16 18:51:02.803 ERROR 20272 --- [           main] o.s.b.d.LoggingFailureAnalysisReporter   : 

***************************
APPLICATION FAILED TO START
***************************

Description:

Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.

Reason: Failed to determine a suitable driver class


Action:

Consider the following:
    If you want an embedded database (H2, HSQL or Derby), please put it on the classpath.
    If you have database settings to be loaded from a particular profile you may need to activate it (no profiles are currently active).

這個錯誤比較容易理解蛤奢,大致意思是說不能配置DataSource鬼癣,因為我們沒有提供url、driverClass等信息啤贩,是的我們的確沒有寫待秃,但是我們在創(chuàng)建DruidDataSource之前已經(jīng)把starter的這些信息設(shè)置進DataSourceProperties了,所以url痹屹、driverClass等信息不應(yīng)該為空才對章郁。

經(jīng)過斷點調(diào)試發(fā)現(xiàn),我們DaoAutoConfigure的注解@AutoConfigureBefore({DruidDataSourceAutoConfigure.class})并沒有起作用志衍,DaoAutoConfigure是在DruidDataSourceAutoConfigure之后被加載暖庄,根據(jù)springboot自動配置原理,我們的DaoAutoConfigure和依賴此starter的項目包名并不一樣楼肪,是不會被@ComponentScan搗亂加載順序的培廓,而且DaoAutoConfigure和DruidDataSourceAutoConfigure都不是普通的Configuration,都是在spring.factories中注冊過的春叫,順序不應(yīng)該亂才對肩钠。

再次斷點調(diào)試DefaultListableBeanFactory,這個類中的beanDefinitionNames字段保存有排好序所有待spring容器加載的beanName

public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
...
    private volatile List<String> beanDefinitionNames = new ArrayList(256);
...
}

調(diào)試發(fā)現(xiàn)我們的DaoAutoConfigure的確是排在DruidDataSourceAutoConfigure之前暂殖,順序并沒有亂价匠,但是在真正加載的過程中卻亂了,繼續(xù)斷點跟蹤到DefaultListableBeanFactory的實例化bean的doCreateBean方法發(fā)現(xiàn)呛每,當加載到一個項目中的controller類時踩窖,DruidDataSourceAutoConfigure也被插隊加載了,問題的根源就在這個controller類晨横,代碼如下:

@RestController
public class TestController {
    @Autowired
    private UserMapper userMapper;

因為TestController類里注入了UserMapper洋腮,而UserMapper會依賴并加載mybatis,mybatis又會依賴并加載DataSource手形,而DataSource又在DruidDataSourceAutoConfigure中創(chuàng)建的啥供,根據(jù)springboot自動配置原理,controller叁幢、service、component加載會優(yōu)先于所有autoConfigure坪稽,所以就導致了AutoConfigureBefore的失效曼玩,DruidDataSourceAutoConfigure進行了彎道超車鳞骤。

知道了這一點解決起來就很簡單了,由我們的DaoAutoConfigure接手向容器中注入DataSource就可以了黍判,而且DruidDataSourceAutoConfigure的

    @ConditionalOnMissingBean
    public DataSource dataSource() {

添加了ConditionalOnMissingBean注解豫尽,也不會重復注入,修改后的DaoAutoConfigure如下:

@Configuration
@ConditionalOnClass({DruidDataSource.class})
@AutoConfigureBefore({DruidDataSourceAutoConfigure.class})
@EnableConfigurationProperties({DataSourceProperties.class, DaoProperties.class})
public class DaoAutoConfigure {

    public DaoAutoConfigure(DataSourceProperties dataSourceProperties, DaoProperties daoProperties) {
        if (Strings.isBlank(dataSourceProperties.getUrl())) {
            dataSourceProperties.setUrl(daoProperties.getJdbc().getUrl());
        }
        if (Strings.isBlank(dataSourceProperties.getDriverClassName())) {
            dataSourceProperties.setDriverClassName(daoProperties.getJdbc().getDriverClassName());
        }
        if (Strings.isBlank(dataSourceProperties.getUsername())) {
            dataSourceProperties.setUsername(daoProperties.getJdbc().getUsername());
        }
        if (Strings.isBlank(dataSourceProperties.getPassword())) {
            dataSourceProperties.setPassword(daoProperties.getJdbc().getPassword());
        }
        if (dataSourceProperties.getType() == null) {
            dataSourceProperties.setType(daoProperties.getJdbc().getType());
        }
    }

    @Bean(initMethod = "init")
    @ConditionalOnMissingBean
    public DataSource dataSource() {
        return DruidDataSourceBuilder.create().build();
    }
}

到此DataSource的自動配置就完全實現(xiàn)了顷帖,剩下的就可以依照此思路接著去寫Druid美旧、Mybatis、PageHelper等其他庫的自動配置了贬墩,我這里就不多啰嗦了榴嗅。

最后:請盡情體驗springboot-starter-autoconfigure帶來的美妙體驗吧


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市陶舞,隨后出現(xiàn)的幾起案子嗽测,更是在濱河造成了極大的恐慌,老刑警劉巖肿孵,帶你破解...
    沈念sama閱讀 218,525評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件唠粥,死亡現(xiàn)場離奇詭異,居然都是意外死亡停做,警方通過查閱死者的電腦和手機晤愧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蛉腌,“玉大人官份,你說我怎么就攤上這事∶继В” “怎么了贯吓?”我有些...
    開封第一講書人閱讀 164,862評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長蜀变。 經(jīng)常有香客問我悄谐,道長,這世上最難降的妖魔是什么库北? 我笑而不...
    開封第一講書人閱讀 58,728評論 1 294
  • 正文 為了忘掉前任爬舰,我火速辦了婚禮,結(jié)果婚禮上寒瓦,老公的妹妹穿的比我還像新娘情屹。我一直安慰自己,他們只是感情好杂腰,可當我...
    茶點故事閱讀 67,743評論 6 392
  • 文/花漫 我一把揭開白布垃你。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪惜颇。 梳的紋絲不亂的頭發(fā)上皆刺,一...
    開封第一講書人閱讀 51,590評論 1 305
  • 那天,我揣著相機與錄音凌摄,去河邊找鬼羡蛾。 笑死,一個胖子當著我的面吹牛锨亏,可吹牛的內(nèi)容都是我干的痴怨。 我是一名探鬼主播,決...
    沈念sama閱讀 40,330評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼器予,長吁一口氣:“原來是場噩夢啊……” “哼浪藻!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起劣摇,我...
    開封第一講書人閱讀 39,244評論 0 276
  • 序言:老撾萬榮一對情侶失蹤珠移,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后末融,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體钧惧,經(jīng)...
    沈念sama閱讀 45,693評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,885評論 3 336
  • 正文 我和宋清朗相戀三年勾习,在試婚紗的時候發(fā)現(xiàn)自己被綠了浓瞪。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,001評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡巧婶,死狀恐怖乾颁,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情艺栈,我是刑警寧澤英岭,帶...
    沈念sama閱讀 35,723評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站湿右,受9級特大地震影響诅妹,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜毅人,卻給世界環(huán)境...
    茶點故事閱讀 41,343評論 3 330
  • 文/蒙蒙 一吭狡、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧丈莺,春花似錦划煮、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽器躏。三九已至,卻和暖如春蟹略,著一層夾襖步出監(jiān)牢的瞬間邀桑,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評論 1 270
  • 我被黑心中介騙來泰國打工科乎, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人贼急。 一個月前我還...
    沈念sama閱讀 48,191評論 3 370
  • 正文 我出身青樓茅茂,卻偏偏與公主長得像,于是被迫代替她去往敵國和親太抓。 傳聞我的和親對象是個殘疾皇子空闲,可洞房花燭夜當晚...
    茶點故事閱讀 44,955評論 2 355

推薦閱讀更多精彩內(nèi)容