반응형

<참조> https://docs.spring.io/spring-framework/docs/3.2.x/spring-framework-reference/html/mvc.html

Spring WEB MVC 프레임워크 (2)

 

3 컨트롤러 구현

일반적으로 컨트롤러는 서비스 인터페이스를 통한 애플리케이션 동작에 대한 접근을 제공한다. 컨트롤러는 사용자 입력을 해석하고 뷰를 통해 사용자에게 표시되는 모델로 변환한다.  Spring 2.5는 @RequestMapping, @RequestParam, @ModelAttribute 같은 주석을 사용하는 MVC 컨트롤러 노테이션ㅇ,ㄹ 제공하는데 Servlet MVC 및 Portlet MVC에서 사용할 수 있다. 이 스타일로 구현된 컨트롤러는 특정 기본 클래스를 확장하거나 특정 인터페이스를 구현할 필요가 없다. 또한 서블릿 또는 포틀릿 기능에 대한 액세스를 쉽게 구성할 수 있지만 일반적으로 Servlet 또는 Portlet API에 대한 직접적인 종속성이 없다.

@Controller
public class HelloWorldController {
    @RequestMapping("/helloWorld")
    public String helloWorld(Model model) {
        model.addAttribute("message", "Hello World!");
        return "helloWorld";
    }
}

@Controller 및 @RequestMapping 노테이션은 유연한 메서드 이름과 서명을 허용합니다. 이 예제에서 메서드는 모델을 받고 뷰 이름을 문자열로 반환하지만 다른 메서드 매개변수와 반환 값을 사용할 수 있다. 

3.1 @Controller와 컨트롤러 정의하기

@Controller 어노테이션은 특정 클래스가 컨트롤러라는 것을 알려준다. Spring은 컨트롤러 기본 클래스를 확장하거나 Servlet API를 참조할 것을 요구하지 않는다(필요한 경우 Servlet 관련 기능을 계속 참조할 수는 있다).

@Controller 어노테이션은 해당 역할을 나타내는 어노테이션이 있는 클래스에 대한 스테레오 타입 역할을 한다. 디스패처는 매핑된 메서드에 대해 어노테이션이 있는 클래스를 검색하고 @RequestMapping 어노테이션을 감지한다.

디스패처 컨텍스트에서 표준 Spring 빈 정의를 사용하여 어노테이션이 달린 컨트롤러 빈을 명시적으로 정의할 수 있다. 그러나 @Controller 스테레오 타입은 클래스 경로에서 구성 요소 클래스를 감지하고 이에 대한 빈 정의를 자동 등록하기 위한 Spring 일반 지원과 정렬된 자동 감지도 허용한다.

이러한 어노테이션이 달린 컨트롤러에 대한 자동 감지를 활성화하려면 구성 요소 스캔을 구성에 추가한다. 다음 XML 스니펫에 표시된 대로 스프링 컨텍스트 스키마를 사용하자.

<?xml version="1.0" encoding="UTF-8"?> 
<beans  xmlns = "http://www.springframework.org/schema/beans" 
    xmlns:xsi = "http://www.w3.org /2001/XMLSchema-instance" 
    xmlns:p = "http://www.springframework.org/schema/p" 
    xmlns:context = "http://www.springframework.org/schema/context" 
    xsi:schemaLocation = "
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd" >
    <context:component-scan  base-package = "org.springframework.samples.petclinic.web" />
    <!-- ... -->
</beans>

 

3.2 @RequestMapping 요청 매핑 

@RequestMapping 어노테이션을 사용하여 /appointments 같은 URL을 전체 클래스 또는 특정 핸들러 메서드에 매핑한다. 일반적으로 클래스 수준 어노테이션은 특정 요청 경로(또는 경로 패턴)를 양식 컨트롤러에 매핑하고 추가 메서드 어노테이션 주석은 특정 HTTP 메서드 요청 메서드("GET", "POST" 등) 또는 HTTP 요청 매개변수 조건에 대한 기본 매핑을 좁힌다.  Petcare 샘플의 다음 예제는 이 어노테이션을 사용하는 Spring MVC 애플리케이션의 컨트롤러를 보여준다.

@Controller
@RequestMapping("/appointments")
public class AppointmentsController {
    private final AppointmentBook appointmentBook;
    @Autowired
    public AppointmentsController(AppointmentBook appointmentBook) {
        this.appointmentBook = appointmentBook;
    }
    @RequestMapping(method = RequestMethod.GET)
    public Map<String, Appointment> get() {
        return appointmentBook.getAppointmentsForToday();
    }
    @RequestMapping(value="/{day}", method = RequestMethod.GET)
    public Map<String, Appointment> getForDay(@PathVariable @DateTimeFormat(iso=ISO.DATE) Date day, Model model) {
        return appointmentBook.getAppointmentsForDay(day);
    }
    @RequestMapping(value="/new", method = RequestMethod.GET)
    public AppointmentForm getNewForm() {
        return new AppointmentForm();
    }
    @RequestMapping(method = RequestMethod.POST)
    public String add(@Valid AppointmentForm appointment, BindingResult result) {
        if (result.hasErrors()) {
            return "appointments/new";
        }
        appointmentBook.addAppointment(appointment);
        return "redirect:/appointments";
    }
}

예제에서 @RequestMapping은 여러 곳에서 사용된다. 첫 번째 사용법은 유형(클래스) 수준에 있으며, 이 컨트롤러의 모든 처리 방법이 /appointments 경로에 상대적임을 나타낸다. get() 메서드에는 @RequestMapping이 더 세분화되어 있고 GET 요청만 수락한다. 즉, /appointments에 대한 HTTP GET이 이 메서드를 호출한다. post()도 유사하며 getNewForm()은 HTTP 메소드와 경로의 정의를 하나로 결합하여 GET 요청이 해당 메소드에 의해 처리되도록 한다.

getForDay() 메소드는 @RequestMapping: URI 템플릿의 또 다른 사용법을 보여준다.

클래스 수준의 @RequestMapping은 필요하지 않다. 그것이 없으면 모든 경로는 절대적이다. PetClinic 애플리케이션의 다음 예제는 @RequestMapping을 사용하는 다중 작업 컨트롤러를 보여준다.

@Controller
public class ClinicController {
    private final Clinic clinic;
    @Autowired
    public ClinicController(Clinic clinic) {
        this.clinic = clinic;
    }
    @RequestMapping("/")
    public void welcomeHandler() {
    }
    @RequestMapping("/vets")
    public ModelMap vetsHandler() {
        return new ModelMap(this.clinic.getVets());
    }
}
 

@RequestMappingSpring MVC 3.1의 메소드에 대한 새로운 지원 클래스

Spring 3.1은 RequestMappingHandlerMapping 및 RequestMappingHandlerAdapter라는 @RequestMapping 메소드에 대한 새로운 지원 클래스 세트를 도입했다. 그것은 권장되며 Spring MVC 3.1 이상에사 새 기능을 활용하는 데에도 필요하다. 새 지원 클래스는 기본적으로 MVC 네임스페이스 및 MVC Java 구성에 의해 활성화되지만 둘 다 사용하지 않는 경우 명시적으로 구성해야 한다.

Spring 3.1 전에는 유형/메소드 레벨 요청 맵핑이 두 단계에서 검사되었다. 컨트롤러는 DefaultAnnotationHandlerMapping에 의해 선택되고 호출할 메소드는 AnnotationMethodHandlerAdapter에 의해 두 번째로 좁혀졌다.

Spring 3.1의 새로운 지원 클래스와 함께 RequestMappingHandlerMapping은 요청을 처리할 메소드 결정이 내려지는 유일한 곳이다. 컨트롤러 메서드를 유형/메서드 수준 @RequestMapping 정보에서 파생된 각 메서드에 대한 매핑이 있는 고유한 엔드포인트 모음이다.

이것은 몇 가지 새로운 가능성을 제공니다. HandlerInterceptor 또는 HandlerExceptionResolver는 객체 기반 핸들러가 HandlerMethod일 것으로 예상할 수 있으며, 이를 통해 정확한 메소드, 해당 매개변수 및 관련 어노테이션을 검사할 수 있다. URL 처리는 더 이상 다른 컨트롤러로 분할할 필요가 없지만 더 이상 가능하지 않은 사항도 있다.

URI 템플릿 패턴

URI 템플릿은 @RequestMapping 메서드에서 URL의 선택된 부분 접근 시 사용할 수 있다. URI 템플릿은 하나 이상의 변수 이름을 포함하는 URI 문자열 값이다. 이때 변수를 값으로 대체하면 템플릿은 실제 URI가 된다. 예를 들어 URI 템플릿 http://www.example.com/users/{userId}는 변수 userId를 포함하고 있다. 이 변수에 'sualchi'라는 값을 할당하면  다음처럼http://www.example.com/users/sualchi URI가 생성된다.

Spring MVC에서는 메소드 인수에 @PathVariable 어노테이션을 사용하여 URI 템플릿 변수 값을 연결할 수 있다.

@RequestMapping(value="/owners/{ownerId}", method=RequestMethod.GET)
public String findOwner(@PathVariable String ownerId, Model model) {
  Owner owner = ownerService.findOwner(ownerId);
  model.addAttribute("owner", owner);
  return "displayOwner";
}

"/owners/{ownerId}" URI 템플릿은 변수 ownerId를 이용한다. 컨트롤러가 요청을 처리할 때 ownerId 값은 URI에서 찾은 값으로 설정된다. 예를 들어 /owners/sualchi  요청이 들어오면 ownerId 값은 sualchi로 설정된다. 

[참고] @PathVariable 주석을 처리하기 위해 Spring MVC는 이름으로 일치하는 
      URI 템플릿 변수를 찾아야 하는데 어노테이션에서 지정할 수 있다.
@RequestMapping(value="/owners/{ownerId}", method=RequestMethod.GET)
public String findOwner(@PathVariable("ownerId") String theOwner, Model model) {
  //  구현 생략
}
URI 템플릿 변수명이 메서드 인수명과 일치하면, 해당 세부정보를 생략할 수 있다. 
디버깅 컴파일을 이용하면, Spring MVC는 메소드 인수명을 URI 템플릿 변수명과 일치시킨다.
@RequestMapping(value="/owners/{ownerId}", method=RequestMethod.GET)
public String findOwner(@PathVariable String ownerId, Model model) {
  // 구현 생략
}

메서드에 @PathVariable 어노테이션을 여러 개 사용할 수 있다.

@RequestMapping(value="/owners/{ownerId}/pets/{petId}", method=RequestMethod.GET)
public String findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
  Owner owner = ownerService.findOwner(ownerId);
  Pet pet = owner.getPet(petId);
  model.addAttribute("pet", pet);
  return "displayPet";
}

@PathVariable 주석이 Map<String, String> 인수에 사용되면 맵은 URI 템플릿 변수로 채워진다.

다음 예제처럼 URI 템플릿은 @RequestMapping 어노테이션에서 설정한다. 결과적으로 findPet() 메소드는 /owners/1004/pets/88 같은 URL로 호출될 수 있다.

@Controller
@RequestMapping("/owners/{ownerId}")
public class RelativePathUriTemplateController {
  @RequestMapping("/pets/{petId}")
  public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
    // 구현 생략
  }
}

@PathVariable 인수는 int, long, Date 같은 유형일 수 있다. Spring은 자동으로 적절한 유형으로 변환시키지만, 그렇게 할 수 없는 경우에 TypeMismatchException 예외를 던진다. 

정규 표현식이 있는 URI 템플릿 패턴

@RequestMapping 어노테이션은 URI 템플릿 변수에서 정규식을 지원한다. 구문은 {varName:regex}이다. 첫 번째 부분은 변수 이름이고 두 번째 부분은 정규식 표현이다. 예를 들면 다음과 같다.

@RequestMapping("/spring-web/{symbolicName:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{extension:\\.[a-z]+}")
  public void handle(@PathVariable String version, @PathVariable String extension) {
    // ...
  }
}

경로 패턴

- URI 템플릿 외에도 @RequestMapping 어노테이션은 Ant 스타일 경로(예: /myPath/*.do)도 지원한다.

- URI 템플릿과 Ant 스타일 globs의 조합도 지원한다(예: /owners/*/pets/{petId}).

자리 표시자가 있는 패턴

@RequestMapping 어노테이션 패턴은 로컬 속성 및/또는 시스템 속성 및 환경 변수에 대해 ${...} 표기를 지원한다. 컨트롤러가 매핑 경로가 구성을 통해 사용자 지정되는 경우에 유용하다.

매트릭스 변수 (경로 매개변수)

경로에 이름-값 쌍 형태의 "URI 경로 매개변수"를 사용할 수 있으며 Spring MVC는 매트릭스 변수(행렬 변수)라고 부른다. 매트릭스 변수들은 ";"(세미콜론)으로 구분된다(예: "/cars;color=red;year=2012"). 한 변수에 여러 값이 필요하면 ","(쉼표)로 구분하거나 변수 이름을 여러 번 사용해도 된다.
 
- "color=red,green,blue"
- "color=red;color=green;color=blue"

URL에 매트릭스 변수 사용이 예상되는 경우, URI 템플릿에 알려줘야 한다. 이렇게 하면 매트릭스 변수 유무나 순서와  관계없이 올바르게 일치될 수 있다.

다음은 행렬 변수 "q"를 추출하는 예이다.
// GET /pets/42;q=11;r=22
@RequestMapping(value = "/pets/{petId}", method = RequestMethod.GET)
public void findPet(@PathVariable String petId, @MatrixVariable int q) {
  // petId == 42
  // q == 11
}

모든 경로 부분에는 매트릭스 변수가 포함될 수 있으므로 경우에 따라 변수가 예상되는 위치를 식별하기 위해 보다 구체적이어야 한다.

// GET /owners/42;q=11/pets/21;q=22
@RequestMapping(value = "/owners/{ownerId}/pets/{petId}", method = RequestMethod.GET)
public void findPet(
    @MatrixVariable(value="q", pathVar="ownerId") int q1,
    @MatrixVariable(value="q", pathVar="petId") int q2) {
  // q1 == 11
  // q2 == 22
}

매트릭스 변수는 선택사항으로 정의하거나 기본값을 지정할 수 있다.

// GET /pets/42
@RequestMapping(value = "/pets/{petId}", method = RequestMethod.GET)
  public void findPet(@MatrixVariable(required=true, defaultValue="1") int q) {
    // q == 1
  }

모든 매트릭스 변수는 Map에서 얻을 수 있다.

// GET /owners/42;q=11;r=12/pets/21;q=22;s=23
@RequestMapping(value = "/owners/{ownerId}/pets/{petId}", method = RequestMethod.GET)
  public void findPet(
        @MatrixVariable Map<String, String> matrixVars,
        @MatrixVariable(pathVar="petId"") Map<String, String> petMatrixVars) {
    // matrixVars: ["q" : [11,22], "r" : 12, "s" : 23]
    // petMatrixVars: ["q" : 11, "s" : 23]
  }

매트릭스 변수를 사용하려면 RequestMappingHandlerMapping의 removeSemicolonContent 속성을 false로 설정한다. 기본은 false이다.

MVC 네임스페이스에서 `mvc:annotation-driven` 요소에 `true`로 설정해야 하는 `enableMatrixVariables` 속성이 있다. 기본은 'false'이다.

요청 미디어 유형 지정

요청자의 미디어 유형을 지정하여 매핑 범위를 좁힐 수 있다. Content-Type 요청 헤더와 consumes에 지정된 미디어 유형이 일치하는 경우에 처리되도록 지정할 수 있다. 예를 들어 다음 예제는 json 형태로 요청한 경우에 처리된다.

@Controller
@RequestMapping(value = "/pets", method = RequestMethod.POST, consumes="application/json")
public void addPet(@RequestBody Pet pet, Model model) {
    // implementation omitted
}

요청자 미디어 유형 표현식은 Content-Type이 'text/plain'인 요청 이외의 모든 요청과 일치하도록 '!text/plain'처럼 재외할 수도 있다.

응답 미디어 유형 지정 

응답 미디어 유형을 지정하여 기본 매핑을 좁힐 수 있다. Accept 요청 헤더가 일치하는 경우에 처리된다. 또한, produces 조건을 사용하면 응답을 생성하는 데 사용되는 실제 콘텐츠 유형이 produces 조건에 지정된 미디어 유형을 준수하도록 합니다. 예를 들어:

@Controller
@RequestMapping(value = "/pets/{petId}", method = RequestMethod.GET, produces="application/json")
@ResponseBody
public Pet getPet(@PathVariable String petId, Model model) {
    // 구현 생략
}

응답 미디어 유형 표현식도 '!text/plain' 같이 부정하여 Accept 헤더 값이 text/plain을 제외한 모든 요청과 일치시킬 수 있다.

 

요청 매개변수 및 헤더 값

"myParam", "!myParam", "myParam=myValue" 같은 요청 매개변수 조건을 통해 요청 일치를 좁힐 수 있다. 처음 두 개는 요청 매개변수 유무 테스트이고 세 번째는 특정 매개변수 값 테스트이다. 다음은 요청 매개변수 값 조건 예이다.

@Controller
@RequestMapping("/owners/{ownerId}")
public class RelativePathUriTemplateController {
  @RequestMapping(value = "/pets/{petId}", method = RequestMethod.GET, params="myParam=myValue")
  public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
    // 구현 생략
  }
}

요청 헤더 유무를 테스트하거나 특정 요청 헤더 값을 기반으로 일치시키기 위해 동일한 작업을 수행할 수 있다.

@Controller
@RequestMapping("/owners/{ownerId}")
public class RelativePathUriTemplateController {
@RequestMapping(value = "/pets", method = RequestMethod.GET, headers="myHeader=myValue")
  public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
    // 구현 생략
  }
}
 

미디어 유형 와일드 카드를 사용하여 Content-Type과 Accept 헤더 값을 일치시킬 수 있지만(예: "content-type=text/*"는 "text/plain" 및 "text/html"과 일치) consumes과 produces  사용하는 것이 좋다. 

3.3 @RequestMapping핸들러 메소드 정의

@RequestMapping 핸들러 메소드는 유연한 서명을 가질 수 있다. BindingResult 인수를 제외하고 대부분의 인수는 임의의 순서로 사용할 수 있다.

[참고] Spring 3.1은 RequestMappingHandlerMapping 및 RequestMappingHandlerAdapter라는 @RequestMapping 메소드에 대한 새로운 클래스를 도입했다. 새 클래스는 기본적으로 MVC 네임스페이스와 MVC Java 구성을 사용하여 활성화되지만 둘 다 사용하지 않는 경우 명시적으로 구성해야 한다.

 

메소드 인수 유형 

Request 또는 response 객체들 (Servlet API). 특정 요청 또는 응답 유형(예: ServletRequest 또는 HttpServletRequest)을 선택한다.

HttpSession 유형 Session 객체 (Servlet API): 이 유형의 인수는 해당 세션의 존재가 필수이다(그 인수는 null이 아니다).

 

[메모] 세션 접근은 특히 서블릿 환경 스레드에서 안전하지 않을 수 있다. 여러 요청이 세션에 동시 접근할 수 있는 경우 RequestMappingHandlerAdapter의 "synchronizeOnSession" 플래그를 "true"로 설정하자.

 

  • org.springframework.web.context.request.WebRequest 또는 org.springframework.web.context.request.NativeWebRequest. 기본 Servlet/Portlet API에 대한 연결 없이 일반 request 매개변수 접근 및 request/session 속성 접근을 허용
  • 현재 요청 로케일에 대한 java.util.Locale : 사용 가능한 구체적인 로케일 해석기에서 결정 (사실상 서블릿 환경에서 구성된 LocaleResolver 이용) 
  • 요청 내용 접근을 위한 java.io.InputStream / java.io.Reader. 이 값은 Servlet API에 의해 노출되는 원시 InputStream/Reader
  • 응답 내용을 생성하기 위한 java.io.OutputStream / java.io.Writer. 이 값은 Servlet API에 의해 노출되는 원시 OutputStream/Writer
  • 현재 인증된 사용자를 포함하는 java.security.Principal
  • URI 템플릿 변수에 접근하기 위한 @PathVariable 어노테이션이 있는 매개변수.
  • URI 경로 세그먼트에 있는 이름-값 쌍에 접근하기 위한 @MatrixVariable 어노테이션 매개변수.
  • 특정 서블릿 요청 매개변수에 액세스하기 위한 @RequestParam 어노테이션 매개변수. 매개변수 값은 선언된 메소드 인수 유형으로 변환됨
  •  특정 서블릿 요청 HTTP 헤더에 접근하기 위한 @RequestHeader 어노테이션이 있는 매개변수. 매개변수 값은 선언된 메소드 인수 유형으로 변환됨
  • HTTP 요청 본문에 접근하기 위한 @RequestBody 어노테이션이 있는 매개변수. 매개변수 값은 HttpMessageConverters를 사용하여 선언된 메서드 인수 유형으로 변환됨 
  • "multipart/form-data" 요청 부분의 내용에 접근하기 위한 @RequestPart 어노테이션이 있는 매개변수. 
  • 서블릿 요청 HTTP 헤더 및 콘텐츠에 대한 액세스를 위한 HttpEntity<?> 매개변수. 요청 스트림은 HttpMessageConverters를 사용하여 엔터티 본문으로 변환됨.
  • java.util.Map / org.springframework.ui.Model / org.springframework.ui.ModelMap은 웹 뷰에 노출되는 모델 UI를 지원
  • org.springframework.web.servlet.mvc.support.RedirectAttributes는 리디렉션에 사용할 속성 집합을 지정. 메서드가 "redirect:" 접두어가 붙은 뷰 이름 또는 RedirectView를 반환하면 암시적 모델 대신 RedirectAttributes가 사용됨.
  • @InitBinder 메소드, HandlerAdapter 구성에 따라 사용자 정의 가능한 유형 변환을 통해 요청 매개변수를 bean 속성(setter 기능 이용) 또는 필드에 직접 바인딩하는 명령 또는 양식 개체. 이 명령 개체는 유효성 검사 결과와 함께 기본적으로 명령 클래스 클래스 이름을 사용하여 모델 속성으로 노출된다. "some.package.OrderAddress" 유형의 명령 개체에 대한 모델 속성 "orderAddress". ModelAttribute 어노테이션은 사용된 모델 속성 이름을 사용자 지정하기 위해 메서드 인수에 사용할 수 있다.
  • org.springframework.validation.Errors / org.springframework.validation.BindingResult 이전 명령 또는 양식 객체(바로 앞의 메서드 인수)에 대한 유효성 검사 결과. 
  • org.springframework.web.bind.support.SessionStatus 처리기 유형 수준에서 @SessionAttributes 어노테이션 표시된 세션 속성의 정리를 시작하는 양식 처리를 완료로 표시하기 위한 상태 핸들러. 
  • org.springframework.web.util.UriComponentsBuilder 현재 요청의 host, port, scheme, context path 및 서블릿 매핑의 리터럴 부분과 관련된 URL을 준비하기 위한 빌더.  

Errors 또는 BindingResult 매개변수는 메소드 서명이 하나 이상 모델 객체를 가질 수 있고 Spring이 별도 BindingResult 인스턴스를 생성하므로, 결합되어지는 모델 객체를 따라야 한다.

예 1. BindingResult 및 @ModelAttribute의 잘못된 순서

@RequestMapping(method = RequestMethod.POST)
public String processSubmit(@ModelAttribute("pet") Pet pet,
    Model model, BindingResult result) { … }

잘못된 예에서 Pet과 BindingResult 사이에 Model 매개변수가 있음에 유의하자. 작업을 수행하려면 다음 예처럼 매개변수순서를 지정한다.

@RequestMapping(method = RequestMethod.POST)
public String processSubmit(@ModelAttribute("pet") Pet pet,
    BindingResult result, Model model) { … }

 

지원되는 메서드 반환 유형

  • ModelAndView 개체 : 모델이 암시적으로 명령 개체와 @ModelAttribute 어노테이션이 있는 참조 데이터 접근자 메서드의 결과로 강화된 개체.
  • Model 개체 : RequestToViewNameTranslator를 통해 암시적으로 결정된 보기 이름과 명령 개체 및 @ModelAttribute 주석이 달린 참조 데이터 접근자 메서드의 결과로 암시적으로 강화된 모델을 포함하는 개체.
  • Map 개체 : RequestToViewNameTranslator를 통해 암시적으로 결정된 보기 이름과 명령 개체 및 @ModelAttribute 주석이 있는 참조 데이터 접근자 메서드의 결과로 암시적으로 보강된 모델을 노출하기 위한 개체.
  • View 개체 : 명령 개체 및 @ModelAttribute 주석이 있는 참조 데이터 접근자 메서드를 통해 암시적으로 결정된 모델이 있는 개체이다. 핸들러 메서드는 Model 인수를 선언하여 프로그래밍 방식으로 모델을 강화할 수도 있다. 
  • 논리 뷰 이름으로 해석되는 Striing 값 : 명령 개체 및 @ModelAttribute 어노테이션이 있는 참조 데이터 접근자 메서드를 통해 암시적으로 결정된 모델을 사용하여 논리 보기 이름으로 해석되는 문자열 값이다. 핸들러 메서드는 Model 인수를 선언하여 프로그래밍 방식으로 모델을 강화할 수도 있다.
  • void : 메소드가 응답 자체를 처리하는 경우(응답 내용을 직접 작성하여 값, ServletResponse / HttpServletResponse 유형의 인수에 선언된 값) 또는 뷰 이름이 RequestToViewNameTranslator를 통해 암시적으로 결정되어야 하는 경우(핸들러 메서드 서명에서 응답 인수를 선언하지 않음).
  • 메소드에 @ResponseBody로 어노테이션 달린 경우 반환 유형은 응답 HTTP 본문에 기록되며, 반환 값은 HttpMessageConverters를 사용하여 선언된 메서드 인수 형식으로 변환된다. 
  • HttpEntity<?> 또는 ResponseEntity<?> 개체 : 서블릿 응답 HTTP 헤더 및 콘텐츠에 대한 액세스를 제공하는 개체이다. 엔터티 본문은 HttpMessageConverters를 사용하여 응답 스트림으로 변환된다. 
  • Callable<?> : 애플리케이션이 Spring MVC에서 관리하는 스레드에서 비동기적으로 반환 값을 생성하려고 할 때 반환될 수 있다.
  • DeferredResult<?>  : 응용 프로그램이 자신이 선택한 스레드에서 반환 값을 생성하려고 할 때 반환될 수 있다.
  • 다른 모든 반환 유형은 메서드 수준에서 @ModelAttribute를 통해 지정된 속성 이름(또는 반환 유형 클래스 이름을 기반으로 하는 기본 속성 이름)을 사용하여 뷰에 노출될 단일 모델 속성으로 간주된다. 모델은 명령 개체와 @ModelAttribute 어노테이션이 있는 참조 데이터 접근자 메서드의 결과이다.
 

@RequestParam 사용하여 요청 매개변수를 메소드 매개변수에 바인딩 

@RequestParam 어노테이션을 사용하여 요청 매개변수를 컨트롤러의 메소드 매개변수에 연결한다. 다음 예제는 사용법을 보여준다.

@Controller
@RequestMapping("/pets")
@SessionAttributes("pet")
public class EditPetForm {
    // ...
    @RequestMapping(method = RequestMethod.GET)
    public String setupForm(@RequestParam("petId") int petId, ModelMap model) {
        Pet pet = this.clinic.loadPet(petId);
        model.addAttribute("pet", pet);
        return "petForm";
    }
    // ...

이 어노테이션을 사용하는 매개변수는 필수이지만 @RequestParam의 required 속성을 false로 설정하여 매개변수가 선택사항임을 지정할 수 있다(예: @RequestParam(value="id", required=false)).

대상 메소드 매개변수 유형이 String이 아닌 경우 유형 변환이 자동으로 적용된다.

 

@RequestBody 주석으로 요청 본문 매핑

@RequestBody 메소드 매개변수 어노테이션은 메소드 매개변수가 HTTP 요청 본문의 값에 연결됨을 나타낸다. 

@RequestMapping(value = "/something", method = RequestMethod.PUT)
public void handle(@RequestBody String body, Writer writer) throws IOException {
  writer.write(body);
}

HttpMessageConverter를 사용하여 요청 본문을 메서드 인수로 변환한다. HttpMessageConverter는 HTTP 요청 메시지에서 개체로 변환하고 개체에서 HTTP 응답 본문으로 변환하는 역할을 한다. RequestMappingHandlerAdapter는 다음과 같은 HttpMessageConverters를 사용하여 @RequestBody 어노테이션을 지원한다.

  • ByteArrayHttpMessageConverter : byte 배열 변환
  • StringHttpMessageConverter : 문자열 변환
  • FormHttpMessageConverter : 폼 데이터와 MultiValueMap<String, String> 간의 변환
  • SourceHttpMessageConverter : javax.xml.transform.Source 변환

XML을 읽고 쓰려면 org.springframework.oxm 패키지의 특정 Marshaller 및 Unmarshaller 구현으로 MarshallingHttpMessageConverter를 구성해야 한다. 아래 예제는 구성에서 직접 수행하는 방법을 보여준다. (애플리케이션이 MVC 네임스페이스 또는 MVC Java 구성을 통해 구성된 경우 대신 MVC Java 구성 또는 MVC XML 네임스페이스 활성화를 참조한다)

<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
    <property name="messageConverters">
      <util:list id="beanList">
        <ref bean="stringHttpMessageConverter"/>
        <ref bean="marshallingHttpMessageConverter"/>
      </util:list>
    </property
</bean>
<bean id="stringHttpMessageConverter"
       class="org.springframework.http.converter.StringHttpMessageConverter"/>
<bean id="marshallingHttpMessageConverter"
      class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter">
  <property name="marshaller" ref="castorMarshaller" />
  <property name="unmarshaller" ref="castorMarshaller" />
</bean>
<bean id="castorMarshaller" class="org.springframework.oxm.castor.CastorMarshaller"/>

@RequestBody 메소드 매개변수는 @Valid로 어노테이션을 달 수 있으며, 이 경우 Validator 인스턴스를 사용하여 유효성을 검사한다. MVC 네임스페이스 또는 MVC Java 구성을 사용할 때 클래스 경로에서 JSR-303 구현을 사용할 수 있다고 가정하면 JSR-303 유효성 검사기가 자동으로 구성된다.

@ModelAttribute 매개변수와 마찬가지로 Errors 인수를 사용하여 오류를 검사할 수 있다. 이러한 인수가 선언되지 않으면 MethodArgumentNotValidException 예외가 발생한다. 이 예외는 400 오류를 클라이언트에 다시 보내는 DefaultHandlerExceptionResolver에서 처리된다.

 

@ResponseBody 어노테이션으로 응답 본문 매핑

@ResponseBody 어노테이션은 @RequestBody와 유사하다. 이것은 메서드에 넣을 수 있으며 반환 유형이 HTTP 응답 본문에 직접 작성되어야 함을 알려준다(모델에 배치되거나 뷰 이름으로 해석되지 않음). 

@RequestMapping(value = "/something", method = RequestMethod.PUT) 
@ResponseBody
 public String helloWorld() {
   return  "Hello World" ;
}

위의 예에서 "Hello World" 텍스트가 HTTP 응답 스트림에 기록된다. @RequestBody와 마찬가지로 Spring은 HttpMessageConverter를 사용하여 반환된 객체를 응답 본문으로 변환한다.

HttpEntity<?> 사용하기

HttpEntity는 @RequestBody 및 @ResponseBody와 유사하다. 요청 및 응답 본문에 대한 접근 권한을 얻는 것 외에도 HttpEntity(그리고 응답별 하위 클래스 ResponseEntity)는 다음과 같이 요청 및 응답 헤더에 대한 접근도 허용한다.

@RequestMapping("/something")
public ResponseEntity<String> handle(HttpEntity<byte[]> requestEntity) throws UnsupportedEncodingException {
  String requestHeader = requestEntity.getHeaders().getFirst("MyRequestHeader"));
  byte[] requestBody = requestEntity.getBody();
  // request header와 body를 이용하여 처리..
  HttpHeaders responseHeaders = new HttpHeaders();
  responseHeaders.set("MyResponseHeader", "MyValue");
  return new ResponseEntity<String>("Hello World", responseHeaders, HttpStatus.CREATED);
}

위의 예는 MyRequestHeader 요청 헤더의 값을 가져오고 본문을 바이트 배열로 읽는다. MyResponseHeader를 응답에 추가하고 "Hello World"를 응답 스트림에 쓰고 응답 상태 코드를 201(Created)로 설정한다. @RequestBody 및 @ResponseBody와 마찬가지로 Spring은 HttpMessageConverter를 사용하여 요청 및 응답 스트림을 변환한다.

@ModelAttribute 사용

@ModelAttribute 어노테이션은 메서드 또는 메서드 인수에 사용할 수 있다. 메소드의 @ModelAttribute는 해당 메소드의 목적이 하나 이상의 모델 속성을 추가하는 것임을 나타낸다. 이러한 메서드는 @RequestMapping 메서드와 동일한 인수 유형을 지원하지만 요청에 직접 매핑할 수 없다. 대신 컨트롤러의 @ModelAttribute 메서드는 동일한 컨트롤러 내에서 @RequestMapping 메서드보다 먼저 호출된다. 

// 속성 1개 추가 
// 메소드의 반환 값은 "account"라는 이름으로 모델에 추가된다.
// @ModelAttribute("myAccount")를 통해 이름을 사용자 정의할 수 있다.
@ModelAttribute
public Account addAccount(@RequestParam String number) {
    return accountManager.findAccount(number);
}
// 여러 속성 추가
@ModelAttribute
public void populateModel(@RequestParam String number, Model model) {
    model.addAttribute(accountManager.findAccount(number));
    // 더 추가 ...
}

@ModelAttribute 메소드는 필요한 속성으로 모델을 넣는 데 사용된다. 예를 들어 드롭다운을 상태 또는 애완 동물 유형으로 채우거나 계정 같은 명령 개체를 검색하여 HTML 양식 데이터를 나타내는 데 사용한다. 

@ModelAttribute 메소드의 두 가지 스타일에 주목하자. 첫 번째에서 메서드는 속성을 반환하여 암시적으로 속성을 추가한다. 두 번째에서 메서드는 모델을 수락하고 모델 속성을 원하는 수만큼 추가한다. 

컨트롤러에는 @ModelAttribute 메서드가 여러 개 존재할 수 있다. 이러한 메소드는 동일한 컨트롤러의 @RequestMapping 메소드보다 먼저 호출된다.

@ModelAttribute 메서드는 @ControllerAdvice 어노테이션이 달린 클래스에서도 정의할 수 있으며 이 메서드는 모든 컨트롤러에 적용된다. @ControllerAdvice 어노테이션은 구현 클래스가 클래스 경로 스캐닝을 통해 자동 감지되도록 하는 컴포넌트 어노테이션이다.

@ModelAttribute 어노테이션은 @RequestMapping 메서드도 사용할 수 있다. 이 경우 @RequestMapping 메서드 반환 값은 뷰 이름이 아닌 모델 속성으로 해석된다. 뷰 이름은 void를 반환하는 메서드와 마찬가지로 뷰 이름 규칙에서 파생된다. 

 

@ModelAttribute메서드 인수에 사용

@ModelAttribute는 메서드 또는 메서드 인수에 사용할 수 있다. 메서드 인수의 @ModelAttribute는 모델에서 인수를 검색해야 함을 나타낸다. 모델에 없으면 인수를 먼저 인스턴스화한 다음 모델에 추가해야 한다. 모델에 존재하면 인수 필드는 이름이 일치하는 모든 요청 매개변수에서 채워져야 한다.

@RequestMapping(value="/owners/{ownerId}/pets/{petId}/edit", method = RequestMethod.POST)
public String processSubmit(@ModelAttribute Pet pet) {
}

위의 예에서 Pet 인스턴스는 어디에서 올 수 있을까? 몇 가지 방법이 있다.

  • @SessionAttributes 사용으로 인해 이미 모델에 존재할 수 있다.
  • 동일한 컨트롤러의 @ModelAttribute 메서드로 인해 이미 모델에 존재할 수 있다.
  • URI 템플릿 변수 및 유형 변환기를 기반으로 검색할 수 있다.
  • 기본 생성자를 사용하여 인스턴스로 존재할 수 있다.

@ModelAttribute 메소드는 @SessionAttributes를 사용하여 요청 사이에 선택적으로 저장할 수 있는 데이터베이스에서 속성을 검색하는 방법이다. 어떤 경우에는 URI 템플릿 변수와 유형 변환기를 사용하여 속성을 검색하는 것이 편리할 수 있다. 다음은 예이다.

@RequestMapping(value="/accounts/{account}", method = RequestMethod.PUT)
public String save(@ModelAttribute("account") Account account) {
}

이 예에서 모델 속성의 이름(즉, "account")은 URI 템플릿 변수의 이름과 일치한다. String 계정 값을 Account 인스턴스로 변환할 수 있는 Converter<String, Account>를 등록하면 @ModelAttribute 메서드 없이 위 예제가 작동한다.

다음 단계는 데이터 바인딩이다. WebDataBinder 클래스는 쿼리 문자열 매개변수 및 양식 필드를 포함한 요청 매개변수 이름을 일치시켜 속성 필드를 이름으로 모델링한다. 필요한 경우 유형 변환(문자열에서 대상 필드 유형으로 변경)이 적용된 후 일치하는 필드가 채워진다. 

데이터 바인딩 결과로 필수인 필드 누락이 있거나 유형 변환 오류 등이 발생할 수 있다. 오류를 확인하려면 @ModelAttribute 인수 바로 다음에 BindingResult 인수를 추가하자.

@RequestMapping(value="/owners/{ownerId}/pets/{petId}/edit", method = RequestMethod.POST)
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) {
    if (result.hasErrors()) {
        return "petForm";
    }
    // ...
}

BindingResult를 사용하면 Spring의 <errors> 양식 태그의 도움으로 오류가 표시될 수 있는 동일한 양식을 렌더링하여 오류 발견을 확인할 수 있다.

데이터 바인딩 외에도 데이터 바인딩 오류를 기록하는 데 사용된 동일한 BindingResult를 전달하는 사용자 지정 유효성 검사기를 사용하여 유효성 검사를 호출할 수도 있다. 이를 통해 데이터 바인딩 및 유효성 검사 오류가 한 곳에 누적되어 사용자에게 다시 보고될 수 있다.

@RequestMapping(value="/owners/{ownerId}/pets/{petId}/edit", method = RequestMethod.POST)
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) {
    new PetValidator().validate(pet, result);
    if (result.hasErrors()) {
        return "petForm";
    }
    // ...
}

또는 JSR-303 @Valid 어노테이션을 추가하여 유효성 검사가 자동 호출되도록 할 수 있다.

@RequestMapping(value="/owners/{ownerId}/pets/{petId}/edit", method = RequestMethod.POST)
public String processSubmit(@Valid @ModelAttribute("pet") Pet pet, BindingResult result) {
    if (result.hasErrors()) {
        return "petForm";
    }
    // ...
}

 

@SessionAttributes 사용

유형 수준 @SessionAttributes 어노테이션은 특정 처리기가 사용하는 세션 속성을 선언한다. 반적으로 세션이나 일부 대화형 저장소에 투명하게 저장되어야 하는 모델 속성 이름 또는 모델 속성 유형이 나열되며, 후속 요청 사이에서 양식 지원 bean 역할을 한다.

@Controller 
@RequestMapping("/editPet.do") 
@SessionAttributes("pet")
public  class EditPetForm {
     // ... 
}

 

redirect 그리고 flash 속성 지정

모든 모델 속성은 리디렉션 URL에서 URI 템플릿 변수로 노출되는 것으로 간주된다. 나머지 속성 중 기본 유형 또는 기본 유형의 컬렉션/배열인 속성은 자동으로 쿼리 매개변수로 추가된다.

그러나 어노테이션이 달린 컨트롤러에서 모델은 원래 렌더링 목적으로 추가된 추가 속성(예: 드롭다운 필드 값)을 포함할 수 있다. 리디렉션 시나리오에서 사용되는 속성을 정확하게 제어하기 위해 @RequestMapping 메서드는 RedirectAttributes 유형 인수를 선언하고 이를 사용하여 RedirectView에서 사용할 속성을 추가할 수 있다. 컨트롤러 메서드가 리디렉션하는 경우 RedirectAttributes의 내용이 사용된다. 그렇지 않으면 기본 모델 내용이 사용된다.

RequestMappingHandlerAdapter는 "ignoreDefaultModelOnRedirect"라는 플래그를 제공한다. 이 플래그는 컨트롤러 메서드가 리디렉션되는 경우 기본 모델의 콘텐츠를 절대 사용해서는 안 된다는 것을 나타낸다. 컨트롤러 메서드는 RedirectAttributes 유형 속성을 선언해야 하며 그렇게 하지 않는 경우 속성이 RedirectView에 전달되어서는 안 된다. MVC 네임스페이스와 MVC Java 구성 모두 이전 버전 호환성을 유지하기 위해 이 플래그를 false로 설정한다. 그러나 새 응용 프로그램의 경우 true로 설정하는 것이 좋다.

RedirectAttributes 인터페이스를 사용하여 flash 속성을 추가할 수도 있다. 대상 리디렉션 URL로 끝나는 다른 redirect 속성과 달리 플래시 속성은 HTTP 세션에 저장되므로 URL에 표시되지 않는다. 대상 리디렉션 URL을 제공하는 컨트롤러 모델은 이러한 flash 속성을 자동으로 수신한 후 세션에서 제거된다.

"application/x-www-form-urlencoded" 가진 데이터 작업

브라우저가 아닌 클라이언트의 요청에도 @ModelAttribute 어노테이션을 사용하는 것이 좋다. 그러나 HTTP PUT 요청 작업과 관련하여 한 가지 차이점이 있다. 브라우저는 HTTP GET 또는 HTTP POST를 통해 양식 데이터를 제출할 수 있다. 브라우저가 아닌 클라이언트도 HTTP PUT을 통해 양식을 제출할 수 있다. 이는 서블릿 사양이  HTTP PUT이 아닌 HTTP POST에 대해서만 양식 필드 접근을 지원하는 ServletRequest.getParameter*() 메소드 패밀리를 요구하기에 문제가 된다.

HTTP PUT 및 PATCH 요청을 지원하기 위해 spring-web 모듈은 web.xml에서 구성할 수 있는 HttpPutFormContentFilter 필터를 제공한다.

<filter>
  <filter-name>httpPutFormFilter</filter-name>
  <filter-class>org.springframework.web.filter.HttpPutFormContentFilter</filter-class>
</filter>
<filter-mapping>
  <filter-name>httpPutFormFilter</filter-name>
  <servlet-name>dispatcherServlet</servlet-name>
</filter-mapping>
<servlet>
  <servlet-name>dispatcherServlet</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
 
위 필터는 콘텐츠 유형이 application/x-www-form-urlencoded인 HTTP PUT 및 PATCH 요청을 가로채고 요청 본문에서 양식 데이터를 읽고 ServletRequest.getParameter*() 메소드 패밀리를 통해 양식 데이터를 사용할 수 있도록 ServletRequest를 감싼다.
 

@CookieValue 주석으로 쿠키 값 매핑

@CookieValue 주석을 사용하면 메서드 매개변수가 HTTP 쿠키 값에 바인딩될 수 있다. 

다음 쿠키가 http 요청과 함께 수신되었다고 가정하자.

JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84

다음 코드는 JSESSIONID 쿠키 값을 가져오는 방법을 보여준다.

@RequestMapping("/displayHeaderInfo.do")
public void displayHeaderInfo(@CookieValue("JSESSIONID") String cookie)  {
  //...
}

대상 메소드 매개변수 유형이 문자열이 아닌 경우 유형 변환이 자동 적용된다. 이 어노테이션은 Servlet 및 Portlet 환경에서 어노테이션이 있는 핸들러 메소드에 대해 지원된다.

@RequestHeader 주석으로 요청 헤더 속성 매핑

@RequestHeader 어노테이션을 사용하면 메서드 매개변수가 요청 헤더에 바인딩될 수 있다. 다음은 요청 헤더 예제다.

Host                    localhost:8080
Accept                  text/html,application/xhtml+xml,application/xml;q=0.9
Accept-Language         fr,en-gb;q=0.7,en;q=0.3
Accept-Encoding         gzip,deflate
Accept-Charset          ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive              300

다음 예제는 Accept-Encoding 및 Keep-Alive 헤더 값을 가져오는 방법을 보여준다.

@RequestMapping("/displayHeaderInfo.do")
public void displayHeaderInfo(@RequestHeader("Accept-Encoding") String encoding,
                              @RequestHeader("Keep-Alive") long keepAlive)  {
  //...
}

메소드 매개변수가 String이 아닌 경우에 형 변환이 자동 적용된다. 이 어노테이션은 Servlet 및 Portlet 환경에서 어노테이션이 있는 핸들러 메소드에 대해 지원된다.

이 기본 기능은 쉼표로 구분된 문자열을 문자열의 배열/컬렉션 또는 유형 변환 시스템에 알려진 기타 유형으로 변환하는 데 사용할 수 있다. 예를 들어 어노테이션이 달린 메소드 매개변수 @RequestHeader("Accept")는 String 형일 수 있지만 String[] 또는 List 유형일 수도 있다. 

 

메소드 매개변수 및 유형 변환

요청 매개변수, 경로 변수, 요청 헤더 및 쿠키 값을 포함하여 요청에서 추출된 문자열 기반 값은 메서드 매개변수 또는 필드의 대상 유형으로 변환해야 할 수 있다(예: @ModelAttribute 필드와 요청 매개변수의 연결). 대상 유형이 문자열이 아닌 경우 Spring은 적절한 유형으로 자동 변환한다. int, long, Date와 같은 단순 유형이 지원된다. WebDataBinder를 통하거나 또는 FormattingConversionService에 Formatter를 등록하여 변환 프로세스를 추가로 지정할 수 있다.

WebDataBinder 초기화 사용자 정의

Spring의 WebDataBinder를 통해 PropertyEditor로 요청 매개변수 바인딩을 사용자정의하려면 컨트롤러 내에  @InitBinder 어노테이션이 달린 메소드를 사용하거나 @ControllerAdvice 클래스에서 @InitBinder 메소드를 사용하거나 사용자 정의 WebBindingInitializer를 제공할 수 있다.

@InitBinder, 데이터 바인딩 사용자 정의

@InitBinder로 컨트롤러 메서드에 주석을 추가하면 컨트롤러 클래스 내에서 직접 웹 데이터 바인딩을 구성할 수 있다. @InitBinder는 명령을 채우고 주석이 달린 핸들러 메서드의 개체 인수를 형성하는 데 사용할 WebDataBinder를 초기화하는 메서드를 식별한다.

이러한 init-binder 메서드는 명령/양식 개체 및 해당 유효성 검사 결과 개체를 제외하고 @RequestMapping이 지원하는 모든 인수를 지원한다. Init-binder 메서드에는 반환 값이 없어야 한다. 따라서 일반적으로 무효로 선언된다. 일반적인 인수에는 WebRequest 또는 java.util.Locale과 함께 WebDataBinder가 포함되어 코드에서 컨텍스트별 편집기를 등록할 수 있다.

다음 예제는 @InitBinder를 사용하여 모든 java.util.Date 양식 속성에 대해 CustomDateEditor를 구성하는 방법을 보여준다.

@Controller
public class MyFormController {
    @InitBinder
    public void initBinder(WebDataBinder binder) {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        dateFormat.setLenient(false);
        binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
    }
    // ...
}
 

사용자 정의 WebBindingInitializer 구성

데이터 바인딩 외부 초기화를 위해 WebBindingInitializer 인터페이스의 사용자 정의 구현을 제공할 수 있다. 그런 다음 AnnotationMethodHandlerAdapter에 대한 사용자 정의 Bean 구성을 제공하여 이를 활성화하고 기본 구성을 재정의한다.

PetClinic 애플리케이션 예제는 여러 PetClinic 컨트롤러에 필요한 PropertyEditor를 구성하는 WebBindingInitializer 인터페이스, org.springframework.samples.petclinic.web.ClinicBindingInitializer의 사용자 정의 구현을 사용하는 예를 보여준다.

<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
    <property name="cacheSeconds" value="0" />
    <property name="webBindingInitializer">
        <bean class="org.springframework.samples.petclinic.web.ClinicBindingInitializer" />
    </property>
</bean>
 

외부화된 @InitBinder 메서드로 데이터 바인딩 사용자 지정

@InitBinder 메서드는 @ControllerAdvice 어노테이션 클래스에서 정의할 수도 있으며 이 경우 모든 컨트롤러에 적용된다. 이것은 WebBindingInitializer 사용에 대한 대안을 제공한다.

@ControllerAdvice는 구현 클래스가 클래스 경로 검사를 통해 자동 감지되도록 하는 컴포넌트 어노테이션이다.

콘텐츠 캐싱을 용이하게 하는 'Last-Modified' 응답 헤더 지원

@RequestMapping 메소드는 콘텐츠 캐싱을 용이하게 하기 위해 Servlet API의 getLastModified 메소드에 대한 계약에 정의된 대로 'Last-Modified' HTTP 요청을 지원하기를 원할 수 있다. 여기에는 주어진 요청에 대한 lastModified  값을 계산하고, 이를 'If-Modified-Since' 요청 헤더 값과 비교하고, 잠재적으로 상태 코드 304(Not Modified)로 응답하는 작업이 포함된다. 어노테이션이 달린 컨트롤러 메서드는 다음과 같이 다룰 수 있다.

@RequestMapping
public String myHandleMethod(WebRequest webRequest, Model model) {
    long lastModified = // 1. application-specific calculation
    if (request.checkNotModified(lastModified)) {
        // 2. shortcut exit - no further processing necessary
        return null;
     }
    // 3. or otherwise further request processing, actually preparing content
    model.addAttribute(...);
    return "myViewName";
}

주목할 두 가지 핵심 요소가 있다. request.checkNotModified(lastModified) 호출과 null 반환이다. 전자는 true를 반환하기 전에 응답 상태를 304로 설정한다. 후자는 전자에 추가하여 Spring MVC가 요청을 더 이상 처리하지 않도록 한다.

3.4 비동기 요청 처리

 
Spring MVC 3.2는 Servlet 3 기반 비동기 요청 처리를 도입했다. 평소처럼 값을 반환하는 대신에 컨트롤러 메서드는  java.util.concurrent.Callable을 반환하고 별도 스레드에서 반환 값을 생성할 수 있다. 그 사이에 메인 서블릿 컨테이너 스레드가 해제되고 다른 요청을 처리할 수 있다. Spring MVC는 TaskExecutor 도움으로 별도 스레드에서 Callable을 호출하고 Callable이 반환되면 요청은 Callable에 의해 반환된 값으로 처리를 재개하기 위해 Servlet 컨테이너로 다시 발송된다. 다음은 컨트롤러 메서드 예이다.
@RequestMapping(method=RequestMethod.POST)
public Callable<String> processUpload(final MultipartFile file) {
  return new Callable<String>() {
    public Object call() throws Exception {
      // ...
      return "someView";
    }
  };
}

두 번째 옵션은 컨트롤러가 DeferredResult의 인스턴스를 반환하는 것이다. 이 경우 반환 값은 별도 스레드에서도 생성된다. 그러나 그 쓰레드는 Spring MVC에 알려지지 않는다. 예를 들어, 결과는 JMS 메시지, 예약된 작업과 같은 일부 외부 이벤트에 대한 응답으로 생성될 수 있다. 다음은 컨트롤러 메서드 예이다.

@RequestMapping("/quotes")
@ResponseBody
public DeferredResult<String> quotes() {
  DeferredResult<String> deferredResult = new DeferredResult<String>();
  // Save the deferredResult in in-memory queue ...
  return deferredResult;
}
// In some other thread...
deferredResult.setResult(data);

 

이 부분은 Servlet 3 비동기 처리 지식이 필요하다. 최소한 다음과 같은 기본 사실을 고려하자.

 

  • ServletRequest는 request.startAsync()를 호출하여 비동기 모드로 설정할 수 있다. 서블릿과 모든 필터가 종료될 수 있지만 응답은 열린 상태로 유지되어 일부 다른 스레드가 처리를 완료할 수 있다는 것이다.
  • request.startAsync()에 대한 호출은 비동기 처리에 대한 추가 제어에 사용할 수 있는 AsyncContext를 반환합니다. 예를 들어 요청을 서블릿 컨테이너로 다시 "dispatch"하기 위해 응용 프로그램 스레드에서 호출할 수 있는 디스패치 메서드를 제공한다. 비동기 디스패치는 한(응용 프로그램) 스레드에서 다른(서블릿 컨테이너) 스레드로 만들어지는 반면 전달은 동일한(서블릿 컨테이너) 스레드에서 동기적으로 발생한다는 점을 제외하고 전달과 유사하다.
  • ServletRequest는 현재 DispatcherType에 대한 접근을 제공하며, 이를 사용하여 Servlet 또는 Filter가 초기 요청 처리 스레드에서 처리 중인지 아니면 비동기 디스패치에서 처리 중인지 구별할 수 있다.

 

비동기 요청에 대한 예외 처리

Callable컨트롤러 메서드에서 반환된 메서드 실행 중에 예외가 발생하면? 컨트롤러 메서드에서 예외가 발생할 때와 비슷하다. 동일한 컨트롤러 또는 구성된 HandlerExceptionResolver 인스턴스 중 하나에서 일치하는 @ExceptionHandler 메서드에 의해 처리된다.

내부적으로 Callable이 예외를 발생시키면 Spring MVC는 처리를 재개하기 위해 Servlet 컨테이너에 디스패치한다. 유일한 차이점은 Callable 실행 결과가 구성된 HandlerExceptionResolver 인스턴스로 처리해야 하는 예외라는 것이다.

DeferredResult를 사용할 때 setErrorResult(Object) 메서드를 호출하고 Exception 또는 결과로 사용하려는 다른 개체를 제공하도록 선택할 수 있다. 결과가 예외인 경우 동일한 컨트롤러 또는 구성된 HandlerExceptionResolver 인스턴스에서 일치하는 @ExceptionHandler 메서드로 처리된다.

비동기 요청 가로채기

기존 HandlerInterceptor는 ConcurrentHandlingStarted 이후에 하나의 추가 메서드를 제공하는 AsyncHandlerInterceptor를 구현할 수 있다. 비동기 처리가 시작된 후 초기 요청 처리 스레드가 종료될 때 호출된다. 

비동기 요청 수명 주기 콜백에 대한 추가 옵션은 onTimeout(Runnable) 및 onCompletion(Runnable) 메서드가 있는 DeferredResult에서 제공된다. 비동기 요청이 시간 초과되거나 완료될 때 각각 호출된다. 시간 초과 이벤트는 DeferredResult를 일부 값으로 설정하여 처리할 수 있다. 그러나 완료 콜백은 최종적이며 결과를 더 이상 설정할 수 없다.

유사한 콜백은 Callable에서도 사용할 수 있습니다. 그러나 WebAsyncTask의 인스턴스에서 Callable을 감싼 다음 이를 사용하여 시간 초과 및 완료 콜백을 등록해야 한다. DeferredResult와 마찬가지로 타임아웃 이벤트를 처리하고 완료 이벤트가 종료되는 동안 값을 반환할 수 있다.

MVC Java 구성이나 MVC 네임스페이스를 통해 전역적인 CallableProcessingInterceptor 또는  DeferredResultProcessingInterceptor를 등록할 수도 있다. 이러한 인터셉터는 전체 콜백 세트를 제공하고 Callable 또는 DeferredResult가 사용될 때마다 적용된다.

 

비동기 요청 처리를 위한 구성

 

Servlet 3 비동기 구성

 

Servlet 3 비동기 요청 처리를 사용하려면 web.xml 버전 3.0으로 업데이트해야 한다.

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    version="3.0">
    ...
</web-app>

DispatcherServlet 및 모든 필터 구성에는 <async-supported>true</async-supported> 하위 요소가 있어야 한다. 또한 비동기 디스패치에도 참여하는 모든 필터는 ASYNC 디스패처 유형을 지원하도록 구성해야 한다. Spring Framework와 함께 제공되는 모든 필터에 대해 ASYNC 디스패처 유형을 활성화하는 것이 안전하다. (필요한 경우가 아니면 비동기 디스패치에 관여하지 않기 때문이다.)

Servlet 3을 사용하는 경우 Java 기반 구성, 예: WebApplicationInitializer를 통해 web.xml과 마찬가지로  "asyncSupported" 플래그와 ASYNC 디스패처 유형도 설정해야 한다. 

Spring MVC 비동기 구성

MVC Java 구성 및 MVC 네임스페이스는 모두 비동기 요청 처리를 구성하기 위한 옵션을 제공한다. WebMvcConfigurer에는 configureAsyncSupport 메서드가 있고 <mvc:annotation-driven>에는 <async-support> 하위 요소가 있다.

이를 통해 비동기 요청에 사용할 기본 시간 제한 값을 구성할 수 있다. 설정하지 않은 경우 기본 서블릿 컨테이너에 따라 다르다(예: Tomcat은 10초). 컨트롤러 메서드에서 반환된 호출 가능한 인스턴스 실행에 사용하도록 AsyncTaskExecutor를 구성할 수도 있다. 기본적으로 Spring MVC는 SimpleAsyncTaskExecutor를 사용하므로 이 속성을 구성하는 것이 좋다. MVC Java 구성 및 MVC 네임스페이스를 사용하면 CallableProcessingInterceptor 및 DeferredResultProcessingInterceptor 인스턴스도 등록할 수 있다.

특정 DeferredResult에 대한 기본 시간 초과 값을 재정의하는 경우 적절한 클래스 생성자를 사용하여 재정의할 수 있다. 마찬가지로 Callable의 경우 WebAsyncTask로 감싸고 적절한 클래스 생성자를 사용하여 시간 초과 값을 사용자 지정할 수 있다. WebAsyncTask의 클래스 생성자는 AsyncTaskExecutor도 제공할 수 있다.

 

3.5 컨트롤러 테스트

spring-test 모듈은 어노테이션이 달린 컨트롤러 테스트 기능을 제공한다. 

 

 

반응형

'SPRING' 카테고리의 다른 글

Spring WEB MVC 프레임워크 (4)  (0) 2022.06.13
Spring WEB MVC 프레임워크 (3)  (0) 2022.06.13
Spring WEB MVC 프레임워크 (1)  (0) 2022.06.12
Spring, JDBC 예제 코드  (0) 2022.06.12
이클립스 메이븐 프로젝트 만들기  (0) 2022.06.06

+ Recent posts