@Controller์์ Exception ์ฒ๋ฆฌ
@Controller์์ Exception ์ฒ๋ฆฌ
Spring์์ ์ ๊ณตํ๋ ๋ค์ํ ์์ธ ์ฒ๋ฆฌ ๋ฐฉ๋ฒ
Spring์์๋ ์ฌ๋ฌ ๊ฐ์ง ์์ธ ์ฒ๋ฆฌ ๋ฐฉ๋ฒ์ ์ ๊ณตํ๋ฉฐ, ๊ทธ ์ค ๋ํ์ ์ธ ๋ฐฉ๋ฒ์ด ExcepitonHandler, ControllerAdvice, ResponseStatusException ๋ฑ์ ํ์ฉํ๋ ๊ฒ์ด๋ค.
ExceptionResolver: Spring(WAS) ๋ด ์์ธ ์ฒ๋ฆฌ
- BasicErrorController: Spring์ ๊ธฐ๋ณธ ์๋ฌ ์ฒ๋ฆฌ๋ฅผ ๋ด๋นํ๋ ์ปจํธ๋กค๋ฌ๋ก, ํ์ด์ง ๊ธฐ๋ฐ ์๋ฌ๋ฅผ ์ฒ๋ฆฌํ๋ค.
- ์์ธ ์ฒ๋ฆฌ๋ฅผ ์ํ try-catch๋ฅผ ์ฌ์ฉํ์ง ์๊ณ , ๊ณตํต ๊ด์ฌ์ฌ(cross-cutting concerns)๋ฅผ ๋ฉ์ธ ๋ก์ง์์ ๋ถ๋ฆฌ
- Spring์ HandlerExceptionResolver๋ ์์ธ ์ฒ๋ฆฌ ์ ๋ต์ ์ถ์ํํ ์ธํฐํ์ด์ค๋ก, ๋ฐ์ํ ์์ธ๋ฅผ ์ฒ๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ์ ์ํ๋ค. Spring์์ ์์ธ ์ฒ๋ฆฌ ์์:
- DefaultErrorAttributes: ์๋ฌ ์์ฑ์ ์ ์ฅํ์ง๋ง, ์์ธ ์์ฒด๋ ์ฒ๋ฆฌํ์ง ์๋๋ค.
- ExceptionHandlerExceptionResolver: @ExceptionHandler๋ฅผ ํตํด ์์ธ๋ฅผ ์ฒ๋ฆฌํ๋ค.
- ResponseStatusExceptionResolver: @ResponseStatus ๋๋ ResponseStatusException์ ์ฒ๋ฆฌํ๋ค.
- DefaultHanlderExceptionResolver: ๊ธฐ๋ณธ์ ์ผ๋ก Spring ๋ด๋ถ์์ ๋ฐํํ๋ ์์ธ๋ฅผ ์ฒ๋ฆฌํ๋ค.
- Spring์ HandlerExceptionResolver๋ ์์ธ ์ฒ๋ฆฌ ์ ๋ต์ ์ถ์ํํ ์ธํฐํ์ด์ค๋ก, ๋ฐ์ํ ์์ธ๋ฅผ ์ฒ๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ์ ์ํ๋ค. Spring์์ ์์ธ ์ฒ๋ฆฌ ์์:
ResponseStatus / ResponseStatusException / ExceptionHandler
1. @ResponseStatus: ํน์ ์์ธ์ ๋ํด ๋ฐํํ HTTP ์ํ ์ฝ๋๋ฅผ ๋ช ์ํ๋ค. ์์ธ ํด๋์ค ์์ฒด ๋๋ @ControllerAdvice์ ํจ๊ป ์ฌ์ฉํ ์ ์๋ค.
- ๋จ์ : ์๋ต ๋ด์ฉ์ ์์ ํ ์ ์๊ณ , ์์ธ ํด๋์ค ๋จ์๋ก๋ง ์ฒ๋ฆฌํ ์ ์์ด ์ธ๋ถ์ ์ธ ์์ธ์ฒ๋ฆฌ๊ฐ ์ด๋ ต๋ค.
@ResponseStatus(HttpStatus.NOT_FOUND)
public class NoSuchElementFoundException extends RuntimeException {
...
}
2. ResponseStatusException: ์์ธ ๋ฐ์๊ณผ ๋์์ HTTP ์ํ ์ฝ๋๋ฅผ ๋ช ์ํ ์ ์๋ ๋ฐฉ์์ด๋ค. ์์ธ ๋ฐ์ ์ HTTP ์ํ ์ฝ๋์ ๋ฉ์ธ์ง๋ฅผ ํจ๊ป ์ ์ํ๋ค.
@GetMapping("/product/{id}")
public ResponseEntity<Product> getProduct(@PathVariable String id) {
try {
return ResponseEntity.ok(productService.getProduct(id));
} catch (NoSuchElementFoundException e) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Item Not Found");
}
}
3. ExceptionHandler: ํน์ ์์ธ๊ฐ ๋ฐ์ํ์ ๋ ๊ทธ ์์ธ๋ฅผ ์ฒ๋ฆฌํ๊ธฐ ์ํ ๋ฉ์๋๋ฅผ ์ ์ํ๋ค. ๊ฐ์ฅ ์ ์ฐํ ์์ธ ์ฒ๋ฆฌ ๋ฐฉ๋ฒ์ผ๋ก, @ControllerAdvice์ ํจ๊ป ์ฌ์ฉํ์ฌ ์ค์์์ ์ฒ๋ฆฌํ ์ ์๋ค.
@ExceptionHandler(NoSuchElementFoundException.class)
public ResponseEntity<String> handleNoSuchElement(NoSuchElementFoundException e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(e.getMessage());
}
Custom Exception ์์ฑํ๋ ๋ฐฉ๋ฒ: ๋ค์ํ Exceptions
์์ธ ์ฒ๋ฆฌ๋ฅผ ์ธ๋ถํํ๊ธฐ ์ํด ํด๋์ค ๋จ์์ ํ๋ ๋จ์๋ก ์์ธ๋ฅผ ์์ฑ ํ ์ ์๋ค.
ํด๋์ค ๋จ์์ Custom Exception
- RuntimeException์ ์์๋ฐ์ ๋ค์ํ ์ฌ์ฉ์ ์ ์ ์์ธ๋ฅผ ๋ง๋ค ์ ์๋ค. ์๋ฅผ ๋ค์ด, ํน์ ์ฌ์ฉ์๋ฅผ ์ฐพ์ ์ ์์ ๋ UserNotFoundException์ ์ ์ํ์ฌ ์์ธ ์ํฉ์ ์ฒ๋ฆฌํ ์ ์๋ค.
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(String message) {
super(message);
}
}
ํ๋ ๋จ์์ Custom Exception: Enum์ ํตํ ๊ด๋ฆฌ
- ํ๋ ๋จ์์ ์์ธ ์ฒ๋ฆฌ๋ Enum์ ์ฌ์ฉํ์ฌ ๊ด๋ฆฌํ ์ ์๋ค. ๋ค์ํ ์์ธ ์ํฉ์ ๋ฐ๋ผ ์ ์ ํ ์ฒ๋ฆฌ ๋ฐฉ๋ฒ์ ๋ช ์ํ๊ณ , ์ด๋ฅผ ํตํด ์์ธ ์ฒ๋ฆฌ๋ฅผ ๊ฐ๊ฒฐํ๊ฒ ๊ตฌํํ ์ ์๋ค. ExceptionType๊ณผ ๊ฐ์ Enum์ ์ฌ์ฉํ๋ฉด ์์ธ ์ผ์ด์ค๋ง๋ค ์ฒ๋ฆฌํด์ผ ํ ๋ด์ฉ๋ค์ ํ ๊ณณ์์ ๊ด๋ฆฌํ ์ ์๋ค. ์ด๋ ๊ฒ ํ๋ฉด ๊ฐ ์์ธ์ ๋ฐ๋ฅธ ๋ฉ์ธ์ง์ HTTP ์ํ ์ฝ๋๋ฅผ Enum์ ์ ์ํด ๊ด๋ฆฌํ ์ ์๋ค.
public enum ExceptionType {
INVALID_REQUEST(HttpStatus.BAD_REQUEST, "์๋ชป๋ ์์ฒญ์
๋๋ค."),
NOT_EXIST(HttpStatus.NOT_FOUND, "์กด์ฌํ์ง ์๋ ๋ฆฌ์์ค์
๋๋ค.");
private final HttpStatus status;
private final String message;
}
@Controller์์ Exception ์ฒ๋ฆฌํ์ง ์์ ๋, 500 ํ์ค ์ค๋ฅ ๋ฐํ
์คํ๋ง์์ ์ปจํธ๋กค๋ฌ์์ ์์ธ๋ฅผ ์ฒ๋ฆฌํ์ง ์์ผ๋ฉด ๊ธฐ๋ณธ์ ์ผ๋ก 500 Internal Server Error๊ฐ ๋ฐํ๋๋ค.
์ด๋ ๋ฐ์ํ ์์ธ๋ฅผ ์ฒ๋ฆฌํ์ง ์์ผ๋ฉด ๊ธฐ๋ณธ์ ์ผ๋ก ์คํ๋ง์ ์ค๋ฅ ํ์ด์ง ๋๋ JSON ์๋ต์ ๋ฐํํ๋ค.
์์ ์ฝ๋
@GetMapping("/user/{id}")
public ResponseEntity<UserResponseDto> getUser(@PathVariable Long id) {
UserResponseDto user = userService.findById(id);
if (user == null) {
throw new NoSuchElementFoundException("User not found");
}
return ResponseEntity.ok(user);
}
์ ์ฝ๋์์ userService.findById()๊ฐ null์ ๋ฐํํ๊ณ ์์ธ ์ฒ๋ฆฌ๋ฅผ ํ์ง ์์ผ๋ฉด, ๊ธฐ๋ณธ์ ์ผ๋ก 500 Internal Server Error๊ฐ ๋ฐ์ํ๋ค.
@Controller์์ Exception ์ฒ๋ฆฌ ๋ฐ ResponseEntity(Wrapper) 404 ๋ฐํ
์คํ๋ง์์๋ ExceptionHandler๋ฅผ ์ฌ์ฉํ์ฌ ์์ธ๋ฅผ ์ฒ๋ฆฌํ๊ณ , ResponseEntity๋ฅผ ํตํด ์ ์ ํ HTTP ์๋ต์ ๋ฐํํ ์ ์๋ค.
@ExceptionHandler(NoSuchElementFoundException.class)
public ResponseEntity<String> handleNoSuchElementFoundException(NoSuchElementFoundException ex) {
return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND);
}
@GetMapping("/user/{id}")
public ResponseEntity<UserResponseDto> getUser(@PathVariable Long id) {
UserResponseDto user = userService.findById(id);
if (user == null) {
throw new NoSuchElementFoundException("User not found");
}
return ResponseEntity.ok(user);
}
์ ์ฝ๋์์ NoSuchElementFoundException ์์ธ๊ฐ ๋ฐ์ํ ๊ฒฝ์ฐ 404 Not Fount ์๋ต์ ๋ฐํํ๋ค.
@Controller์์ ๋ค์ํ Exception ์ฒ๋ฆฌ ํ์์ฑ
๋ค์ํ Exception ์ฒ๋ฆฌํ ๋๋ ExceptionHandler๋ฅผ ์ฌ์ฉํ์ฌ ๊ฐ ์์ธ์ ๋ง๋ ์๋ต์ ์ ๊ณตํ๋ ๊ฒ์ด ์ค์ํ๋ค. ๋ชจ๋ ์์ธ๋ฅผ ๋์ผํ๊ฒ ์ฒ๋ฆฌํ๋ฉด ์ ์ ํ ์๋ต์ ๋ฐํํ ์ ์์ผ๋ฏ๋ก, ์์ธ์ ๋ฐ๋ผ ๋ค๋ฅธ HTTP ์๋ต ์ํ์ ๋ฉ์ธ์ง๋ฅผ ํด๋ผ์ด์ธํธ์๊ฒ ์ ๊ณตํด์ผํ๋ค.
- ๋ค์ํ ์ข ๋ฅ์ Exception ์ฒ๋ฆฌ ํ์(ํ ์ข ๋ฅ์ Exception๋ง ์ฒ๋ฆฌํ๋ ์ฝ๋)
@ExceptionHandler(NoSuchElementFoundException.class)
public ResponseEntity<String> handleNoSuchElementFoundException(NoSuchElementFoundException ex) {
log.warn(ex.getMessage());
ex.printStackTrace();
return new ResponseEntity<>("Resource not found", HttpStatus.NOT_FOUND);
}
@GetMapping("/user/{id}")
public ResponseEntity<UserResponseDto> getUser(@PathVariable Long id) {
UserResponseDto user = userService.findById(id);
if (user == null) {
throw new NoSuchElementFoundException("User not found");
}
return ResponseEntity.ok(user);
}
์ด ์ฝ๋๋ ๋ชจ๋ ์์ธ๋ฅผ 404 Not Found๋ก ๋ฐํํ๋ ๋ฐฉ์์ด๋ฏ๋ก, ์ค์ ๋ค์ํ ์ํฉ์ ๋ง์ถฐ ์ ์ ํ ์๋ต์ ์ ๊ณตํ์ง ๋ชปํ๋ค.
- CustomException์ ํตํ ๋ค์ํ Exception ์ฒ๋ฆฌ
public class CustomException extends RuntimeException {
private String type;
public CustomException(String type, String message) {
super(message);
this.type = type;
}
public String getType() {
return type;
}
}
@ExceptionHandler(CustomException.class)
public ResponseEntity<String> handleCustomException(CustomException ex) {
if ("INVALID_REQUEST".equals(ex.getType())) {
log.warn(ex.getMessage());
ex.printStackTrace();
return new ResponseEntity<>("Invalid request", HttpStatus.BAD_REQUEST);
} else if ("NOT_EXIST".equals(ex.getType())) {
log.warn(ex.getMessage());
ex.printStackTrace();
return new ResponseEntity<>("Resource not found", HttpStatus.NOT_FOUND);
} else {
log.error(ex.getMessage());
ex.printStackTrace();
return new ResponseEntity<>("Internal server error", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
์ด ์ฝ๋๋ CustomException์ ์ฌ์ฉํ์ฌ ์์ธ์ ์ ํ์ ๋ฐ๋ผ ์ ์ ํ ์ํ ์ฝ๋์ ๋ฉ์ธ์ง๋ฅผ ๋ฐํํ๋ค. ์๋ฅผ ๋ค์ด, INVALID_REQUEST ์์ธ๋ 404 Bad Reqeust, NOT_EXIST ์์ธ๋ 404 Not Found๋ก ์ฒ๋ฆฌ ๋๋ค.
- Enum์ ์ฌ์ฉํ Exception ์ฒ๋ฆฌ
@Getter
@AllArgsConstructor
public enum ExceptionType {
INVALID_REQUEST(HttpStatus.BAD_REQUEST, "์๋ชป๋ ์์ฒญ์
๋๋ค."),
NOT_EXIST(HttpStatus.NOT_FOUND, "์กด์ฌํ์ง ์๋ ๋ฆฌ์์ค์
๋๋ค.");
private final HttpStatus status;
private final String message;
}
public class CustomException extends RuntimeException {
private final ExceptionType type;
public CustomException(ExceptionType type) {
super(type.getMessage());
this.type = type;
}
public ExceptionType getType() {
return type;
}
}
@ExceptionHandler(CustomException.class)
public ResponseEntity<String> handleCustomException(CustomException ex) {
return new ResponseEntity<>(ex.getMessage(), ex.getType().getStatus());
}
์ ์ฝ๋์์๋ ExceptionType Enum์ ์ฌ์ฉํ์ฌ ์์ธ ์ ํ์ ๋ฐ๋ฅธ ์ํ ์ฝ๋์ ๋ฉ์ธ์ง๋ฅผ ์ ์ํ๊ณ , CustomException์ด ๋ฐ์ํ๋ฉด ํด๋น Enum์ ๊ฐ์ ์ฌ์ฉํ์ฌ ์ฒ๋ฆฌํ๋ค.
Exception ๋ฐ์์ง(๊ทผ์์ง)๋ฅผ ์ด๋๋ก ๋์ด์ผ ํ ๊น? + @JsonCreator ์ญ์ง๋ ฌํ
ํต์ฌ ๋ก์ง ๋ด๋ถ์ Exception์ ๋ฐ์์์ผ์ผ ํ๋ ์ด์
Enum ํ์ ๋ณํ ์ ๋ฐ์ํ๋ ์์ธ๋ ๋ณํ ๋ก์ง์์ ๋ฐ์ํด์ผ ํ๋ค. ๋ณํ ๋ก์ง๊ณผ ๋ถ๋ฆฌ๋ ์์ธ ์ฒ๋ฆฌ๋ ์๋ชป๋ ์ค๊ณ์ด๋ค. ์๋ฅผ ๋ค์ด String์์ Enum์ผ๋ก ๋ณํํ ๋ ์์ธ ๋ฐ์ ์ฌ๋ถ๋ฅผ ์ธ๋ถ ๋ก์ง์ ๋งก๊ธฐ๋ ๋์ , ๋ณํ ๋ฉ์๋ ๋ด๋ถ์์ ์์ธ๋ฅผ ์ฒ๋ฆฌํ๋ฉด ๋ ๋ช ํํ๊ณ ์ ์ง๋ณด์๊ฐ ์ฌ์์ง๋ค.
์์ ์ฝ๋(์๋ชป๋ ๋ฐฉ์) - ํต์ฌ ๋ก์ง ์ธ๋ถ์์ ์์ธ ๋ฐ์
public enum Status {
ACTIVE, INACTIVE;
public static Status from(String status) {
for (Status s : Status.values()) {
if (s.name().equalsIgnoreCase(status)) {
return s;
}
}
return null; // ํต์ฌ ๋ก์ง ์ธ๋ถ์์ ์์ธ ์ฒ๋ฆฌ
}
}
public class UserController {
public void processStatus(String status) {
Status userStatus = Status.from(status);
if (userStatus == null) {
throw new CustomException("Invalid status");
}
}
}
ํด๋น ์ฝ๋๋ from() ๋ฉ์๋ ์ธ๋ถ์์ ์์ธ๋ฅผ ์ฒ๋ฆฌํ๋ค.
์์ ์ฝ๋(์ฌ๋ฐ๋ฅธ ๋ฐฉ์) - ํต์ฌ ๋ก์ง ๋ด๋ถ์์ ์์ธ ๋ฐ์
public enum Status {
ACTIVE, INACTIVE;
public static Status from(String status) {
return Arrays.stream(Status.values())
.filter(s -> s.name().equalsIgnoreCase(status))
.findFirst()
.orElseThrow(() -> new CustomException("Invalid status: " + status));
}
}
public class UserController {
public void processStatus(String status) {
Status userStatus = Status.from(status); // ์์ธ๋ ๋ด๋ถ์์ ์ฒ๋ฆฌ
}
}
ํด๋น ์ฝ๋์์๋ Enum ๋ณํ ๋ก์ง ๋ด๋ถ์์ ์์ธ๊ฐ ๋ฐ์ํ๋ฏ๋ก ๋ณํ ๋ก์ง์ ์ฌ์ฉํ ๋๋ง๋ค ์ผ๊ด๋ ๋ฐฉ์์ผ๋ก ์์ธ ์ฒ๋ฆฌ๊ฐ ์ด๋ฃจ์ด์ง๋ค.
@RequestBody์ Null + Validation์ ๋ฐ๋ฅธ Exception ๋ฐ์
DTO ๊ฐ์ฒด๋ฅผ ํตํด Null ์ฒดํฌ์ Validation์ ์ค์ํํ์ฌ ์ฒ๋ฆฌํ ์ ์๋ค. ์๋ฅผ ๋ค์ด, ์์ฒญ DTO์์ ๋น ๊ฐ์ด๋ ์๋ชป๋ ๋ฐ์ดํฐ๋ฅผ ๋ฐ๋ ๊ฒฝ์ฐ, ์ด๋ฅผ ์ผ์ผ์ด ์ปจํธ๋กค๋ฌ์์ ์ฒ๋ฆฌํ๋ ๋์ DTO ํด๋์ค ์์ฒด์์ ์ฒ๋ฆฌํ๋ ๋ฐฉ์์ด๋ค.
Null ๋ฐ Validation ๋ก์ง์ ์ธ๋ถ์์ ์ฒ๋ฆฌํ๋ ์๋ชป๋ ๋ฐฉ์
@PostMapping("/product")
public ResponseEntity<String> createProduct(@RequestBody ProductRequestDto requestDto) {
if (requestDto.getName() == null || requestDto.getName().isEmpty()) {
throw new CustomException("Product name is required");
}
if (requestDto.getPrice() <= 0) {
throw new CustomException("Price must be greater than zero");
}
productService.createProduct(requestDto);
return ResponseEntity.ok("Product created");
}
DTO์์ Null ๋ฐ Validation ์ฒ๋ฆฌํ๋ ์ฌ๋ฐ๋ฅธ ๋ฐฉ์
public class ProductRequestDto {
@NotNull(message = "Product name is required")
private String name;
@Min(value = 1, message = "Price must be greater than zero")
private int price;
// getters and setters
}
@PostMapping("/product")
public ResponseEntity<String> createProduct(@Valid @RequestBody ProductRequestDto requestDto) {
productService.createProduct(requestDto);
return ResponseEntity.ok("Product created");
}
์ ์ฝ๋๋ DTO ๊ฐ์ฒด์์ ๋ฐ๋ก Validation์ ์ฒ๋ฆฌํ๋ฏ๋ก ์ปจํธ๋กค๋ฌ์์๋ Validation ๋ก์ง์ ์ ๊ฒฝ์ฐ์ง ์์๋๋๋ค.
@Controller์ ๋ฐํ ๋ฐ์ดํฐ์ ๋ํ ์ปค์คํ Wrapper Class
@Controler์์ Exception์ ๋ฐ๋ฅธ ์ปค์คํ Wrapper Class๋ก JSON ๋ฐํ
๋ฌธ์ ์ํฉ
ํ๋ก ํธ์๋ ๋๋ API์ ์ผ๊ด๋ JSON ํํ๋ก ์๋ต์ ์ ๊ณตํด์ผ ํ๋ค. ๊ธฐ์กด ๋ฐฉ์์์ API ๋ฉ์๋๋ง๋ค ๋ฐํํ๋ ๋ฐ์ดํฐ ๊ตฌ์กฐ๊ฐ ๋ฌ๋ผ, ํ๋ก ํธ์๋์์ ์ผ์ผ์ด ์ฒ๋ฆฌ๊ฐ ํ์ํ๋ค. ๋ํ, ๋ค์ํ ์์ธ ๋ฐ์ ์ ์ผ๊ด์ฑ ์์ด ์์ธ๊ฐ ์ฒ๋ฆฌ๋์๊ณ , ์๋ฌ ๋ฉ์ธ์ง์ ๊ด๋ฆฌ์ ์ฒ๋ฆฌ ๋ฐฉ์์ด ๋ณต์กํด์ก๋ค.
ํด๊ฒฐ ๋ฐฉ์
๋ชจ๋ @ReponseBody์์ ์ผ๊ด๋ ๊ฐ์ฒด(JSON)ํํ๋ก ๋ฐํํ๋๋ก ์ปค์คํ Wrapper ํด๋์ค๋ฅผ ์ฌ์ฉํ๋ ๋ฐฉ์์ด๋ค. ์ด๋ฅผ ํตํด ์ผ๊ด๋ ํํ์ ์๋ต์ ์ ๊ณตํ๊ณ ์ฑ๊ณต๊ณผ ์คํจ์ ๋ํ ๋ฉ์ธ์ง ์ฒ๋ฆฌ๋ฅผ ๋์ฑ ์ฒด๊ณํํ ์ ์๋ค.
- ์์ ์ฝ๋: ์ปค์คํ Response ํด๋์ค ์ฌ์ฉํ์ฌ ์ผ๊ด๋ JSON ์๋ต
RequestResult ํด๋์ค ์ ์
public class RequestResult<T> {
private boolean success;
private T data;
private String message;
// ์ฑ๊ณต ์
public static <T> RequestResult<T> success(T data) {
RequestResult<T> result = new RequestResult<>();
result.success = true;
result.data = data;
result.message = "Operation Successful";
return result;
}
// ์คํจ ์
public static <T> RequestResult<T> failure(String message) {
RequestResult<T> result = new RequestResult<>();
result.success = false;
result.data = null;
result.message = message;
return result;
}
// Getters and Setters
}
Controller์์ RequestResult ๋ฐํ
@RestController
public class ProductController {
@GetMapping("/product/{id}")
public RequestResult<Product> getProduct(@PathVariable String id) {
try {
Product product = productService.getProductById(id);
return RequestResult.success(product);
} catch (NoSuchElementException e) {
return RequestResult.failure("Product not found");
}
}
}
์ ์ฝ๋๋ ์ฑ๊ณต๊ณผ ์คํจ๋ฅผ ๋ชจ๋ RequestResult ๊ฐ์ฒด๋ก ์ฒ๋ฆฌํ์ฌ, ์ผ๊ด๋ ํํ๋ก JSON ์๋ต์ ์ ๊ณตํ๋ค.
์ปค์คํ Wrapper Class(JSON)์ ๊ฐ์ฒด ์์ฑ์ ์ํ ์ ์ ํฉํ ๋ฆฌ ๋ฉ์๋ ํ์ฉ
์ ์ ํฉํ ๋ฆฌ ๋ฉ์๋๋ฅผ ํตํด ์ฑ๊ณต ๋ฐ ์คํจ ์ ๊ฐ์ฒด๋ฅผ ๊ฐ๋จํ ์์ฑํ์ฌ, ๊ฐ์ฒด ์์ฑ ์ ๋ชจ๋ ๊ฒฝ์ฐ์ ์๋ฅผ ์์ฑ์์ ๋ ธ์ถํ์ง ์๊ณ ํ์ํ ๊ฒฝ์ฐ์ ๋ฐ๋ผ ๊ฐ์ฒด๋ฅผ ์ฝ๊ฒ ์์ฑํ ์ ์๋ค.
์์ ์ฝ๋
public class ApiResponse<T> {
private boolean success;
private T data;
private String error;
// ์ฑ๊ณต ์๋ต
public static <T> ApiResponse<T> success(T data) {
ApiResponse<T> response = new ApiResponse<>();
response.success = true;
response.data = data;
return response;
}
// ์คํจ ์๋ต
public static <T> ApiResponse<T> failure(String error) {
ApiResponse<T> response = new ApiResponse<>();
response.success = false;
response.error = error;
return response;
}
// Getters and Setters
}
์ด ๋ฐฉ์์ Controller๋ฟ ์๋๋ผ ์๋น์ค ๊ณ์ธต, DTO ๋ฑ์์๋ ์ฝ๊ฒ ํ์ฉํ ์ ์์ผ๋ฉฐ, ์ผ๊ด๋ ์๋ต ๊ตฌ์กฐ๋ฅผ ์ ์งํ๊ณ ์ฒ๋ฆฌ ๋ก์ง์ ๋จ์ํํ๋๋ฐ ํฐ ๋์์ด ๋๋ค.
Exception ๊ณตํต ์ฒ๋ฆฌ์ ํ์์ฑ
Spring์์ @Valid์ @Secured๋ฅผ ์ฒ๋ผ ์ถ์ํ๋ ์ฒ๋ฆฌ๋ try-catch ๊ตฌ๋ฌธ์ ํตํด ๊ฐ๋ฐ์๊ฐ ์ง์ ์ฒ๋ฆฌํ ์ ์๋ค.
์ด์ ๋ฐฉ์์ ๋ฌธ์ :
- ๊ธฐ์กด์๋ ์์ธ๋ฅผ ์ฒ๋ฆฌํ๊ธฐ ์ํด์ @Controller ๋ด try-catch ๊ตฌ๋ฌธ์ผ๋ก ๋ฐฉ์ดํ๋ค.
- ๋ง์ฝ @Controller์ ์๋ฐฑ ๊ฐ์ API ๋ฉ์๋๊ฐ ์๋ค๋ฉด, ๊ฐ ๋ฉ์๋๋ง๋ค try-catch๋ฅผ ๋ฐ๋ณต์ ์ผ๋ก ์์ฑํด์ผ ํ๋ฏ๋ก, ์ค๋ณต ์ฝ๋๊ฐ ๋ฐ์ํ๊ณ ์ ์ง ๋ณด์๋ ์ด๋ ค์์ง๋ค.
์ดํ ํด๊ฒฐ์ฑ :
- @ControllerAdvice์ @ExceptionHandler๋ฅผ ์ฌ์ฉํ๋ฉด try-catch๋ฅผ ๋์ ํ์ฌ ์์ธ ์ฒ๋ฆฌ๋ฅผ ์ค์์์ ์ผ๊ด๋๊ฒ ๊ด๋ฆฌํ ์ ์๋ค.
- @ExceptionHandler๋ฅผ ํตํด ์ด๋ค ์์ธ๋ฅผ ์ฒ๋ฆฌํ ์ง ๋ช ํํ๊ฒ ์ง์ ํ ์ ์๋ค.
- ์ฌ๋ฌ ์์ธ๋ฅผ ํ ๋ฒ์ ์ฒ๋ฆฌํ๋ ค๋ฉด @ExceptionHandler({AException.class, BException.class})์ฒ๋ผ ์ฒ๋ฆฌํ ์ ์๋ฐ.
- ๋๋ ์์ธ๋ณ๋ก ๊ฐ๋ณ @ExceptionHandler ๋ฉ์๋๋ฅผ ์ ์ํ ์ ์์ง๋ง, ์์ธ ํด๋์ค๋ค์ด ๊ณตํต๋ ๋ถ๋ชจ ํด๋์ค๋ฅผ ์์๋ฐ์ง ์์ผ๋ฉด ์ค๋ณต ์ฝ๋๊ฐ ๋ฐ์ํ ์ ์๋ค.
์ถ๊ฐ ๊ธฐ๋ฅ:
- HttpServletRequest ๊ฐ์ฒด๋ฅผ ํตํด ์ด๋ค ์ ์ ๊ฐ, ์ด๋ค URL์์ ์๋ฌ๊ฐ ๋ฐ์ํ๋์ง ์ถ์ ํ ์ ์๋ค.
- @ResponseBody์ @ControllerAdvice๋ฅผ ์กฐํฉํ๋ฉด @RestControllerAdvice๋ฅผ ์ฌ์ฉํ ์ ์์ด, RESTful ๋ฐฉ์์ผ๋ก JSON์๋ต์ ์ฝ๊ฒ ๋ฐํํ ์ ์๋ค.
- ํน์ ํจํค์ง์ @Controller์๋ง ์ ์ฉํ ์ ์๋๋ก @ControllerAdvice์ ํจํค์ง ํํฐ๋ฆญ ์ต์ ์ ์ถ๊ฐํ ์ ์๋ค.
@ControllerAdvice๋ก Controller์ Exception ์ฒ๋ฆฌ๋ฅผ ์ฑ ์ ์์
๊ธฐ์กด์๋ @Controller ๋ด์์ try-catch๋ฌธ์ผ๋ก ์ฒ๋ฆฌํด์ผ ํ์ง๋ง, @ControllerAdvice๋ฅผ ์ฌ์ฉํ๋ฉด ์ด๋ฅผ ์ค์ํ ํ ์ ์๋ค. ์ด๋ฅผ ํตํด ๊ฐ๋ฐ์๋ ๊ฐ Controller์์ ์์ธ ์ฒ๋ฆฌ ์ฝ๋๋ฅผ ๋ฐ๋ณตํ์ง ์๊ณ , ์ค์์์ ์ผ๊ด๋ ์์ธ ์ฒ๋ฆฌ๋ฅผ ํ ์ ์๋ค.
- ์ด ๋ฐฉ๋ฒ์ Entity๋ DTO ๊ฐ์ฒด์๋ ์ ์ฉํ ์ ์์ผ๋ฉฐ, ์ํฉ์ ๋ง๋ ์ ์ ํฉํ ๋ฆฌ ๋ฉ์๋๋ฅผ ํตํด ๊ฐ์ฒด ์์ฑ ์ ๋ชจ๋ ์ ๋ฌ ์ธ์๋ฅผ ๋ ธ์ถํ์ง ์๋๋ก ํ๋ค.
- ๋ง์ฝ @ResponseBody๋ฅผ ๋ช ์ํ์ง ์์ผ๋ฉด 500์๋ฌ๊ฐ ๋ฐ์ํ๋ฉฐ JSON์ผ๋ก ์๋ต๋์ง ์๊ธฐ ๋๋ฌธ์ ์ฃผ์ํด์ผํ๋ค.
@RestControllerAdvice = @ControllerAdvice + @ResponseBody
- @RestControllerAdvice๋ @ControllerAdvice์ @ResponseBody๋ฅผ ํฉ์น ํํ๋ก, @RestController๊ฐ @Controller์ @ResponseBody๋ฅผ ๊ฒฐํฉํ ๊ฒ๊ณผ ๋์ผํ ๊ฐ๋ ์ด๋ค.
- ์ด๋ฅผ ํตํด @RestController์ฒ๋ผ, ์์ธ ์ฒ๋ฆฌ์ JSON ์๋ต์ ๊ฐํธํ๊ฒ ๋ฐํํ ์ ์๋ค.
@FieldDefaults: ํ๋ ์ ์ ์ ์ ๊ทผ ์ ์ด์ ๋ฐ final ์ฌ์ฉ
@FieldDefaults๋ ํด๋์ค์ ํ๋ ์ ์ ์ ์ ๊ทผ ์ ์ด์์ final ํค์๋๋ฅผ ๋ช ์์ ์ผ๋ก ์ ์ธํ๋ ์๊ณ ๋ฅผ ๋์ด์ค๋ค. ํนํ, Service๋ DTO ๊ฐ์ ๊ฐ์ฒด์์ ์ ์ฉํ๊ฒ ์ฌ์ฉํ ์ ์๋ค.
- ํ๋์ ๋งค๋ฒ private final์ ๋ถ์ด๋ ๊ฒ์ด ๋ฒ๊ฑฐ๋ก์ธ ๋, @FieldDefaults๋ฅผ ํตํด ํ ๋ฒ์ ์ฒ๋ฆฌํ ์ ์๋ค.
- @Autowired๋ฅผ ๋์ฒดํ๊ธฐ ์ํด @RequiredArgsContsructor์ final์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ์๋ ์ ์ฉํ๋ค.
@RequiredArgsConstructor
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
public class PostService {
final PostRepository postRepository;
}
- ๋ํ, ์์ฑ์ ๋ฐ์ ์ Lombok์์ @NoArgsConstructor๋ @AllArgsConstructor์ access = AccessLevel.PROTECTED๋ฅผ ์ถ๊ฐํ์ฌ ์ ๊ทผ ์ ์ด๋ฅผ ์ค์ ํ ์๋ ์๋ค.
โน๏ธ ์ฐธ๊ณ
[ASAC 6๊ธฐ ๊ฐ์์๋ฃ]
'๐ปDEV-STUDY > Spring' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Spring] Spring JDBC (0) | 2024.12.22 |
---|---|
[Spring Boot, DB] DB์ Spring Boot ์ฐ๋ (1) | 2024.11.29 |
[Spring] ์ค๋ฌด์์์ Best Practices #1 (0) | 2024.10.07 |
[Spring] Spring Boot ์ฅ์ ๋ฐ ๋์ (0) | 2024.10.06 |
[Spring] Spring ์์ฒญ๊ฐ ์ฒ๋ฆฌ ๋ฐฉ์ (0) | 2024.10.06 |