Search
Duplicate
📒

[Spring Study] 05-4. HTTP Message Converter, RequestMappingHandlerAdapter

상태
완료
수업
Spring Study
주제
Converter
연관 노트
3 more properties
참고

HTTP 메시지 컨버터

NOTE
HTTP 메시지 컨버터는 클라이언트로부터 받은 HTTP 요청의 본문을 컨트롤러가 처리할 수 있는 자바 객체로 변환하거나, 반환된 객체를 HTTP 응답의 본문으로 바꾸는데 사용됩니다.
HTTP 데이터를 읽고, HTTP 응답 데이터를 쓰는 컨버터의 어노테이션인 @RequestBody, @ResponseBody가 대표적이며, 해당 어노테이션들이 붙으면 HttpMessageConverter가 데이터 변환을 담당하게 됩니다.
@RequestBody: HTTP 요청 본문의 데이터가 메서드 파라미터로 변환됩니다.
@ResponseBody: 메서드에서 반환된 값이 HTTP 응답 본문에 직접 쓰입니다.
public interface HttpMessageConverter<T> { boolean canRead(Class<?> clazz, @Nullable MediaType mediaType); boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType); List<MediaType> getSupportedMediaTypes(); T read(Class<? extends T> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException; void write(T t, @Nullable MediaType contentType, HttpOutputMessag outputMessage) throws IOException, HttpMessageNotWritableException; }
Java
복사
스프링에서 제공하는 주요 HTTP 메시지 컨버터는 다음과 같습니다.
1.
ByteArrayHttpMessageConverter
2.
StringHttpMessageConverter
3.
MappingJackson2HttpMessageConverter

HTTP 메시지 컨버터의 작동 방식

NOTE
HTTP 요청/응답에서 사용되는 데이터의 읽기 및 쓰기 작업을 처리하는데 사용되며, 이전에 설명한 Converter와 다른 인터페이스를 구현하지만 개념은 동일합니다.

HTTP 요청 데이터 읽기

컨트롤러에서 @RequestBody, HttpEntity를 사용하는 컨트롤러에서 HTTP 요청이 들어오면, 해당 데이터 타입과 Content-Type을 지원하는지 확인합니다. (canRead() 호출)
지원되면 read() 메서드가 호출되어 요청 데이터가 객체로 반환됩니다.

HTTP 응답 데이터 쓰기

컨트롤러에서 @ResponseBodyHttpEntity를 통해 데이터를 반환할 때, 해당 데이터 타입과 클라이언트의 Accpe 헤더를 지원하는지 확인합니다. (canWrite() 호출)
지원되면 write() 메서드가 호출되어 객체 데이터가 HTTP 응답 메시지 바디에 쓰입니다.

RequestMappingHandlerAdapter 구조

NOTE
RequestMappingHandlerAdapter는 스프링 MVC에서 @RequestMapping 어노테이션을 처리하며. 요청이 들어올 때 해당 요청을 처리할 수 있는 가장 적합한 메서드를 결정합니다.
HTTP 메시지 컨버터는 4번에서 사용됨 (디스패쳐 서블릿 - 핸들러 사이)

ArgumentResolver

NOTE
스프링은 HandlerMethodArgumentResolver 인페이스를 통해 컨트롤러 메소드의 파라미터를 동적으로 처리합니다. 이 인터페이스의 구현체는 특정 타입의 파라미터를 지원하는지 확인하고, 지원한다면 필요한 객체를 생성합니다.
아래의 코드는 Session 정보에서 유저의 정보를 추출해서 자동으로 MemberResponseStatus 객체를 생성해주는 작업입니다.
@Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface SignIn {}
Java
복사
1. 커스텀 어노테이션 생성
@Slf4j public class SignInArgumentResolver implements HandlerMethodArgumentResolver { // 해당 파라미터를 지원하는가? @Override public boolean supportsParameter(MethodParameter parameter) { boolean hasLoginAnnotation = parameter.hasParameterAnnotation(SignIn.class); // @Login이 있는가? boolean hasMemberType = MemberResponseStatus.class.isAssignableFrom(parameter.getParameterType()); // MemberResponseStatus가 있는가? return hasLoginAnnotation && hasMemberType; } // 파라미터를 지원한다면 실제 객체를 생성 @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest(); // 원본 HTTP 요청 HttpSession session = request.getSession(false); // 현재 Session을 가져온다. // session에 저장된 유저객체가 없다면 Null 반환 if(session == null || session.getAttribute(Const.SIGNIN_MEMBER) == null) { log.info("not login member {}", session); return null; } // 저장된 객체가 있다면 저장된 객체 반환 log.info("login member {}", session.getAttribute(Const.SIGNIN_MEMBER)); return session.getAttribute(Const.SIGNIN_MEMBER); } }
Java
복사
2. Argument 구현
@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) { resolvers.add(new SignInArgumentResolver()); } }
Java
복사
3. 스프링 설정에 등록
@RestController @RequestMapping("/auth") @RequiredArgsConstructor public class AuthController { private final MemberService memberService; // 4. @SignIn 사용 @GetMapping("/status") public ResponseEntity<?> memberStatus(@SignIn MemberResponseStatus member){ return new ResponseEntity<>(member, HttpStatus.OK); } @PostMapping("/signIn") public ResponseEntity<?> memberSignIn(@Valid @RequestBody MemberRequestSignInDto memberRequestSignInDto, HttpSession httpSession){ MemberResponseStatus signInMember = memberService.signIn(memberRequestSignInDto); log.info("signMember : {}", signInMember); httpSession.setAttribute(Const.SIGNIN_MEMBER, signInMember); return new ResponseEntity<>(signInMember, HttpStatus.OK); } }
Java
복사
4. 컨트롤러에서 사용

ReturnValueHandler

NOTE
ReturnValueHandler는 컨트롤러에서 반환된 값이 어떻게 HTTP 응답에 매핑될지를 처리합니다. 만약 문자열을 반환할 경우, 뷰 이름 혹은 ResponeBody가 붙은경우 JSON 형식으로 직렬화되어 Respone 본문에 포함될 수 있습니다.
스프링은 다양한 ReturnValudeHandler를 지원하며 대표적인 예시는 다음과 같습니다.
ex) ModelAndView, @ResponseBody, HttpEntity, String
public class CustomReturnValueHandler implements HandlerMethodReturnValueHandler { @Override public boolean supportsReturnType(MethodParameter returnType) { // 특정 조건에 따라 처리 가능 여부를 결정 (예: MyResponse 타입인 경우만 처리) return MyResponse.class.isAssignableFrom(returnType.getParameterType()); } @Override public void handleReturnValue( Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, ServerHttpRequest request, ServerHttpResponse response) throws Exception { // 반환값을 처리하고 응답 설정 if (returnValue instanceof MyResponse) { MyResponse myResponse = (MyResponse) returnValue; // 반환 객체를 JSON 문자열로 변환 (여기서는 예시로 단순 문자열 사용) String json = "{\"message\": \"" + myResponse.getMessage() + "\"}"; response.getBody().write(json.getBytes()); // ModelAndView 진행 방지 mavContainer.setRequestHandled(true); } } }
Java
복사
등록코드