きり丸の技術日記

技術検証したり、資格等をここに残していきます。

SpringAOPで特定のpackage配下のExceptionを共通的に翻訳する

きり丸アドベントカレンダー2020の18記事目です。


RestControllerAdviseで共通的に例外をキャッチする方法をこちらの記事で紹介しました。

しかし、RestControllerAdvise発生した例外をキャッチするのが役割で、例外を自作例外に翻訳することはこのクラスの役割ではありません。各ロジックでキャッチして翻訳するのもいいですが、DBの接続エラー等のハンドリングするのは明らかに同じハンドリングする必要があるので面倒です。

今回の記事ではSpring AOPを使用して、特定の例外を共通的に翻訳します。

翻訳したうえで例外をキャッチするRestControllerAdviseを組み合わせると有効です。

ゴール

  • 特定のパッケージ配下の特定のExceptionを翻訳する

環境

  • Java
    • 15
  • org.springframework.boot:spring-boot-starter-aop
    • 2.4.0

手順

Spring AOPを依存関係に入れる


Spring AOPを使うには依存関係に含める必要があります。

ファイル名:build.gradle

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-aop'
}

翻訳先のExceptionを作成する


今回はSpringのNotFoundExceptionから、自作のNotFoundExceptionに翻訳したいので、自作のNotFoundExceptionを作成します。

public class NotFoundException extends RuntimeException {
  NotFoundException(Throwable ex){
    super(ex);
  }
}

Spring AOPでExceptionをキャッチして翻訳する


@Aspect@Componentをクラスに付与する必要があります。

@AfterThrowingを使用すると、Exceptionをキャッチできます。valueにはキャッチ対象のパッケージ、throwingにパラメータでExceptionを受け取れます。受け取った後で自作例外に翻訳します。

@Aspect
@Component
public class ExceptionHandlerRepository {
  @AfterThrowing(
      value = "execution(* com.example.demo.repository..*(..))",
      throwing = "ex")
  public void dataAccessException(DataAccessException ex) {
    throw new com.example.demo.exception.DataAccessException(ex);
  }
}

Spring AOPのテストを行う


@Mockを有効にしたいので、@ExtendWith(MockitoExtenstion.class)を指定します。

AspectJProxyFactoryを使用して、AOPが有効なコードを作成します。具体的なコードは次を参考にしてください。

SpringのDataAccessExceotionはabstractクラスで直接生成できないので、サブクラスをThrowさせます。Throwさせたサブクラスを自作例外に翻訳できることは、AssertJを使用して例外を確認します。

@ExtendWith(MockitoExtension.class)
class ExceptionHandlerRepositoryTests {
  @Mock
  RepositoryForTest repository;
  RepositoryForTest proxy;

  @BeforeEach
  void beforeEach() {
    AspectJProxyFactory factory = new AspectJProxyFactory(repository);
    factory.addAspect(new ExceptionHandlerRepository());
    proxy = factory.getProxy();
  }

  @DisplayName("SpringのDataAccessExceptionを自作のDataAccessExceptionに翻訳する")
  @Test
  void test_01() {
    Mockito.doThrow(new InvalidDataAccessResourceUsageException("test"))
        .when(repository).execute();

    assertThatThrownBy(() -> proxy.execute())
        .isInstanceOf(DataAccessException.class);
  }
}

ソースコード

アドベントカレンダー18日目。

GitHubソースコードでは、自作Exception等々は直接RuntimeExceptionではなく、自作の基底例外のBusinessExceptionを継承しています。 github.com

終わりに

AOPを使うと、デバッグが難しくなるので多用しないほうがいいです。

ですので、私はユースケースとしてはDB系の例外を翻訳する以外では思いつきません。もし、他の有効なケースがある場合は教えていただけると幸いです。


この記事がお役に立ちましたら、各種SNSでのシェアや、今後も情報発信しますのでフォローよろしくお願いします。

参考記事

敬称略

公式リファレンス:Spring-Coreの使い方 docs.spring.io

類似記事

きり丸アドベントカレンダー2020 adventar.org

きり丸のHerokuページ
https://kirimaru-todoapp.herokuapp.com/

18日目のアドベントカレンダーの記事 nainaistar.hatenablog.com

f:id:nainaistar:20201109133010p:plain