Spring Boot 如何覆蓋自動(dòng)配置
本文轉(zhuǎn)載自微信公眾號(hào)「七哥聊編程」,作者七哥。轉(zhuǎn)載本文請(qǐng)聯(lián)系七哥聊編程公眾號(hào)。
本文提供完整代碼示例,詳見(jiàn) https://gitee.com/isevenluo/spring-road/ 的 spring-road03 目錄。
1. 緣起
眾所周知,Spring Boot 提供一個(gè)牛逼哄哄的特性,幫助我們少寫(xiě)了很多模板化的配置代碼,這個(gè)特性就是:自動(dòng)配置。
比如 Spring Data JPA,Spring Security 只要引入相關(guān)的依賴(lài)包,就會(huì)幫助我們自動(dòng)配置好數(shù)據(jù)源 Bean 和 安全設(shè)置相關(guān)的 Bean,不用我們自己寫(xiě)配置就實(shí)現(xiàn)了相應(yīng)的功能。
但是具體應(yīng)用中,我們的實(shí)際情況往往都是比較復(fù)雜的,僅僅通過(guò) Spring 自動(dòng)配置是不能滿足我們的需求的。就拿安全配置來(lái)說(shuō),在 Classpath 添加 Spring Security 后,默認(rèn)為我們應(yīng)用程序提供的安全設(shè)施是比較基礎(chǔ)且粗暴的。我們?cè)?Web 中訪問(wèn)應(yīng)用程序,就會(huì)看到如下類(lèi)似的身份驗(yàn)證對(duì)話框:
此處的用戶名是 user,密碼就比較蛋疼了,在應(yīng)用每次啟動(dòng)時(shí)隨機(jī)生成寫(xiě)入到日志里面了:
Spring Security 自動(dòng)配置生成的密碼
雖然我們的應(yīng)用已經(jīng)算是一個(gè)安全的 Web 應(yīng)用程序了,但是也有如下顯著缺點(diǎn):
- 頁(yè)面太簡(jiǎn)陋,這個(gè)對(duì)話框很不友好呀;
- 應(yīng)用程序只有一個(gè)登錄用戶;
- 用戶密碼還要在應(yīng)用啟動(dòng)日志中查看(默認(rèn)用戶名是user,密碼是在應(yīng)用啟動(dòng)時(shí)隨機(jī)生成寫(xiě)入日志的);
那如何調(diào)整自動(dòng)配置讓 Spring 按照我們的需要進(jìn)行配置呢?別著急,我們接著往下看。
一般我們覆蓋 Spring Boot 默認(rèn)自動(dòng)配置的方式有兩種:自定義配置Bean、修改外置屬性進(jìn)行配置。
2. 自定義配置Bean覆蓋自動(dòng)配置
覆蓋自動(dòng)配置的第一種方法呀,就是我們當(dāng)自動(dòng)配置不存在,自己手動(dòng)配置對(duì)應(yīng)的 Spring Bean。配置方式可以選擇 XML,也可以用 Java 形式的配置。
今天七哥就選擇目前更加流行的 Java 形式的配置,來(lái)演示一下如何覆蓋我們上面所描述的自動(dòng)配置的 Spring Security。
我們所要做的很簡(jiǎn)單,那就是寫(xiě)一個(gè)擴(kuò)展類(lèi)。
- @Configuration
- @EnableWebSecurity
- public class SecurityConfig extends WebSecurityConfigurerAdapter {
- private Logger logger = LoggerFactory.getLogger(SecurityConfig.class);
- @Resource
- private ViewerRepository viewerRepository;
- @Override
- protected void configure(AuthenticationManagerBuilder auth) throws Exception {
- auth.userDetailsService(username -> {
- viewerRepository.save(new Viewer(username, "{noop}123","ADMIN"));
- logger.info(viewerRepository.findViewerByUsername(username).toString());
- return viewerRepository.findViewerByUsername(username);
- });
- }
- }
上面的代碼中的 SecurityConfig 是個(gè)非?;A(chǔ)的 Spring Security 配置,有了它我們的 Spring Boot 應(yīng)用程序就跳過(guò)了安全自動(dòng)配置,不再使用默認(rèn)的用戶名和密碼,而是使用我們定義的用戶和密碼來(lái)鑒權(quán)。
當(dāng)然上面的代碼展示的不全,但是剩下的類(lèi)都不是重點(diǎn),我還定義了一個(gè) Viewer 類(lèi),實(shí)現(xiàn)了 UserDetails 類(lèi)(Spring Security 中的用戶接口),還有一個(gè) Spring Data JPA 倉(cāng)庫(kù)接口,用來(lái)保存用戶信息。詳細(xì)的代碼,都已經(jīng)上傳到開(kāi)源倉(cāng)庫(kù)了,有需要的小伙伴自取哦 ~
??? 這里我們?cè)俅螐?qiáng)調(diào)一遍:想要覆蓋 Spring Boot 的自動(dòng)配置,我們只需要寫(xiě)一個(gè)顯式的配置,這樣 Spring Boot 就會(huì)發(fā)現(xiàn)我們的配置,然后就會(huì)降低自動(dòng)配置的優(yōu)先級(jí),以我們自己寫(xiě)的為準(zhǔn)。
3. 通過(guò)配置屬性來(lái)調(diào)整自動(dòng)配置
這種配置屬性文件的方式,相比于上面這種自定義 Bean 配置要簡(jiǎn)單很多。比如假設(shè)我們僅僅要 調(diào)整一個(gè)數(shù)據(jù)庫(kù)的 URL、Spring Security 的默認(rèn)用戶名、應(yīng)用日志的級(jí)別,如果都像上面那樣覆蓋自動(dòng)配置,自己完整的聲明一個(gè) Bean,這就有點(diǎn)傻了!畢竟配置一個(gè)屬性要比完整寫(xiě)一個(gè) Bean 的配置要簡(jiǎn)單的多。
那如何配置屬性呢?
Spring Boot 應(yīng)用程序支持的配置源有很多,比如說(shuō)環(huán)境變量、命令行參數(shù),當(dāng)然最常見(jiàn)的還是屬性文件里配置。
接下來(lái)給大家演示幾個(gè)很常見(jiàn)的例子。
- Spring Boot 應(yīng)用程序啟動(dòng)時(shí),命令行會(huì)打印一個(gè) Banner,如果你想禁用這個(gè) Banner,可以將 spring.main.banner-mode 屬性設(shè)置為 off 即可;
添加屬性前:
在屬性文件 application.properties 中添加屬性 spring.main.banner-mode=off 后:
結(jié)果已經(jīng)打開(kāi)在控制臺(tái)了,啟動(dòng)時(shí)的 Banner 已經(jīng)不見(jiàn)了。
- 調(diào)整配置應(yīng)用程序的日志;
Spring Boot 默認(rèn)使用 Logback來(lái)記錄日志,并且用 INFO 級(jí)別輸出到控制臺(tái)。Logback一般情況下能很好的滿足我們的需要,但是這里為了演示,假如我們要使用 Log4j2 替換默認(rèn)的 Logback實(shí)現(xiàn),需要怎么配置呢?
以 Gradle 為例,我們只需要修改起步依賴(lài),在構(gòu)建說(shuō)明文件中引入對(duì)應(yīng)的日志實(shí)現(xiàn)然后排除掉 Logback。
- configurations {
- all*.exclude group:'org.springframework.boot' , module:'spring-boot-starter-logging'
- }
上面我們排除掉默認(rèn)的日志依賴(lài)后,就可以引入我們需要的 Log4j2 日志依賴(lài)。
- dependencies {
- implementation 'org.springframework.boot:spring-boot-starter-log4j2'
- }
接下來(lái)如果還需要做一個(gè)常規(guī)的操作:修改日志級(jí)別和指定日志輸出的文件。
當(dāng)然我們以往的做法就是自己添加一個(gè) log4j2.xml 文件,用來(lái)配置日志相關(guān)的信息,雖然這樣可以讓我們完全掌握應(yīng)用程序的日志配置,但是僅僅修改日志級(jí)別和日志輸出的文件則可以完全不用創(chuàng)建 log4j2.xml 文件,使用屬性配置就可以實(shí)現(xiàn)。
我們?cè)?Spring Boot 應(yīng)用程序的 application.properties 文件中加入 logging 開(kāi)頭的屬性就可以滿足我們期望。
- // 指定日志級(jí)別為debug
- logging.level.root=debug
- // 指定日志文件路徑
- logging.file.name=/Users/sevenluo/IdeaProjects/spring-road/spring-labs/spring-road03/logs/spring-road03.log
至此,說(shuō)明了通過(guò)上面的屬性配置也實(shí)現(xiàn)了我們對(duì)于 Spring Boot 應(yīng)用程序自動(dòng)配置的微調(diào)。
4. 總結(jié)
今天我們介紹了兩種覆蓋 Spring Boot 自動(dòng)配置的方法。
第一種就是自定義配置 Bean,實(shí)現(xiàn)原理是通過(guò) Spring 的條件化配置,這塊有一個(gè)非常重要的注解就是 @ConditionalOnMissingBean ,意思是如果我們的 classpath 中沒(méi)有發(fā)現(xiàn)相應(yīng)類(lèi)型的 Bean,Spring Boot 才會(huì)幫我們自動(dòng)配置一個(gè)。Spring Boot 的設(shè)計(jì)是優(yōu)先加載我們應(yīng)用里面配置的類(lèi),然后在考慮自動(dòng)配置類(lèi)。
第二種就是通過(guò)配置屬性來(lái)調(diào)整 Spring Boot 自動(dòng)配置,實(shí)現(xiàn)原理就是設(shè)定不同屬性源里配置的屬性?xún)?yōu)先級(jí)不同,而我們應(yīng)用程序?qū)傩晕募刑砑拥膶傩耘渲脙?yōu)先級(jí)高于默認(rèn)屬性,所以實(shí)現(xiàn)了調(diào)整自動(dòng)配置的目的。
今天就先聊到這里,更多關(guān)于 Spring 相關(guān)的內(nèi)容,也在持續(xù)更新中,敬請(qǐng)關(guān)注!