Java

Spring Bootの@RequestParamでバリデーションする時に、発生する例外の方式による違いについて

2018年11月19日

このページに書いてあること。

Spring Bootのリクエストチェックを・・・・

  • メソッド引数として単項目(@RequestHeader)で実施する。
  • オブジェクトとBeanValidationにより複数項目まとめて実施する。
  • この時の例外ハンドリングの違いについての覚書(←これが重要)

Springの機能として、リクエストチェックをメソッドの引数とアノテーションで実施することができます。

やり方は他の記事で大量に出てくるので省略して簡単に済ませましょう。

1)メソッド引数として単項目(@RequestHeader)で実施する。

@RequestHeaderを付けるだけです。

public class UserInfoEndpoint {

	@Autowired
	LogUtil logUtil;

	@RequestMapping("userinfo")
	public String index(@RequestHeader(name = "Authorization") String authorization) {

		return null;
	}
}


これに対して、AOPにより何らかの操作を実施したい場合、通常の@Aspectでは動作しませんでした。簡単に調べたところでは、引数のバインディングは@AspectのBeforerとか以前に落ちてしまうからという記載を見つけました。
そこで、ExceptionHandlerを使います。

@RestControllerAdvice
public class RestControllerExceptionHandler {

	@Autowired
	LogUtil logUtil;

	@ExceptionHandler(Exception.class)
	public ResponseEntity<Object> handleConstraintViolationException(Exception ex, WebRequest request) {
		logUtil.appl(ex,ApplicationLogEnum.WA000001,request);
		return null;
	}
}

こうすることで、何らかの例外が発生した場合、とりあえず例外のスタックトレースをログに吐いてくれるのでその後の作り込みに役立ちます。(リリース時も全例外に対してスタックトレース出すとウザいので注意)。

この時に発生した例外は、以下の通り。

2018-11-19 21:05:41.108 [http-nio-8080-exec-3] [WARN] - WA000001 [ServletWebRequest: uri=/userinfo;client=0:0:0:0:0:0:0:1]. {}.
org.springframework.web.bind.ServletRequestBindingException: Missing request header 'Authorization' for method parameter of type String

ServletRequestBindingExceptionが発生しています。

ここで、バリデーション対象のリクエスト項目が増えたので、メソッドの引数に夜バリデーションをやめてオブジェクトに詰めて一気にバリデーションを実施します。

2)オブジェクトとBeanValidationにより複数項目まとめて実施する。

簡単に言えば、DTOに変数を定義して、そこにオートバインドしてもらった上で、@notnullとかを使ってバリデーションしてしまおうというお話。
こんなDTOクラスを用意。

import javax.validation.constraints.NotBlank;

import lombok.Getter;
import lombok.Setter;

/**
 * AuthorizationEndpointのリクエストを格納するDTO
 *
 */
@Getter
@Setter
public class AuthorizationRequestDto {

	@NotBlank
	private String response_type;

	private String client_id;
}


そして、Controllerを修正。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

/**
 * OIDCに於ける、OPのAuthorizationEndpointを提供するクラス<br>
 *
 */
@RestController
public class AuthorizationEndpoint {

	@Autowired
	LogUtil logUtil;

	/**
	 * Authorizationリクエストを受診して、認証を実施するメソッド<br>
	 * 
         */
	@RequestMapping(value = "authorize", method = RequestMethod.GET)
	public String index(@Validated AuthorizationRequestDto authorizationRequestDto) {
		logUtil.appl(ApplicationLogEnum.DE000001,authorizationRequestDto.getResponse_type(),authorizationRequestDto.getClient_id());
		return null;
	}
}

すると、先ほどのExceptionHandlerに以下のログが出ました、

2018-11-19 21:05:33.277 [http-nio-8080-exec-1] [WARN] - WA000001 [ServletWebRequest: uri=/authorize;client=0:0:0:0:0:0:0:1]. {}.
org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object 'authorizationRequestDto' on field 'response_type': rejected value [null]; codes [NotBlank.authorizationRequestDto.response_type,NotBlank.response_type,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [authorizationRequestDto.response_type,response_type]; arguments []; default message [response_type]]; default message [must not be blank]

なんか長いですが、BindExceptionが発生していますね。

結論

考えれば当たり前で、なんてことはない話ですが・・・・

1)@RequestHeaderでバリデーションエラーすると、ServletRequestBindingExceptionが発生する。

2)引数をオブジェクト化して、@Validatedで@notNullとかを使ってバリデーションエラーすると、BindExceptionが発生する。

なので、どちらのバリデーションを使うのかで、ExceptionHandlerの処理を分けないと、適切に例外処理ができなくなるという覚書でした。

###############お知らせ################
ブログランキングのITカテゴリに参加してみました。
この記事が役に立ったなどお力になれたら、 このバナーを押していただけると嬉しいです。

#####################################

-Java