📺

Springの@Aroundで好きなステータスコードを返したい

2022/02/10に公開

Springには各リクエストに対して共通の処理を行いたい時に当該処理の「前」と「後」に別処理をねじ込め割り込む事ができる、Interceptorなるものがあります。
「アスペクト指向プログラミング(AOP)」 と呼ばれる物の一つの用語...らしい()

そこで@Aroundを使ってログインチェックを行っていたのですが、返却するステータスコードが変えられない事に3カ月ぐらい悩まされてました。
ので書き残しておきます。

結論

@annotation(requestMapping)@Aroundに追加し、
RequestMappingを引っ張り出して、HttpServletResponseを取ってくる。
そしてsetStatus(int)を使えば終わり。

@annotation(requestMapping)を@Aroundに追加
@Around("execution(<対象パッケージの指定>) && @annotation(requestMapping)")
HttpServletResponseを取ってくる
HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse());

問題のコード

ブラウザからアプリAPIに対してGET,POSTリクエストが飛び交うポイントでログインの確認をしたい。レスポンスはJSONで返したい
=> HttpSessionにログインの爪痕があるかを検査し、本来の処理に回すかを確認している。

@Autowired
private HttpSession session;

@Around("execution(<対象パッケージの指定>)")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
	boolean isLogin = false;
	HttpHeaders headers = new HttpHeaders();
	headers.add("Connection", "Keep-Alive");
	//  ログイン確認
	Object data = null;
	// JSONで返したいのでHashMapでレスポンスを組み立て。
	HashMap<String, Object> res = new HashMap<>();
	if(isLogin){ // ok.
	    try{
	        data = pjp.proceed(); // 本来のController
	    } catch (Exception e){
	        e.printStackTrace();
	    }
	}
	HttpStatus status = HttpStatus.OK;
	if(!isLogin){ // ログインしてない
	    res.put("message", "認証に失敗しました。");
	    status = HttpStatus.UNAUTHORIZED;
	} else if(data == null){
	    res.put("message", "エラーが発生しました。");
	    status = HttpStatus.INTERNAL_SERVER_ERROR;
	} else {
	    res.put("message", "ok");
	}
	res.put("statusCode", status.value());
	return new ResponseEntity<HashMap<String, Object>>(out, headers, status);
}

こんなコード書いてみたけどResponseEntityを受け付けてくれなかった。
returnにStatusCodeを入れるのはダメそう。

解決

ネットの海を彷徨い、行き着いた先でRequestMappingを入れてみた...

@Autowired
private HttpSession session;

@Around("execution(<対象パッケージの指定>) && @annotation(requestMapping)") // 追記箇所
public Object around(
	ProceedingJoinPoint pjp,
	RequestMapping requestMapping // 追記箇所
) throws Throwable {
	boolean isLogin = false;
	//  ログイン確認
	HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse(); // 追記箇所
	Object data = null;
	// JSONで返したいのでHashMapでレスポンスを組み立て。
	HashMap<String, Object> res = new HashMap<>();
	if(isLogin){ // ok.
	    try{
	        data = pjp.proceed(); // 本来のControllerの処理
	    } catch (Exception e){
	        e.printStackTrace();
	    }
	}
	HttpStatus status = HttpStatus.OK;
	if(!isLogin){ // ログインしてない
	    res.put("message", "認証に失敗しました。");
	    status = HttpStatus.UNAUTHORIZED;
	} else if(data == null){
	    res.put("message", "エラーが発生しました。");
	    status = HttpStatus.INTERNAL_SERVER_ERROR;
	} else {
	    res.put("message", "ok");
	}
	res.put("statusCode", status.value());
	response.setStatus(status.value()); // 追記箇所
	return res;
}

これで思い通りにStatusCodeを返せるようになった。

そりゃそうなんだけどHttpServletResponseがそのリクエストなのかわからなかったし、
感覚的にどこからともなく表れたオブジェクトに力はあるのか疑問だったので、
「これで動くのかぁ」とびっくり。

参考

https://stackoverflow.com/questions/31075594/aop-around-return-bad-request-response

(アスペクト比じゃないよ > 絵文字)

Discussion