目錄
- 一個優秀的Controller層邏輯
- 從現狀看問題
- 改造 Controller 層邏輯
- 總結
一個優秀的Controller層邏輯
說到 Controller,相信大家都不陌生,它可以很方便地對外提供數據接口。它的定位,我認為是「不可或缺的配角」,說它不可或缺是因為無論是傳統的三層架構還是現在的COLA架構,Controller 層依舊有一席之地,說明他的必要性;說它是配角是因為 Controller 層的代碼一般是不負責具體的邏輯業務邏輯實現,但是它負責接收和響應請求
從現狀看問題
Controller 主要的工作有以下幾項
接收請求并解析參數
調用 Service 執行具體的業務代碼(可能包含參數校驗)
捕獲業務邏輯異常做出反饋
業務邏輯執行成功做出響應
//DTO
@Data
public class TestDTO {
private Integer num;
private String type;
//Service
@Service
public class TestService {
public Double service(TestDTO testDTO) throws Exception {
if (testDTO.getNum() <= 0) {
throw new Exception("輸入的數字需要大于0");
}
if (testDTO.getType().equals("square")) {
return Math.pow(testDTO.getNum(), 2);
}
if (testDTO.getType().equals("factorial")) {
double result = 1;
int num = testDTO.getNum();
while (num > 1) {
result = result * num;
num -= 1;
}
return result;
}
throw new Exception("未識別的算法");
}
}
//Controller
@RestController
public class TestController {
private TestService testService;
@PostMapping("/test")
public Double test(@RequestBody TestDTO testDTO) {
try {
Double result = this.testService.service(testDTO);
return result;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Autowired
public DTOid setTestService(TestService testService) {
this.testService = testService;
}
}
如果真的按照上面所列的工作項來開發 Controller 代碼會有幾個問題
參數校驗過多地耦合了業務代碼,違背單一職責原則
可能在多個業務中都拋出同一個異常,導致代碼重復
各種異常反饋和成功響應格式不統一,接口對接不友好
改造 Controller 層邏輯
統一返回結構
統一返回值類型無論項目前后端是否分離都是非常必要的,方便對接接口的開發人員更加清晰地知道這個接口的調用是否成功(不能僅僅簡單地看返回值是否為 null就判斷成功與否,因為有些接口的設計就是如此),使用一個狀態碼、狀態信息就能清楚地了解接口調用情況
//定義返回數據結構
public interface IResult {
Integer getCode();
String getMessage();
//常用結果的枚舉
public enum ResultEnum implements IResult {
SUCCESS(2001, "接口調用成功"),
VALIDATE_FAILED(2002, "參數校驗失敗"),
COMMON_FAILED(2003, "接口調用失敗"),
FORBIDDEN(2004, "沒有權限訪問資源");
private Integer code;
private String message;
//省略get、set方法和構造方法
}
//統一返回數據結構
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result {
private Integer code;
private String message;
private T data;
public static Result success(T data) {
return new Result<>(ResultEnum.SUCCESS.getCode(), ResultEnum.SUCCESS.getMessage(), data);
}
public static Result success(String message, T data) {
return new Result<>(ResultEnum.SUCCESS.getCode(), message, data);
}
public static Result failed() {
return new Result<>(ResultEnum.COMMON_FAILED.getCode(), ResultEnum.COMMON_FAILED.getMessage(), null);
}
public static Result failed(String message) {
return new Result<>(ResultEnum.COMMON_FAILED.getCode(), message, null);
}
public static Result failed(IResult errorResult) {
return new Result<>(errorResult.getCode(), errorResult.getMessage(), null);
}
public static Result instance(Integer code, String message, T data) {
Result result = new Result<>();
result.setCode(code);
result.setMessage(message);
result.setData(data);
return result;
}
}
統一返回結構后,在 Controller 中就可以使用了,但是每一個 Controller 都寫這么一段最終封裝的邏輯,這些都是很重復的工作,所以還要繼續想辦法進一步處理統一返回結構
統一包裝處理
Spring 中提供了一個類
,能幫助我們實現上述需求ResponseBodyAdvice
ResponseBodyAdvice
是對 Controller 返回的內容在HttpMessageConverter
進行類型轉換之前攔截,進行相應的處理操作后,再將結果返回給客戶端。那這樣就可以把統一包裝的工作放到這個類里面。
public interface ResponseBodyAdvice {
boolean supports(MethodParameter returnType, Classextends HttpMessageConverter> converterType);
@Nullable
T beforeBodyWrite(@Nullable T body, MethodParameter returnType, MediaType selectedContentType, Classextends HttpMessageConverter> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response);
}
supports:判斷是否要交給 beforeBodyWrite 方法執行,ture:需要;false:不需要
beforeBodyWrite:對 response 進行具體的處理
// 如果引入了swagger或knife4j的文檔生成組件,這里需要僅掃描自己項目的包,否則文檔無法正常生成
@RestControllerAdvice(basePackages = "com.example.demo")
public class ResponseAdvice implements ResponseBodyAdvice
特別聲明:以上內容(如有圖片或視頻亦包括在內)為自媒體平臺“網易號”用戶上傳并發布,本平臺僅提供信息存儲服務。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.