SpringBoot 가 백 엔 드 반환 형식 을 어떻게 통일 하 는 지 자세히 알 아 보기

오늘 은 SpringBoot 전후 단 분리 개발 모델 에서 어떻게 우호 적 으로 통 일 된 표준 형식 으로 돌아 가 고 전체적인 이상 을 우아 하 게 처리 하 는 지 에 대해 이야기 합 니 다.
우선 통 일 된 표준 격식 으로 돌아 가 는 이 유 를 살 펴 보 자.
왜 SpringBoot 에 대해 통 일 된 표준 형식 으로 돌아 가 야 합 니까?
기본 적 인 상황 에서 SpringBoot 의 반환 형식 은 세 가지 가 있 습 니 다.
첫 번 째:String 복귀

@GetMapping("/hello")
public String getStr(){
  return "hello,javadaily";
}
이 때 인터페이스 에서 가 져 온 반환 값 은 다음 과 같 습 니 다.
hello,javadaily
두 번 째:사용자 정의 대상 되 돌리 기

@GetMapping("/aniaml")
public Aniaml getAniaml(){
  Aniaml aniaml = new Aniaml(1,"pig");
  return aniaml;
}
이 때 인터페이스 에서 가 져 온 반환 값 은 다음 과 같 습 니 다.

{
  "id": 1,
  "name": "pig"
}
세 번 째:인터페이스 이상

@GetMapping("/error")
public int error(){
    int i = 9/0;
    return i;
}
이 때 인터페이스 에서 가 져 온 반환 값 은 다음 과 같 습 니 다.

{
  "timestamp": "2021-07-08T08:05:15.423+00:00",
  "status": 500,
  "error": "Internal Server Error",
  "path": "/wrong"
}
상기 여러 가지 상황 을 바탕 으로 만약 당신 이 전단 개발 자 와 인 터 페 이 스 를 연결 하면 그들 은 매우 어 리 석 을 것 입 니 다.왜냐하면 우 리 는 그 에 게 통 일 된 격식 을 주지 않 았 기 때문에 전단 직원 들 은 반환 치 를 어떻게 처리 해 야 할 지 모 릅 니 다.
그리고 심지어 어떤 친구 들 은 예 를 들 어 장 군 이 결 과 를 포장 하 는 것 을 좋아한다.그 는 Result 대상 을 사 용 했 고 왕 군 도 결 과 를 포장 하 는 것 을 좋아 하지만 그 는 Response 대상 을 사용 했다.이런 상황 이 발생 했 을 때 나 는 전단 인원 이 반드시 미 칠 것 이 라 고 믿는다.
그래서 우리 프로젝트 에 서 는 통 일 된 표준 반환 형식 을 정의 해 야 합 니 다.
정의 반환 표준 형식
표준 반환 형식 은 최소 3 부분 을 포함 합 니 다:
  • status 상태 값:백 엔 드 에서 여러 결 과 를 되 돌려 주 는 상태 코드 를 통일 적 으로 정의 합 니 다
  • message 설명:이번 인터페이스 호출 결과 설명
  • data 데이터:이번 반환 데이터
  • 
    {
      "status":"100",
      "message":"    ",
      "data":"hello,javadaily"
    }
    
    물론 필요 에 따라 다른 확장 값 을 추가 할 수도 있다.예 를 들 어 우 리 는 반환 대상 에 인터페이스 호출 시간 을 추가 했다.
    timestamp:인터페이스 호출 시간
    정의 반환 대상
    
    @Data
    public class ResultData<t> {
      /**      ,       ResultData.java*/
      private int status;
      private String message;
      private T data;
      private long timestamp ;
    
    
      public ResultData (){
        this.timestamp = System.currentTimeMillis();
      }
    
    
      public static <t> ResultData<t> success(T data) {
        ResultData<t> resultData = new ResultData<>();
        resultData.setStatus(ReturnCode.RC100.getCode());
        resultData.setMessage(ReturnCode.RC100.getMessage());
        resultData.setData(data);
        return resultData;
      }
    
      public static <t> ResultData<t> fail(int code, String message) {
        ResultData<t> resultData = new ResultData<>();
        resultData.setStatus(code);
        resultData.setMessage(message);
        return resultData;
      }
    
    }
    
    정의 상태 코드
    
    public enum ReturnCode {
        /**    **/
        RC100(100,"    "),
        /**    **/
        RC999(999,"    "),
        /**    **/
        RC200(200,"        ,     !"),
        /**    **/
        RC201(201,"        ,     !"),
        /**      **/
        RC202(202,"      ,     !"),
        /**       **/
        RC203(203,"         ,     !"),
        /**       **/
        RC204(204,"       ,     !"),
        /**access_denied**/
        RC403(403,"     ,          "),
        /**access_denied**/
        RC401(401,"               "),
        /**    **/
        RC500(500,"    ,     "),
    
        INVALID_TOKEN(2001,"       "),
        ACCESS_DENIED(2003,"         "),
        CLIENT_AUTHENTICATION_FAILED(1001,"       "),
        USERNAME_OR_PASSWORD_ERROR(1002,"        "),
        UNSUPPORTED_GRANT_TYPE(1003, "        ");
    
    
    
        /**      **/
        private final int code;
        /**     **/
        private final String message;
    
        ReturnCode(int code, String message){
            this.code = code;
            this.message = message;
        }
    
    
        public int getCode() {
            return code;
        }
    
        public String getMessage() {
            return message;
        }
    }
    
    일괄 반환 형식
    
    @GetMapping("/hello")
    public ResultData<string> getStr(){
    	return ResultData.success("hello,javadaily");
    }
    
    이 때 인터페이스 에서 가 져 온 반환 값 은 다음 과 같 습 니 다.
    
    {
      "status": 100,
      "message": "hello,javadaily",
      "data": null,
      "timestamp": 1625736481648,
      "httpStatus": 0
    }
    
    이렇게 해서 우리 가 원 하 는 결 과 를 실 현 했 습 니 다.제 가 많은 프로젝트 에서 본 것 은 모두 이런 기법 입 니 다.Controller 층 에서ResultData.success()을 통 해 결 과 를 포장 한 후에 전단 으로 돌아 갑 니 다.
    이곳 을 보고 우 리 는 멈 춰 서서 생각해 보 자.이렇게 하 는 것 이 무슨 폐단 이 있 겠 는가?
    가장 큰 단점 은 우리 뒤에 인 터 페 이 스 를 쓸 때마다 호출ResultData.success()이라는 코드 를 사용 하여 결 과 를 포장 하고 중복 노동 을 하 며 체력 을 낭비 하 는 것 이다.또 다른 늙 은 새들 에 게 놀림 을 받 기 쉽다.
    그래서 우 리 는 코드 를 최적화 시 켜 야 한다.목 표 는 모든 인터페이스 가 손 으로ResultData반환 값 을 제정 하지 않 는 것 이다.
    고급 실현 방식
    이 코드 를 최적화 하 는 것 은 매우 간단 하 다.우 리 는 SpringBoot 가 제공 하 는ResponseBodyAdvice만 빌 리 면 된다.
    Response Body Advice 의 역할:Controller 방법의 반환 값 을 차단 하고 반환 값/응답 체 를 통일 적 으로 처리 하 며 보통 반환 형식,복호화,서명 등 을 통일 적 으로 처리 합 니 다.
    먼저ResponseBodyAdvice의 소스 코드 를 살 펴 보 겠 습 니 다.
    
    public interface ResponseBodyAdvice<t> {
    		/**
    		*     advice  
    		* true   ,false    
    		*/
        boolean supports(MethodParameter var1, Class<!--? extends HttpMessageConverter<?-->> var2);
    
    	  /**
    		*           
    		*/
        @Nullable
        T beforeBodyWrite(@Nullable T var1, MethodParameter var2, MediaType var3, Class<!--? extends HttpMessageConverter<?-->> var4, ServerHttpRequest var5, ServerHttpResponse var6);
    }
    
    우 리 는 구체 적 인 실현 클래스 만 작성 하면 된다.
    
    /**
     * @author jam
     * @date 2021/7/8 10:10   
     */
    @RestControllerAdvice
    public class ResponseAdvice implements ResponseBodyAdvice<object> {
        @Autowired
        private ObjectMapper objectMapper;
    
        @Override
        public boolean supports(MethodParameter methodParameter, Class<!--? extends HttpMessageConverter<?-->> aClass) {
            return true;
        }
    
        @SneakyThrows
        @Override
        public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class<!--? extends HttpMessageConverter<?-->> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
            if(o instanceof String){
                return objectMapper.writeValueAsString(ResultData.success(o));
            }        
            return ResultData.success(o);
        }
    }
    
    두 가지 주의 가 필요 합 니 다.@RestControllerAdvice주해@RestControllerAdvice@RestController주해 의 강화 로 세 가지 기능 을 실현 할 수 있다.
  • 전역 이상 처리
  • 전역 데이터 귀속 전
  • 국 데이터 예비 처리
  • 문자열 형식 판단
    
    if(o instanceof String){
      return objectMapper.writeValueAsString(ResultData.success(o));
    } 
    
    이 코드 는 반드시 추가 해 야 합 니 다.Controller 가 String 으로 직접 돌아 가면 SpringBoot 는 직접 돌아 오기 때문에 json 으로 수 동 으로 변환 해 야 합 니 다.
    위의 처 리 를 통 해 우 리 는 더 이상ResultData.success()을 통 해 전환 할 필요 가 없다.원시 데이터 형식 으로 직접 돌아 가면 SpringBoot 는 자동 으로 포장 류 의 포장 을 실현 할 수 있다.
    
    @GetMapping("/hello")
    public String getStr(){
        return "hello,javadaily";
    }
    
    이때 우리 가 인 터 페 이 스 를 호출 하여 되 돌려 준 데이터 결 과 는 다음 과 같다.
    
    @GetMapping("/hello")
    public String getStr(){
      return "hello,javadaily";
    }
    완벽 하 다 고 생각 하 시 죠?서 두 르 지 마 세 요.또 문제 가 기다 리 고 있 습 니 다.
    인터페이스 이상 문제
    이때 문제 가 있 습 니 다.우 리 는 Controller 의 이상 을 처리 하지 않 았 기 때문에 우리 가 호출 하 는 방법 에 이상 이 생기 면 문제 가 발생 합 니 다.예 를 들 어 아래 의 이 인터페이스 등 입 니 다.
    
    @GetMapping("/wrong")
    public int error(){
        int i = 9/0;
        return i;
    }
    
    돌아 온 결 과 는:

    이것 은 분명히 우리 가 원 하 는 결과 가 아니다.인터페이스 가 잘못 되 었 고 조작 에 성공 한 응답 코드 를 되 돌려 주 었 다.앞에서 보면 때 릴 것 이다.
    서 두 르 지 마 세 요.다음은 두 번 째 의제 로 들 어가 서 전체적인 이상 을 우아 하 게 처리 하 는 방법 입 니 다.
    SpringBoot 는 왜 전역 이상 프로세서 가 필요 합 니까?
    손 으로 try...catch 를 쓰 지 않 고 전역 이상 프로세서 에서 일괄 적 으로 캡 처 합 니 다.
    전역 이상 처리 기 를 사용 하 는 가장 큰 편리 함 은 프로그래머 가 코드 를 쓸 때 손 으로 쓸 필요 가 없다 는 것 입 니 다try...catch.앞에서 말씀 드 렸 듯 이 기본적으로 SpringBoot 에 이상 이 생 겼 을 때 돌아 오 는 결 과 는 다음 과 같 습 니 다.
    
    {
      "timestamp": "2021-07-08T08:05:15.423+00:00",
      "status": 500,
      "error": "Internal Server Error",
      "path": "/wrong"
    }
    
    이러한 데이터 형식 은 전단 에 되 돌아 가 고 전단 은 알 아 볼 수 없 기 때문에 우 리 는 보통try...catch을 통 해 이상 을 처리 합 니 다.
    
    @GetMapping("/wrong")
    public int error(){
        int i;
        try{
            i = 9/0;
        }catch (Exception e){
            log.error("error:{}",e);
            i = 0;
        }
        return i;
    }
    
    우리 가 추구 하 는 목 표 는 더 이상 수 동 으로 쓰 지 않 아 도 된다 는 것 이다try...catch.
    사용자 정의 이상 에 대해 서 는 전역 이상 프로세서 로 만 처리 할 수 있 습 니 다.
    
    @GetMapping("error1")
    public void empty(){
    	throw  new RuntimeException("     ");
    }
    
    Validator 매개 변수 검사 기 를 도입 할 때 매개 변수 검사 가 통과 되 지 않 으 면 이상 이 발생 합 니 다.이 때 는try...catch로 캡 처 할 수 없고 전역 이상 처리 장치 만 사용 할 수 있 습 니 다.
    SpringBoot 통합 매개 변수 검사 이 글 을 참고 하 십시오SpringBoot 개발 비적-통합 매개 변수 검사 및 고급 기술
    어떻게 전역 이상 처리 기 를 실현 합 니까?
    
    @Slf4j
    @RestControllerAdvice
    public class RestExceptionHandler {
        /**
         *         。
         * @param e the e
         * @return ResultData
         */
        @ExceptionHandler(Exception.class)
        @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
        public ResultData<string> exception(Exception e) {
            log.error("       ex={}", e.getMessage(), e);
            return ResultData.fail(ReturnCode.RC500.getCode(),e.getMessage());
        }
    
    }
    
    세 가지 세부 사항 을 설명해 야 한다.
  • @RestControllerAdvice,RestController 의 증강 류 는 전역 이상 프로세서
  • 를 실현 하 는 데 사용 할 수 있 습 니 다.
  • @ExceptionHandler특정한 이상 을 통일 적 으로 처리 하여 코드 중복 율 과 복잡 도 를 줄인다.예 를 들 어 사용자 정의 이상 을 얻 으 려 면@ExceptionHandler(BusinessException.class)
  • @ResponseStatus클 라 이언 트 가 받 은 http 상태 코드 지정
  • 체험 효과
    이때 우 리 는 다음 과 같은 인 터 페 이 스 를 호출 합 니 다.
    
    @GetMapping("error1")
    public void empty(){
        throw  new RuntimeException("     ");
    }
    
    돌아 온 결 과 는 다음 과 같다.
    
    {
      "status": 500,
      "message": "     ",
      "data": null,
      "timestamp": 1625795902556
    }
    
    기본적으로 우리 의 요 구 를 만족 시 켰 다.
    그러나 우리 가 통 일 된 표준 형식 패 키 징 기능ResponseAdviceRestExceptionHandler전역 이상 처리 장 치 를 동시에 사용 할 때 새로운 문제 가 발생 했 습 니 다.
    
    {
      "status": 100,
      "message": "    ",
      "data": {
        "status": 500,
        "message": "     ",
        "data": null,
        "timestamp": 1625796167986
      },
      "timestamp": 1625796168008
    }
    
    이때 돌아 오 는 결 과 는 이 렇 습 니 다.통일 형식 강화 기능 은 돌아 오 는 이상 한 결 과 를 다시 포장 하기 때문에 다음 에 우 리 는 이 문 제 를 해결 해 야 합 니 다.
    전역 이상 접속 되 돌아 오 는 표준 형식
    전역 이상 접속 표준 형식 은 간단 합 니 다.전역 이상 프로세서 가 표준 형식 을 봉인 해 주 었 기 때문에 클 라 이언 트 에 게 직접 되 돌려 주면 됩 니 다.
    
    @SneakyThrows
    @Override
    public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class<!--? extends HttpMessageConverter<?-->> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
      if(o instanceof String){
        return objectMapper.writeValueAsString(ResultData.success(o));
      }
      if(o instanceof ResultData){
        return o;
      }
      return ResultData.success(o);
    }
    
    키 코드:
    
    if(o instanceof ResultData){
      return o;
    }
    
    돌아 온 결과 가 ResultData 대상 이 라면 바로 돌아 가면 된다.
    이때 우리 가 위의 잘못된 방법 을 다시 호출 하면 돌아 오 는 결 과 는 우리 의 요구 에 부합된다.
    
    {
      "status": 500,
      "message": "     ",
      "data": null,
      "timestamp": 1625796580778
    }
    
    자,오늘 의 글 은 여기까지 입 니 다.이 글 을 통 해 프로젝트 에서 우호 적 으로 통 일 된 표준 형식 을 되 돌려 주 고 전체 이상 을 우아 하 게 처리 할 수 있 는 방법 을 알 수 있 기 를 바 랍 니 다.
    github 주소:https://github.com/jianzh5/cloud-blog/
    여기 서 SpringBoot 가 백 엔 드 반환 형식 을 어떻게 통일 하 는 지 에 대한 자세 한 설명 은 여기까지 입 니 다.SpringBoot 통일 백 엔 드 반환 형식 에 관 한 내용 은 이전 글 을 검색 하거나 아래 의 관련 글 을 계속 찾 아 보 세 요.앞으로 많은 응원 부 탁 드 리 겠 습 니 다!

    좋은 웹페이지 즐겨찾기