前言
前面介紹了SpringBoot的自動配置原理,用一句話概括是:啟動時載入所有,最終按照條件進行裝配。本章節表面上是講資料訪問,但其核心還是講SpringBoot的自動配置,只不過自動配置的物件是資料庫相關的依賴(如:druid、MyBatis、MyBatis-Plugs等)。這些依賴的匯入與裝配都是SpringBoot幫我們自動完成的。
SpringBoot預設使用的資料來源是Hikari
(下面有原始碼分析),以後我們將使用阿里的Druid
資料來源進行資料庫相關配置與操作。
在本篇,我們可以知道:
- 資料來源的自動配置原理(2.);
- SpringBoot整合第三方技術的兩種方式(3.);
- SpringBoot在處理自動配置時的邏輯(3.2.2);
- Druid資料來源自動配置原理(3.2.2);
可以直接跳轉至第四點總結那檢視原始碼結構圖。
1. 資料來源的自動配置
我們先基於SpringBoot預設的HikariDataSource資料來源,匯入JDBC場景,看看SpringBoot幫我們自動配置了什麼。
首先匯入JDBC場景依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
匯入JDBC場景依賴後,我們可以在Maven的Dependencies依賴裡看出spring-boot-starter-data-jdbc
自動幫我們引入了資料來源、JDBC與事務相關jar包。
少了資料庫連線驅動依賴
我們可以發現,這其中沒有資料庫連線驅動依賴,道理很簡單,SpringBoot並不知道我們要使用什麼資料庫(MySQL還是Oracle或其他)。
直接在pom.xml檔案裡新增資料庫驅動依賴即可
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
我們不用關注資料庫驅動的版本,在SpringBoot裡使用版本仲裁自動匹配( 資料庫版本和驅動版本對應 ),當然,我們也可以自定義版本。
自定義連線驅動版本:
-
pom.xml
裡dependency
直接依賴引入具體版本(maven的就近依賴原則)<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.49</version> </dependency>
-
pom.xml
裡properties
重新宣告版本(maven屬性的就近優先原則)<properties> <java.version>1.8</java.version> <mysql.version>5.1.49</mysql.version> </properties>
2. *資料來源自動配置原始碼分析
基於上面的例子,我們來剖析SpringBoot資料來源的自動裝配原理。
由於資料來源的配置是SpringBoot幫我們自動配置的,因此我們在外部依賴庫裡找到jdbc相關的自動配置:
往下翻翻找到jdbc的包:
可以看到引入jdbc相關依賴後SpringBoot幫我們引入了很多自動配置類,如:
-
DataSourceAutoConfiguration
:資料來源的自動配置類; -
DataSourceTransactionManagerAutoConfiguration
:事務管理器的自動配置; -
JdbcTemplateAutoConfiguration
:JdbcTemplate的自動配置,可以來對資料庫進行crud(JdbcTemplate是Spring對JDBC的封裝,目的是使JDBC更加易於使用); -
JndiDataSourceAutoConfiguration
:jndi的自動配置; -
XADataSourceAutoConfiguration
:分散式事務相關的自動配置; -
等......
我們對其中兩個進行分析:
2.1 DataSourceAutoConfiguration:資料來源自動配置類
解釋了SpringBoot底層的資料來源是Hikari;
其中DataSourceAutoConfiguration
是資料來源的自動配置類,我們點進去看原始碼,發現其定義了一些靜態方法,其中底層資料來源相關的是:
@Configuration(proxyBeanMethods = false)
@Conditional({DataSourceAutoConfiguration.PooledDataSourceCondition.class})
@ConditionalOnMissingBean({DataSource.class, XADataSource.class})
@Import({Hikari.class, Tomcat.class, Dbcp2.class, OracleUcp.class, Generic.class, DataSourceJmxConfiguration.class})
protected static class PooledDataSourceConfiguration {
protected PooledDataSourceConfiguration() {
}
}
其中老熟人@ConditionalOnMissingBean
註解的含義是當容器內沒有DataSource資料來源時,才進行下面的自動配置預設的資料來源;而@Import
註解則說明了我們要引入的預設資料來源是Hikari資料來源。也就是說,在我們不做任何處理的情況下,SpringBoot為我們底層配置好的連線池是:HikariDataSource
。
2.2 JdbcTemplateAutoConfiguration:JdbcTemplate的自動配置類
說明了jdbc配置的語法:包括字首與使用者可配置的屬性有哪些;
JdbcTemplate是Spring對JDBC的封裝,目的是使JDBC更加易於使用;
JdbcTemplateAutoConfiguration原始碼:
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({DataSource.class, JdbcTemplate.class})
@ConditionalOnSingleCandidate(DataSource.class)
@AutoConfigureAfter({DataSourceAutoConfiguration.class})
@EnableConfigurationProperties({JdbcProperties.class})
@Import({JdbcTemplateConfiguration.class, NamedParameterJdbcTemplateConfiguration.class})
public class JdbcTemplateAutoConfiguration {
public JdbcTemplateAutoConfiguration() {
}
}
從@EnableConfigurationProperties
註解可知,自動配置的相關屬性在JdbcProperties.class
類裡,我們點進去看看:
通過原始碼我們可以知道以下資訊:
- 可以在application.properties檔案裡修改jdbc相關屬性,字首是:
spring.jdbc
- 可配置項有:
- fetchSize:為jdbc驅動程式提供一個提示,它提示此Statement生成的ResultSet物件需要更多行時應該從資料庫獲取的行數。
- maxRows:將此Statement 物件生成的所有ResultSet物件可以包含的最大行數限制設定為給定數。
- queryTimeout:超時設定。
- 等……
2.3 修改資料來源的配置項
這裡直接給出示例:
spring:
datasource:
url: jdbc:mysql://localhost:3306/db_account
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
# 預設配置,可不寫
# type: com.zaxxer.hikariDataSource
測試類如下:
@Slf4j
@SpringBootTest
class Boot05WebAdminApplicationTests {
@Autowired
JdbcTemplate jdbcTemplate;
@Test
void contextLoads() {
// jdbcTemplate.queryForObject("select * from account_tbl")
// jdbcTemplate.queryForList("select * from account_tbl",)
Long aLong = jdbcTemplate.queryForObject("select count(*) from account_tbl", Long.class);
log.info("記錄總數:{}",aLong);
}
}
3. 配置Druid資料來源的兩種方式
在實際生產中,我們一般使用阿里的Druid替換預設的Hikari資料來源。
Druid官方github地址為:https://github.com/alibaba/druid
在SpringBoot中,整合第三方技術有兩種方式:自定義與starter。在實際生產中一般使用starter,它只需要引入一個xxx-spring-boot-starter依賴就好,能幫我們省去很多配置工作。但我們也可能遇到自定義配置的需求,因此這兩種方法都會提及。
3.1 自定義配置Druid資料來源
重點不在這,這裡只列出相關步驟即可;
1) 首先引入druid資料來源依賴
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.17</version>
</dependency>
2) SpringBoot採用編寫配置類的方式
在config包下新建MyDataSourceConfig類
@Configuration
public class MyDataSourceConfig {
// 預設的自動配置是判斷容器中沒有才會配@ConditionalOnMissingBean(DataSource.class),即有@Bean後容器自動配置不生效
@ConfigurationProperties("spring.datasource") //繫結屬性
@Bean
public DataSource dataSource() throws SQLException {
DruidDataSource druidDataSource = new DruidDataSource();
//在註冊自己的資料來源時要給核心屬性賦值,又因為不能寫死在程式碼裡,故抽取到配置檔案裡
// druidDataSource.setUrl();
// druidDataSource.setUsername();
// druidDataSource.setPassword();
//加入監控功能(可寫進配置檔案)
druidDataSource.setFilters("stat,wall");
//設定最大活躍執行緒數(可寫進配置檔案)
druidDataSource.setMaxActive(10);
return druidDataSource;
}
/**
* 配置 druid的監控頁功能
* @return
*/
@Bean
public ServletRegistrationBean statViewServlet(){
StatViewServlet statViewServlet = new StatViewServlet();
ServletRegistrationBean<StatViewServlet> registrationBean = new ServletRegistrationBean<>(statViewServlet, "/druid/*");
//配置初始化引數:需要賬號密碼才能登陸檢視監控頁面
registrationBean.addInitParameter("loginUsername","admin");
registrationBean.addInitParameter("loginPassword","123456");
return registrationBean;
}
/**
* WebStatFilter 用於採集web-jdbc關聯監控的資料
*/
@Bean
public FilterRegistrationBean webStatFilter(){
WebStatFilter webStatFilter = new WebStatFilter();
FilterRegistrationBean<WebStatFilter> filterRegistrationBean = new FilterRegistrationBean<>(webStatFilter);
//設定攔截路徑
filterRegistrationBean.setUrlPatterns(Arrays.asList("/*"));
filterRegistrationBean.addInitParameter("exclusions","*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
return filterRegistrationBean;
}
}
優化寫法:凡是setxxx方法都可以在配置檔案中說明,優化後可在配置檔案中直接說明。Spring原生配置方式:
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close">
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<property name="maxActive" value="20" />
<property name="initialSize" value="1" />
<property name="maxWait" value="60000" />
<property name="minIdle" value="1" />
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<property name="minEvictableIdleTimeMillis" value="300000" />
<property name="testWhileIdle" value="true" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<property name="poolPreparedStatements" value="true" />
<property name="maxOpenPreparedStatements" value="20" />
3.2 自動配置Druid資料來源及原始碼分析
在實際生產中,我們大部分時候使用自動配置方式;相比上面繁瑣而複雜的工作,自動配置就簡單很多了,這裡先列出相關步驟方法在,再做原始碼分析,看看SpringBoot為我們幹了些什麼。
3.2.1 自動配置Druid資料來源
這裡也是直接給出示例程式碼;
1) 引入druid-starter
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.17</version>
</dependency>
*小知識:第三方提供的Starter統一用xxx-spring-boot-starter;而官方提供的Starter統一用spring-boot-starter-xxx。
2) 直接配置即可
spring:
datasource: #資料來源的基本屬性
url: jdbc:mysql://localhost:3306/db_account
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
druid: #druid資料來源相關配置
aop-patterns: com.atguigu.admin.* #監控SpringBean
filters: stat,wall # 底層開啟功能,stat(sql監控),wall(防火牆)
stat-view-servlet: # 配置監控頁功能
enabled: true #預設false,需要手動開啟
login-username: admin #登入的使用者名稱
login-password: admin #登入的密碼
resetEnable: false #禁用重置按鈕
web-stat-filter: # 監控web
enabled: true
urlPattern: /* #匹配的路徑
exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*' #排除的路徑
filter:
stat: # 對上面filters裡面的stat的詳細配置
enabled: true
slow-sql-millis: 1000 #慢查詢時間,超過1000ms的查詢都是慢查詢
logSlowSql: true #是否使用日誌記錄慢查詢
wall: # 對上面filters裡面的wall的詳細配置
enabled: true
config:
drop-table-allow: false
SpringBoot配置官方示例:https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter
3.2.2 *自動配置的原始碼分析
按上面操作就能把jdbc配置好了,但我們需要知道兩個問題:
1.為什麼引入一個依賴就能將jdbc配置好,換句話說引入這個依賴後SpringBoot幫我們幹了些什麼?
2.使用者可修改的配置項有哪些?
這些問題可以在下面的原始碼分析中找到答案。
【核心原理】原理概述:
-
匯入starter場景啟動器後,根據SpringBoot的設定模式,首先找到
META-INF
包下的spring.factories
工廠,通過讀取EnableAutoConfiguration
獲取啟動時載入的類 :XXXAutoConfiguration
自動配置類; -
自動配置類會利用
@Bean
註解把場景下相關元件註冊進容器中,這些元件的核心配置項會通過@EnableConfigurationProperties
註解跟XXXProperties
配置檔案繫結; -
由此我們可以獲得配置類(XXXAutoConfiguration)與配置項(XXXProperties)資訊。
-
配置類(XXXAutoConfiguration)裡配置了核心元件;
-
配置項(XXXProperties)裡主要包含兩個資訊。其一是通過
@ConfigurationProperties
註解可以獲取配置檔案的字首(prefix=Constants.XXX);其二是配置項可修改的引數(YYY)名稱及引數(ZZZ)。我們在yml裡通過[字首.引數名稱=引數](XXX.YYY=ZZZ)修改預設引數;
-
【案例分析】Druid資料來源自動配置的實現:
上述原理在Druid資料來源自動配置中簡而言之就是:匯入starter場景啟動器,根據SpringBoot的設定模式,會有DruidDataSourceAutoConfigure自動配置類,自動配置類會把場景下相關元件註冊進容器中,相關元件的核心配置項跟配置檔案繫結。
引入Druid的stater依賴後,可以在META-INF
包下的spring.factories
工廠裡找到Druid資料來源的自動配置類:
按住“Ctrl+左鍵”點進去檢視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();
}
}
這裡有幾個需要注意的點:
-
@AutoConfigureBefore(DataSourseAutoConfiguration.class)
語句的含義是:在SpringBoot自動配置資料來源前先配置Druid資料來源;這是一個優先順序的關係,即Druid場景的自動配置類優先執行;- 原因:官方DataSourseAutoConfiguration裡配置了Hikari資料來源,只有在容器裡沒有資料來源時才能配置資料來源。(詳情見本篇2.1點)
@ConditionalOnMissingBean public DataSource dataSource(){ return new DruidDataSourceWrapper(); }
- 原因:官方DataSourseAutoConfiguration裡配置了Hikari資料來源,只有在容器裡沒有資料來源時才能配置資料來源。(詳情見本篇2.1點)
-
@Import()
註解給我們匯入了以下元件:-
DruidSpringAopConfiguration.class
:利用AOP配置SpringBean監控的相關元件;- 配置項:spring.datasource.druid.aop-patterns
-
DruidStatViewServletConfiguration.class
:監控頁相關配置;- 配置項:spring.datasource.druid.stat-view-servlet,預設開啟
-
DruidWebStatFilterConfiguration.class
:web監控配置;- 配置項:spring.datasource.druid.web-stat-filter:預設開啟
-
DruidFilterConfiguration.class
:所有Druid預設filter的配置;private static final String FILTER_STAT_PREFIX = "spring.datasource.druid.filter.stat"; private static final String FILTER_CONFIG_PREFIX = "spring.datasource.druid.filter.config"; private static final String FILTER_ENCODING_PREFIX = "spring.datasource.druid.filter.encoding"; private static final String FILTER_SLF4J_PREFIX = "spring.datasource.druid.filter.slf4j"; private static final String FILTER_LOG4J_PREFIX = "spring.datasource.druid.filter.log4j"; private static final String FILTER_LOG4J2_PREFIX = "spring.datasource.druid.filter.log4j2"; private static final String FILTER_COMMONS_LOG_PREFIX = "spring.datasource.druid.filter.commons-log"; private static final String FILTER_WALL_PREFIX = "spring.datasource.druid.filterwall";
-
-
在DataSource的構造方法裡,給我們new了一個DruidDataSourceWrapper(Druid資料來源包裝器);點進去檢視原始碼:
- 可以知道:擴充套件配置項(繫結屬性)在
spring.datasource.druid
裡; - 包裝器主要作用是讀取使用者在application檔案裡的字首為spring.datasource.druid.XXX,屬性為XXX的值,並用這些值對Druid資料來源進行配置;
- 可以知道:擴充套件配置項(繫結屬性)在
DruidDataSourceWrapper原始碼:
@ConfigurationProperties("spring.datasource.druid")
class DruidDataSourceWrapper extends DruidDataSource implements InitializingBean {
@Autowired
private DataSourceProperties basicProperties;
DruidDataSourceWrapper() {
}
public void afterPropertiesSet() throws Exception {
if (super.getUsername() == null) {
super.setUsername(this.basicProperties.determineUsername());
}
if (super.getPassword() == null) {
super.setPassword(this.basicProperties.determinePassword());
}
if (super.getUrl() == null) {
super.setUrl(this.basicProperties.determineUrl());
}
if (super.getDriverClassName() == null) {
super.setDriverClassName(this.basicProperties.getDriverClassName());
}
}
@Autowired(
required = false
)
public void autoAddFilters(List<Filter> filters) {
super.filters.addAll(filters);
}
public void setMaxEvictableIdleTimeMillis(long maxEvictableIdleTimeMillis) {
try {
super.setMaxEvictableIdleTimeMillis(maxEvictableIdleTimeMillis);
} catch (IllegalArgumentException var4) {
super.maxEvictableIdleTimeMillis = maxEvictableIdleTimeMillis;
}
}
}
4. 總結
在本篇裡我們知道了資料來源的自動配置原理,其原理結構圖如下:
- org.springframework.boot.autoconfigure.jdbc:SpringBoot自動配置包
- DataSourceAutoConfiguration:資料來源的自動配置類;
- DataSourceTransactionManagerAutoConfiguration:事務管理器的自動配置;
- JdbcTemplateAutoConfiguration:JdbcTemplate的自動配置;
- JndiDataSourceAutoConfiguration:jndi的自動配置;
- XADataSourceAutoConfiguration:分散式事務相關的自動配置;
以及Druid資料來源自動配置原理,其原始碼結構圖如下:
- DruidDataSourceAutoConfigure:Druid資料來源自動配置類;
- @AutoConfigureBefore():優先配置Druid資料來源;
- @Import():匯入元件;
- DruidSpringAopConfiguration.class:利用AOP配置SpringBean監控的相關元件;
- DruidStatViewServletConfiguration.class:監控頁相關配置;
- DruidWebStatFilterConfiguration.class:web監控配置;
- DruidFilterConfiguration.class:所有Druid預設filter的配置;
- DruidDataSourceWrapper:Druid資料來源包裝器;
最重要的是知道了SpringBoot整合第三方技術的兩種方式:
- 自定義;
- starter(推薦);
對SpringBoot的自動配置邏輯也有一定的認識:
-
匯入starter場景啟動器後,根據SpringBoot的設定模式,首先找到
META-INF
包下的spring.factories
工廠,通過讀取EnableAutoConfiguration
獲取啟動時載入的類 :XXXAutoConfiguration
自動配置類; -
自動配置類會利用
@Bean
註解把場景下相關元件註冊進容器中,這些元件的核心配置項會通過@EnableConfigurationProperties
註解跟XXXProperties
配置檔案繫結; -
由此我們可以獲得配置類(XXXAutoConfiguration)與配置項(XXXProperties)資訊。
- 配置類(XXXAutoConfiguration)裡配置了核心元件;
- 配置項(XXXProperties)裡主要包含兩個資訊。其一是通過
@ConfigurationProperties
註解可以獲取配置檔案的字首(prefix=Constants.XXX);其二是配置項可修改的引數(YYY)名稱及引數(ZZZ)。我們在yml裡通過[字首.引數名稱=引數](XXX.YYY=ZZZ)修改預設引數;