[Spring] SpringMVC 의 세부적인 구조 (핸들러, 핸들러 어댑터, 메시지 컨버터)

728x90

 

SpringMVC 전체 구조

DispatcherServlet

- SpringMVC 의 프런트 컨트롤러

- `HttpServlet` 을 상속받아 사용하고, 서블릿으로 동작

- 스프링 부트가 `DispatcherServlet` 을 서블릿으로 자동 등록하며 모든 경로 (`urlParrerns`) 에 대하여 매핑

 

SpringMVC 구조

    핸들러 매핑        
   
① 핸들러 조회
      핸들러 어댑터 목록
         ↗ 
  ② 핸들러 어댑터 조회
 ↗ 
   
클라이언트 HTTP 요청
→ → → →
Dispatcher
Servlet
③ handle(handler)
→ → → → → →
← ← ← ← ← ←
⑤ModelAndView 반환
핸들러
어댑터
④ 핸들러 호출
→ → → →
핸들러
(컨트롤러)
 
⑧ render(model)
호출
 ↖ ↘ ⑥ ViewResolver 호출
     ↖ ↘
         ↖ ⑦ View 반환
            ViewResolver
   
HTML 응답
← ← ← ←
View        

 

1. 핸들러 조회 : 핸들러 매핑을 통해 요청 URL 에 매핑된 핸들러를 조회

2. 핸들러 어댑터 조회 : 핸들러를 실행할 수 있는 핸들러 어댑터를 조회

3. 핸들러 어댑터 실행 : 핸들러 어댑터를 실행

4. 핸들러 실행 : 핸들러 어댑터가 실제 핸들러를 실행

5. ModelAndView 반환 : 핸들러 어댑터는 핸들러가 반환하는 정보를 ModelAndView 로 변환하여 반환

6. ViewResolver 호출 : 뷰 리졸버를 찾고 실행

( JSP 의 경우, `InternalResourceViewResolver` 자동 등록 )

7. View 반환 : 뷰 리졸버가 뷰 논리 이름을 물리 이름으로 바꾸고, 렌더링 역할을 담당하는 View 객체 반환

( JSP 의 경우, `InternalResourceView(JstlView) 반환, 내부에 `forward()` 로직 )

8. 뷰 렌더링 : 뷰를 통해 뷰를 렌더링

 

핸들러 매핑 / 핸들러 어댑터

HandlerMapping

0 = RequestMappingHandlerMapping

  : 애노테이션 기반의 컨트롤러인 @RequestMapping 에서 사용

1 = BeanNameUrlHandlerMapping 

  : 스프링 빈 이름으로 핸들러 찾음

( 생략 )

 

HandlerAdapter

0 = RequestMappingHandlerAdapter

  : 애노테이션 기반의 컨트롤러인 @RequestMapping 에서 사용

1 = HttpRequestHandlerAdapter

  : HttpRequestHandler 처리

2 = SimpleControllerHandlerAdapter

  : Controller 인터페이스 (애노테이션 X, 과거에 사용) 처리

( 생략 )

 

HandlerAdapter 의 `supports()` 를 순서대로 호출하여 핸들러 어댑터를 조회한다.

 

Controller 인터페이스 (과거에 사용)

@Component("/springmvc/old-controller")
public class OldController implements Controller {

  @Override
  public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
    return null;
  }
}

`/springmvc/old-controller` 이름으로 스프링 빈이 등록되었다.

빈 이름으로 URL 을 매핑한다.

 

1. HandlerMapping 을 순서대로 실행하여 핸들러를 찾는다.

2. 빈 이름으로 핸들러를 찾는 `BeanNameUrlHandlerMapping` 이 실행에 성공하고 핸들러를 반환한다.

3. HandlerAdapter 의 `supports()` 를 호출하여 핸들러 어댑터를 찾는다.

4. 찾은 `SimpleControllerHandlerAdapter` 로 핸들러를 실행하고, 결과를 반환한다.

 

HttpRequestHandler 인터페이스 (과거에 사용)

@Component("/springmvc/request-handler")
public class MyHttpRequestHandler implements HttpRequestHandler {
  
  @Override
  public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  }
}

`/springmvc/request-handler` 이름으로 스프링 빈이 등록되었다.

빈 이름으로 URL 을 매핑한다.

 

1. HandlerMapping 을 순서대로 실행하여 핸들러를 찾는다.

2. 빈 이름으로 핸들러를 찾는 `BeanNameUrlHandlerMapping` 이 실행에 성공하고 핸들러를 반환한다.

3. HandlerAdapter 의 `supports()` 를 호출하여 핸들러 어댑터를 찾는다.

4. 찾은 `HttpRequestHandlerAdapter` 로 핸들러를 실행하고, 결과를 반환한다.

 

@RequestMapping (실무)

실무에서 사용하는 애노테이션 기반의 컨트롤러를 지원하는 매핑과 어댑터인

`RequestMappngHandlerMapping` 과 `RequestMappingHandlerAdapter` 를 가장 많이 사용한다.

 

- @Controller 

  : 내부의 @Component 애노테이션으로 인해 컴포넌트 스캔의 대상이 되어 스프링 빈으로 등록

  : 스프링 MVC 에서 애노테이션 기반 컨트롤러로 인식

- @RequestMapping 

  : 요청 정보를 매핑하여 해당 URL 이 호출되면 메서드가 호출된다.

  (애노테이션 기반이기 때문에 메서드 이름 임의로 지정 가능)

 

@Controller
public class SpringMemberFormControllerV1 {
  
  @RequestMapping("/springmvc/v1/members/new-form")
  public ModelAndView process() {
    return new ModelAndView("new-form");
  }
}

 

1. HandlerMapping 을 순서대로 실행하여 핸들러를 찾는다.

2. `RequestMappingHandlerMapping` 이 실행에 성공하여  `@RequestMapping("url")` 이 설정된 컨트롤러를 찾아 반환한다.

4. HandlerAdapter 의 `supprots()` 를 호출하여 핸들러 어댑터를 찾는다.

5. 찾은 `RequestMappingHandlerAdpater` 로 컨트롤러의 `@RequestMapping` 메서드를 실행하고, 결과를 반환한다.

애노테이션 기반 컨트롤러

특징

- 컨트롤러 통합

  : @RequestMapping 는 클래스 단위가 아닌 메서드 단위에 적용되었기 때문에, 유연하게 하나로 통합할 수 있다.

- Model 을 사용한 데이터 관리

  : Model 객체를 이용하여 데이터를 저장/조회 기능을 제공한다.

- ViewName 직접 반환

  : ModelAndView 객체를 반환하는 것이 아니라, String 형식의 뷰의 논리 이름을 반환해도 된다.

 

RequestMappingHandlerAdater

애노테이션 기반의 컨트롤러의 `@RequestMapping` 을 처리하는 핸들러 어댑터 구조를 자세히 살펴보자

   

ArgumentResolver
 
DispatcherServlet → → → RequestMapping
HandlerAdapter
→ → → → → → 핸들러
(컨트롤러)
   

ReturnValueHandler


 

① 컨트롤러의 파라미터, 애노테이션 정보를 기반으로 전달 데이터 생성 

  ex. `HttpServletRequest`, `Model`, `@RequestParam`, `@ModelAttribute`, `@ReuqestBody`, `HttpEntity` 등

호출

③ 컨트롤러의 반환값을 변환

  ex. `ModelAndView`, `@ResponseBody`, `HttpEntity` 등

 

ArgumentResolver (HandlerMethodArgumentResolver)

- 다양한 방식의 컨트롤러의 파라미터를 생성하는 역할

- 인터페이스로 제공하여 확장 가능

 

ReturnValueHandler (HandlerMethodReturnValueHandler)

- 컨트롤러가 반환한 데이터를 적절한 HTTP 응답 형태로 변환하는 역할

- 인터페이스로 제공하여 확장 가능

 

HTTP 메시지 컨버터 (HttpMessageConverter)

- `ArgumentResolver` 와 `ReturnValueHandler` 가 데이터를 변환할 때 핵심적인 변환 기능을 제공

- 인터페이스로 제공하여 확장 가능

 

HTTP 메시지 컨버터

HTTP 요청 : `@RequestBody`, `HttpEntity`, `RequestEntity`

HTTP 응답 : `@ResponseBody`, `HttpEntity`, `ResponseEntity`

 

사용 시에 HTTP 메시지 컨버터가

`요청/응답 데이터의 타입`과 `미디어 타입(Content-Type, Accept)`을 확인하여 변환한다.

 

기본 메시지 컨버터

0 = ByteArrayHttpMessageConverter

  : byte 처리

1 = StringHttpMessageConverter

  : 기본 문자 처리

2 = MappingJackson2HttpMessageConverter

  : 기본 객체 처리

( 생략 )

 

대상 클래스 타입, 미디어 타입을 체크해서 사용여부를 결정한다.

 

ByteArrayHttpMessageConverter

- byte[ ] 데이터를 처리

- 클래스 타입 : `byte[ ]`

- 미디어 타입 : `*/*`

ex. 요청 `@RequestBody byte[] data`

ex. 응답 `@ResponseBody return byte[]` 쓰기 미디어타입 `application/octet-stream` 로 자동 설정

 

StringHttpMessageConverter

- String 문자로 데이터를 처리

- 클래스 타입 : `String`

- 미디어 타입 : `*/*`

ex. 요청 `@RequestBody String data`

ex. 응답 `@ResponseBody return "ok"` 쓰기 미디어타입 `text/plain` 로 자동 설정 

 

MappingJackson2HttpMessageConverter

- application/json 처리

- 클래스 타입 : `객체` or `HashMap`

- 미디어 타입 : `application/json` 관련

ex. 요청 `@RequestBody HelloData data`

ex. 응답 `@ResponseBody return helloData` 쓰기 미디어타입 `application/json` 관련으로 자동 설정

 

HTTP 요청 데이터 읽기

1. HTTP 요청이 오고, 컨트롤러에서 `@RequestBody`, `HttpEntity`, `RequestEntity` 파라미터를 사용한다.

2. 메시지 컨버터가 메시지를 읽을 수 있는지 확인하기 위해 `canRead()` 를 호출한다.

  > 대상 클래스 타입을 지원하는가 ?

    ex. `@RequestBody` 의 대상 클래스 (`byte[]`, `String`, `HelloData`)

  > HTTP 요청의 Content-Type 미디어 타입을 지원하는가 ?

    ex. `text/plain`, `application/json`, `*/*`

3. 위 조건으로 `canRead()` 를 만족하면, `read()` 를 호출하여 객체를 생성하고, 반환한다.

 

HTTP 응답 데이터 생성

1. 컨트롤러에서 `@ResponseBody`, `HttpEntity`, `ResponseEntity` 로 값이 반환된다.

2. 메시지 컨버터가 메시지를 쓸 수 있는지 확인하기 위해 `canWrite()` 를 호출한다.

  > 대상 클래스 타입을 지원하는가 ?

    ex. return 의 대상 클래스 (`byte[]`, `String`, `HelloData`)

  > HTTP 요청의 Accept 미디어 타입을 지원하는가 ?

    ex. `text/plain`, `application/json`, `*/*`

3. 위 조건으로 `canWrite()` 를 만족하면, `write()` 를 호출하여 HTTP 응답 메시지 바디에 데이터를 생성한다.

 

 

 

 

 

 

 

 

 

 

 

 

 

출처 | 스프링 MVC 1(김영한) - 인프런

728x90