스프링 타입 컨버터 - Converter 타입 컨버터를 사용하려면 org.springframework.core.convert.converter.Converter 인터페이스를 구현하면 된다. 주의 Converter 라는 이름의 인터페이스가 많으니 조심해야 한다. org.springframework.core.convert.converter.Converter 를 사용해야 한다. 컨버터 인터페이스 package org.springframework.core.convert.converter; public interface Converter { T convert(S source); } 먼저 가장 단순한 형태인 문자를 숫자로 바꾸는 타입 컨버터를 만들어보자. StringToIntegerConverter - 문자를 숫자로 변환하는 타입 컨버터 package h..
API 예외 처리 - @ControllerAdvice @ExceptionHandler 를 사용해서 예외를 깔끔하게 처리할 수 있게 되었지만, 정상 코드와 예외 처리 코드가 하나의 컨트롤러에 섞여 있다. @ControllerAdvice 또는 @RestControllerAdvice 를 사용하면 둘을 분리할 수 있다. ExControllerAdvice package hello.exception.exhandler.advice; import hello.exception.exception.UserException; import hello.exception.exhandler.ErrorResult; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springfr..
API 예외처리 - @ExceptionHandler HTML 화면 오류 vs API 오류 웹 브라우저에 HTML 화면을 제공할 때는 오류가 발생하면 BasicErrorController 를 사용하는게 편하다. 이때는 단순히 5xx, 4xx 관련된 오류 화면을 보여주면 된다. BasicErrorController 는 이런 메커니즘을 모두 구현해두었다. 그런데 API는 각 시스템 마다 응답의 모양도 다르고, 스펙도 모두 다르다. 예외 상황에 단순히 오류 화면을 보여주는 것이 아니라, 예외에 따라서 각각 다른 데이터를 출력해야 할 수도 있다. 그리고 같은 예외라고 해도 어떤 컨트롤러에서 발생했는가에 따라서 다른 예외 응답을 내려주어야 할 수 있다. 한마디로 매우 세밀한 제어가 필요하다. 앞서 이야기했지만, 예를 들어서 상품 API와 주문 API는 오류가 발생했..
스프링이 제공하는 ExceptionResolver 스프링 부트가 기본으로 제공하는 ExceptionResolver 는 다음과 같다. HandlerExceptionResolverComposite 에 다음 순서로 등록 1. ExceptionHandlerExceptionResolver 2. ResponseStatusExceptionResolver 3. DefaultHandlerExceptionResolver -> 우선 순위가 가장 낮다. ExceptionHandlerExceptionResolver @ExceptionHandler를 처리한다. API 예외처리는 대부분 이 기능으로 해결한다. ResponseStatusExceptionResolver HTTP 상태코드를 지정해준다. 예) @ResponseStatus(value = HttpStatus.NOT_FOUN..
썸네일 API 예외처리 - HandlerExceptionResolver 목표 예외가 발생해서 서블릿을 넘어 WAS까지 예외가 전달되면 HTTP 상태코드가 500으로 처리된다. 발생하는 예외에 따라서 400, 404 등등 다른 상태코드로 처리하고 싶다. 오류 메시지, 형식등을 API마다 다르게 처리하고 싶다. 상태코드 변환 예를 들어서 IllegalArgumentException 을 처리하지 못해서 컨트롤러 밖으로 넘어가는 일이 발생하면 HTTP 상태코드를 400으로 처리하고 싶다. 어떻게 해야할까? ApiExceptionController.java @GetMapping("/api/members/{id}") public MemberDto getMemberDto(@PathVariable("id") String id){ if (id.equals("ex")){ throw new R..
API 예외처리 - 스프링 부트 기본 오류 처리 스프링 부트가 제공하는 BasicErrorController를 살펴보자. @RequestMapping(produces = MediaType.TEXT_HTML_VALUE) public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) { HttpStatus status = getStatus(request); Map model = Collections .unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML))); response.setStatus(status.value()); ModelAndVi..
Bean Validation - 6 - HTTP 메시지 컨버터 @Valid, @Validated는 HttpMessageConverter(@RequestBody)에도 적용할 수 있다. 참고 @ModelAttribute 는 HTTP 요청 파라미터(URL 쿼리 스트링, POST Form)를 다룰 때 사용한다. @RequestBody 는 HTTP Body의 데이터를 객체로 변환할 때 사용한다. 주로 API JSON 요청을 다룰 때 사용한다. 컨트롤러 생성 @Slf4j @RestController @RequestMapping("/validation/api/items") public class ValidationItemApiController { @PostMapping("/add") public Object addItem(@RequestBody @Validated ItemSa..
Bean Validation - 5 Form 전송 객체 분리 방법 실무에서는 groups 를 잘 사용하지 않는데, 그 이유가 다른 곳에 있다. 바로 등록시 폼에서 전달하는 데이터가 Item 도메인 객체와 딱 맞지 않기 때문이다. 소위 "Hello World" 예제에서는 폼에서 전달하는 데이터와 Item 도메인 객체가 딱 맞는다. 하지만 실무에서는 회원 등록시 회원과 관련된 데이터만 전달받는 것이 아니라, 약관 정보도 추가로 받는 등 Item 과 관계없는 수 많은 부가 데이터가 넘어온다. 그래서 보통 Item 을 직접 전달받는 것이 아니라, 복잡한 폼의 데이터를 컨트롤러까지 전달할 별도의 객체를 만들어서 전달한다. 예를 들면 ItemSaveForm 이라는 폼을 전달받는 전용 객체를 만들어서 @ModelAttribute 로 사용한다. 이것을 ..
Bean Validation - 4(groups) 한계 수정시 검증 요구사항 데이터를 등록할 때와 수정할 때는 요구사항이 다를 수 있다. 등록시 기존 요구사항 타입 검증 가격, 수량에 문자가 들어가면 검증 오류 처리 필드 검증 상품명: 필수, 공백X 가격: 1000원 이상, 1백만원 이하 수량: 최대 9999 특정 필드의 범위를 넘어서는 검증 가격 * 수량의 합은 10,000원 이상 수정시 요구사항 등록시에는 quantity 수량을 최대 9999까지 등록할 수 있지만 수정시에는 수량을 무제한으로 변경할 수 있다. 등록시에는 id 에 값이 없어도 되지만, 수정시에는 id 값이 필수이다 수정 요구사항 적용 @Data public class Item { @NotNull // 수정 요구사항 private Long id; @NotBlank private Stri..
Bean Validation - 3 Bean Validation - 오브젝트 오류 Bean Validation에서 특정필드(FieldError)가 아닌 해당 오브젝트 관련 오류(ObjectError)는 어떻게 처리할 수 있는가? @ScriptAssert() 사용하면 된다. @Data @ScriptAssert(lang = "javascript", script = "_this.price * _this.quantity >= 10000") public class Item { //... } 메시지 코드 ScriptAssert.item ScriptAssert 그러나 사용해 보면 제약이 많고 복잡하다. 검증 기능이 해당 객체의 범위를 넘는 경우가 있는데 그럴 경우 대응이 어렵다. 따라서 오브젝트 오류(글로벌 오류)의 경우 @ScriptAssert를 억..
Bean Validation - 2(스프링 적용) 스프링 MVC는 어떻게 Bean Validator를 사용? 스프링 부트가 spring-boot-starter-validation 라이브러리를 넣으면 자동으로 Bean Validator를 인지하고 스프링에 통합한다. 스프링 부트는 자동으로 글로벌 Validator로 등록한다. LocalValidatorFactoryBean 을 글로벌 Validator로 등록한다. 이 Validator는 @NotNull 같은 애노테이션을 보고 검증을 수행한다. 이렇게 글로벌 Validator가 적용되어 있기 때문에, @Valid , @Validated 만 적용하면 된다. 검증 오류가 발생하면, FieldError , ObjectError 를 생성해서 BindingResult 에 담아준다. 주의! 다음과 같이 직접 글로벌 Va..
Bean Validation - 1 Bean Validation 먼저 Bean Validation은 특정한 구현체가 아니라 Bean Validation 2.0(JSR-380)이라는 기술 표준이다. 쉽게 이야기해서 검증 애노테이션과 여러 인터페이스의 모음이다. 마치 JPA가 표준 기술이고 그 구현체로 하이버네이트가 있는 것과 같다. Bean Validation을 구현한 기술중에 일반적으로 사용하는 구현체는 하이버네이트 Validator이다. 이름이 하이버네이트가 붙어서 그렇지 ORM과는 관련이 없다. 먼저 스프링과 연동하지 않고 순수한 Bean Validation 사용법부터 알아보자. package hello.itemservice.domain.item; import lombok.Data; import org.hibernate.validato..