Spring Boot 項目是不是經常失敗,顯示一大堆的錯誤資訊,如端口重複綁定時會列印以下異常:
***************************
APPLICATION FAILED TO START
***************************
Description:
Embedded servlet container failed to start. Port 8080 was already in use.
Action:
Identify and stop the process that's listening on port 8080 or configure this application to listen on another port.
這個大家應該很熟悉了吧!
錯誤資訊大家都能看懂,但很不友好,那麼,Spring Boot 是怎麼實作這樣一個異常錯誤資訊輸出的呢?今天棧長分享一個 Spring Boot 啟動失敗的簡單易懂的玩法,讓新來的實習生 1 秒都能看出問題。
如果你對 Spring Boot 還不是很熟悉,或者隻是會簡單的使用,那還是建議你深入學習下吧,推薦這個 Spring Boot 學習倉庫,歡迎 Star 關注:
https://github.com/javastacks/spring-boot-best-practiceFailure Analyzers 介紹
Spring Boot 中注冊了許多 "Failure Analyzers",即 "失敗分析器",Spring Boot 中的啟動失敗的場景都是由這些失敗分析器攔截處理的。
Spring Boot 提供了 FailureAnalyzers 接口:
package org.springframework.boot.diagnostics;
/**
* A {@code FailureAnalyzer} is used to analyze a failure and provide diagnostic
* information that can be displayed to the user.
*
* @author Andy Wilkinson
* @since 1.4.0
*/
@FunctionalInterface
public interface FailureAnalyzer {
/**
* Returns an analysis of the given {@code failure}, or {@code null} if no analysis
* was possible.
* @param failure the failure
* @return the analysis or {@code null}
*/
FailureAnalysis analyze(Throwable failure);
}
這個接口的目的就是: 分析啟動失敗異常并顯示給使用者有用的診斷資訊。
Spring Boot 内置注冊的所有失敗分析器在這個檔案裡面:
/org/springframework/boot/spring-boot/2.3.5.RELEASE/spring-boot-2.3.5.RELEASE-sources.jar!/META-INF/spring.factories
注冊的所有失敗分析器清單:
# Failure Analyzers
org.springframework.boot.diagnostics.FailureAnalyzer=\
org.springframework.boot.context.properties.NotConstructorBoundInjectionFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BeanCurrentlyInCreationFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BeanDefinitionOverrideFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BeanNotOfRequiredTypeFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BindFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BindValidationFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.UnboundConfigurationPropertyFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.ConnectorStartFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.NoSuchMethodFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.NoUniqueBeanDefinitionFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.PortInUseFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.ValidationExceptionFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyNameFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyValueFailureAnalyzer
再回到上面的端口重複綁定啟動失敗異常,就是注冊了 PortInUseFailureAnalyzer 這個失敗分析器,可以看到 PortInUseFailureAnalyzer 失敗分析器就在注冊清單裡面。
再來看下 PortInUseFailureAnalyzer 的源碼:
/**
* A {@code FailureAnalyzer} that performs analysis of failures caused by a
* {@code PortInUseException}.
*
* @author Andy Wilkinson
*/
class PortInUseFailureAnalyzer extends AbstractFailureAnalyzer<PortInUseException> {
@Override
protected FailureAnalysis analyze(Throwable rootFailure, PortInUseException cause) {
return new FailureAnalysis("Web server failed to start. Port " + cause.getPort() + " was already in use.",
"Identify and stop the process that's listening on port " + cause.getPort() + " or configure this "
+ "application to listen on another port.",
cause);
}
}
隻要應用啟動過程上抛出了 PortInUseException 異常就會被這個失敗分析器攔截并輸出可讀性的錯誤資訊,現在知道綁定重複綁定錯誤是怎麼輸出的了。
自定義 Failure Analyzers
從内置的失敗分析器中可以發現,所有的分析器都繼承了這個抽象基類是:AbstractFailureAnalyzer,它實作了 FailureAnalyzer 接口,一般基于這個抽象基類就可以實作自定義失敗分析器的擴充。
下面棧長通過兩個示例帶大家了解下,如何擴充或者自定義一個 FailureAnalyzer。
1、重寫端口失敗分析器
比如說上面的PortInUseFailureAnalyzer 輸出内容是英文的,不是很直覺的看出,我們可以自己實作一個中文的端口失敗分析器。
很簡單,建立一個失敗分析器繼承 AbstractFailureAnalyzer 抽象類即可:
/**
* 來源微信公衆号:Java技術棧
*/
package cn.javastack.springboot.features.analyzer;
import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
import org.springframework.boot.diagnostics.FailureAnalysis;
import org.springframework.boot.web.server.PortInUseException;
public class PortInUseFailureAnalyzer extends AbstractFailureAnalyzer<PortInUseException> {
@Override
protected FailureAnalysis analyze(Throwable rootFailure, PortInUseException cause) {
return new FailureAnalysis("你啟動的端口 " + cause.getPort() + " 被占用了.",
"快檢查下端口 " + cause.getPort() + " 被哪個程式占用了,或者強制殺掉程序.",
cause);
}
}
重寫 analyze 方法,并傳回一個 FailureAnalysis 對象,FailureAnalysis 類的三個主要資訊分别是:
public FailureAnalysis(String description, String action, Throwable cause) {
this.description = description;
this.action = action;
this.cause = cause;
}
即要展示的:可讀性的錯誤描述、建議的檢查修複動作、原始異常。
然後在自己的資源目錄下建立 META-INF/spring.factories 檔案,内容添加:
org.springframework.boot.diagnostics.FailureAnalyzer=\
cn.javastack.springboot.features.analyzer.PortInUseFailureAnalyzer
啟動輸出:
***************************
APPLICATION FAILED TO START
***************************
Description:
你啟動的端口 8080 被占用了.
Action:
快檢查下端口 8080 被哪個程式占用了,或者強制殺掉程序.
這樣重新實作一下是不是要清楚多了?實習生都能看懂!
2、自定義失敗分析器
下面再來自定義一個全新的失敗分析器,讓大家能更清楚的認識失敗分析器。
我們在建立 Bean 的過程中手動抛出一個自定義的異常:
/**
* 來源微信公衆号:Java技術棧
*/
@Bean
public CommandLineRunner commandLineRunner(){
throw new JavastackException("Java技術棧異常");
}
添加一個失敗分析器攔截該異常:
/**
* 來源微信公衆号:Java技術棧
*/
package cn.javastack.springboot.features.analyzer;
import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
import org.springframework.boot.diagnostics.FailureAnalysis;
public class JavastackFailureAnalyzer extends AbstractFailureAnalyzer<JavastackException> {
@Override
protected FailureAnalysis analyze(Throwable rootFailure, JavastackException cause) {
return new FailureAnalysis("Java技術棧發生異常了……",
"趕快去檢查一下吧!",
cause);
}
}
添加注冊:
org.springframework.boot.diagnostics.FailureAnalyzer=\
cn.javastack.springboot.features.analyzer.PortInUseFailureAnalyzer,\
cn.javastack.springboot.features.analyzer.JavastackFailureAnalyzer
***************************
APPLICATION FAILED TO START
***************************
Description:
Java技術棧發生異常了……
Action:
趕快去檢查一下吧!
如果不注冊該失敗分析器,這個自定義的異常就不會被内置的失敗分析器攔截,就會輸出大堆的異常資訊,使用失敗分析器能很直覺的看出是什麼錯誤及怎麼修複這個錯誤。
總結
Spring Boot 提供的失敗分析器以友好的錯誤資訊和修複建議代替了大堆的錯誤異常資訊,可以幫助我們更直覺的定位應用啟動故障,你學會了嗎?
本文的所有示例源代碼都已上傳到了 Github: