[스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술] 06. 스프링 MVC 기본 기능

Turtle·2024년 6월 28일
0
post-thumbnail

🙄로깅 간단히 알아보기

✔️로깅 라이브러리

스프링 부트 라이브러리를 사용하면 스프링 부트 로깅 라이브러리가 함께 포함된다. 스프링 푸트 로깅 라이브러리는 기본으로 다음 로깅 라이브러리를 사용한다.

SLF4J
Logback

✔️application.properties

spring.application.name=springmvc
logging.level.hello.springmvc=trace

✔️로그 선언 및 로그 호출

private final Logger log = LoggerFactory.getLogger(getClass());

// ...

log.info("hello");

✔️LogTestController

@RestController
public class LogTestController {
	private final Logger log = LoggerFactory.getLogger(getClass());

	@RequestMapping("/log-test")
	public String logTest() {
		String name = "Spring";
		System.out.println("name = " + name);

		log.trace("trace log={}", name);
		log.debug("debug log={}", name);
		log.info("info log={}", name);
		log.warn("warn log={}", name);
		log.error("error log={}", name);
		return "ok";
	}
}

실행 결과

  • ✔️테스트
    • 로그가 출력되는 포멧 확인
      • 시간 , 로그 레벨, 프로세스 ID, 쓰레드 명, 클래스명, 로그 메시지
    • 로그 레벨 설정을 변경해서 출력 결과를 보자.
      • LEVEL : TRACE > DEBUG > INFO > WARN > ERROR
      • 개발 서버의 경우 debug, 운영 서버의 경우 info
    • @Slf4j로 변경
  • ✔️로그 사용시 장점
    • 쓰레드 정보, 클래스 이름 같은 부가 정보를 함께 볼 수 있고 출력 모양을 조정할 수 있다.
    • 로그 레벨에 따라 개발 서버에서는 모든 로그를 출력하고 운영 서버에서는 로그를 상황에 맞게 조절할 수 있다.
    • 시스템 아웃 콘솔에만 출력하는 것이 아니라 파일이나 네트워크 등, 로그를 별도의 위치에 남길 수 있다. 특히 파일로 남길 때는 일별, 특정 용량에 따라 로그를 분할하는 것도 가능하다.
    • 성능도 일반 System.out 보다 좋다.(내부 버퍼링, 멀티 쓰레드 등등)

✔️@Slf4j 롬복 어노테이션 기반 LogTestController

@Slf4j
@RestController
public class LogTestController {

	@RequestMapping("/log-test")
	public String logTest() {
		String name = "Spring";
		System.out.println("name = " + name);

		log.trace("trace log={}", name);
		log.debug("debug log={}", name);
		log.info("info log={}", name);
		log.warn("warn log={}", name);
		log.error("error log={}", name);
		return "ok";
	}
}

🙄요청 매핑

//@Slf4j
@RestController
public class MappingController {

	private final Logger log = LoggerFactory.getLogger(getClass());

	@RequestMapping(value = "/hello-basic")
	public String helloBasic() {
		log.info("helloBasic");
		return "ok";
	}

	// ✔️HTTP 메서드
    // 특정 HTTP 메서드 요청만 허용
    // GET, HEAD, PUT, POST, PATCH, DELETE
	@RequestMapping(value = "/mapping-get-v1", method = RequestMethod.GET)
	public String mappingGetV1() {
		log.info("mappingGetV1");
		return "ok";
	}

	// ✔️HTTP 메서드 매핑 축약
    // 편리한 축약 어노테이션
    // @GetMapping, @PostMapping, @PutMapping, @DeleteMapping, @PatchMapping
	@GetMapping(value = "/mapping-get-v2")
	public String mappingGetV2() {
		log.info("mappingGetV2");
		return "ok";
	}

	// ✔️PathVariable(경로 변수) 사용
	@GetMapping(value = "/mapping/{userId}")
	public String mappingPath1(@PathVariable(name = "userId") String userId) {
		log.info("mappingPath1 userId={}", userId);
		return "ok";
	}

	// ✔️PathVariable(경로 변수) 사용 - 다중
	@GetMapping(value = "/mapping/{userId}/order/{orderId}")
	public String mappingPath2(@PathVariable(name = "userId") String userId, @PathVariable(name = "orderId") Long orderId) {
		log.info("mappingPath2 userId={}, orderId={}", userId, orderId);
		return "ok";
	}

	// ✔️특정 파라미터 조건 매핑
	@GetMapping(value = "/mapping-param", params = "mode=debug")
	public String mappingParam() {
		log.info("mappingParam");
		return "ok";
	}

	// ✔️특정 헤더 조건 매핑
	@GetMapping(value = "/mapping-header", headers = "mode=debug")
	public String mappingHeader() {
		log.info("mappingHeader");
		return "ok";
	}

	// ✔️미디어 타입 조건 매핑 - HTTP 요청 Content-Type, consume
	@PostMapping(value = "/mapping-consume", consumes = "application/json")
	public String mappingConsume() {
		log.info("mappingConsume");
		return "ok";
	}

	// ✔️미디어 타입 조건 매핑 - HTTP 요청 Accept, produce
	@PostMapping(value = "/mapping-produce", produces = "text/html")
	public String mappingProduce() {
		log.info("mappingProduce");
		return "ok";
	}
}

🙄요청 매핑 - API 예시

✔️회원 관리 HTTP API 예시

  1. 회원 목록 조회 : GET → /users
  2. 회원 등록 : POST → /users
  3. 회원 조회 : GET → /users/{userId}
  4. 회원 수정 : PATCH → /users/{userId}
  5. 회원 삭제 : DELETE → /users/{userId}
@RestController
@RequestMapping(value = "/mapping/users")	// 계층적 매핑👍
public class MappingClassController {

	@GetMapping()
	public String users() {
		return "get users";
	}

	@PostMapping()
	public String addUser() {
		return "add user";
	}

	@GetMapping("/{userId}")
	public String findUser(@PathVariable(name = "userId") String userId) {
		return "get userId = " + userId;
	}

	@PatchMapping("/{userId}")
	public String updateUser(@PathVariable(name = "userId") String userId) {
		return "update userId = " + userId;
	}

	@DeleteMapping("/{userId}")
	public String deleteUser(@PathVariable(name = "userId") String userId) {
		return "delete userId = " + userId;
	}
}

🙄HTTP 요청 - 기본, 헤더 조회

@Slf4j
@RestController
public class RequestHeaderController {

	@RequestMapping("/headers")
	public String headers(
    		// ✔️ HttpServletResponse
			HttpServletResponse response,
            // ✔️ HttpServletRequest
			HttpServletRequest request,
            // ✔️ HttpMethod : HTTP 메서드를 조회한다.
			HttpMethod httpMethod,
            // ✔️ Locale : Locale 정보를 조회한다.
			Locale locale,
            // ✔️ @RequestHeader : 모든 HTTP 헤더를 MultiValueMap 형식으로 조회한다.
			@RequestHeader MultiValueMap<String, String> headerMap,
            // ✔️ @RequestHeader : 특정 HTTP 헤더를 조회한다.
			@RequestHeader(name = "host") String host,
            // ✔️ @CookieValue : 특정 쿠키를 조회한다.
			@CookieValue(value = "mvCookie", required = false) String cookie
			) {
		log.info("request={}", request);
		log.info("response={}", response);
		log.info("httpMethod={}", httpMethod);
		log.info("locale={}", locale);
		log.info("headerMap={}", headerMap);
		log.info("header host={}", host);
		log.info("myCookie={}", cookie);
		return "ok";
	}
}

Method Arguments
Return Values

🙄HTTP 요청 파라미터 - 쿼리 파라미터, HTML Form

@Slf4j
@Controller
public class RequestParamController {

	@RequestMapping("/request-param-v1")
	public void requestParamV1(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		String username = request.getParameter("username");
		int age = Integer.parseInt(request.getParameter("age"));
		log.info("username={}, age={}", username, age);
		response.getWriter().write("ok");
	}
}

Jar를 사용하면 webapp 경로를 사용할 수 없다. 정적 리소스도 클래스 경로에 함께 포함해야한다.

🙄HTTP 요청 파라미터 - @RequestParam

✔️RequestParamControllerV1

@Slf4j
@Controller
public class RequestParamControllerV1 {

	@RequestMapping("/request-param-v1")
	public void requestParamV1(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		String username = request.getParameter("username");
		int age = Integer.parseInt(request.getParameter("age"));
		log.info("username={}, age={}", username, age);
		response.getWriter().write("ok");
	}
}

✔️RequestParamControllerV2

@Slf4j
@Controller
public class RequestParamControllerV2 {

	@ResponseBody
	@RequestMapping("/request-param-v2")
	public String requestParamV2(@RequestParam(name = "username") String username, @RequestParam(name = "age") int age)  {
		log.info("username={}, age={}", username, age);
		return "ok";
	}
}

❗Controller 어노테이션과 String 타입이 같이 사용되면 이는 뷰를 렌더링하라는 의미가 되는데 현재 ok라는 이름의 정적 리소스가 없기 때문에 문자 ok를 리턴하려면 @ResponseBody를 사용하면 된다.

✔️RequestParamControllerV3

@Slf4j
@Controller
public class RequestParamControllerV3 {

	@ResponseBody
	@RequestMapping("/request-param-v3")
	public String requestParamV2(@RequestParam String username, @RequestParam int age)  {
		log.info("username={}, age={}", username, age);
		return "ok";
	}
}

✔️RequestParamControllerV4

@Slf4j
@Controller
public class RequestParamControllerV4 {

	@ResponseBody
	@RequestMapping("/request-param-v4")
	public String requestParamV2(String username, int age)  {
		log.info("username={}, age={}", username, age);
		return "ok";
	}
}

❗어노테이션을 생략해도 되지만 없는 것보다는 @RequestParam이 있으면 명확하게 요청 파라미터에서 데이터를 읽는다는 것을 알 수 있다.

✔️RequestParamControllerV5 - 요청 파라미터 필수

@Slf4j
@Controller
public class RequestParamControllerV5 {

	@ResponseBody
	@RequestMapping("/request-param-required")
	public String requestParamRequired(String username, @RequestParam(required = false) int age)  {
		log.info("username={}, age={}", username, age);
		return "ok";
	}
}

❗String타입의 username은 입력을 필수로 받지 않아도 null값이 들어갈 수 있으나 int는 기본형 타입이기 때문에 null이란 값이 들어갈 수 없다. 따라서 int타입의 age에서 입력을 필수로 받지 않으려면 래퍼 클래스인 Integer를 사용해야한다.

✔️RequestParamControllerV6 - 기본값 지정

@Slf4j
@Controller
public class RequestParamControllerV6 {

	@ResponseBody
	@RequestMapping("/request-param-default")
	public String requestParamRequired(@RequestParam(defaultValue = "GUEST") String username, @RequestParam(defaultValue = "-1") Integer age)  {
		log.info("username={}, age={}", username, age);
		return "ok";
	}
}

✔️RequestParamControllerV7 - 파라미터를 Map으로 조회하기

  • 파라미터의 값이 1개가 확실한 경우 Map을 사용한다.
@Slf4j
@Controller
public class RequestParamControllerV7 {

	@ResponseBody
	@RequestMapping("/request-param-map")
	public String requestParamMap(@RequestParam Map<String, Object> paramMap)  {
		log.info("username={}, age={}", paramMap.get("username"), paramMap.get("age"));
		return "ok";
	}
}

✔️RequestParamControllerV8 - 파라미터를 MultiValueMap으로 조회하기

  • 파라미터의 값이 1개가 확실하지않고 여러가지라면 MultiValueMap을 사용한다.
@Slf4j
@Controller
public class RequestParamControllerV8 {

	@ResponseBody
	@RequestMapping("/request-param-map")
	public String requestParamMap(@RequestParam MultiValueMap<String, Object> paramMap)  {
		log.info("username={}, age={}", paramMap.get("username"), paramMap.get("age"));
		return "ok";
	}
}

🙄HTTP 요청 파라미터 - @ModelAddAttribute

✔️@Data → @Getter, @Setter, @ToString, @EqualsAndHashCode, @RequiredArgsConstructor 지원

@Data
public class HelloData {
	private String username;
	private int age;
}

✔️ModelAttribute 적용 - modelAttributeV1

  1. 스프링 MVC는 @ModelAttribute가 있으면 다음을 실행한다.
  2. HelloData 객체를 생성한다.
  3. 요청 파라미터 이름으로 HelloData 객체의 프로퍼티를 찾는다. 그리고 해당 프로퍼티의 setter를 호출해서 파라미터의 값을 바인딩한다.
  4. 이 때, 데이터 타입이 일치하지 않는 파라미터를 넣게 되면 바인딩 오류가 발생한다.
@Slf4j
@Controller
public class ModelAttributeV1 {
	@ResponseBody
	@RequestMapping("/model-attribute-v1")
	public String modelAttributeV1(@ModelAttribute HelloData helloData) {
		log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
		log.info("helloData={}", helloData);
		return "ok";
	}
}

✔️ModelAttribute 생략 - modelAttributeV2

@Slf4j
@Controller
public class ModelAttributeV2 {
	@ResponseBody
	@RequestMapping("/model-attribute-v2")
	public String modelAttributeV2(HelloData helloData) {
		log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
		log.info("helloData={}", helloData);
		return "ok";
	}
}

🙄HTTP 요청 메시지 - 단순 텍스트

✔️RequestBodyStringController

  1. 스프링 MVC는 InputStream과 OutputStream을 지원한다.
  • InputStream(Reader) : HTTP 요청 메시지 바디의 내용을 직접 조회
  • OutputStream(Writer) : HTTP 응답 메시지 바디에 직접 결과 출력
  1. HttpEntity : HTTP header, body 정보를 편리하게 조회
  • 메시지 바디 정보를 직접 조회
  • 요청 파라미터를 조회하는 기능과 관계 없음
  • 응답에도 사용 가능
  • 메시지 바디 정보 직접 반환
  • 헤더 정보 포함 가능
  • view 조회 X
  1. HttpEntity를 상속받은 다음 객체들도 같은 기능을 제공한다.
  • ResponseEntity : HTTP 상태 코드 설정 가능, 응답에서 사용
  • RequestEntity : HTTP Method, url정보 추가, 요청에서 사용
  1. @RequestBody를 사용하면 HTTP 메시지 바디 정보를 편리하게 조회할 수 있다. 메시지 바디를 직접 조회하는 기능은 요청 파라미터를 조회하는 @RequestParam이나 @ModelAttribute와는 전혀 관계가 없다.

  2. @ResponseBody를 사용하면 응답 결과를 HTTP 메시지 바디에 직접 담아서 전달할 수 있다.
    물론 이 경우에도 view를 사용하지 않는다.

  • ✔️요청 파라미터를 조회하는 기능
    • @RequestParam
    • @ModelAttribute
  • ✔️HTTP 메시지 바디
    • @RequestBody
@Slf4j
@Controller
public class RequestBodyStringController {

	@PostMapping("/request-body-string-v1")
	public void requestBodyString1(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
		ServletInputStream inputStream = request.getInputStream();
		String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);

		log.info("messageBody={}", messageBody);
		response.getWriter().write("ok");
	}

	@PostMapping("/request-body-string-v2")
	public void requestBodyString2(InputStream inputStream, Writer outputStream) throws IOException {
		String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
		log.info("messageBody={}", messageBody);
		outputStream.write("ok");
	}

	@PostMapping("/request-body-string-v3")
	public HttpEntity<String> requestBodyString3(HttpEntity<String> httpEntity) throws IOException {
		String messageBody = httpEntity.getBody();
		log.info("messageBody={}", messageBody);
		return new HttpEntity<>("ok");
	}

	@PostMapping("/request-body-string-v4")
	public ResponseEntity<String> requestBodyString4(RequestEntity<String> httpEntity) throws IOException {
		String messageBody = httpEntity.getBody();
		log.info("messageBody={}", messageBody);
		return new ResponseEntity<String>("ok", HttpStatus.ACCEPTED);
	}

	@PostMapping("/request-body-string-v5")
	public HttpEntity<String> requestBodyString5(@RequestBody String messageBody) {
		log.info("messageBody={}", messageBody);
		return new ResponseEntity<String>("ok", HttpStatus.ACCEPTED);
	}
}

🙄HTTP 요청 메시지 - JSON

@Slf4j
@Controller
public class RequestBodyJsonController {

	private ObjectMapper objectMapper = new ObjectMapper();

	@PostMapping("/request-body-json-v1")
	public void requestBodyJsonV1(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		ServletInputStream inputStream = request.getInputStream();
		String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);

		log.info("messageBody={}", messageBody);
		HelloData helloData = objectMapper.readValue(messageBody, HelloData.class);
		log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
		response.getWriter().write("ok");
	}

	// ✔️응답의 경우 @ResponseBody를 사용하면 해당 객체를 HTTP 메시지 바디에 직접 넣을 수 있다.
	@ResponseBody
	@PostMapping("/request-body-json-v2")
	public String requestBodyJsonV2(@RequestBody String messageBody) throws IOException {
		HelloData data = objectMapper.readValue(messageBody, HelloData.class);
		log.info("username={}, age={}", data.getUsername(), data.getAge());
		return "ok";
	}

	@ResponseBody
	@PostMapping("/request-body-json-v3")
	public String requestBodyJsonV3(@RequestBody HelloData data) {
		log.info("username={}, age={}", data.getUsername(), data.getAge());
		return "ok";
	}

	// ✔️HttpEntity를 사용해도 된다.
	@ResponseBody
	@PostMapping("/request-body-json-v4")
	public String requestBodyJsonV4(HttpEntity<HelloData> httpEntity) {
		HelloData data = httpEntity.getBody();
		log.info("username={}, age={}", data.getUsername(), data.getAge());
		return "ok";
	}

	// ✔️@RequestBody 요청 → JSON 요청 → HTTP 메시지 컨버터 → 객체 
    // ✔️@ResponseBody 응답 → 객체 → HTTP 메시지 컨버터 → JSON 응답
	@ResponseBody
	@PostMapping("/request-body-json-v5")
	public HelloData requestBodyJsonV5(@RequestBody HelloData data) {
		log.info("username={}, age={}", data.getUsername(), data.getAge());
		return data;
	}
}

🙄응답 - 정적 리소스, 뷰 템플릿

  • ✔️정적 리소스
    • 웹 브라우저에 정적인 HTML, CSS, JS를 제공할 때는 정적 리소스를 사용한다.
    • 스프링 부트는 클래스패스의 다음 디렉터리에 있는 정적 리소스를 제공한다.
      • /static
      • /public
      • /resources/
      • /META-INF/resources/
  • ✔️뷰 템플릿
    • 웹 브라우저에 동적인 HTML을 제공할 때는 뷰 템플릿을 사용한다.
    • 스프링 부트는 기본 뷰 템플릿 경로를 제공한다.
      • src/main/resources/template
  • ✔️HTTP 메시지 사용
    • HTTP API를 제공하는 경우에는 HTML이 아니라 데이터를 전달해야 하므로, HTTP 메시지 바디에 JSON같은 형식으로 데이터를 실어 보낸다.

  • ✔️뷰 템플릿
    • String을 반환하는 경우 - View or HTTP 메시지
    • @ResponseBody가 없으면 뷰 리졸버를 실행하여 뷰를 찾고 뷰를 렌더링한다.
    • @ResponseBody가 있으면 류 리졸버를 실행하지 않고 HTTP 메시지 바디에 문자가 입력된다.
  • ✔️HTTP 메시지
    • @ResponseBody, HttpEntity를 사용하면 뷰 템플릿을 사용하느게 아니라 HTTP 메시지 바디에 직접 응답 데이터를 출력하게끔 한다.
@Controller
public class ResponseViewController {

	@RequestMapping("/response-view-v1")
	public ModelAndView responseViewV1() {
		ModelAndView mv = new ModelAndView("/response/hello").addObject("data", "hello");
		return mv;
	}

	// ✔️반환 타입이 String인 경우
    // 뷰 리졸버를 실행해서 뷰를 찾고 뷰를 렌더링한다.
	@RequestMapping("/response-view-v2")
	public String responseView2(Model model) {
		model.addAttribute("data", "hello");
		return "response/hello";
	}

	// ✔️반환 타입이 void인 경우
    // 요청 URL과 뷰 템플릿의 경로가 완전히 동일한 경우에만 사용 가능
    // 명시성이 너무 떨어지고 딱 맞는 경우도 맞지 않아서 권장하지 않는다.
	@RequestMapping("/response/hello")
	public void responseView3(Model model) {
		model.addAttribute("data", "hello");
	}
}

🙄HTTP 응답 - HTTP API, 메시지 바디에 직접 입력

@Slf4j
@Controller
public class ResponseBodyController {

	// ✔️서블릿을 직접 다룰 때처럼
    // HttpServletReponse 객체를 통해서 HTTP 메시지 바디에 직접 응답 메시지 전달
	@GetMapping("/response-body-string-v1")
	public void responseBodyV1(HttpServletResponse response) throws IOException {
		response.getWriter().write("OK");
	}

	// ✔️HttpEntity 사용
    // HttpEntity는 HTTP 메시지의 헤더, 바디 정보를 가지고 있다.
    // ResponseEntity는 여기에 더해서 HTTP 응답 코드를 설정할 수 있다.
	@GetMapping("/response-body-string-v2")
	public ResponseEntity<String> responseBodyV2()  {
		return new ResponseEntity<String>("ok", HttpStatus.OK);
	}

	// ✔️@ResponseBody를 사용하면 view를 사용하지 않고 HTTP 메시지 컨버터를 통해서 메시지를 직접 입력할 수 있다.
	@ResponseBody
	@GetMapping("/response-body-string-v3")
	public String responseBodyV3()  {
		return "ok";
	}

	// ✔️ResponseEntity를 반환
    // HTTP 메시지 컨버터를 통해서 JSON 형식으로 반환된다.
	@ResponseBody
	@GetMapping("/response-body-json-v1")
	public ResponseEntity<HelloData> responseBodyJsonV1() {
		HelloData helloData = new HelloData();
		helloData.setUsername("userA");
		helloData.setAge(20);
		return new ResponseEntity<HelloData>(helloData, HttpStatus.OK);
	}

	// ✔️반환 객체 타입으로 지정
	@ResponseStatus(HttpStatus.OK)
	@ResponseBody
	@GetMapping("/response-body-json-v2")
	public HelloData responseBodyJsonV2(HelloData helloData) {
		helloData.setUsername("userA");
		helloData.setAge(20);
		return helloData;
	}
}

✔️@RestController

  1. @Controller 대신에 @RestController 어노테이션을 사용하면 해당 컨트롤러에 모두 @ResposeBody가 적용되는 효과가 있다. 따라서 뷰 템플릿을 사용하는 것이 아니라 HTTP 메시지 바디에 직접 데이터를 입력한다. 이름 그대로 REST API를 만들 때 사용하는 컨트롤러이다.
  2. @Controller를 사용할 때 각각의 메서드 레벨에 @ResponseBody를 두는 것보다 클래스 레벨에 @ResponseBody를 두면 중복을 제거할 수 있다.

🙄HTTP 메시지 컨버터

  • ✔️주요 메시지 컨버터
    • ByteArrayHttpMessageConverter : byte[] 데이터를 처리
      • 클래스 타입 : byte[], 미디어 타입 : */*
      • 요청 예시 : @RequestBody byte[] data
      • 응답 예시 : @ResponseBody return byte[]
      • 쓰기 미디어 타입 : application/octet-stream
    • StringHttpMessageConverter : String 문자로 데이터를 처리
      • 클래스 타입 : String, 미디어 타입 : */*
      • 요청 예시 : @RequestBody String data
      • 응답 예시 : `@ResponseBody return "ok"
      • 쓰기 미디어 타입 : text/plain
    • MappingJackson2HttpMessageConverter : application/json
      • 클래스 타입 : HashMap, 미디어 타입 : application/json 관련
      • 요청 예시 : @RequestBody HelloData data
      • 응답 예시 : @ResponseBody return hellodata
      • 쓰기 미디어 타입 : application/json
  • ✔️HTTP 요청 데이터 읽기
    • HTTP 요청이 오고 컨트롤러에서 @RequestBody, HttpEntity 파라미터를 사용한다.
    • 메시지 컨버터가 메시지를 읽을 수 있는지 확인하기 위해 canRead()를 호출한다.
    • 대상 클래스 타입을 지원하는가?
      • @RequestBody의 대상 클래스
    • HTTP 요청의 Content-Type 미디어 타입을 지원하는가?
      • text/plain, application/json
  • ✔️HTTP 응답 데이터 생성
    • 컨트롤러에서 @ResponseBody, HttpEntity로 값이 반환된다.
    • 메시지 컨버터가 메시지를 쓸 수 있는지 확인하기 위해 canWrite()를 호출한다.
    • 대상 클래스 타입을 지원하는가?
      • return의 대상 클래스(byte[], String, HelloData)
    • HTTP 요청의 Accept 미디어 타입을 지원하는가?
      • text/plain, application/json
    • canWrite() 조건을 만족하면 write()를 호출해서 HTTP 응답 메시지 바디에 데이터 생성

🙄요청 매핑 핸들러 어댑터 구조

✔️RequestMappingHandlerAdapter 동작 방식

@Slf4j
@Controller
public class RequestParamControllerV1 {

	@RequestMapping("/request-param-v1")
	public void requestParamV1(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		String username = request.getParameter("username");
		int age = Integer.parseInt(request.getParameter("age"));
		log.info("username={}, age={}", username, age);
		response.getWriter().write("ok");
	}
}

컨트롤러를 호출할 때 누군가로부터 HttpServletRequest를 받아야 하고 HttpServletResponse를 받아야 하며 또 @RequestParam로 파라미터 정보를 받아야 하며 @ModelAttribute로 관련 객체를 만들어 넘겨야 한다.

✔️ArgumentResolver

어노테이션 기반의 컨트롤러는 매우 다양한 파라미터를 사용할 수 있었다. HttpServletRequest, HttpServletResponse, Model@RequestParam, @PathVariable, @ModelAttribute등의 어노테이션 그리고 @ResponseBody, HttpEntity 등과 같이 HTTP 메시지를 처리하는 부분까지 처리를 해주는데 이렇게 처리할 수 있는 이유는 바로 ArgumentResolver 덕분이다.

어노테이션 기반 컨트롤러를 처리하는 RequestMapping 핸들러 어댑터는 이 ArgumentResolver를 호출해 컨트롤러가 필요로 하는 파라미터 값(객체)을 생성한다. 그리고 이렇게 파라미터 값이 모두 준비되면 컨트롤러를 호출해서 값을 넘겨준다.

public interface HandlerMethodArgumentResolver {
    boolean supportsParameter(MethodParameter var1);

    @Nullable
    Object resolveArgument(MethodParameter var1, @Nullable ModelAndViewContainer var2, NativeWebRequest var3, @Nullable WebDataBinderFactory var4) throws Exception;
}

ArgumentResolversupportsParameter()를 호출해서 해당 파라미터를 지원하는지 체크하고 지원한다면 resolveArgument()를 호출해서 실제 객체를 생성한다. 그리고 이렇게 생성된 객체가 컨트롤러 호출 시 넘어가게 되는 것이다.

✔️ReturnValueHandler

응답 값을 변환하고 처리한다. 컨트롤러에서 String 타입으로 뷰 이름을 반환할 때 동작하는 이유가 바로 이 핸들러 때문이다.

0개의 댓글