스프링 mvc 1

이주인·2023년 1월 16일
0

스프링 공부

목록 보기
2/11

구조 이해

front controller

요청에 맞는 컨트롤러를 찾아서 호출하는 컨트롤러

스프링의 DispatcherServlet이 프런트 컨트롤러 패턴으로 구현되어 있다.

mvc 프레임워크 구조

동작 순서

1.핸들러 조회: url에 맵핑된 핸들러(컨트로러)를 조회
2. 핸들러 어댑터 조회 및 실행
3. 핸들러 실행
4. ModelAndView 반환: 핸들러가 반환하는 정보를 ModelAndView로 변환 후 반환
5. 뷰 리졸버 호출
6. View 반환
7. 뷰 랜더링

@Controller

  • 스프링이 자동으로 스프링 빈으로 등록하며, 애노테이션 기반 컨트롤러로 인식

@RequestMapping

  • 요청 정보를 매핑한다.

기본 기능

요청 매핑

@Slf4j
@RestController
public class MappingController {
 
 @RequestMapping("/hello-basic")
 public String helloBasic() {
 log.info("helloBasic");
 return "ok";
 }

@RestController: 반환 값을 Http 메시지 바디에 바로 입력
@RequestMapping: url 호출이 오면 이 메소드가 실행되도록 매핑

PathVariable(경로 변수) 사용

@GetMapping("/mapping/{userId}")
public String mappingPath(@PathVariable("userId") String data) {
 log.info("mappingPath userId={}", data);
 return "ok";
}

@PathVariable을 이용하여 매칭되는 부분을 편리하게 조회한다.
@PathVariable의 이름과 파라미터 이름이 같다면 생략 가능

요청 매핑 - API 예시

@RestController
@RequestMapping("/mapping/users")
public class MappingClassController {
 
 @GetMapping
 public String users() {
 	return "get users";
 }
 
 @PostMapping
 public String addUser() {
 	return "post user";
 }
 
 @GetMapping("/{userId}")
 public String findUser(@PathVariable String userId) {
  return "get userId=" + userId;
 }

http 요청 파리미터

쿼리 파라미터, html form

3가지 방법이 있다.

  1. GET - 쿼리 파라미터
    url파라미터에 데이터를 포함해서 전달
    ex) http://localhost:8080/request-param?username=hello&age=20

  2. Post - HTML form
    메시지 바디에 쿼리 파리미터 형식으로 전달
    ex)

POST /request-param ...
content-type: application/x-www-form-urlencoded
username=hello&age=20
  1. HTTP message body에 데이터를 직접 담아서 요청

요청 파라미터 조회

@RequestParam

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

// 파라미터와 변수 이름이 같은 경우 생략 가능
@ResponseBody
@RequestMapping("/request-param-v3")
public String requestParamV3(
 	@RequestParam String username,
 	@RequestParam int age) {
 
 	log.info("username={}, age={}", username, age);
 	return "ok";
}

//파라미터를 Map으로 조회하는 방법
@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";
}

@RequestParam: 파라미터 이름으로 바인딩.
name() 속성이 파라미터 이름으로 사용
ex)

@RequestParam("username") String memberName
==
//서블릿 방식으로 조회
request.getParameter("username")
  • @RequestParam(required = true)를 적용하여 파라미터 필수 여부를 설정할 수 있다.
  • @RequestParam(required = true, defaultValue = "guest")
    기본 값을 적용할 때 사용한다

@ModelAttribute

요청 파라미터를 받아줄 객체를 자동으로 만들어준 후, 파라미터를 처리한다(set, get 등)

//HelloData
// 요청 파라미터를 바인딩 받을 객체

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

-본문-

@ResponseBody
@RequestMapping("/model-attribute-v1")
public String modelAttributeV1(@ModelAttribute HelloData helloData) {
	
    log.info("username={}, age={}", helloData.getUsername(),helloData.getAge());
	return "ok";
}

// @ModelAttribute는 생략이 가능하다
//@RequestParam이랑 햇갈릴 수 있음
@ResponseBody
@RequestMapping("/model-attribute-v2")
public String modelAttributeV2(HelloData helloData) {
 	
    log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
	return "ok";
}

스프링은 @ModelAttribute가 있을 경우 다음을 실행한다.

  1. HelloData 객체를 생성
  2. 요청 파라미터의 이름으로 HelloData 객체의 프로퍼티를 찾은 후 파라미터의 값을 입력(바인딩)한다.

단순 텍스트

  • HTTP 메시지 바디를 통해 데이터가 직접 넘어오는 경우 @RequestParam이나 @ModelAttribute를 사용할 수 없다
  • 이 경우 InputStream을 이용하여 읽을 수 있다.

@PostMapping("/request-body-string-v1")
public void requestBodyString(HttpServletRequest request, HttpServletResponse response) throws IOException {

	ServletInputStream inputStream = request.getInputStream();
	String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
	
    log.info("messageBody={}", messageBody);
 	response.getWriter().write("ok");
 }
  • InputStream(Reader): HTTP 요청 메시지 바디의 내용을 직접 조회
  • OutputStream(Writer): HTTP 응답 메시지의 바디에 직접 결과 출력
@PostMapping("/request-body-string-v3")
public HttpEntity<String> requestBodyStringV3(HttpEntity<String> httpEntity) {
	
    String messageBody = httpEntity.getBody();
	log.info("messageBody={}", messageBody);
	return new HttpEntity<>("ok");
}

HttpEntity: http 헤더, 바디 정보를 편리하게 조회

  • 메시지 바디 정보를 직접 조회 및 반환
  • 헤더 정보를 포함 가능

RequestEntity, ResponseEntity

  • HttpMethod, url 정보가 추가, 요청에서 사용
  • HTTP 상태 코드 설정 가능, 응답에서 사용
@ResponseBody
@PostMapping("/request-body-string-v4")
public String requestBodyStringV4(@RequestBody String messageBody) {
	log.info("messageBody={}", messageBody);
	return "ok";
}

json 조회시

@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-v5")
public HelloData requestBodyJsonV5(@RequestBody HelloData data) {
 log.info("username={}, age={}", data.getUsername(), data.getAge());
 return data;
}

@RequestBody: HTTP 메시지 정보를 편리하게 조회 가능

  • 직접 만든 객체또한 지정할 수 있다
  • JSON 요청 HTTP 메시지 컨버터 객체

@ResponseBody: 응답 결과를 HTTP 메시지 바디에 직접 담아서 전달할 수 있다

  • 객체 HTTP 메시지 컨버터 JSON 응답

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

3가지 방법이 있다

  1. 정적 리소스: 정적인 HTML 사용시

  2. 뷰 템플릿: 동적인 HTML 사용시

  3. HTTP 메시지 이용: json 같은 데이터 전송시

//뷰 템플릿 호출 컨트롤러
@RequestMapping("/response-view-v1")
 public ModelAndView responseViewV1() {
	ModelAndView mav = new ModelAndView("response/hello").addObject("data", "hello!");
 	return mav;
 }
 
 //String 반환
 @RequestMapping("/response-view-v2")
 public String responseViewV2(Model model) {
 	model.addAttribute("data", "hello!!");
 	return "response/hello";
 }
 
 //void 반환 - 권장하지 않는 방법
 @RequestMapping("/response/hello")
 public void responseViewV3(Model model) {
 	model.addAttribute("data", "hello!!");
}

String으로 반환시

  • 비어있는 model 인스턴스를 받아온다음, model.addAttribute를 통해 모델 안에 hashMap 형식?으로 view에 전달할 key와 value를 담는다.
  • @ResponseBody가 없으면 입력한 url로 뷰 리졸버가 실행되어 뷰를 찾은 뒤 랜더링 한다
  • @ResponseBody가 있으면 http 메시지 바디에 직접 메시지가 입력된다. (예제로 치면 "response/hello"가 입력됨)

void 반환시

  • @Controller를 사용하고, http메시지를 처리하는 파라미터가 없을시 요청 url을 참고후 논리 뷰 이름으로 사용
  • 이런 방식은 명시성이 떨어져서 권장하는 방법이 아니다

HTTP 메시지

  • @ResponseBody , HttpEntity 를 사용하면, 뷰 템플릿을 사용하는 것이 아니라, HTTP 메시지 바디에 직접 응답 데이터를 출력할 수 있다

HTTP API, 메시지 바디에 직접 입력

//ResponseEntity는 http 메시지의 헤더, 바디 정보를 가지고 있다.
 @GetMapping("/response-body-string-v2")
 public ResponseEntity<String> responseBodyV2() {
	 return new ResponseEntity<>("ok", HttpStatus.OK);
 }
 
 //@ResponseBody를 이요하여 직접 데이터 전달
 @ResponseBody
 @GetMapping("/response-body-string-v3")
 public String responseBodyV3() {
 	return "ok";
 }
 
 
 //ResponseEntity 를 반환한다. HTTP 메시지 컨버터를 통해서 JSON 형식으로 변환되어서 반환된다.
 @ResponseStatus(HttpStatus.OK)   // http 응답 코드를 설정
 @GetMapping("/response-body-json-v1")
 public ResponseEntity<HelloData> responseBodyJsonV1() {
 	
    HelloData helloData = new HelloData();
 	helloData.setUsername("userA");
 	helloData.setAge(20);
 	
    return new ResponseEntity<>(helloData, HttpStatus.OK);
 }
 

@RestController

  • 해당 컨트롤러에 모두 @ResponseBody가 적용된다
  • Http 메시지 바디에 직접 데이터를 입력하는, Rest API를 만들때 사용하는 컨트롤러

HTTP 메시지 컨버터

HTTP API처럼 JSON 데이터를 HTTP 메시지
바디에서 직접 읽거나 쓰는 경우 HTTP 메시지 컨버터를 사용하면 편리하다.

다음의 경우 스프링은 메시지 컨버터를 적용한다

  • HTTP 요청: @RequestBody , HttpEntity(RequestEntity)
  • HTTP 응답: @ResponseBody , HttpEntity(ResponseEntity)

주요 메시지 컨버터

  • StringHttpMessageConverter: 문자로 데이터 처리
    클래스 타입: String
    미디어 타입: /
  • MappingJackson2HttpMessageConverter
    클래스 타입: 객체, HashMap
    미디어 타입: application/json
  • ByteArrayHttpMessageConverter
    클래스 타입: byte[]
    미디어 타입: /

-Http 요청 데이터 읽기-

http 요청이 오고, 컨트롤러에서 @RequestBody , HttpEntity 파라미터를 사용하는 경우
-> 메시지 컨버터가 메시지를 읽을 수 있는지 확인한다

  • 대상 클래스 타입을 지원하는지
  • HTTP 요청의 Content-Type 미디어 타입을 지원하는지

조건을 만족하는 경우 객체를 생성한 뒤 반환한다.

-HTTP 응답 데이터 생성-

컨트롤러에서 @ResponseBody , HttpEntity 로 값이 반환되는 경우
-> 메시지 컨버터가 메시지를 쓸 수 있는지 확인한다

  • 대상 클래스 타입을 지원하는가.
  • HTTP 요청의 Accept 미디어 타입을 지원하는가

조건 만족시 Http 응답 메시지 바디에 데이터를 생성


개인적으로 햇갈리는 거 정리

@ModelAttribute

view에서 입력 폼 등을 이용해서 값을 받아올때,

  1. @ModelAttribute는 지정한 객체의 프로퍼티(getter, setter)를 찾은 후, 객체 안에 입력받은 데이터를 프로퍼티를 이용하여 넣어준다.
  2. 그리고 model.attribute() 역할도 대신 해주는데, 이때 key는 객체 맨 앞의 문자를 소문자로 변환하여 사용한다.
profile
소프트웨어공

0개의 댓글