1.4. Annotated Controllers #2

편집일시: 2021-04-12 15:52 조회수: 347 댓글수: 0
## 1.4.4. Model [Web MVC](https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-modelattrib-methods) `@ModelAttribute` 어노테이션을 사용할 수 있다. 모델에서 개체를 만들거나 액세스하고 `WebDataBinder`를 통해 요청에 바인딩 `@RequestMapping` 메서드의 메서드 인수 . `@Controller` 또는 `@ControllerAdvice` 클래스의 메서드 레벨의 어노테이션으로 `@RequestMapping` 메서드를 호출하기 전에 모델을 초기화 할 수 있다. `@RequestMapping` 메서드에서 반환 값을 모델 속성으로 표시한다. 이 섹션에서는 `@ModelAttribute` 메서드 또는 이전 목록의 두 번째 항목에 대해 설명한다. 컨트롤러는 `@ModelAttribute` 방법을 얼마든지 가질 수 있다. 이러한 메서드는 모두 동일한 컨트롤러의 `@RequestMapping` 메서드 전에 호출된다. `@ModelAttribute` 메서드는 `@ControllerAdvice`을 통해 컨트롤러간에 공유 할 수 있다. 자세한 내용은 컨트롤러의 조언 섹션을 참조해라. `@ModelAttribute` 메서드는 유연한 메서드 시그너처 있다. 이들은 `@RequestMapping` 메서드와 같은 인수의 많은 지원한다 ( `@ModelAttribute` 자체 및 요청 본문에 관련하는 것을 제외한다). 다음 예제에서는 `@ModelAttribute` 방법을 사용하고 있다. Java ``` @ModelAttribute public void populateModel(@RequestParam String number, Model model) { model.addAttribute(accountRepository.findAccount(number)); // add more ... } ``` Kotlin ``` @ModelAttribute fun populateModel(@RequestParam number: String, model: Model) { model.addAttribute(accountRepository.findAccount(number)) // add more ... } ``` 다음 예제에서는 하나의 속성만 추가한다. Java ``` @ModelAttribute public Account addAccount(@RequestParam String number) { return accountRepository.findAccount(number); } ``` Kotlin ``` @ModelAttribute fun populateModel(@RequestParam number: String, model: Model) { model.addAttribute(accountRepository.findAccount(number)) // add more ... } ``` > 이름이 명시적으로 지정되지 않은 경우 `Conventions(Javadoc)`의 javadoc에 따라 유형에 따라 기본 이름이 선택된다. 오버로드 된 `addAttribute` 메서드를 사용하거나 `@ModelAttribute` name 속성 (반환 용)를 사용하여 명시적인 이름을 언제든지 지정할 수 있다. Spring WebFlux은 Spring MVC와 달리 모델의 리액티브 형을 명시 적으로 지원한다 (예를 들어, `Mono<Account>` 또는 `io.reactivex.Single<Account>`). 다음의 예와 같이, `@ModelAttribute` 인수가 래퍼없이 선언 된 경우, 그 같은 비동기 모델 속성은 `@RequestMapping` 호출 시점에서 실제 값으로 투명하게 해결 (및 모델 업데이트) 할 수 있다. Java ``` @ModelAttribute public void addAccount(@RequestParam String number) { Mono<Account> accountMono = accountRepository.findAccount(number); model.addAttribute("account", accountMono); } @PostMapping("/accounts") public String handle(@ModelAttribute Account account, BindingResult errors) { // ... } ``` Kotlin ``` import org.springframework.ui.set @ModelAttribute fun addAccount(@RequestParam number: String) { val accountMono: Mono<Account> = accountRepository.findAccount(number) model["account"] = accountMono } @PostMapping("/accounts") fun handle(@ModelAttribute account: Account, errors: BindingResult): String { // ... } ``` 또한 리액티브 형 래퍼를 가진 모델 속성은 뷰의 렌더링 직전에 실제 값으로 해결된다(그리고 모델이 업데이트된다). `@ModelAttribute`이 `@RequestMapping` 메서드의 메서드 레벨의 어노테이션으로 사용할 수 있다. 이 경우 `@RequestMapping` 메서드의 반환 값은 모델 속성으로 해석된다. 반환 값이 뷰 이름으로 해석되는 String 경우를 제외하고 이것은 HTML 컨트롤러의 기본 동작이기 때문에 일반적으로 필요하지 않다. `@ModelAttribute` 다음의 예와 같이 모델 속성 이름의 사용자 정의에도 도움이 된다. Java ``` @GetMapping("/accounts/{id}") @ModelAttribute("myAccount") public Account handle() { // ... return account; } ``` Kotlin ``` @GetMapping("/accounts/{id}") @ModelAttribute("myAccount") fun handle(): Account { // ... return account } ``` ## 1.4.5. DataBinder 웹 MVC `@Controller` 또는 `@ControllerAdvice` 클래스는 `WebDataBinder`인스턴스를 초기화하기 위해 `@InitBinder` 메서드를 가질 수 있다. 이들은 다음과 같은 목적으로 사용된다. 요청 매개 변수 (즉, 양식 데이터 또는 쿼리)를 모델 객체에 바인드한다. `String` 기반 요청 값 (요청 매개 변수, 경로 변수 헤더, Cookie 등)를 컨트롤러 메서드 인수 대상 유형으로 변환한다. HTML 양식을 렌더링 할 때 모델 객체 값을 String 값으로 포맷한다. `@InitBinder` 메서드는 컨트롤러 고유의 `java.beans.PropertyEditor` 또는 Spring `Converter` 및 `Formatter` 구성 요소를 등록 할 수 있다. 또한 WebFlux Java 구성 을 사용하여 `Converter` 및 `Formatter` 유형을 전 세계적으로 공유되는 `FormattingConversionService`에 등록 할 수 있다. `@InitBinder` 방법은 `@ModelAttribute` (명령 개체) 인수를 제외하고 `@RequestMapping` 메서드와 같은 인수의 많은 지원한다. 일반적으로 이러한 등록의 `WebDataBinder` 인수 및 void 반환 값으로 선언된다. 다음 예제에서는 `@InitBinder` 어노테이션을 사용하고 있다. Java ``` @Controller public class FormController { @InitBinder // (1) public void initBinder(WebDataBinder binder) { SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); dateFormat.setLenient(false); binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false)); } // ... } ``` Kotlin ``` @Controller class FormController { @InitBinder // (1) fun initBinder(binder: WebDataBinder) { val dateFormat = SimpleDateFormat("yyyy-MM-dd") dateFormat.isLenient = false binder.registerCustomEditor(Date::class.java, CustomDateEditor(dateFormat, false)) } // ... } ``` - (1) `@InitBinder` 어노테이션을 사용한다. 또는 공유 `FormattingConversionService`를 통해 Formatter 기반 설치를 사용하는 경우 다음의 예와 같이 동일한 접근 방식을 다시 사용하여 컨트롤러 고유의 `Formatter` 인스턴스를 등록 할 수 있다. Java ``` @Controller public class FormController { @InitBinder protected void initBinder(WebDataBinder binder) { binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd")); // (1) } // ... } ``` Kotlin ``` @Controller class FormController { @InitBinder fun initBinder(binder: WebDataBinder) { binder.addCustomFormatter(DateFormatter("yyyy-MM-dd")) // (1) } // ... } ``` - (1) 사용자 정의 포맷터 (이 경우 `DateFormatter`)를 추가한다. ## 1.4.6. 예외 관리 [Web MVC](https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-exceptionhandler) `@Controller`와 `@ControllerAdvice` 클래스는 컨트롤러 메서드에서 예외를 처리하는 `@ExceptionHandler` 메서드를 포함 할 수 있다. 다음의 예제는 이러한 핸들러 메서드가 포함되어 있다. Java ``` @Controller public class SimpleController { // ... @ExceptionHandler // (1) public ResponseEntity<String> handle(IOException ex) { // ... } } ``` Kotlin ``` @Controller class SimpleController { // ... @ExceptionHandler // (1) fun handle(ex: IOException): ResponseEntity<String> { // ... } } ``` - (1) `@ExceptionHandler` 선언. 예외는 전파되는 최상위 예외 (즉, 직접 `IOException` 이 발생되는) 또는 최상위 래퍼 예외의 직접적인 원인(예를 들어, `IllegalStateException`내 랩 된 `IOException`)과 일치한다. 예외의 유형을 일치 시키려면 이전의 예와 같이 대상의 예외를 메서드 인수로 선언하는 것이 좋습니다. 또는 어노테이션 선언은 일치하는 예외 유형을 제한 할 수 있다. 일반적으로 인수 서명을 최대한 구체적으로 지정하고 해당 순서로 우선 순위 된 `@ControllerAdvice`에서 기본 루트 예외 매핑을 선언하는 것이 좋습니다. 자세한 내용은 MVC 섹션 을 참조해라. WebFlux의 `@ExceptionHandler` 메서드는 `@RequestMapping` 메서드와 같은 메서드 인수와 반환 값을 지원하고 있지만, 요청 내용 및 `@ModelAttribute` 관련 메서드 인수는 예외이다. Spring의 `@ExceptionHandler` 방식 지원 WebFlux는 `@RequestMapping` 메서드의 `HandlerAdapter `의해 제공된다. 자세한 내용은 `DispatcherHandler`을 참조해라. ### REST API에 대한 예외 [Web MVC](https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-rest-exceptions) REST 서비스의 일반적인 요구 사항은 응답 본문에 오류 정보를 포함하는 것이다. Spring Framework는 응답 내용의 오류 세부 표현이 응용 프로그램 고유이기 때문에 자동으로 그렇게하지 않는다. 단, `@RestController`이 `ResponseEntity` 반환 값을 가지는 `@ExceptionHandler `메서드를 사용하여 응답의 상태와 본문을 설정할 수 있다. 그런 메서드를 `@ControllerAdvice` 클래스로 선언하여 전역에 적용 할 수 있다. > Spring WebFlux는 Spring MVC `ResponseEntityExceptionHandler`에 해당하는 것이 없는 점에 유의해라. 이것은 WebFlux이 `ResponseStatusException` (또는 그 서브 클래스)만을 생성하고 HTTP 상태 코드로 변환 할 필요가 없기 때문이다. ## 1.4.7. 컨트롤러 조언(Controller Advice) [Web MVC](https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-controller-advice) 일반적으로 `@ExceptionHandler`, `@InitBinder`, `@ModelAttribute` 방법은 그들이 선언되는 `@Controller` 클래스 (또는 클래스 계층 구조)에 적용된다. 이 같은 방법을 (컨트롤러 전체)보다 전체적으로 적용하는 경우 `@ControllerAdvice` 또는 `@RestControllerAdvice` 어노테이션이 붙은 클래스에서 선언 할 수 있다. `@ControllerAdvice` 에는 `@Component` 어노테이션이 선엉되어 있다. 이것은 이러한 클래스를 구성 요소 검사 에 의해 Spring Bean으로 등록 할 수 있는 것을 의미한다. `@RestControllerAdvice`은 `@ControllerAdvice`완 `@ResponseBody` 모두 어노테이션이 붙은 합성 어노테이션이다. 이것은 본질적으로 `@ExceptionHandler` 메서드가 메시지 변환(뷰 리졸버 조치 또는 템플릿 렌더링에) 의해 응답 본문에 렌더링되는 것을 의미한다. 시작할 때 `@RequestMapping`와 `@ExceptionHandler` 메서드의 인프라 클래스는 `@ControllerAdvice` 어노테이션이 선언된 Spring Bean을 감지하고 실행시에 그 방법을 적용한다. 글로벌 `@ExceptionHandler` 메서드 (`@ControllerAdvice`에서) 로컬 메서드 (`@Controller`에서)의 후 적용 되고 있다. 대조적으로, 글로벌 `@ModelAttribute` 및 `@InitBinder` 메서드는 로컬 메서드의 전에 적용된다. 기본적으로 `@ControllerAdvice` 메서드는 모든 요청(즉, 모든 컨트롤러)에 적용되지만 다음의 예와 같이 어노테이션 속성을 사용하여 컨트롤러의 하위 집합에 범위를 줄일 수 있다. Java ``` // Target all Controllers annotated with @RestController @ControllerAdvice(annotations = RestController.class) public class ExampleAdvice1 {} // Target all Controllers within specific packages @ControllerAdvice("org.example.controllers") public class ExampleAdvice2 {} // Target all Controllers assignable to specific classes @ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class}) public class ExampleAdvice3 {} ``` Kotlin ``` // Target all Controllers annotated with @RestController @ControllerAdvice(annotations = [RestController::class]) public class ExampleAdvice1 {} // Target all Controllers within specific packages @ControllerAdvice("org.example.controllers") public class ExampleAdvice2 {} // Target all Controllers assignable to specific classes @ControllerAdvice(assignableTypes = [ControllerInterface::class, AbstractController::class]) public class ExampleAdvice3 {} ``` 이전 예제 셀렉터는 런타임시 평가되고 광범위하게 사용하면 성능에 악영향을 미칠 수 있다. 자세한 내용은 [`@ControllerAdvice`](https://docs.spring.io/spring-framework/docs/5.3.5/javadoc-api/org/springframework/web/bind/annotation/ControllerAdvice.html)를 참조해라.