springboot自定義http反饋狀態碼
最近在開發一些http server型別程式,通過spring boot構建一些web程式,這些web程式之間通過http進行資料訪問、共享,如下圖,
假設現在client發起一次儲存資料的請求到server,server可能會返回如下類似的資料
{
"status":1,
"message":"xxxxxx"
}
然後client通過解析json獲得status來判斷當前的請求操作是否成功,開發過程中通過都是這麼做的,但是這樣在restful設計中不怎麼好,其實這個status欄位的表達完全可以通過http status來表示,類似404、500、502這種都有明確的定義並且相互理解、溝通起來也方便。
文章主要記錄一下我是如何在spring boot中實現自定反饋狀態碼的,以及我找到的三種實現方式。
第一種,使用@ResponseStatus。這是一個註解,可以作用在方法和類上面,如下使用,
在方法上使用方式,
@RequestMapping(value = "/user", method = RequestMethod.GET)
@ResponseStatus(code=HttpStatus.INTERNAL_SERVER_ERROR,reason="server error")
public String getUser(){
return "im zhangsan" ;
}
啟動web程式,通過postman訪問http://127.0.0.1:8100/user
,會出現下面結果,
{
"timestamp": 1497850427325,
"status": 500,
"error": "Internal Server Error",
"message": "server error",
"path": "/user"
}
這裡我一開始覺得很奇怪,為什麼我的getUser方法中沒有錯誤,結果還是出現了500錯誤?原因就是@ResponseStatus
註解的問題,我後面猜測它會強制的將對映轉化成500的狀態碼。這種應用場景我想不太明白在什麼地方會用到。
在類中使用方式,
@ResponseStatus(code=HttpStatus.INTERNAL_SERVER_ERROR,reason="111")
public class ServerException extends Exception {
}
這種使用方式就是將自定義異常和狀態碼結合在一起,合理使用自定義異常機制可以最大化的提高程式的健壯性,下面看如何使用,
@RequestMapping(value = "/user", method = RequestMethod.GET)
public String getUser(@RequestParam String userName) throws ServerException{
if(StringUtils.isEmpty(userName)){
throw new ServerException();
}
return "im zhangsan";
}
這段程式碼的意思是當userName
欄位為null的時候會丟擲ServerException
異常,但是ServerException
類被標記了@ResponseStatus
註解,因此會直接報500錯誤,如果覺得500不適合還可以定義其它的錯誤程式碼。
這種方式看著已經很好了,可以按照邏輯自定義反饋碼,程式夠健壯。這種方式也有不好地方,如果反饋碼太多需要定義太多的異常類,並且錯誤內容reason
還是不能手動定義。
到這裡,我基本上放棄了@ResponseStatus
的使用了。
第二種,使用HttpServletResponse,HttpServletResponse是javax.servlet
下的一個介面,如下使用,
@RequestMapping(value = "/user", method = RequestMethod.GET)
public void getUser(HttpServletResponse response) throws IOException{
response.setStatus(500);
response.getWriter().append("server error");
}
這種方式可以很好的實現同時滿足自定義反饋碼+訊息內容,一般的實現方式也都是這樣。但是這樣也不是太好,
1. 在括號內建立了一個response內建變數,這樣顯得不夠美觀,反而有些多餘。
2. 在方法中呼叫了源生的方法來設定反饋碼和訊息體,並且如果需要返回json格式資料還需要設定response.setContentType("application/json");
和response.setCharacterEncoding("UTF-8");
,這樣做有些多餘,重複的工作太多,雖然可以進行封裝。
3. 最嚴重的問題這個方法必須是void
型別,否則就會和@ResponseBody
出現衝突,其次就是不能利用@ResponseBody
自動封裝json的特性,在spring mvc框架中如果在方法上加上@ResponseBody
是可以對返回值自動進行json封裝的。
再找找其他的,如果沒有找到,估計也只能接受這個不完美的東西了。
後來在翻閱spring boot文件的時候找到了ResponseEntity
這麼一個東西,這就是我要說的第三種方式。
第三種,使用ResponseEntity
不多說,直接上程式碼,
@RequestMapping(value = "/user", method = RequestMethod.GET)
public ResponseEntity<Map<String,Object>> getUser() throws IOException{
Map<String,Object> map = new HashMap<String,Object>();
map.put("name", "zhangsan");
return new ResponseEntity<Map<String,Object>>(map,HttpStatus.OK);
}
通過postman檢視返回結果,如下,
{
"name": "zhangsan"
}
可以直接將map物件幫我轉化成json物件,並且可以獲得自定義狀態碼,很好,很強大。
這種方式很和我意,
- 不需要多於的
HttpServletResponse
,看著很乾淨。 - 可以充分利用
@ResponseBody
註解,直接將我的返回值幫我轉化成json物件。 - 在設定返回值的時候同時還可以設定http反饋碼,
HttpStatus
是springframework
提供的一個列舉類,裡面封裝了所有的http反饋碼,方便使用命名統一,不會有任何歧義。
相比於前面兩種,這種方式很對我胃口。
仔細看了ResponseEntity
的說明,發現spring mvc其它很多地方也都有使用,如下,下面內容摘自org.springframework.http.ResponseEntity
檔案註釋,
In RestTemplate, this class is returned by getForEntity() and exchange():
ResponseEntity<String> entity = template.getForEntity("http://example.com", String.class);
String body = entity.getBody();
MediaType contentType = entity.getHeaders().getContentType();
HttpStatus statusCode = entity.getStatusCode();
Can also be used in Spring MVC, as the return value from a @Controller method:
@RequestMapping("/handle")
public ResponseEntity<String> handle() {
URI location = ...;
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setLocation(location);
responseHeaders.set("MyResponseHeader", "MyValue");
return new ResponseEntity<String>("Hello World", responseHeaders, HttpStatus.CREATED);
}
這就是上面說過的。
Or, by using a builder accessible via static methods:
@RequestMapping("/handle")
public ResponseEntity<String> handle() {
URI location = ...;
return ResponseEntity.created(location).header("MyResponseHeader", "MyValue").body("Hello World");
}
自定義http反饋碼在設計優良的restful api中起到關鍵作用,http反饋碼是業內統一、共識的,建議在儘量不要通過解析json來獲得status判斷操作結果。
完。