SPRING/PROJECT

[๊ตฌํ˜„] Spring์—์„œ ์—๋Ÿฌ๋ฅผ ๋‚ด๋Š” 3๊ฐ€์ง€ ๋ฐฉ๋ฒ•

ozllzL 2023. 4. 2. 21:48

์—๋Ÿฌ๋ฅผ.. ๋‚ด๋ด…์‹œ๋‹ค

ํ”„๋กœ์ ํŠธ์—์„œ ์—๋Ÿฌ๋ฅผ ๋‚ผ ๋•Œ ๋‚ด๊ฐ€ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ์ด ์„ธ ๊ฐ€์ง€์ด๋‹ค.

  1. ResponseEntity ์‚ฌ์šฉ
  2. Custom ์˜ˆ์™ธ ํด๋ž˜์Šค ๋งŒ๋“ค๊ธฐ
  3. ControllerAdvice ์‚ฌ์šฉํ•˜๊ธฐ

๊ฐœ์ธ์ ์œผ๋กœ ์•„์ฃผ ๊ฐ„๋‹จํ•œ ํ”„๋กœ์ ํŠธ๊ฐ€ ์•„๋‹ˆ๋ผ๋ฉด 3๋ฒˆ์„ ์ถ”์ฒœํ•œ๋‹ค.

ํ•˜๋‚˜์”ฉ ๋ด๋ณด์ž๋ฉด,

 

1. ResponseEntity ์‚ฌ์šฉ

์„ค๋ช…ํ•  ๊ฒŒ ์—†์„ ์ •๋„๋กœ ๋งค์šฐ ๊ฐ„๋‹จํ•œ ๊ฒƒ์ด ์žฅ์ ์ด๋‹ค.

๋‹จ์ ์€ ์ฝ”๋“œ๊ฐ€ ์ง€์ €๋ถ„ํ•ด์ง„๋‹ค๋Š” ์ ์ด๋‹ค.

new ResponseEntity<>({๋ณด๋‚ผ ๋ฐ์ดํ„ฐ}, HttpStatus.{status});

์ด ํ•œ ์ค„๋งŒ ๋ฆฌํ„ดํ•˜๋ฉด ๋œ๋‹ค.

ํ•˜์ง€๋งŒ ์˜ˆ์™ธ์ฒ˜๋ฆฌํ•  ๊ฒŒ ์—ฌ๋Ÿฌ๊ฐœ๊ฐ€ ๋˜๋ฉด ๋„ˆ๋ฌด ๋ณต์žกํ•ด์ง„๋‹ค.

    @PostMapping("/users/login")
    public ResponseEntity<HashMap> socialLogin(@RequestBody UserDto.LoginDto loginDto) {

        HashMap<String, Object> responseMap = new HashMap<>();
        User user = userService.signIn(loginDto.getSocialType(), loginDto.getSocialToken());

        if (loginDto.getSocialType().equals("kakao")) {
            if (user == null) {
                responseMap.put("status", 404);
                responseMap.put("message", "ํšŒ์› ์ •๋ณด ์—†์Œ");
                return new ResponseEntity<>(responseMap, HttpStatus.NOT_FOUND);
            }

            responseMap.put("status", 200);
            responseMap.put("message", "๋กœ๊ทธ์ธ ์„ฑ๊ณต");
            responseMap.put("data", new HashMap<String, String>() {{
                put("token", jwtTokenProvider.createToken(user.getSocialId(), user.getRoles()));
            }});
            return new ResponseEntity<>(responseMap, HttpStatus.OK);
        } else {
            responseMap.put("status", 401);
            responseMap.put("message", "์†Œ์…œ ํƒ€์ž… ์˜ค๋ฅ˜");
            return new ResponseEntity<>(responseMap, HttpStatus.NOT_FOUND);
        }
        responseMap.put("status", 404);
        responseMap.put("message", "ํšŒ์› ์ •๋ณด ์—†์Œ");
        return new ResponseEntity<>(responseMap, HttpStatus.NOT_FOUND);
    }

์œ„ ๊ฒฝ์šฐ ์‘๋‹ต์€ ์•„๋ž˜ ๋ชจ์–‘์œผ๋กœ ๋‚˜๊ฐ„๋‹ค.

{
    "message": "ํšŒ์› ์ •๋ณด ์—†์Œ",
    "status": 404
}

 

ResponseEntity๋ฅผ ์ „๋ถ€ Service ๋‹จ๊ณ„์—์„œ return ํ•˜๊ณ , ๊ทธ๋ฅผ ๊ทธ๋Œ€๋กœ Controller์—์„œ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋ฐฉ๋ฒ•๋„ ์žˆ๊ธด ํ•˜์ง€๋งŒ,

์ฝ”๋“œ ๊ฐ€๋…์„ฑ์ด ๋ณ„๋กœ์ผ ๊ฒƒ ๊ฐ™๋‹ค.

๊ฐ„๋‹จํ•˜๊ฒŒ ์—๋Ÿฌ๋ฅผ ๋‚ด์•ผํ•  ๋•Œ ์ข‹์€ ๋ฐฉ๋ฒ•์ด๋‹ค.

 

2. Custom ์˜ˆ์™ธ ํด๋ž˜์Šค ๋งŒ๋“ค๊ธฐ

์—ญ์‹œ ๋งค์šฐ ๊ฐ„๋‹จํ•˜๋‹ค.

ํ•˜์ง€๋งŒ ๋ชจ๋“  ์—๋Ÿฌ์— ๋Œ€ํ•ด ์ „๋ถ€ ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค์–ด์•ผ ํ•œ๋‹ค๋Š” ๋‹จ์ ์ด ์žˆ๋‹ค.

@ResponseStatus(code = HttpStatus.NOT_FOUND, reason = "ํ•ด๋‹น id์— ํ•ด๋‹นํ•˜๋Š” ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.")
public class NotFoundIdException extends RuntimeException{ }

์ด๋Ÿฐ ํด๋ž˜์Šค๋ฅผ ํ•˜๋‚˜ ๋งŒ๋“ค์–ด์„œ ํ•„์š”ํ•  ๋•Œ Throw ํ•ด์ฃผ๋ฉด ๋œ๋‹ค.

{
  "timestamp": "2023-04-02T13:13:11.287+00:00",
  "status": 400,
  "error": "Bad Request",
  "trace": "{trace}",
  "message": "์ž˜๋ชป๋œ ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ ๊ฐ’ ์ž…๋‹ˆ๋‹ค.",
  "path": "/api/dresses/search"
}

์ด๋Ÿฐ ํ˜•์‹์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ์ „์†ก๋œ๋‹ค.

3. ControllerAdvice ์‚ฌ์šฉํ•˜๊ธฐ

๊ฐ€์žฅ ์ถ”์ฒœํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค.

๊น”๋”ํ•˜๊ณ , ๋งŽ์€ ์—๋Ÿฌ๋ฅผ ๋งŽ์€ ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค์ง€ ์•Š๊ณ ๋„ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค.

@RequiredArgsConstructor
@Getter
public enum ErrorResponseStatus {

    BAD_REQUEST_WRONG_TAG_EXCEPTION(HttpStatus.BAD_REQUEST, "์ž˜๋ชป๋œ ํƒœ๊ทธ"),
    
    UNAUTHORIZED_WRONG_SOCIAL_TYPE(HttpStatus.UNAUTHORIZED, "์ž˜๋ชป๋œ ์†Œ์…œ ํƒ€์ž…"),
    UNAUTHORIZED_INVALID_SOCIAL_TOKEN(HttpStatus.UNAUTHORIZED, "์ž˜๋ชป๋œ ์†Œ์…œ ํ† ํฐ"),

    NOT_FOUND_USER_EXCEPTION(HttpStatus.NOT_FOUND, "ํšŒ์› ์ •๋ณด ์—†์Œ"),
    NOT_FOUND_PASSWORD_EXCEPTION(HttpStatus.NOT_FOUND, "๋น„๋ฐ€๋ฒˆํ˜ธ ๋ถˆ์ผ์น˜"),

    CONFLICT_DUPLICATED_NICKNAME(HttpStatus.CONFLICT, "์ค‘๋ณต๋œ ๋‹‰๋„ค์ž„");

    private final HttpStatus code;
    private final String message;
}

์ด๋Ÿฐ enum ํŒŒ์ผ์„ ํ•˜๋‚˜ ์ƒ์„ฑํ•˜๊ณ ,

์ด enum ํŒŒ์ผ๋กœ ์ƒ์„ฑ๋  ์ˆ˜ ์žˆ๋Š” Exception ํŒŒ์ผ์„ ์ƒ์„ฑํ•œ๋‹ค.

@Getter
@AllArgsConstructor
public class CustomException extends RuntimeException {
    private final ErrorResponseStatus errorResponseStatus;
}

๋‹ค์Œ ์œ„ Exception ํŒŒ์ผ์„ handleํ•˜๋Š” ExceptionHandler๋ฅผ ๋งŒ๋“ค์–ด์ค€๋‹ค.

@RestControllerAdvice ๋˜๋Š” @ControllerAdvice๋ฅผ ๋ถ™์—ฌ์•ผ Spring์ด ์ธ์‹ํ•ด์„œ ์—๋Ÿฌ๋ฅผ ๋ฐœ์ƒ์‹œ์ผœ์ค€๋‹ค.

@RestControllerAdvice
public class ExceptionHandler extends ResponseEntityExceptionHandler{

    @org.springframework.web.bind.annotation.ExceptionHandler(value = CustomException.class)
    protected ResponseEntity<ResponseTemplate> handleCustomException(CustomException e) {
        return ResponseTemplate.toResponseEntity(e.getResponseCode());
    }
}

ResponseTemplete์˜ toResponseEntity ๋ฉ”์†Œ๋“œ๋ฅผ ๋ฆฌํ„ดํ•˜๋ฉด์„œ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š”๋ฐ,

toResponseEntity์—์„œ๋Š” ErrorResponseStatus์˜ status์™€ ๋‚ด์šฉ์„ ๋ฐ›์•„์™€ ResponseEntity์— ์ง‘์–ด ๋„ฃ์–ด์„œ ์œ„ 1๋ฒˆ๊ณผ ๊ฐ™์€ ๋ฐฉ๋ฒ•์œผ๋กœ ์—๋Ÿฌ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๊ณ  ์žˆ๋‹ค.

@Builder
@AllArgsConstructor
public class ResponseTemplate {

    public int status;

    public String message;

    private final LocalDateTime timestamp = LocalDateTime.now();

    public static ResponseEntity<ResponseTemplate> toResponseEntity(ErrorResponseStatus errorResponseStatus) {
        return ResponseEntity
                .status(errorResponseStatus.getHttpStatus())
                .body(ResponseTemplate.builder()
                        .status(errorResponseStatus.getHttpStatus().value())
                        .message(errorResponseStatus.getMessage())
                        .build()
                );
    }
}

 

์œ„ 4๊ฐœ์˜ ํด๋ž˜์Šค๋งŒ ๋งŒ๋“ค์–ด์ฃผ๋ฉด ์ˆ˜ ๋งŽ์€ ์—๋Ÿฌ๋ฅผ ์ฝ”๋“œ๋ฅผ ์žฌํ™œ์šฉํ•˜์—ฌ ๋ฐœ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์–ด ๊ฐ€์žฅ ์• ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค.

1, 2๋ฒˆ๋ณด๋‹ค๋Š” ์กฐ๊ธˆ ๋ณต์žกํ•˜๊ฒŒ ๋А๊ปด์งˆ ์ˆ˜ ์žˆ๋‹ค๋Š” ๋‹จ์ ์€ ์žˆ์œผ๋‹ˆ ์ƒํ™ฉ์— ๋”ฐ๋ผ ํ•„์š”ํ•œ ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•˜๋ฉด ์ข‹์„ ๊ฒƒ ๊ฐ™๋‹ค.