侯培彬 / 微服務 / 自定義springboot

0 0

   

自定義springboot

2020-03-09  侯培彬

在編寫分布式微服務架構項目的時候,我們一般在一個idea的project里創建多個獨立的module,有時候我們多個服務的數據庫、連接池的、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的自動配置已經極大的為我們避免了xml地獄,我們已經少寫了很多東西,但如果在每一個module的application.yml中都再寫一遍依然很麻煩,維護起來也不方便。

如果能把這些都集中起來做成一個starter,所有需要數據庫功能的服務就可以依賴這個starter實現自動配置,并且如果某個服務的數據庫信息和自動配置的基礎信息不一致,比如username是root2,starter中的基礎配置是root,那么就只需要在自己的yml中設置username即可覆蓋starter的基礎配置中的username。

本例中我使用的是Druid,其實無論使用哪種連接池,最后應該都是通過其自動配置將自己的DataSource實現放入到spring容器中,所以spring.datasource下的信息應該是在Druid的autoConfigure類中讀取到,并設置到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實現, 而且還加了@EnableConfigurationProperties({DruidStatProperties.class, DataSourceProperties.class})注解,這時候DataSourceProperties就已經被加載到spring容器中,這個類中就保存著spring.datasource下的信息,其源碼如下:

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

那么實現思路就簡單了,只需要在我們自己的starter中創建一個自動配置類,讓其被加載順序優先于Druid的DruidDataSourceAutoConfigure,并在我們的配置類中搶先一步加載DataSourceProperties,判斷下我們starter自己的基礎配置中的信息在DataSourceProperties里是否是空值。
如果是空值,就代表當前依賴此starter項目的application.yml中沒有填寫此值,我們就可以把starter中的信息set進DataSourceProperties,等到DruidDataSourceAutoConfigure被加載時,它創建的DataSource就是用的我們搶先修改過的DataSourceProperties,這樣就實現了項目中可以一句配置都不用寫,只要引入了我們的starter依賴就會為其自動配置,而如果項目中寫了配置就又可以覆蓋starter的配置。
具體實現如下:

@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等信息,是的我們的確沒有寫,但是我們在創建DruidDataSource之前已經把starter的這些信息設置進DataSourceProperties了,所以url、driverClass等信息不應該為空才對。

經過斷點調試發現,我們DaoAutoConfigure的注解@AutoConfigureBefore({DruidDataSourceAutoConfigure.class})并沒有起作用,DaoAutoConfigure是在DruidDataSourceAutoConfigure之后被加載,根據springboot自動配置原理,我們的DaoAutoConfigure和依賴此starter的項目包名并不一樣,是不會被@ComponentScan搗亂加載順序的,而且DaoAutoConfigure和DruidDataSourceAutoConfigure都不是普通的Configuration,都是在spring.factories中注冊過的,順序不應該亂才對。

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

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

調試發現我們的DaoAutoConfigure的確是排在DruidDataSourceAutoConfigure之前,順序并沒有亂,但是在真正加載的過程中卻亂了,繼續斷點跟蹤到DefaultListableBeanFactory的實例化bean的doCreateBean方法發現,當加載到一個項目中的controller類時,DruidDataSourceAutoConfigure也被插隊加載了,問題的根源就在這個controller類,代碼如下:

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

因為TestController類里注入了UserMapper,而UserMapper會依賴并加載mybatis,mybatis又會依賴并加載DataSource,而DataSource又在DruidDataSourceAutoConfigure中創建的,根據springboot自動配置原理,controller、service、component加載會優先于所有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的自動配置就完全實現了,剩下的就可以依照此思路接著去寫Druid、Mybatis、PageHelper等其他庫的自動配置了,我這里就不多啰嗦了。

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


    本站是提供個人知識管理的網絡存儲空間,所有內容均由用戶發布,不代表本站觀點。如發現有害或侵權內容,請點擊這里 或 撥打24小時舉報電話:4000070609 與我們聯系。

    猜你喜歡

    0條評論

    發表

    請遵守用戶 評論公約

    類似文章 更多
    喜歡該文的人也喜歡 更多

    fun888 <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <文本链> <文本链> <文本链> <文本链> <文本链> <文本链>