일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- 자동차수리시스템
- Java
- 자바
- 메소드오버로딩
- 오라클
- 객체 비교
- exception
- 사용자예외클래스생성
- 인터페이스
- 한국건설관리시스템
- 어윈 사용법
- 참조형변수
- 정수형타입
- 예외처리
- abstract
- 다형성
- NestedFor
- oracle
- 컬렉션프레임워크
- cursor문
- GRANT VIEW
- 대덕인재개발원
- 컬렉션 타입
- 예외미루기
- 제네릭
- EnhancedFor
- 집합_SET
- 추상메서드
- 환경설정
- 생성자오버로드
- Today
- Total
거니의 velog
(10) 스프링 부트와 API 서버 5 본문
6. @RestControllerAdvice
* API 서버는 화면이 없는 상태에서 개발되기 때문에 잘못된 파라미터 등으로 인한 서버 내부의 예외 처리를 @RestControllerAdvice로 처리해 주는 것이 안전하다.
* 예를 들어 존재하지 않는 번호의 Todo를 조회하면 NoSuchElementException 에러가 발생한다.
- http://localhost:8080/api/todo/33
* 또한 페이지 번호를 숫자가 아닌 문자로 전달하면 MethodArgumentNotValidException 에러가 발생한다.
- http://localhost:8080/api/todo/list?page=A&size=10
* 이러한 예외를 처리하기 위해서 controller 패키지 내에서 advice 패키지를 추가하고 CustomControllerAdvice.java 파일을 추가한다.
package com.unlimited.mallapi.controller.advice;
import java.util.Map;
import java.util.NoSuchElementException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class CustomControllerAdvice {
// NoSuchElementException을 처리하는 예외 핸들러
@ExceptionHandler(NoSuchElementException.class)
protected ResponseEntity<?> notExist(NoSuchElementException e) {
// 예외 메시지를 추출하여 클라이언트에게 NOT_FOUND 상태와 함께 반환
String msg = e.getMessage();
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(Map.of("msg", msg));
}
// MethodArgumentNotValidException을 처리하는 예외 핸들러
@ExceptionHandler(MethodArgumentNotValidException.class)
protected ResponseEntity<?> handleIllegalArgumentException(MethodArgumentNotValidException e) {
// 예외 메시지를 추출하여 클라이언트에게 NOT_ACCEPTABLE 상태와 함께 반환
String msg = e.getMessage();
return ResponseEntity.status(HttpStatus.NOT_ACCEPTABLE).body(Map.of("msg", msg));
}
}
* @RestControllerAdvice가 적용되면 예외가 발생해도 호출한 쪽으로 HTTP 상태 코드와 JSON 메시지를 전달할 수 있게 된다.
- http://localhost:8080/api/todo/33
- http://localhost:8080/api/todo/list?page=A&size=10
7. REST 관련 툴을 이용한 POST/PUT/DELETE
* GET 방식과 달리 POST 방식은 브라우저에서 확인이 어렵기 때문에 별도의 테스트 방식을 고민해야 한다. 크롬 확장 프로그램을 사용할 수도 있고, Postman 같은 프로그램을 사용할 수도 있다. 우리는 Postman으로 POST/PUT/DELETE 방식을 테스트한다.
(1) Formatter를 이용한 LocalDate 처리
* 처리를 위해서 전달되는 데이터는 JSON 형식의 데이터일 수도 있고 첨부파일 등이 포함되는 경우에는 form-data 혹은 일반적인 웹에서 사용하는 x-www-form-urlencoded 일 수도 있다. 예제에서는 JSON 데이터를 가정하고 @RequestBody를 이용해서 TodoDTO로 처리한다.
* 이러한 처리 과정에서 날짜/시간은 항상 주의해야 한다. 날짜/시간은 브라우저에서 문자열로 전송되지만, 서버에서는 LocalDate 혹은 LocalDateTile으로 처리된다. 그렇기 때문에 이를 변환해 주는 Formatter를 추가해서 이 과정을 자동으로 할 수 있도록 설정한다.
* controller 패키지 하위로 formatter 패키지를 선언하고 LocalDateFormatter 클래스를 추가한다.
package com.unlimited.mallapi.controller.formatter;
import java.text.ParseException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
import org.springframework.format.Formatter;
public class LocalDateFormatter implements Formatter<LocalDate> {
// 문자열을 LocalDate로 파싱하는 메서드
@Override
public LocalDate parse(String text, Locale locale) throws ParseException {
// 주어진 형식("yyyy-MM-dd")으로 문자열을 LocalDate로 변환하여 반환
return LocalDate.parse(text, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
}
// LocalDate를 문자열로 변환하는 메서드
@Override
public String print(LocalDate object, Locale locale) {
// 주어진 형식("yyyy-MM-dd")으로 LocalDate를 문자열로 변환하여 반환
return DateTimeFormatter.ofPattern("yyyy-MM-dd").format(object);
}
}
* 작성된 LocalDateFormatter는 스프링의 MVC의 동작 과정에서 사용될 수 있도록 설정을 추가해 주어야 한다. config 패키지에 CustomServletConfig 클래스를 추가한다.
package com.unlimited.mallapi.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import com.unlimited.mallapi.controller.formatter.LocalDateFormatter;
@Configuration
public class CustomServletConfig implements WebMvcConfigurer {
// FormatterRegistry에 LocalDateFormatter를 등록하는 메서드
@Override
public void addFormatters(FormatterRegistry registry) {
// LocalDateFormatter를 FormatterRegistry에 추가하여 날짜 형식을 지원
registry.addFormatter(new LocalDateFormatter());
}
}
(2) POST 방식의 등록 처리
* 새로운 Todo의 등록은 단순 JSON 데이터라고 가정하고 이를 처리하기 위한 메서드를 TodoController에 추가한다.
package com.unlimited.mallapi.controller;
import java.util.Map;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.unlimited.mallapi.dto.PageRequestDTO;
import com.unlimited.mallapi.dto.PageResponseDTO;
import com.unlimited.mallapi.dto.TodoDTO;
import com.unlimited.mallapi.service.TodoService;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
@RestController
@RequiredArgsConstructor
@Log4j2
@RequestMapping("/api/todo")
public class TodoController {
private final TodoService service;
// 특정 번호의 Todo 항목 조회
@GetMapping("/{tno}")
public TodoDTO get(@PathVariable(name = "tno") Long tno) {
return service.get(tno);
}
// Todo 항목 목록 조회
@GetMapping("/list")
public PageResponseDTO<TodoDTO> list(PageRequestDTO pageRequestDTO) {
log.info(pageRequestDTO);
return service.list(pageRequestDTO);
}
// Todo 항목 등록
@PostMapping("/")
public Map<String, Long> register(@RequestBody TodoDTO todoDTO) {
log.info("TodoDTO : {}", todoDTO);
Long tno = service.register(todoDTO);
return Map.of("TNO", tno);
}
}
* 프로젝트를 실행하고 Postman을 사용해서 POST 방식으로 JSON 데이터를 전달해서 동작 여부를 확인한다. 실행 결과로는 새로운 번호(tno)가 생성된다.
{
"title" : "Sample Title",
"writer" : "user1",
"dueDate" : "2023-10-10"
}
- http://localhost:8080/api/todo/
(3) PUT 방식의 수정 처리
* Todo의 수정은 PUT 방식으로 한다. 수정될 수 있는 필드는 제목(title), 완료여부(complete), 만료일(dueDate)이다. TodoController에 @PutMapping으로 메서드를 추가한다.
package com.unlimited.mallapi.controller;
import java.util.Map;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.unlimited.mallapi.dto.PageRequestDTO;
import com.unlimited.mallapi.dto.PageResponseDTO;
import com.unlimited.mallapi.dto.TodoDTO;
import com.unlimited.mallapi.service.TodoService;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
@RestController
@RequiredArgsConstructor
@Log4j2
@RequestMapping("/api/todo")
public class TodoController {
private final TodoService service;
// 특정 번호의 Todo 항목 조회
@GetMapping("/{tno}")
public TodoDTO get(@PathVariable(name = "tno") Long tno) {
return service.get(tno);
}
// Todo 항목 목록 조회
@GetMapping("/list")
public PageResponseDTO<TodoDTO> list(PageRequestDTO pageRequestDTO) {
log.info(pageRequestDTO);
return service.list(pageRequestDTO);
}
// Todo 항목 등록
@PostMapping("/")
public Map<String, Long> register(@RequestBody TodoDTO todoDTO) {
log.info("TodoDTO : {}", todoDTO);
Long tno = service.register(todoDTO);
return Map.of("TNO", tno);
}
// 특정 번호의 Todo 항목 수정
@PutMapping("/{tno}")
public Map<String, String> modify(
@PathVariable(name = "tno") Long tno,
@RequestBody TodoDTO todoDTO) {
todoDTO.setTno(tno);
log.info("Modify : {}", todoDTO);
service.modify(todoDTO);
return Map.of("RESULT", "SUCCESS");
}
}
* Postman에서 아래와 같은 문자열을 이용해서 테스트를 진행한다.
{
"tno" : 102,
"title" : "Updated Title",
"complete" : true,
"dueDate" : "2024-03-03"
}
- http://localhost:8080/api/todo/102
* REST 방식은 특별히 정해진 규격이 있는 것이 아니기 때문에 반드시 수정 작업을 PUT 방식으로 해야 할
필요는 없다. 경우에 따라서 POST 방식을 이용하더라도 잘못 설계된 것은 아니다.
관례적인 표현이다.
(4) DELETE 방식의 삭제 처리
* TodoController에서 삭제 처리는 번호를 이용해서 처리한다.
package com.unlimited.mallapi.controller;
import java.util.Map;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.unlimited.mallapi.dto.PageRequestDTO;
import com.unlimited.mallapi.dto.PageResponseDTO;
import com.unlimited.mallapi.dto.TodoDTO;
import com.unlimited.mallapi.service.TodoService;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
@RestController
@RequiredArgsConstructor
@Log4j2
@RequestMapping("/api/todo")
public class TodoController {
private final TodoService service;
// 특정 번호의 Todo 항목 조회
@GetMapping("/{tno}")
public TodoDTO get(@PathVariable(name = "tno") Long tno) {
return service.get(tno);
}
// Todo 항목 목록 조회
@GetMapping("/list")
public PageResponseDTO<TodoDTO> list(PageRequestDTO pageRequestDTO) {
log.info(pageRequestDTO);
return service.list(pageRequestDTO);
}
// Todo 항목 등록
@PostMapping("/")
public Map<String, Long> register(@RequestBody TodoDTO todoDTO) {
log.info("TodoDTO : {}", todoDTO);
Long tno = service.register(todoDTO);
return Map.of("TNO", tno);
}
// 특정 번호의 Todo 항목 수정
@PutMapping("/{tno}")
public Map<String, String> modify(
@PathVariable(name = "tno") Long tno,
@RequestBody TodoDTO todoDTO) {
todoDTO.setTno(tno);
log.info("Modify : {}", todoDTO);
service.modify(todoDTO);
return Map.of("RESULT", "SUCCESS");
}
// 특정 번호의 Todo 항목 삭제
@DeleteMapping("/{tno}")
public Map<String, String> remove(@PathVariable(name = "tno") Long tno) {
log.info("Remove : {}", tno);
service.remove(tno);
return Map.of("RESULT", "SUCCESS");
}
}
- http://localhost:8080/api/todo/102
* DELETE 방식의 경우 POST/PUT과 달리 전달하는 데이터(payload)가 제한적이다. URL에서 사용되는 특수문자
등을 이용하기 위해서는 URL 인코딩 처리를 해 주어야 한다.
공백문자나 특수문자가 포함된 데이터는 정상적으로 처리가 되지 않기 때문에 주의해야 한다.
[CORS 관련 설정]
* Ajax를 이용해서 서비스를 호출하게 되면 반드시 '교차 출처 리소스 공유(Cross-Origin Resource Sharing - 이하 CORS)'로 인해 정상적으로 호출이 제한된다. 리액트에서 스프링 부트로 동작하는 서버를 호출해야 하므로 CustomServletConfig에 추가적인 설정이 필요하다.
* CORS 설정은 @Controller가 있는 클래스에 @CrossOrigin을 적용하거나 Spring Security를 이용하는 설정이
있다. @CrossOrigin 설정은 모든 컨트롤러에 개별적으로 적용해야 하므로 예제에서는 WebMvcConfigurer의
설정으로 사용한다.
package com.unlimited.mallapi.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import com.unlimited.mallapi.controller.formatter.LocalDateFormatter;
@Configuration
public class CustomServletConfig implements WebMvcConfigurer {
// 날짜 형식 변환을 위한 LocalDateFormatter를 등록하는 메서드
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addFormatter(new LocalDateFormatter());
}
// CORS(Cross-Origin Resource Sharing) 설정을 위한 메서드
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*") // 모든 오리진에 대한 접근 허용
.allowedMethods("HEAD", "GET", "POST", "PUT", "DELETE", "OPTIONS") // 허용하는 HTTP 메서드
.maxAge(300) // 캐시 유효 시간 설정
.allowedHeaders("Authorization", "Cache-Control", "Content-Type"); // 허용하는 헤더
}
}
'SpringBoot_React 풀스택 프로젝트' 카테고리의 다른 글
(12) 리액트와 API 서버 통신 2 (0) | 2024.02.28 |
---|---|
(11) 리액트와 API 서버 통신 1 (0) | 2024.02.28 |
(9) 스프링 부트와 API 서버 4 (0) | 2024.02.28 |
(8) 스프링 부트와 API 서버 3 (0) | 2024.02.28 |
(7) 스프링 부트와 API 서버 2 (0) | 2024.02.28 |