配置綁定的場景
最常見的配置綁定的場景华蜒,是在自定義的bean中通過@Value
注解將某個屬性和對應的配置綁定:
application.yml
di-task:
root-path: "/usr/lib/cluster001/..."
某個bean
@Component
public class TaskApplicationConfig {
@Value("${di-task.root-path}")
private String rootPath;
...
}
在創(chuàng)建定時任務的bean時,使用的@Scheduled
注解也可以通過相同的方式綁定配置途样,本質(zhì)上和上面是相同的。
@Component
public class InitStatTask {
@Scheduled(cron = "${di-task.task.init-stat.cron}")
public void initTask() {
// do business
}
}
批量綁定
@ConfigurationProperties
提供了更強大的功能徊哑,比如在我們需要批量綁定的時候硼啤,可以將多個配置項一起綁定到某個bean的多個屬性上,而不用每一個屬性都使用一個@Value
注解:
@Component
@ConfigurationProperties(prefix = "hbm")
public class HbmConfig {
/**
* CM master 的IP
*/
private String agentHostIp;
/**
* CM master 的帳號.
*/
private String agentHostUser;
/**
* CM master 的密碼.
*/
private String agentHostPassword;
/**
* CM master 的端口.
*/
private Integer sshdPort;
// getters and setters
}
這種定義横堡,暗示著應用中存在下面的配置:
hbm:
agent-host-ip: ...
agent-host-user: ...
agent-host-password: ...
sshd-port: ...
這樣埋市,四個屬性就可以一次性綁定到一個spring bean上。
為第三方依賴綁定配置
上面的示例命贴,都有一個共同的特點:在自定義的bean中綁定配置道宅。更直白一點,作為spring bean的代碼是項目內(nèi)的代碼胸蛛,不是第三方引入的依賴污茵。
其中一個特征,就是我們直接在類上使用諸如@Component
葬项、@Service
泞当、@RestController
的注解來聲明為spring的bean。
如果是引入的第三方依賴民珍,要實例化為spring的bean襟士,需要我們自己進行配置。在當前項目環(huán)境中嚷量,這種類一般很少陋桂,直接寫配置類就可以了:
@Configuration
public class SomeConfig {
@Bean
public SomeType someType() {
SomeType bean = new SomeType();
// init bean
return bean;
}
}
只要這個配置類能被@SpringBootApplication
或@ComponentScan
掃描到,對應的bean就會被創(chuàng)建蝶溶。
順便提一下嗜历,如果我們在配置bean的時候,需要注入其他的bean依賴抖所,可以將依賴作為參數(shù)梨州。spring會根據(jù)類型和參數(shù)名來尋找對應的bean,然后作為參數(shù)參入進來:
@Configuration
public class SomeConfig {
@Bean
public SomeType someType() {
SomeType bean = new SomeType();
// init bean
return bean;
}
@Bean
public OtherType otherType(SomeType someType) {
OtherType bean = new OtherType();
// init bean with some type
return bean;
}
}
一個新的問題:當我們需要為第三方依賴的bean綁定配置的時候部蛇,應該如何做摊唇。
手工綁定
基于已有的知識,我們可以將配置項先綁定到自己的代碼涯鲁,然后再調(diào)用第三方依賴的setter方法巷查,將對應的配置一一設置到bean中:
@Configuration
@ConfigurationProperties(prefix = "some-type")
public class SomeConfig {
private String field1;
private String field2;
...
// getters and setters for field*
@Bean
public SomeType someType() {
SomeType bean = new SomeType();
// init bean
bean.setField1(field1);
bean.setField2(field2);
...
return bean;
}
}
對應的配置大概下面的樣子:
some-type:
field1: ...
field2: ...
自動綁定
@ConfigurationProperties
簡化了這個工作,我們可以將它和@Bean
一起使用抹腿,這種綁定的工作就會被自動完成:
@Configuration
public class SomeConfig {
@Bean
@ConfigurationProperties(prefix = "some-type")
public SomeType someType() {
SomeType bean = new SomeType();
// init bean
return bean;
}
}
spring會在someType()
執(zhí)行完之后岛请,按照默認的綁定策略將配置綁定到返回的bean中。其效果和上文中我們逐個將配置綁定到bean中是一樣的警绩。
需要注意的是崇败,spring按照setter的規(guī)范去綁定變量,配置的key和bean的setter方法要相呼應。比如某個bean有屬性jdbcUrl
后室,但對應的setter方法是setUrl
缩膝,那么配置文件中應該配置的是url
而不是jdbc-url
。
如果setter方法在bean對應的類型的父類中岸霹,綁定同樣有效疾层。
可選擇的綁定
我們可能會有不同的場景:允許用戶選擇使用哪一個數(shù)據(jù)源的實現(xiàn),比如druid或者hikari贡避。我們需要根據(jù)用戶選擇的數(shù)據(jù)源類型痛黎,來配置DataSource
的實例化和參數(shù)綁定的動作。
換句話來說刮吧,上文中someType()
方法可能返回的具體類型有多種湖饱,而使用@ConfigurationProperties
的前提是我知道返回的具體是哪一種,以根據(jù)其setter方法設計對應的配置杀捻。所以我們所說的上述場景井厌,就無法直接使用了。
選擇并手工綁定
如果我們退一步水醋,依然可以通過顯示的逐個綁定來完成這項工作旗笔。首先,我們將所有的數(shù)據(jù)源類型的相關參數(shù)拄踪,都一一綁定到我們自己的類里面,然后在構造數(shù)據(jù)源Bean的時候拳魁,根據(jù)用戶配置的類型來構建具體的數(shù)據(jù)源Bean惶桐,并將對應的配置參數(shù)通過setter方法進行設置。代碼大意大概如下:
spring.datasource:
type: druid
druid:
url: jdbc:hive2://10.3.70.118:10000
driver-class-name: org.apache.hive.jdbc.HiveDriver
hikari:
jdbc-url: jdbc:hive2://10.3.70.118:10000
driver-class-name: org.apache.hive.jdbc.HiveDriver
connection-test-query: SELECT 'di-sql-engine'
你可能注意到druid和hikari的配置有些不同潘懊,這是與具體的實現(xiàn)類及其父類所提供的setter方法相關的姚糊。
@ConfigurationProperties(prefix = "spring.datasource.druid")
public class DruidConfig {
private String url;
private String driverClassName;
...
// getters and setters for field*
}
@ConfigurationProperties(prefix = "spring.datasource.hikari")
public class HikariConfig {
private String jdbcUrl;
private String driverClassName;
private String connectionTestQuery;
...
// getters and setters for field*
}
@Configuration
@ConfigurationProperties(prefix = "spring.datasource")
public class SomeConfig {
@Autowired
private DruidConfig druidConfig;
@Autowired
private HikariConfig hikariConfig;
private String type;
...
// getters and setters for field*
@Bean
public DataSource datasource() {
DataSource ds = null;
switch(type) {
case "druid":
DruidDataSource druid = ... // init druid datasource
druid.setUrl(druidConfig.getUrl);
druid.setDriverClassName(druidConfig.getDriverClassName);
ds = druid;
break;
case "hikari":
// ...
break;
}
...
return ds;
}
}
上面的代碼應該可以自己想得到,它只是組合了我們已知的知識授舟。
選擇并自動綁定
但是在我們接觸了自動綁定之后救恨,再手動逐個綁定看起來有點蠢。接下來介紹另一種更加可控的自動綁定方式释树,幫助我們消除那些無聊的代碼肠槽。
依然是基于同樣的配置,我們可以這樣編寫配置類:
@Configuration
@ConfigurationProperties(prefix = "spring.datasource")
public class SomeConfig {
@Autowired
private Environment environment;
private String type;
...
// getters and setters for field*
@Bean
public DataSource datasource() {
Class<? extends DataSource> classType = resolveFromType(type);
DataSource dataSource = DataSourceBuilder.create()
.type(
(Class<? extends DataSource>) Class.forName(classType)
).build();
final Bindable<? extends DataSource> bindable =
Bindable.ofInstance(dataSource);
final Binder binder = Binder.get(environment);
switch(type) {
case "druid":
binder.bind("spring.datasource.druid", bindable);
break;
case "hikari":
binder.bind("spring.datasource.hikari", bindable);
break;
}
...
return dataSource;
}
}
關鍵的代碼是:
binder.bind("spring.datasource.druid", bindable);
// or
binder.bind("spring.datasource.hikari", bindable);
只是為了要獲取關鍵代碼中的binder和bindable奢啥,需要學習關于Bindable和Binder的相關API秸仙。
簡單來說,Bindable包裝了待綁定的bean桩盲,它的構造也是基于已有的bean來構造的寂纪。Binder需要基于當前的Environment來構造,這樣它可以讀到相關的環(huán)境變量和配置。Environment是自動配置的捞蛋,我們可以直接注入孝冒。
這樣我們就實現(xiàn)了自動綁定,而且是根據(jù)場景去綁定不同的配置拟杉。
附錄
補充問題
為什么hikari要配置connection-test-query庄涡?
提出這個問題的同學很細心。這是因為我們連接的是hive數(shù)據(jù)源捣域,所使用的驅(qū)動包是hive-jdbc-1.2.1.spark2啼染。這個驅(qū)動包里的Connection實現(xiàn)類,即HiveConnection并沒有真正實現(xiàn)所有的抽象方法焕梅。而沒有實現(xiàn)的那些方法迹鹅,就直接拋出異常:
@Override
public boolean isValid(int timeout) throws SQLException {
// TODO Auto-generated method stub
throw new SQLException("Method not supported");
}
而使用hikari數(shù)據(jù)源,在沒有配置connection-test-query的情況下贞言,它會調(diào)用Connection的isValid方法來驗證Connection的可用性斜棚,結(jié)果就是得到一個異常。
所以我們主動配置了connection-test-query该窗,讓它使用這個sql語句來驗證弟蚀。