Spring Web Reactive | 1. Spring WebFlux | 1.4. Annotated Controllers #2

1.4.4. Model

Web MVC

@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 ConverterFormatter 구성 요소를 등록 할 수 있다. 또한 WebFlux Java 구성 을 사용하여 ConverterFormatter 유형을 전 세계적으로 공유되는 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

@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

REST 서비스의 일반적인 요구 사항은 응답 본문에 오류 정보를 포함하는 것이다. Spring Framework는 응답 내용의 오류 세부 표현이 응용 프로그램 고유이기 때문에 자동으로 그렇게하지 않는다. 단, @RestControllerResponseEntity 반환 값을 가지는 @ExceptionHandler 메서드를 사용하여 응답의 상태와 본문을 설정할 수 있다. 그런 메서드를 @ControllerAdvice 클래스로 선언하여 전역에 적용 할 수 있다.

Spring WebFlux는 Spring MVC ResponseEntityExceptionHandler에 해당하는 것이 없는 점에 유의해라. 이것은 WebFlux이 ResponseStatusException (또는 그 서브 클래스)만을 생성하고 HTTP 상태 코드로 변환 할 필요가 없기 때문이다.

1.4.7. 컨트롤러 조언(Controller Advice)

Web MVC

일반적으로 @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를 참조해라.




최종 수정 : 2021-04-12