@ResponseStatus這個注解確實是個令我頭疼的注解.
先記錄下@ResponseStatus注解的定義. 記錄幾個得到的信息:@ResponseStatus聲明在方法、類上, Spring3.0開始才有的, 三個屬性其中 HttpStatus類型的 value 和 code是一個含義, 默認值就是 服務器 500錯誤的 HttpStatus.
用法一.標注在@RequestMapping方法上.
@Controller @RequestMapping("/simple") public class SimpleController { @RequestMapping("/demo2") @ResponseBody @ResponseStatus(code = HttpStatus.OK) public String demo2(){ return "hello world"; } }
上面說了 code 和 value一個意思,這里我就用code了,相對而言比較喜歡code單詞. 這里作用就是改變服務器響應的狀態碼 ,比如一個本是200的請求可以通過@ResponseStatus 改成404/500等等.
常見的幾個狀態碼 HttpStatus.OK 就是 200 , HttpStatus.INTERNAL_SERVER_ERROR 就是 500 等等 ,具體的查看 HttpStatus枚舉 有詳細說明.
實現原理呢,注解底層還是通過設置 response.setStatus來實現.
在@RequestMapping方法執行完成,Spring解析返回值之前,進行了responseStatus設置.
代碼片段位於:org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#setResponseStatus
this對象指當前的ServletInvocableHandlerMethod,看到 @ResponseStatus的reason不為空,就調用response.sendError ; reason為空,就調用setStatus方法僅僅設置響應狀態碼.
記錄下在哪里調用了這個responseStatus方法?
代碼片段位於:org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#invokeAndHandle
發現如果ServletInvocableHandlerMethod的responseReason有值,也就是@ResponseStatus有reason屬性,@RequestMapping方法返回值都不處理了,直接返回;
也就是說只要有@ResponseStatus的 reason屬性標注在 處理器Controller類或者方法上,比如響應狀態碼code設置為 404,reason設置為頁面沒找到 ,那 tomcat 展示界面是這樣大概,展示信息就是我們寫的reason屬性.
@ResponseStatus(code=A,reason=B)標注在 @RequestMapping方法上,作用效果與 response.sendError(A,B)是一樣的.
所以,@ResponseStatus我建議啊, 這種方式下使用千萬不要加 reason, 就把@ResponseStatus 當做一個用來改變響應狀態碼的方式!
用法二.標注在@ControllerAdvice中.
@ControllerAdvice @ResponseStatus public class MyControllerAdvice { @ExceptionHandler({ArithmeticException.class}) public ModelAndView fix(Exception e){ Map map=new HashMap(); map.put("ex",e.getMessage()); return new ModelAndView("error",map); } }
@ControllerAdvice標注初衷我想就是程序運行過程中發生異常,對異常如何處理? 而@ResponseStatus標注在@ControllerAdvice類或者該類下的@ExceptionHandler上,區別大概就是,
原來比如請求程序拋出異常,異常被捕獲,走@ExceptionHandler,正常走完狀態碼是200.
@ControllerAdvice或者 @ExceptionHandler標注了@ReponseStatus,那走完狀態碼就是500.
如果你再給@ResponseStatus添加了reason屬性,不管捕獲異常方法咋返回,都是服務器的錯誤碼捕獲界面,比如上面我的例子,給@ResponseStatus添加reason=”your defined message”.
不管怎么說,下面界面比一大堆異常堆棧信息看起來更簡潔,但我還是不推薦使用誒,原因啊,界面不友好.
用法三.自定義類型的異常添加注解@ResponseStatus
@ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR,reason = "not an error , just info") public class MyException extends RuntimeException { public MyException() { } public MyException(String message) { super(message); } }
這樣子,在SpringMvc中如果有某個 @RequestMapping方法拋出該異常, 只要開啟<mvc:annotation-driven/>,異常自動展示的界面都是如下的:
SpringMvc異常捕獲方法如下.
代碼片段位於:org.springframework.web.servlet.DispatcherServlet#processHandlerException
<mvc:annotation-driven/>注冊了三個HandlerExceptionResolver:ExceptionHandlerExceptionResolver用來處理@ExceptionHandler,而ResponseStatusExceptionResolver是用來處理拋出的異常上標注了@ResponseStatus的解析器.
ResponseStatusExceptionResolver解析異常方式:
代碼片段位於:org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver#doResolveException
ex就是@RequestMapping方法拋出自定義的異常,使用工具類解析自定義異常上的@ResponseStatus注解,找到注解就調用resolveResponseStatus進行響應的處理。
protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { ResponseStatus responseStatus = AnnotatedElementUtils.findMergedAnnotation(ex.getClass(), ResponseStatus.class); if (responseStatus != null) { try { return resolveResponseStatus(responseStatus, request, response, handler, ex); } catch (Exception resolveEx) { logger.warn("Handling of @ResponseStatus resulted in Exception", resolveEx); } } else if (ex.getCause() instanceof Exception) { ex = (Exception) ex.getCause(); return doResolveException(request, response, handler, ex); } return null; }
ResponseStatusExceptionResolver如何根據異常上的@ResponseStatus處理響應?
代碼片段位於:org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver#resolveResponseStatus
獲取了@ReponseStatus的 code reason屬性,reason不為空就 response.sendError(statusCode, reason) ,並且返回一個空的ModelAndView,這里的ModelAndView已經沒有意義了,SendError方法簡單來說就是會跳轉到web.xml中配置的 錯誤狀態碼 對應的頁面, 沒有配置就是默認的服務器的那種錯誤界面,且只展示 前面的reason信息,即響應類型為text/html .
總結:
不管哪種方式,@ReponseStatus最后都是通過response.setStatus或response.sendError來處理.
如果只是為了返回狀態碼,建議只使用 @ResponseStatus(code=xxxx)這樣來設置響應狀態碼;
如果拋出異常呢,不建議@ControllerAdvice里面的 @ResponseStatus和 自定義異常上的 @ResponseStatus一起使用, 按照我的閱讀理解,兩個一起使用肯定是一個生效,而且是 @ControllerAdvice中的@ResponseStatus生效.
場景分析:假設拋出異常不是我們自定義的異常,我們想改變響應的狀態碼,通過@ExceptionHandler來處理異常,並在@ExceptionHandler方法上也可以設置@ResponseStatus來達到效果;
假如拋出自定義的異常,自己沒有定義異常處理界面,那在異常上標注@ResponseStatus就可以走 服務器默認的界面展示,或者通過web.xml 配置error-code \ error-page來自定義界面處理異常;