最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringBoot到底該不該用統(tǒng)一包裝類詳解

 更新時(shí)間:2026年03月04日 15:08:57   作者:風(fēng)象南  
在微服務(wù)和前后端分離的場(chǎng)景里,統(tǒng)一的返回包裝類成為提高API可預(yù)測(cè)性、降低前端與服務(wù)端溝通成本的重要手段,這篇文章主要介紹了SpringBoot到底該不該用統(tǒng)一包裝類的相關(guān)資料,需要的朋友可以參考下

在SpringBoot項(xiàng)目中,你一定見(jiàn)過(guò)這樣的代碼:

@GetMapping("/user/{id}")
public Result<User> getUser(@PathVariable Long id) {
    return Result.success(userService.getById(id));
}

或者這樣的:

@GetMapping("/user/{id}")
public User getUser(@PathVariable Long id) {
    return userService.getById(id);
}

支持統(tǒng)一包裝的人認(rèn)為這樣做規(guī)范、統(tǒng)一,前端對(duì)接方便;反對(duì)的人則認(rèn)為這是多此一舉,增加了代碼復(fù)雜度,而且HTTP本身就有狀態(tài)碼體系,沒(méi)必要重新發(fā)明輪子。

今天從實(shí)際使用場(chǎng)景出發(fā),把這個(gè)問(wèn)題梳理清楚,希望能給你一些參考。

先說(shuō)清楚什么是統(tǒng)一包裝類

所謂統(tǒng)一包裝類,就是將業(yè)務(wù)數(shù)據(jù)再包一層,常見(jiàn)形態(tài)如下:

// 形態(tài)一:code + msg + data
public class Result<T> {
    private Integer code;
    private String msg;
    private T data;
}

// 形態(tài)二:帶上時(shí)間戳、traceId等
public class Response<T> {
    private Integer code;
    private String msg;
    private T data;
    private Long timestamp;
    private String traceId;
}

這樣包裝的理由主要有三個(gè):

第一,前端解析方便。所有接口返回結(jié)構(gòu)一致,前端只需要寫(xiě)一套解析邏輯,不需要每個(gè)接口單獨(dú)處理。

第二,可以攜帶業(yè)務(wù)錯(cuò)誤碼。比如"用戶不存在"對(duì)應(yīng)10001,"余額不足"對(duì)應(yīng)10002,"參數(shù)校驗(yàn)失敗"對(duì)應(yīng)10003,前端可以根據(jù)不同的錯(cuò)誤碼做不同的處理,比如10002直接跳轉(zhuǎn)到充值頁(yè)面。

第三,方便統(tǒng)一做異常轉(zhuǎn)換。通過(guò) @ControllerAdvice 可以把所有異常統(tǒng)一轉(zhuǎn)換成 Result 格式,避免異常信息直接暴露給前端。

上面主要介紹了包裝類的一些特點(diǎn),下面我們?cè)倏纯窗b及不包裝的三種常見(jiàn)方案的具體實(shí)現(xiàn)方式和優(yōu)缺點(diǎn)。

三種常見(jiàn)方案

方案一:手動(dòng)包裝

每個(gè)接口自己動(dòng)手包一層:

@GetMapping("/user/{id}")
public Result<User> getUser(@PathVariable Long id) {
    User user = userService.getById(id);
    return Result.success(user);
}

@PostMapping("/user")
public Result<Long> createUser(@RequestBody UserCreateDTO dto) {
    Long userId = userService.create(dto);
    return Result.success(userId);
}

這種方式的好處是明確、可控。你在代碼里一眼就能看出這個(gè)接口返回的是什么,想搞特殊也很方便,直接返回 ResponseEntity 就行。

但寫(xiě)多了你會(huì)覺(jué)得很繁瑣,滿屏都是 Result.success()、Result.error(),改起來(lái)也很麻煩。而且這種方式有個(gè)更大的問(wèn)題:某些場(chǎng)景根本無(wú)法包裝。

最典型的就是文件下載。文件下載需要設(shè)置 Content-Disposition 響應(yīng)頭,指定文件名,還需要設(shè)置正確的 Content-Type,如果用 Result 包一下,這些都沒(méi)法處理了:

@GetMapping("/download")
public ResponseEntity<ByteArrayResource> download() {
    byte[] data = fileService.getReport();
    return ResponseEntity.ok()
        .header("Content-Disposition", "attachment; filename=report.xlsx")
        .contentType(MediaType.APPLICATION_OCTET_STREAM)
        .body(new ByteArrayResource(data));
}

ResponseEntity 需要直接返回,無(wú)法塞進(jìn) Result 里。類似的場(chǎng)景還有 SSE 推送、文件流、圖片直接輸出等。

所以如果采用手動(dòng)包裝的方案,你還需要在文檔里明確約定哪些接口需要特殊處理,增加維護(hù)成本。

方案二:不包裝,直接返回

@GetMapping("/user/{id}")
public User getUser(@PathVariable Long id) {
    return userService.getById(id);
}

@PostMapping("/user")
public Long createUser(@RequestBody UserCreateDTO dto) {
    return userService.create(dto);
}

這種方式代碼簡(jiǎn)潔,沒(méi)有冗余,通用性好(比如一些負(fù)載、網(wǎng)關(guān)設(shè)備默認(rèn)識(shí)別的是標(biāo)準(zhǔn)HTTP狀態(tài)碼),Swagger 生成的文檔也很清晰,前端一眼就能看出這個(gè)接口返回什么結(jié)構(gòu)。

異常處理怎么解決呢?通過(guò) @ControllerAdvice + @ExceptionHandler

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(BusinessException.class)
    public ResponseEntity<ErrorResponse> handleBusiness(BusinessException e) {
        return ResponseEntity
            .status(e.getHttpStatus())  // 404、400等HTTP狀態(tài)碼
            .body(new ErrorResponse(e.getMessage()));
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleUnknown(Exception e) {
        log.error("系統(tǒng)異常", e);
        return ResponseEntity
            .status(HttpStatus.INTERNAL_SERVER_ERROR)
            .body(new ErrorResponse("系統(tǒng)繁忙,請(qǐng)稍后重試"));
    }
}

這種方式直接使用 HTTP 標(biāo)準(zhǔn)狀態(tài)碼:200 表示成功,404 表示資源不存在,400 表示參數(shù)錯(cuò)誤,401 表示未登錄,403 表示無(wú)權(quán)限,500 表示服務(wù)器錯(cuò)誤。不需要再定義一套業(yè)務(wù)錯(cuò)誤碼,前端也更容易理解。

不過(guò)有些團(tuán)隊(duì)習(xí)慣了自己定義錯(cuò)誤碼體系,比如 10001、10002 這種,覺(jué)得 HTTP 狀態(tài)碼不夠細(xì)分。其實(shí)大可不必,HTTP 狀態(tài)碼已經(jīng)足夠覆蓋大部分場(chǎng)景了,真需要細(xì)分可以通過(guò)錯(cuò)誤消息來(lái)區(qū)分。

接受這種方式的前提是團(tuán)隊(duì)統(tǒng)一認(rèn)知,不再搞自定義錯(cuò)誤碼那套。如果團(tuán)隊(duì)已經(jīng)有一套成熟的錯(cuò)誤碼體系,遷移成本會(huì)比較高。

方案三:ResponseBodyAdvice自動(dòng)包裝

這是一種折中方案,Controller 代碼保持簡(jiǎn)潔,由框架自動(dòng)包裝:

@RestControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice<Object> {

    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return !returnType.hasMethodAnnotation(NoWrap.class);
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType,
            MediaType selectedContentType, Class selectedConverterType,
            ServerHttpRequest request, ServerHttpResponse response) {
        if (body instanceof Result) {
            return body;
        }
        return Result.success(body);
    }
}

Controller 里就可以直接寫(xiě):

@GetMapping("/user/{id}")
public User getUser(@PathVariable Long id) {
    return userService.getById(id);  // 被自動(dòng)包裝成 Result<User>
}

這個(gè)方案看起來(lái)很完美,Controller 代碼簡(jiǎn)潔,返回格式又統(tǒng)一。但實(shí)際用起來(lái)有幾個(gè)坑需要特別注意。

第一個(gè)坑是 String 類型的特殊處理。Spring 的消息轉(zhuǎn)換器鏈在處理 String 時(shí),會(huì)優(yōu)先使用 StringHttpMessageConverter,如果我們返回 Result<String>,會(huì)導(dǎo)致類型轉(zhuǎn)換異常。所以需要單獨(dú)判斷:

if (body instanceof String) {
    return objectMapper.writeValueAsString(Result.success(body));
}

第二個(gè)坑是調(diào)試不直觀。前端收到數(shù)據(jù)格式不對(duì),你第一反應(yīng)是看 Controller 代碼,但代碼明明寫(xiě)得很正常啊,最后排查半天才發(fā)現(xiàn)是 ResponseAdvice 里的邏輯出了問(wèn)題。這種隱式的包裝,對(duì)不熟悉代碼的人來(lái)說(shuō)就是個(gè)黑盒,調(diào)試成本較高。

第三個(gè)坑是特殊返回類型需要排除。ResponseEntitySseEmitter、StreamingResponseBody 這些類型不能被包裝,否則就廢了。你需要在 supports() 方法里把這些類型排除掉,或者定義一個(gè) @NoWrap 注解,需要例外的接口自己標(biāo)注。

@Override
public boolean supports(MethodParameter returnType, Class converterType) {
    // 排除 ResponseEntity
    if (ResponseEntity.class.isAssignableFrom(returnType.getParameterType())) {
        return false;
    }
    // 排除標(biāo)注了 @NoWrap 的方法
    return !returnType.hasMethodAnnotation(NoWrap.class);
}

按場(chǎng)景選擇

三種方案沒(méi)有絕對(duì)優(yōu)劣,關(guān)鍵是根據(jù)場(chǎng)景選擇。

場(chǎng)景建議方案理由
內(nèi)部前后端對(duì)接接口統(tǒng)一包裝前端解析省心,只需一套邏輯
對(duì)外開(kāi)放 RESTful API直接返回HTTP 狀態(tài)碼語(yǔ)義更清晰,符合REST規(guī)范
文件下載/流式接口直接返回無(wú)法包裝,必須直接控制響應(yīng)
第三方回調(diào)接口按對(duì)方要求對(duì)方規(guī)定什么格式就返回什么格式

具體項(xiàng)目中可能會(huì)出現(xiàn)多種方案混用的情況,可以通過(guò)三種方式區(qū)分是否返回包裝對(duì)象:

方式一:按接口路徑

@Override
public boolean supports(MethodParameter returnType, Class converterType) {
    String path = getPath();
    // 只有 /api 開(kāi)頭的內(nèi)部接口才包裝
    return path != null && path.startsWith("/api/");
}

約定 /api 開(kāi)頭的是內(nèi)部接口,自動(dòng)包裝;其他路徑保持原樣。這種方式簡(jiǎn)單粗暴,不需要改動(dòng)任何業(yè)務(wù)代碼,但需要團(tuán)隊(duì)遵守路徑規(guī)范。

方式二:按注解

@GetMapping("/download")
@NoWrap
public ResponseEntity<ByteArrayResource> download() {
    // ...
}

@GetMapping("/user/{id}")
public User getUser(@PathVariable Long id) {
    // 默認(rèn)包裝
}

定義一個(gè) @NoWrap 注解,需要例外的接口標(biāo)注一下。這種方式靈活,但缺點(diǎn)是容易漏,每次新增特殊接口都得記得加注解。

方式三:按返回類型

@Override
public boolean supports(MethodParameter returnType, Class converterType) {
    Class<?> type = returnType.getParameterType();
    // ResponseEntity、SseEmitter 等類型不包裝
    if (ResponseEntity.class.isAssignableFrom(type) ||
        SseEmitter.class.isAssignableFrom(type) ||
        StreamingResponseBody.class.isAssignableFrom(type)) {
        return false;
    }
    return true;
}

這種方式最省心,不用記路徑規(guī)范也不用在每個(gè)接口上加注解,框架自動(dòng)識(shí)別特殊類型。但前提是你的代碼風(fēng)格要統(tǒng)一,不要一會(huì)兒用 ResponseEntity,一會(huì)兒用 Result。

總結(jié)

無(wú)論選哪種方案,關(guān)鍵是規(guī)則清晰并執(zhí)行到位。前端對(duì)接的成本,很大程度上取決于能不能把規(guī)則說(shuō)清楚并堅(jiān)持執(zhí)行下去。

到此這篇關(guān)于SpringBoot到底該不該用統(tǒng)一包裝類的文章就介紹到這了,更多相關(guān)SpringBoot統(tǒng)一包裝類內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Springboot工具類ReflectionUtils使用教程

    Springboot工具類ReflectionUtils使用教程

    這篇文章主要介紹了Springboot內(nèi)置的工具類之ReflectionUtils的使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧
    2022-12-12
  • IDEA 2021.1 操作SVN 最新超詳細(xì)教程(圖文)

    IDEA 2021.1 操作SVN 最新超詳細(xì)教程(圖文)

    本教程將通過(guò)idea從svn服務(wù)器中的任意一個(gè)分支檢出代碼(本文采用branches),然后再idea中創(chuàng)建新的分支、提交代碼、拉取代碼、合并分支等操作進(jìn)行一一記錄,暫不包含代碼合并,對(duì)idea2021.1操作svn相關(guān)知識(shí)感興趣的朋友一起學(xué)習(xí)下吧
    2021-05-05
  • MyBatis多表查詢和注解開(kāi)發(fā)案例詳解

    MyBatis多表查詢和注解開(kāi)發(fā)案例詳解

    這篇文章主要介紹了MyBatis多表查詢和注解開(kāi)發(fā),本文通過(guò)示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-05-05
  • Java動(dòng)態(tài)初始化數(shù)組,元素默認(rèn)值規(guī)則詳解

    Java動(dòng)態(tài)初始化數(shù)組,元素默認(rèn)值規(guī)則詳解

    動(dòng)態(tài)初始化數(shù)組涉及先定義數(shù)組長(zhǎng)度,后填充具體數(shù)據(jù),適用于數(shù)據(jù)量已知但具體值未定的情況,這種初始化方式允許程序運(yùn)行過(guò)程中賦值,并會(huì)根據(jù)數(shù)據(jù)類型設(shè)定默認(rèn)值,如整型為0,字符串為null,動(dòng)態(tài)初始化與靜態(tài)初始化格式不能混用
    2024-10-10
  • EntityWrapper如何在and條件中嵌套o(hù)r語(yǔ)句

    EntityWrapper如何在and條件中嵌套o(hù)r語(yǔ)句

    這篇文章主要介紹了EntityWrapper如何在and條件中嵌套o(hù)r語(yǔ)句,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-03-03
  • jpa實(shí)現(xiàn)多對(duì)多的屬性時(shí)查詢的兩種方法

    jpa實(shí)現(xiàn)多對(duì)多的屬性時(shí)查詢的兩種方法

    這篇文章主要介紹了jpa實(shí)現(xiàn)多對(duì)多的屬性時(shí)查詢的兩種方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-11-11
  • 基于application和bootstrap的加載順序及區(qū)別說(shuō)明

    基于application和bootstrap的加載順序及區(qū)別說(shuō)明

    這篇文章主要介紹了application和bootstrap的加載順序及區(qū)別說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-07-07
  • SpringBoot靜態(tài)資源及原理解析

    SpringBoot靜態(tài)資源及原理解析

    這篇文章主要介紹了SpringBoot靜態(tài)資源及原理解析,當(dāng)創(chuàng)建一個(gè)jar工程時(shí),想引入css等靜態(tài)資源時(shí),需要遵守SpringBoot的靜態(tài)資源映射關(guān)系,通過(guò)WebMvcAutoConfiguration查看靜態(tài)配置資源的規(guī)則,需要的朋友可以參考下
    2023-12-12
  • SpringBoot啟動(dòng)時(shí)執(zhí)行某些操作的8種方式

    SpringBoot啟動(dòng)時(shí)執(zhí)行某些操作的8種方式

    在真實(shí)項(xiàng)目開(kāi)發(fā)過(guò)程中,我們經(jīng)常會(huì)需要在程序啟動(dòng)時(shí)執(zhí)行一些特定的業(yè)務(wù)操作,比如系統(tǒng)預(yù)熱、系統(tǒng)初始化等,小編為大家介紹 8 種實(shí)現(xiàn)方式,需要的朋友可以參考下
    2025-10-10
  • 實(shí)例分析Java單線程與多線程

    實(shí)例分析Java單線程與多線程

    本篇文章通過(guò)代碼實(shí)例給大家詳細(xì)講述了Java單線程與多線程的相關(guān)原理和知識(shí)點(diǎn)總結(jié),需要的朋友可以學(xué)習(xí)下。
    2018-02-02

最新評(píng)論

五大连池市| 灵寿县| 尉氏县| 吉林省| 凤台县| 叶城县| 秦皇岛市| 扶余县| 永仁县| 盐亭县| 海城市| 榆林市| 阿城市| 夹江县| 安溪县| 鹤山市| 旌德县| 浦江县| 新邵县| 修武县| 铜川市| 馆陶县| 精河县| 昂仁县| 上饶县| 全州县| 平舆县| 绥芬河市| 文成县| 壶关县| 宜兰县| 织金县| 滨州市| 佛山市| 道真| 翁牛特旗| 青铜峡市| 闵行区| 资中县| 九台市| 仙桃市|