[Spring] 統一イベント処理(インターセプタ、統一例外処理、統一データ形式リターン)


序文

最新の Web アプリケーションの開発では、ユーザー権限、例外、データ戻り形式などの多くの側面に対処する必要があることがよくあります。Spring フレームワークは、これらの課題に対処するための強力なツールとメカニズムを提供します。この記事では、Spring フレームワークのインターセプター、統合された例外処理、および統合されたデータ戻り形式に焦点を当てます。

1.スプリングインターセプター

Web アプリケーション開発において、インターセプターは、リクエスト処理のさまざまな段階で特定のアクションを実行できるようにする非常に便利なメカニズムです。これらの操作には、権限の検証、ログ記録、データの前処理などが含まれます。Spring フレームワークのインターセプター関連のコンテンツについては、以下で詳しく説明します。

1.1 ユーザーのログイン権限を確認する場合

まず、一般的なアプリケーション ケースを紹介します。

  • ユーザーのログイン権限の確認。通常、Web アプリケーションでは、一部のリソースや機能にアクセスするにはログインが必要なため、ユーザーのログイン状態を確認する必要があります。インターセプタがない場合は、従来の方法を使用してこの検証ロジックを実装できます。初期ユーザーのログイン認証方法を見てみましょう

1.1.1 初回のユーザーログインの検証

インターセプターを使用する前に、ユーザー ログイン認証を使用する最初の方法を確認してみましょう。

@RestController
public class UserController {
    
    

    // 最开始实现用户登录权限验证的方式

    @RequestMapping("/method1")
    public Object method1(HttpServletRequest request){
    
    
        // 获取Session,没有不创建
        HttpSession session = request.getSession(false);
        if(session == null || session.getAttribute("userinfo") == null){
    
    
            // 没有获取到 session, 此时说明用户未登录
            return false;
        }

        // 此时则说明已经登录了,执行后续业务逻辑

        // ...

        return true;
    }

    @RequestMapping("/method2")
    public Object method2(HttpServletRequest request){
    
    
        // 获取Session,没有不创建
        HttpSession session = request.getSession(false);
        if(session == null || session.getAttribute("userinfo") == null){
    
    
            // 没有获取到 session, 此时说明用户未登录
            return false;
        }

        // 此时则说明已经登录了,执行后续业务逻辑

        // ...

        return true;
    }

	// 其他方法...
}

元のメソッドを使用して、ユーザーのログイン許可の検証を必要とする各メソッドに同じログイン検証ロジックを追加すると、いくつかの問題が発生します。

  1. コードの繰り返し:検証が必要な各メソッドで同じ検証コードをコピーして貼り付ける必要があります。これにより、コードの冗長性が高まり、メンテナンスや変更が困難になります。

  2. 可読性の悪さ:同じ検証ロジックが複数のメソッドに含まれているため、コードの可読性が低下し、各メソッドの実際の機能をすぐに理解することが困難になります。

  3. メンテナンスが難しい:検証ロジックを変更したり、新しい検証条件を追加したりする必要がある場合、複数の場所で変更する必要があるため、エラーが発生しやすくなります。

  4. コードの結合:検証ロジックを各メソッドに直接埋め込むと、ビジネス ロジックと検証ロジックが緊密に結合することになり、コードの分離や単体テストには役立ちません。

  5. スケーラビリティが低い:将来的に検証ロジックを追加したり、検証ロジックをカスタマイズしたりする必要がある場合は、複数の方法を変更する必要があり、作業負荷とリスクが増加します。

1.1.2 Spring AOPを使用してログイン認証を実装する場合の問題点

上記のような問題を解決するには、統一したユーザーログイン認証の利用を検討する必要があります。統合ログイン検証に関しては、Spring AOP のアスペクト指向プログラミングを使用して実現することが考えられますが、他にも大きな問題があります。まず、Spring AOP コードの実装を確認してみましょう。

// 创建一个切面(类)
@Aspect
@Component
public class UserAspect{
    
    
    // 创建切点(方法)定义拦截规则
    @Pointcut("execution(public * com.example.demo.controller.UserController.*(..))")
    public void pointcut() {
    
    
    }

    // 前置通知
    @Before("pointcut()")
    public void doBefore() {
    
    
        System.out.println("执行了前置通知:" + LocalDateTime.now());
    }

    // 后置通知
    @After("pointcut()")
    public void doAfter() {
    
    
        System.out.println("执行了后置通知:" + LocalDateTime.now());
    }

    // 返回后通知
    @AfterReturning("pointcut()")
    public void doAfterReturning() {
    
    
        System.out.println("执行了返回后通知:" + LocalDateTime.now());
    }

    // 抛异常后通知
    @AfterThrowing("pointcut()")
    public void doAfterThrowing() {
    
    
        System.out.println("抛异常后通知:" + LocalDateTime.now());
    }

    // 环绕通知
    @Around("pointcut()")
    public Object doAround(ProceedingJoinPoint joinPoint) {
    
    

        Object proceed = null;
        System.out.println("Around 方法开始执行:" + LocalDateTime.now());
        try {
    
    
            // 执行拦截的方法
            proceed = joinPoint.proceed();
        } catch (Throwable e) {
    
    
            e.printStackTrace();
        }
        System.out.println("Around 方法结束执行: " + LocalDateTime.now());
        return proceed;
    }
}

上記の Spring AOP の通知メソッドを使用してユーザーのログイン検証を実装したい場合、主に 2 つの問題があります。

1.HttpSessionオブジェクトの取得問題

  • AOP では、アドバイス (Advice) は、ポイントカット (Pointcut) に一致する接続ポイント (結合ポイント) 上で実行されますが、これらの接続ポイントはメソッド レベルであり、 や などの関連オブジェクトへのアクセスを直接提供することはできませHttpServletRequestHttpSessionしたがって、AOP アスペクトではオブジェクトに直接アクセスできませんHttpSession

2. 選択的傍受問題

  • 実際のアプリケーションでは、一部の機能のログイン許可を確認するだけで十分な場合があります需要排除一些其他方法,比如用户的注册和登录
  • 切点表达式メソッド名ベースのメソッドでこれを正確に実現することは困難です。これには、複雑な正規表現の照合が必要となる場合があり、除外する必要がある新しいメソッドがある場合は、ポイントカット式を手動で更新する必要があります

これらの問題を解決するには、Spring のインターセプター メカニズムがそのような状況の処理に適しています。HttpServletRequestインターセプタは、や などのリクエスト関連のオブジェクトにアクセスできHttpSession、どのパスをインターセプトする必要があり、どのパスをインターセプトしないかをより簡単に構成できます。

1.2 スプリングインターセプターの使用

1.2.1 Spring インターセプターの概念と使用手順

Spring インターセプターは Spring フレームワークのインターセプト メカニズムであり、リクエストがコントローラーに入る前後に特定の操作を実行するために使用されます。インターセプターを使用して、ログイン許可の検証、ログ記録、データの前処理などの機能を実装できます。タンジェンシャル プログラミング (AOP) の考え方を使用すると、インターセプターはリクエストのさまざまな段階で独自のロジックを挿入し、さまざまな機能を実現できます

Spring インターセプターを使用する一般的な手順は次のとおりです。

1. インターセプタクラスを作成する

まず、Spring のインターフェースを実装するクラスを作成しHandlerInterceptor、その中でメソッドをオーバーライドします。通常、次の 3 つのメソッドが含まれます。

  • preHandle: リクエストが処理される前に実行され、権限の検証やその他の操作に使用できます。返された場合はfalseリクエスト処理を中断します。
  • postHandle: リクエストが処理された後、ビューがレンダリングされる前に実行されます。ロギングなどの操作が可能です。
  • afterCompletion: ビューがレンダリングされた後に実行され、リソースのクリーンアップなどの一部の操作を実行できます。

2. インターセプタを追加して構成する

Spring Boot でのインターセプタの設定と追加は、次の 2 つの手順に大別できます。

1) 作成したインターセプター クラスを Spring コンテナーに追加します。 Spring Boot では、@Componentアノテーションを使用して、作成したインターセプター クラスをマークし、それを管理のために Spring コンテナーに渡すことができます。このようにして、Spring Boot は自動的にスキャンして、このインターセプター クラスをアプリケーション コンテキストに組み込みます。

たとえば、次のようにインターセプタ クラスにアノテーションを追加します@Component

import org.springframework.stereotype.Component;

@Component
public class AuthInterceptor implements HandlerInterceptor {
    
    
    // 拦截器的具体实现
}

2)WebMvcConfigurerインターフェイスを実装し、addInterceptorsシステム構成にインターセプターを追加するメソッドを書き直す: Spring Boot では、WebMvcConfigurerインターフェイスを実装し、addInterceptorsインターセプターを追加するメソッドを書き直すことができます。このメソッドは、作成されたインターセプターをシステムに追加し、リクエストの処理時に有効になります。

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {
    
    

    @Autowired
    private AuthInterceptor authInterceptor; // 注入你的拦截器类

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    
    
        // 添加拦截器,并指定拦截的路径
        registry.addInterceptor(authInterceptor)
        		.addPathPatterns("/**")
        		.excludePathPatterns("/user/login")
        		.excludePathPatterns("/user/reg")
        		;
    }
}

上記のコードでは、addInterceptorsメソッドはインターセプタ クラスをauthInterceptorシステム構成に追加し、addPathPatternsを通じてインターセプトするパスを指定し、excludePathPatternsメソッドを使用して、ログインや登録機能などのインターセプトを放棄する指定されたインターフェイスを指定します。このようにして、プログラムの実行中に、構成されたインターセプターが自動的に有効になります。

3. インターセプタの順序を構成する
複数のインターセプタを同時に使用する場合、それらの実行順序は構成によって定義できます。Spring MVC では、インターセプターは構成ファイルで宣言されたのと同じ順序で実行されます。
4. インターセプターの使用
インターセプターを構成すると、レンダリング フェーズでリクエストが処理される前、後、または処理後に、対応するロジックが実行されます。このようにして、権限の検証、ロギングなど、必要な機能をインターセプターに実装できます。

1.2.2 インターセプタを使用してユーザーのログイン権限を確認する

1. ユーザーのログイン検証用のインターセプターを作成します。

/**
 * Spring MVC拦截器(LoginInterceptor),用于检查用户是否已登录。
 * 如果用户未登录,则重定向到登录页面。
 */
@Component
public class LoginInterceptor implements HandlerInterceptor {
    
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
    
        // 判断用户登录
        HttpSession session = request.getSession(false); // 默认值是true
        if (session != null && session.getAttribute(ApplicationVariable.SESSION_KEY_USERINFO) != null) {
    
    
            // 用户已经登录了
            return true;
        }

        // 当代码执行到此次,表示用户未登录
        response.sendRedirect("/login.html");
        return false;
    }
}

2. インターセプトルールを追加して構成する

/**
 * 配置拦截规则
 */
@Configuration
public class MyConfig implements WebMvcConfigurer {
    
    

    @Autowired
    private LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    
    
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**") // 拦截所有的 url
                // 放开拦截的内容
                .excludePathPatterns("/**/*.js")
				.excludePathPatterns("/**/*.css")
				.excludePathPatterns("/**/*.jpg")
				.excludePathPatterns("/login.html")
                .excludePathPatterns("/user/reg")
                .excludePathPatterns("/user/login")
                // ...
        ;
    }
}

1.3 インターセプタの実装原理

インターセプターの実装原理には、Spring フレームワークの中心概念であるアスペクト指向プログラミング (AOP) が含まれます。AOP を利用すると、インターセプタをメソッド呼び出しチェーンに組み込んで、リクエスト処理のさまざまな段階で対応するアクションを実行できます。インターセプタの実装原理とワークフローを以下に紹介します。

1. AOP コンセプトのレビュー

AOP は、横断的な懸念事項 (Cross-cutting Concerns) の問題を解決するために設計されたプログラミング パラダイムです。これにより、主要なビジネス ロジックから横断的な関心事が分離されるため、コードの管理と保守が向上します。Spring では、AOP はプロキシ モードと動的プロキシを通じて実装されます。

AOP には、ポイントカットとアドバイスという 2 つの重要な概念があります。

  • ポイントカット:ポイントカットは、アドバイスを適用する結合ポイント (結合ポイント) を定義します。結合ポイントは、メソッド呼び出し、メソッド実行、フィールド アクセスなどです。ポイントカットでは、式を使用して結合ポイントを照合し、どの結合ポイントがアドバイスの影響を受けるかを決定します。

  • アドバイス:アドバイスは、ポイントカットで実行するコードを定義します。Springでは、プレアドバイス(メソッド呼び出し前に実行)、ポストアドバイス(メソッド呼び出し後に実行)、サラウンドアドバイス(メソッド呼び出しの前後に実行)、例外アドバイス(メソッドスロー後に実行)など、さまざまな種類のアドバイスがあります。例外発生時)、最終通知(メソッド呼び出し終了後に実行)。

2. インターセプターのワークフロー

インターセプタは実際には AOP のアプリケーションであり、AOP によるリクエスト処理プロセスへの介入を実現します。インターセプターのワークフローは次のとおりです。

  1. リクエストが DispatcherServlet (フロント コントローラー) に到着すると、インターセプターはまず、設定されたインターセプト ルール (ポイント カット) に従ってリクエストをインターセプトするかどうかを判断します。

  2. インターセプターがリクエストをインターセプトすると決定した場合、事前通知など、カットポイントの前に通知のロジックを実行します。このようにして、インターセプターは、リクエストを処理する前に、アクセス許可の検証、ロギングなどの前処理操作を実行できます。

  3. 次に、リクエストは処理のために対応するコントローラーに渡され続けます。インターセプターはリクエストのフローを中断せず、処理前に独自のロジックを挿入するだけです。

  4. コントローラーがリクエストの処理を完了すると、インターセプターは通知後などの通知ロジックを再度実行します。このようにして、インターセプターは、リクエスト処理後に、データのカプセル化やロギングなどの後続の操作を実行できます。

  5. 最後に、インターセプターは、最後の通知など、カットポイントの後に通知のロジックを実行します。このようにして、インターセプターはリクエストの処理が完了した後にいくつかのクリーンアップ操作を実行できます。

インターセプターを通じて、リクエスト処理のさまざまな段階に介入して、横断的な問題を解決できます。このメカニズムにより、コードの管理と保守が向上し、コードの保守性と再利用性が向上します。

1.4 Spring インターセプターと Spring AOP の違い

Spring インターセプターと Spring AOP (アスペクト指向プログラミング) は、Spring Framework で横断的な問題を処理するために使用される 2 つのメカニズムで、似ていますが、いくつかの重要な違いがあります。それらの主な違いは次のとおりです。

1. 対象地域の違い

  • Spring インターセプター:主に Web リクエストの処理に使用されます。アクセス許可の検証、ログ記録など、リクエスト処理のさまざまな段階で特定の操作を実行します。インターセプターは主に、Web リクエストの処理フローに介入し、リクエストの前後にカスタム ロジックを挿入することに関係します。

  • Spring AOP: Web リクエストに限定されず、アプリケーションにおける横断的な問題を処理します。AOP は、メソッド呼び出し、メソッド実行、フィールド アクセスなどのさまざまな接続ポイントで通知を実行し、トランザクション管理やパフォーマンス監視など、主要なビジネス ロジックから切り離されたいくつかの機能を実現できます。

2.適用範囲が異なります

  • Spring インターセプター:主に Web リクエストを処理するために Web 層で使用されます。リクエストの処理フローを制御できますが、Web 層でのみ機能し、ビジネス層のメソッド呼び出しには影響しません。

  • Spring AOP:ビジネス層、永続化層、Web 層などの複数のレベルに適用できます。AOP は異なる層にまたがることができ、接続ポイントをインターセプトすることで異なる層間で共通の関心事の処理を実現します。

3. さまざまな処理方法

  • Spring Interceptor:インターセプターは Java 動的プロキシに基づくメカニズムで、リクエストをリクエスト処理プロセスに織り込むことでリクエストに介入します。インターセプターは主にリクエスト処理のフロントリンクとバックリンクに焦点を当てており、前処理、後処理、リソースの解放などの操作を実行できます。

  • Spring AOP: AOP はプロキシ モードを通じて実装され、JDK ダイナミック プロキシまたは CGLIB を使用してプロキシ オブジェクトを生成できます。AOP は、メソッド呼び出しの前後、例外がスローされたとき、およびメソッド実行の終了時に接続ポイントで通知を実行して、さまざまな種類の横断的な問題を実現できます。

4. 原則と柔軟性

  • Spring インターセプター:インターセプターは Spring MVC によって提供されるメカニズムであり、Web 層の処理をより目的としています。ある程度の柔軟性はありますが、レイヤー全体にわたる一般的な問題への対処には比較的制限があります。

  • Spring AOP: AOP は Spring フレームワークの中核機能の 1 つであり、メソッド呼び出しやフィールド アクセスなどのさまざまな接続ポイントの処理を含む、複数のレベルで適用できます。AOP はより汎用的であり、横断的なさまざまな問題を処理するのに適しています。

つまり、Spring インターセプターと Spring AOP は両方とも、横断的な問題に対処するための重要なメカニズムですが、アプリケーションの範囲、処理方法、柔軟性にいくつかの違いがあります。適切なメカニズムの選択は、実際の要件と、横断的な問題をどのレベルで処理する必要があるかによって異なります。

2. 統一された例外処理

2.1 例外処理を統合する理由

アプリケーションの開発プロセスでは、データベース接続の失敗や null ポインタ例外など、さまざまな異常事態が必然的に発生します。より良いユーザー エクスペリエンスとより優れたエラー メッセージ管理を提供するには、統一された例外処理メカニズムを採用する必要があります。統合された例外処理を採用する必要がある理由は次のとおりです。

  1. フレンドリーなユーザー エクスペリエンス:統合された例外処理により、さまざまな例外をキャッチし、統合されたエラー応答を返すことができます。このようにして、ユーザーはアプリケーション内のエラーの詳細を直接公開することなく、よりフレンドリーでわかりやすいエラー プロンプトを取得できます。

  2. コードの重複を減らす:アプリケーションでは、同じ種類の例外が複数の場所で発生する可能性があります。例外処理を統合することで、同じエラー処理ロジックを 1 か所に抽出できるため、コードの重複が減り、コードの保守性が向上します。

  3. 一元化されたエラー管理: 統合された例外処理により、エラー情報を一元管理して、ロギング、監視、およびエラー分析を容易にすることができます。これは、問題を迅速に特定し、アプリケーションの安定性を向上させるのに役立ちます。

2.2 統一された例外処理の使用

Spring フレームワークでは、@ControllerAdvice@ExceptionHandlerアノテーションを使用して、統一された例外処理を実現できます。

次のように進めます。

  1. 例外処理クラスを作成し、@ControllerAdviceそのクラスに注釈を付けて、グローバル例外を処理するためのコントローラー通知クラスであることを示します。

  2. 例外処理クラスでは、@ExceptionHandlerアノテーションを使用して例外処理メソッドを定義します。異なるタイプの例外に対して異なるハンドラーを作成することも、共通のメソッドを使用してすべての例外を処理することもできます。

  3. 例外処理メソッドでは、エラー メッセージをカスタマイズし、エラー応答を構築し、適切なビューまたは JSON 応答を返すことができます。

以下は、統合例外処理の使用方法を示すサンプル コードです。


/**
 * 统一异常处理
 */
@ControllerAdvice
@ResponseBody
public class MyExceptionAdvice {
    
    

    /**
     * 处理空指针异常
     * @param e
     * @return
     */
    @ExceptionHandler(NullPointerException.class)
    public HashMap<String, Object> doNullPointerException(NullPointerException e){
    
    
        HashMap<String, Object> result = new HashMap<>();
        result.put("code", -300);
        result.put("msg", "空指针: " + e.getMessage());
        result.put("data", null);
        return result;
    }

    /**
     * 默认异常处理(当具体的异常处匹配不到时,会执行此方法)
     * @param e
     * @return
     */
    @ExceptionHandler(Exception.class)
    public HashMap<String, Object> doException(Exception e){
    
    
        HashMap<String, Object> result = new HashMap<>();
        result.put("code", -300);
        result.put("msg", "Exception: " + e.getMessage());
        result.put("data", null);
        return result;
    }

}

このコード例では、MyExceptionAdviceという名前のグローバル例外処理クラスが作成され@ControllerAdvice、アノテーションが付けられます。このクラスには 2 つの例外処理メソッドが含まれています。

  1. doNullPointerExceptionnull ポインタ例外を処理するメソッド ( NullPointerException)。アプリケーションが null ポインター例外をスローすると、このメソッドが呼び出され、エラー情報を含む を構築してHashMap戻ります。

  2. doException他のタイプの例外を処理するためのメソッド。アプリケーションによってスローされた例外の種類が、以前に定義された処理メソッドと一致しない場合、このデフォルトの例外処理メソッドが実行されます。また、エラー応答を作成して返します。

これにより、さまざまな種類の例外に対するカスタマイズされた処理が実現され、統一されたエラー応答形式が提供されるため、ユーザー エクスペリエンスとコードの保守性が向上します。

3. 統一されたデータ返却形式

3.1 なぜデータの返却形式を統一する必要があるのか

開発中、異なるインターフェイスが異なるデータ形式を返す可能性があり、これにより、データ処理時にフロントエンドが異なるインターフェイスの戻り形式に従って異なる処理ロジックを実行することになり、コードの複雑さが増大し、メンテナンス コストが増加する可能性があります。フロントエンド処理ロジックを簡素化し、コードの保守性を向上させるために、データの戻り形式を統一することを検討できます。

統一されたデータ戻り形式には次のような利点があります。

  1. フロントエンド ロジックの複雑さを軽減します。フロントエンドは、インターフェイスごとに異なる処理ロジックを記述する必要がなく、データ形式を均一に処理してコードの複雑さを軽減できます。

  2. フロントエンドとバックエンドのコラボレーション効率の向上:フロントエンドとバックエンドは、明確なデータ形式の合意により、インターフェイス開発と共同デバッグをより迅速に実装し、通信コストを削減できます。

  3. 標準化されたエラー処理:データの戻り形式が統一されることでエラー処理方法が標準化され、フロントエンドによるリクエストの成否の判断やエラー情報の取得が容易になります。

3.2 統一されたデータ返却形式の実装

統一されたデータ形式で返す場合は、 Springフレームワークが提供するResponseBodyAdviceインターフェースやアノテーションが主に利用され、@ControllerAdviceインターフェース内のsupportsメソッドを書き換える必要があるが、メソッドは統一したデータ形式で返すかどうかを示し、beforeBodyWriteそれを実現することであるデータフォーマットの統一。supportsbeforeBodyWrite

以下は、返されるデータ形式を次のように規定する、統一されたデータ戻り形式を実装する方法を示す例です。

{
    
    
	"cede":code,
	"msg":msg,
	"data":data
}

返却時の整理に使えますHashMap具体的な実装コードは次のとおりです。

/**
 * 统一数据格式处理
 */
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
    
    

    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
    
    
        return true;
    }

    /**
     * 返回数据之前进行处理
     */
    @SneakyThrows
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
    
    
        //规定标准格式为:HashMap<String, Object> -> code,msg.data

        if (body instanceof HashMap) {
    
    
            // 如果已经是标准格式
            return body;
        }

        // 重写返回结果,让其返回一个统一的数据格式
        HashMap<String, Object> result = new HashMap<>();
        result.put("code", 200);
        result.put("data", body);
        result.put("msg", "");

        return result;
    }
}

@ControllerAdvice上記のサンプル コードは、アノテーションとResponseBodyAdviceインターフェイスを使用して統一されたデータ戻り形式を実現する方法を明確に示しています。supportsとメソッドを書き換えることでbeforeBodyWrite、返却されるデータ形式の統一処理を実現します。

この例では、統一されたデータ形式が指定されています。つまり、返される JSON には、「code」、「msg」、および「data」のフィールドが含まれます。返されたデータがすでに標準形式 ( HashMap<String, Object>) である場合は、それを直接返します。それ以外の場合は、標準形式で返されるオブジェクトを構築し、その中に元のデータを入れます。

3.3 ボディがString型エラーとなる問題について

プリミティブ データ型を扱う場合は、たとえば次のようになります。

@RestController
@RequestMapping("/user")
public class UserController {
    
    

    @RequestMapping("/login")
    public int login() {
    
    
        return 1;
    }
}

ブラウザアクセス結果:

現時点では問題はなく、データ形式の統一復帰に成功しました。しかし、戻り値の型が String の場合はどうなるでしょうか?


@RestController
@RequestMapping("/user")
public class UserController {
    
    
	@RequestMapping("/hello")
	public String hello(){
    
    
	    return "hello world";
	}
}

ブラウザ経由で再度アクセスします。

1. 戻り値 String 型エラーの原因分析:

エラーの内容はざっくり言うとHashMap型に変換できないというものですStringなぜそうなるのでしょうか? まず、返品プロセス中のデータの実行フローを理解する必要があります

  1. コントローラー メソッドは String 型を返します。
  2. String 型に対して統一データ形式処理を実行します。つまり、String 型の as を;bodyに設定します。valuekeydataHashMap
  3. HashMap文字列に変換しapplication/json、ネットワーク経由でフロントエンドに送信します。

次の点に注意してください。

  • このうち、bodyの型がString型の場合はStringHttpMessageConverter型変換に使用されます。
  • それ以外の場合は、型変換に使用しますHttpMessageConverter

ただし、使用するのは3番目のステップで、bodyは String 型であるためStringHttpMessageConverter、String 型データを JSON 文字列に変換することしかできないクラスを使用しており、HashMap変換するとエラーが発生します。

2. 解決策:

ソリューションは大きく 2 つのタイプに分類できます。

  1. 解決策 1:型が String の場合はbody、String 型を直接返します。文字列連結を使用するか、jacksonObjectMapper を使用して JSON 形式に変換できます。
    1) 結合して戻り用の文字列を形成します。
@SneakyThrows
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
    
    

    if (body instanceof String) {
    
    
        // 返回一个 String 字符串
        return "{\"code\" : 200, \"msg\": \"\", \"data\":\"" + body + "\"}";
    }

    if (body instanceof HashMap) {
    
    
        // 如果已经是标准格式
        return body;
    }

    // 重写返回结果,让其返回一个统一的数据格式
    HashMap<String, Object> result = new HashMap<>();
    result.put("code", 200);
    result.put("data", body);
    result.put("msg", "");
    return result;
}

2)jacksonでの使用ObjectMapper:

@SneakyThrows
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
    
    

    if (body instanceof HashMap) {
    
    
        // 如果已经是标准格式
        return body;
    }

    // 重写返回结果,让其返回一个统一的数据格式
    HashMap<String, Object> result = new HashMap<>();
    result.put("code", 200);
    result.put("data", body);
    result.put("msg", "");

    if(body instanceof String){
    
    
        // 返回一个 String 字符串
        return objectMapper.writeValueAsString(result);
    }
    return result;
}

2. 解決策 2:クラスを直接無効にしますStringHttpMessageConverter

これは、構成クラスのメソッドをオーバーライドすることでconfigureMessageConverters無効にできますStringHttpMessageConverter

@Configuration
public class MyConfig implements WebMvcConfigurer {
    
    
    /**
     * 移除 StringHttpMessageConverter
     * @param converters
     */
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
    
    
        converters.removeIf(converter -> converter instanceof StringHttpMessageConverter);
    }
}

ここで、は のインスタンスであるかどうかを確認する式converter -> converter instanceof StringHttpMessageConverterです。「はい」の場合は、インスタンスを削除します。LambdaconverterStringHttpMessageConverter

解決策を選択した後、String 型を返すコントローラー メソッドに再度アクセスすると、結果は次のようになります。


この時点で、統一されたデータ形式を正しく返すことができます。

おすすめ

転載: blog.csdn.net/qq_61635026/article/details/132207168