Spring Boot起動中に出た例外をFailureAnalyzerでつかむ

# 経緯

こちらを参照
https://qiita.com/ShassBeleth/items/476436b7c81fe81ddbb2

まぁ指定のポートがすでに使われてた場合のExceptionは
もともとSpring Bootに用意されてるんだけどね

上書きもできるし追加もできるよっていう話

# 環境

Spring Boot 2.2.2.RELEASE - 2.2.4.RELEASE
Kotlin 1.3.61
Gradle

環境は以下の記事のまんま

https://qiita.com/ShassBeleth/items/3f3afca83b8ef0b98ede

# Spring Boot側が用意してくれてるFailureAnalyzer

例外つかむには`AbstractFailureAnalyzer`を継承する必要がある
検索かけて出てきたクラスがこれ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/195499/4d04f2ac-6daa-edb7-cf99-8d6cfa6dc090.png)

全部で17個
さらにそれぞれのクラスを継承するかもしれないからもっとあるかも

ポートが既に使われてた時に呼ばれるExceptionは`PortInUseException`
それをつかむのが`PortInUseFailureAnalyzer`

```PortInUseFailureAnalyzer.java
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);
	}

}
```
中のクラスはこんな感じ

適当に裏でポート8080使っといて
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/195499/e210db1f-9d19-b40f-a3d0-d323712416e9.png)

Boot Runでアプリを起動すると・・・

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/195499/01acdf77-37c4-268e-28c3-456af454da6e.png)

こんな感じ

FailureAnalyzerにあるDescriptionとActionがログに書き込まれてるっていうね


# Spring Bootが用意してるFailureAnalyzerを独自のものに書き換える

正直おすすめはできない

```MyPortInUseFailureAnalyzer.kt
class MyPortInUseExceptionFailureAnalyzer : AbstractFailureAnalyzer<PortInUseException>() {
    override fun analyze(rootFailure: Throwable, cause: PortInUseException): FailureAnalysis {
        return FailureAnalysis(
                cause.message,
                "Port already in use!!!",
                cause
        )
    }
}
```

`AbstractFailureAnalyzer<PortInUseException>`を継承したクラスを作っておく

`resources/META-INF/`フォルダ配下に`spring.factories`ファイルを作って

```resources/META-INF/spring.factories
org.springframework.boot.diagnostics.FailureAnalyzer =\
  com.example.spring.boot.web.failure.analyzer.MyPortInUseFailureAnalyzer 
```

その中身はこんな感じ
これで登録OK

また適当にポート8080を裏で使っておいてからBoot Runすると

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/195499/720a688c-eced-e718-8cb5-42e59eb0d447.png)

上書きできましたー

おすすめしないっていうのも例外時に出力される情報を変更できちゃうので、実際に例外が発生したときに何があったかわからなくなるから
出力される内容は変えずに処理をはさんだりとかするのがいいのかな・・・？

FailureAnalyzerのカスタマイズの使い道としては以下の方が実用的かも

# 独自のExceptionをFailureAnalyzerでつかむ

```SampleFailureAnalyzer.kt
/**
 * 独自の例外
 */
class SampleException(message: String?) : RuntimeException(message)

/**
 * カスタムFailureAnalyzerのサンプル
 * アプリ起動中に発生したSampleExceptionをつかむ
 */
class SampleFailureAnalyzer : AbstractFailureAnalyzer<SampleException>() {
    override fun analyze(rootFailure: Throwable, cause: SampleException): FailureAnalysis {
        return FailureAnalysis(
                cause.message,
                "Sample Exception!",
                cause
        )
    }
}
```

こんな感じに独自の例外を作っておいて、それをつかむFailureAnalyzerを作成しておく

`spring.factories`はこんな感じ

```resources/META-INF/spring.factories
org.springframework.boot.diagnostics.FailureAnalyzer =\
  com.example.spring.boot.web.failure.analyzer.MyPortInUseFailureAnalyzer ,\
  com.example.spring.boot.web.failure.analyzer.SampleFailureAnalyzer
```

複数のFailureAnalyzerを登録することもできます

で、作ったSampleFailureAnalyzerがちゃんとつかめるか確認したいから
以下を適当に作っておく

```SampleFailureAnalyzer.kt
/**
 * アプリ起動中に読み込まれるサンプルComponent
 */
@Component
class SampleComponent {
    fun run() {
        throw SampleException("exception!")
    }
}

/**
 * リフレッシュ時にイベント発火するListenerクラス
 */
@Component
class SampleApplicationListener : ApplicationListener<ContextRefreshedEvent?> {

    @Autowired
    private val sampleComponent: SampleComponent? = null

    /**
     * リフレッシュ時にSampleComponentのrunを実行する(例外を発生させる)
     */
    override fun onApplicationEvent(event: ContextRefreshedEvent) {
        sampleComponent!!.run()
    }

}

```

1. リフレッシュ時にSampleApplicationListenerのonApplicationEventが呼ばれる
2. sampleComponentのrunが呼ばれる
3. runの中でExceptionが発生する

って感じ
これでアプリ起動時にエラーが発生するようになりました

実行してみると

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/195499/f562b73d-410a-46d7-8a42-3ecedd09222a.png)

ちゃんとメッセージ出せてます！

spring.factoriesを登録していない場合はスタックトレースが表示されます

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/195499/e9260ec0-cb6c-912d-20b4-44f2a9cfe5ea.png)


# まとめ

* FailureAnalyzerでアプリ起動中の例外もつかめる！
* 既にSpring Bootが用意してくれてるものも上書きできる！
* 自分で作った例外もつかめる！

スタックトレースのほうがわかりやすい場合もあるし、一概にすべてのExceptionをつかんだほうがいいとは言えないのかな・・・？
でも把握できてるExceptionかどうか、発生しうる例外かどうかの判断はできるし、まぁまぁ便利だなとは思う