2024.09.12.목.TIL Request Mapping

김기남·2024년 9월 12일
post-thumbnail

안녕하세요, 오늘은 Request Mapping 에 대해 정리해 보았습니다.

HTTP Request Parameter

@ModelAttribute


💡 Spring은 요청 파라미터를 받아 필요한 Object로 변환하는것을 자동화 해준다. 그 방법중 하나인 @ModelAttribute 에 대해 알아보자
  1. 기존코드
    • ex) http://localhost:8080/model/attribute?name=wonuk&age=100
public class Member {

	private String name;
	private int age;

}

@Controller
public class RequestParamController {

	@ResponseBody
	@GetMapping("/request/param")
	public String requestParam (
					@RequestParam String name,
					@RequestParam int age													
	) {
		Membmer member = new Member();
		member.setName(name);
		member.setAge(age);
		// or
		// Membmer member = new Member(name, age);

		return "success";
	}

}
  • @RequestParam 의 Mapping을 사용하게 되면 위와같은 코드가 만들어진다.
  • @ModelAttribute 는 해당 과정을 자동화 하였다.
  1. @ModelAttribute 적용
    • ex) http://localhost:8080/model/attribute?name=wonuk&age=100
@Data // 실제로는 사용하지 않고, 테스트 용도로 많이 사용 @Getter @Setter @Equals...등등
public class Member {

	private String name
	private int age;

}

@Controller
public class RequestParamController {

	@ResponseBody
	@GetMapping("/model/attribute")
	public String modelAttribute (
					@ModelAttribute Member member													
	) {
		String memberName = member.getName();
		int memberAge = member.getAge();

		return "success";
	}

}
  • @Data는 @Getter, @Setter, @ToString, @EqualsAndHashCode, @RequiredArgsConstructer를
    자동으로 설정해주는 역할을한다. 보통 테스트 용도로만 사용하고
    실무에서는 사용하지 않는다.
  • Spring은 @ModelAttirubte가 있으면
    1. Member 객체를 생성하고
    2. 요청 파라미터 이름으로 객체 필드의 Setter를 호출해서 바인딩한다.
      → 파라미터 이름이 name 이면 setName(value) 메서드를 호출한다
      → 파라미터 이름과 필드 이름이 같아야한다!
  • 만약 요청 파라미터 ageint가 아닌 String 이 전달된다면?
    • BindException 발생
    • 이런 경우 때문에 Validation이 필요하다.
  1. @ModelAttirubte 생략
    • @ModelAttribute와 지난시간에 배운 @RequestParam은 모두 생략이 가능하다
    • ex) http://localhost:8080/model/attribute?name=wonuk&age=100
@Data
public class Member {

	private String name
	private int age;

}

@Controller // + @Responsebody = @RestController
public class RequestParamController {

	@ResponseBody
	@GetMapping("/model/attribute")
	public String modelAttribute (Member member) {

		String memberName = member.getName();
		int memberAge = member.getAge();

		return "success";
	}

}
  • Spring에서는 @RequestParam이나 @ModelAttribute가 생략되면
    • String, int, Integer 와 같은 기본 타입은 @RequestParam과 Mapping한다.
    • 나머지 경우들은 모두 @ModelAttribute 와 Mapping한다

HTTP Request Body

💡 지난시간 배운 @RequestParam과 오늘배울 @ModelAttribute는 GET 쿼리 파라미터와, POST HTML Form Data를 바인딩하는 방법입니다.
  • 이번에는 Request Body의 Data를 바인딩 하는방법에 대해 알아봅시다!
  • 주로 HTTP API(Rest API)에서 사용하고 주로 Json, XML, TEXT 등이 사용된다.
  • HTTP Method POST, PUT, PATCH 에서 주로 사용된다. (GET은 Request Body가 존재할수는 있지만 안쓴다.)
  • 이런 경우는 @RequestParam, @ModelAttribute를 사용할 수 없다.
    • HTML Form Data 형식은 가능

Text


💡 HTTP Request Body에 Text 형태로 Data가 전송되는 경우 사실 잘 사용되지 않음 현재는 대부분(99.99%) JSON으로 통신
  • 코드예시1
    • request.getInputStream();
@Controller
public class RequestBodyStringController {
	
	@PostMapping("/request-body/text")
	public void requestBodyText(
					HttpServletRequest request,
					HttpServletResponse response	
	) throws IOException { 
		
		ServletInputStream inputStream = request.getInputStream();
		String bodyText = StreamUtils.copyToString(inputStream, StandardCharsets.UTF-8);
		
	}

}

  • Request Header Content-Type : text/plain

  • 코드예시2

    • inputStream(읽기) 파라미터 지원 : HTTP Request Body Data 직접 조회
    • OutputStream(쓰기) 파라미터 지원 : HTTP Response Body 직접 결과 출력
@Controller
public class RequestBodyStringController {
	
	@PostMapping("/request-body/text")
	public void requestBodyText(
					InputStream inputStream,
					Writer responseWriter
	) throws IOException { 
		
		String body = StreamUtils.copyToString(inputStream, StandardCharsets.UTF-8);
		
	}

}
  • 코드예시3
    • HttpMessageConverter 사용 추후 설명
    • HttpEntity<> 사용하면 HttpMessageConverter를 사용한다.
@Controller
public class RequestBodyStringController {
	
	@PostMapping("/request-body/text")
	public HttpEntity<String> requestBodyText(HttpEntity<String> httpEntity) { 
		
		// HttpMessageConverter가 동작해서 아래 코드가 동작하게됨
		String body = httpEntity.getBody();
		
		return new HttpEntity<>("success"); // 매개변수 = Body Message
	}

}
  1. HttpEntity를 사용하면 HttpMessageConverter가 동작하여 자동으로 매핑된다.
  2. 응답까지 HttpEntity 하나만으로 사용이 가능해진다.
  3. Spring 덕분이다. 감사합니다.

HttpEntity

💡 HTTP Header, Body 정보를 편리하게 조회할 수 있도록 만들어준다
  • Http Request Body Message를 직접 조회한다
  • Request Parameter를 조회하는 기능들과는 아무 관계가 없다.
  • Response 또한 사용할 수 있도록 만들어준다
  • Response Header 또한 사용할 수 있다
  • 즉, View를 반환하지 않는다.
  • HttpEntity를 상속받은 객체
    • RequestEntity
      • HTTP Method, URL 정보가 추가 되어있다.
    • ResponseEntity
      • HTTP Response 상태코드 설정이 가능하다.
    • 코드예시
@Controller
public class RequestBodyStringController {
	
	@PostMapping("/request-body/text")
	public HttpEntity<String> requestBodyText(RequestEntity<String> httpEntity) { 
		
		// HttpMessageConverter가 동작해서 아래 코드가 동작하게됨
		String body = httpEntity.getBody();
		// url, method 사용 가능
		
		return new ResponseEntity<>("success", HttpStatus.CREATED); // Body Data, 상태코드
	}

}
  • 응답은 아래에서 따로 다뤄보자
  • 위 방법을 적용해도 아직까지도 뭔가 불편하다.
    • 더 쉽게 만든건 뭐지..? 써본거같은데..?

Spring은 Http RequestBody Message를 읽어서 String이나 Object로 자동으로 변환해준다.
이때 HttpMessageConverter가 사용된다.

@RequestBody, @ResponseBody

  • 코드예시4
@Controller
public class RequestBodyStringController {
	
  @ResponseBody
	@PostMapping("/request-body/text")
	public HttpEntity<String> requestBodyText(@RequestBody String body) { 
		
		// HttpMessageConverter가 동작해서 아래 코드가 동작하게됨
		String bodyMessage = body;
		
		return "success";
	}

}
  • 결국 위과정을 모두 거치고 이 두가지 어노테이션으로 사용하게된다.
  • @RequestBody : 요청
    • 요청 메세지 Body Data를 쉽게 조회할 수 있다
    • @RequestHeader : 헤더 정보
  • @ResponseBody : 응답
    • 응답 메세지 바디에 값을 쉽게 담아서 전달할 수 있도록 해준다 → View 반환하는게 아님

정리

  1. 요청 파라미터를 조회하는 경우 : @RequestParam, @ModelAttribute 를 사용한다.
  2. Http Message Body 를 조회하는 경우 : @RequestBody 를 사용한다.

Json

💡 Json은 @RestController 에서 가장 많이 사용되는 데이터 형식이다. 현재 대부분의 API는 Request, Response 모두 JSON 형태로 통신한다.
  • 주의! Json 형태로 Data를 전송할 때는 Request Header의 content-type이
    application/json 이여야한다

  • 코드예시1(기존방식)

@Data
public class Member {
	private String name;
	private int age;
}

@Controller
public class JsonController {
	
	private ObjectMapper objectMapper = new ObjectMapper();

	@PostMapping("/request-body/json")
	public void requestJson(
				HttpServletRequest request, 
				HttpServletResponse response
	) throws IOException {

		// request body message를 Read
		ServletInputStream inputStream = request.getInputStream();
		// UTF-8 형식의 String으로 변환한다.
		String requestBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);

		// String requestBody를 ObjectMapper를 사용하여 변환 "{\"name\":\"wonuk\", \"age\":10}"
		Member member = 
			objectMapper.readValue(requestBody, Member.class);
		
	}
}

  • HttpServletRequest를 사용하여 HTTP Message Body 데이터를 Read하여 문자로 변환
  • 문자로 만들어진 JSON을 Jackson 라이브러리인 objectMapper를 사용하여 Java Object로 변환


코드예시2(@RequestBody)

@Data
public class Member {
	private String name;
	private int age;
}

@Controller
public class JsonController {
	
	private ObjectMapper objectMapper = new ObjectMapper();

	@PostMapping("/request-body/json")
	public String requestJson(@RequestBody String body) throws IOException {
		
		Member member = objectMapper.readValue(requestBody, Member.class);

		return "success";
	}
}
  • @RequestBody를 사용하여 HTTP Request Body의 Data에 접근한다

  • @ModelAttribute 처럼 객체로 바로 반환은 안되는건가?

    • Spring 형은 다 알수가 있다?
  • 코드예시3(ObjectMapper 제거)

@Data
public class Member {
	private String name;
	private int age;
}

@Controller
public class JsonController {
	
	@PostMapping("/request-body/json")
	public String requestJson(@RequestBody Member member) {
		
		Member requestBodymember = member;

		return "success";
	}
}
  • 위 Controller가 동작하는 이유는 무엇일까?
  • @RequestBody
    • @RequestBody 어노테이션을 사용하면 Object를 Mapping할 수 있다.

    • HttpEntity<Member>, @RequestBody를 사용하면 HTTPMessageConverter
      Request Body의 Data를 개발자가 원하는 String, Object로 변환해준다.

    • JSON to Object의 Mapping까지 해준다.MappingJackson2HttpMessageConverter

  • 쉽게 설명하면 HTTP Message Converter가 ObjectMapper를 대신 실행해준다.
  • 주의 : @RequestBody는 생략할 수 없다
@Data
public class Member {
	private String name;
	private int age;
}

@Slf4j
@Controller
public class JsonController {
	
	@PostMapping("/request-body/json")
	public String requestJson(@RequestBody Member member) {
		
		Member requestBodymember = member;
		log.info("name={}, age={}", member.getName(), member.getAge());

		return "success";
	}
}
  • member.getName() = null

  • member.getAge() = 0

  • 값이 비어있는 형태(아무것도 하지않음)가 되버린다.

  • 생략하면 @ModelAttribute가 되버리기 때문! 위에 기억해 달라고 했습니다!

    • 요청 파라미터를 처리하도록 설정된것!
  • 꼭 Request Header의 contentType은 꼭 application/json 이여야한다

    • 이 설정 정보를 기반으로 MessageConverter가 실행된다
  • 코드예시4(HttpEntity)

@Data
public class Member {
	private String name;
	private int age;
}

@Controller
public class JsonController {
	
	@PostMapping("/request-body/json")
	public String requestJson(
		HttpEntity<Member> httpEntity
	) {
		// 값을 꺼내서 사용해야한다!
		Member member = httpEntity; // .getBody();
		
		return "success";
	}
}
  • HttpEntity<Member> Generic Type으로 Member가 지정되어 있기때문에
    해당 Class 로 반환된다.

  • 코드예시5(@ResponseBody)

@Data
public class Member {
	private String name;
	private int age;
}

@Controller
public class JsonController {
	
	@ResponseBody // 여기
	@PostMapping("/request-body/json")
	public String requestJson(@RequestBody Member member) {
		
		return member; // 여기
	}
}
  • @RequestBody 는 생략이 불가능하다. → @ModelAttribute가 적용되기 때문

  • HttpMessageConverter를 사용한다.

    • MappingJackson2HttpMessageConverter(application/json) 적용 Json → Object
  • @ResponseBody

    • View를 조회하지 않고 Response Body에 Data를 입력해서 직접 반환한다.
    • 요청 뿐만이 아니라 응답에도 HttpMessageConverter가 동작한다.
      • MaapingJackson2HttpMessageConverter 적용
    • HttpEntity를 사용해도 된다.
💡 **컨버터란?** 어떤 뭔가를 다른 뭔가로 바꿔주는(Convert) 장치를 말한다. 예를 들면 오디오 CD를 [MP3](https://namu.wiki/w/MP3) 형식의 음원 파일로 변환해주는 프로그램도 컨버터이고, 전력의 전압, 전류, 주파수를 전환해주는 기계도 컨버터, 태양빛을 전기로 바꿔주는 장치 또한 컨버터이다. 하지만 컨버터라는 용어는 너무나 광범위하기 때문에 각각의 경우에 특화된 이름을 따로 부여하는 것이 일반적이다. HttpMessageConverter의 역할 ![](https://velog.velcdn.com/images/maltize92/post/acbf5229-b187-4c7f-86a8-0e5e082c745c/image.png) - `@RequestBody` - HTTP Request Body(JSON Data) → Converter(Jackson) → Object(Member) - Reqeust Header → Content-Type : application/json(전달할 데이터 형식) - `@ResponseBody` - Object(Member) → Converter(Jackson) → HTTP Response Body(JSON Data) - Request Header → Accept : application/json(허용할 데이터 형식) > ### HTTP Response 💡 Spring에는 Response Data를 서버에서 Client에 전달하는 다양한 방법이 있다.

Spring(Server)에서 HTTP 응답을 클라이언트에 전달하는 세가지 방법

  1. 정적 리소스

    1. ex) 정적인 HTML, CSS, JS → 파일의 변경없이 그대로 반환
    2. 참고 :
    3. /static
    4. /public
    5. /META-INF/resources
    6. src/main/resources

    위 4가지 경로는 Spring Boot에서 정적 resource를 보관하는 경로들이다.

    • Spring Boot Application Directory 구조
  • src/main/resources/static/hello/world.html 디렉토리 구조라면
    • http://localhost:8080/hello/world.html URL 로 리소스에 접근이 가능하다.
  • /static 대신 /public 혹은 /META-INF/resources 도 사용가능
  1. View Template(Server Side Rendering)

    💡 Model은 View Template을 거쳐서 HTML 등이 동적으로 만들어지고 Client에 응답한다. Spring Boot는 기본적으로 View Template 경로를 제공한다. ex) `src/main/resources/templates`
    • ex) 동적인 HTML을 제공하는 JSP, Thymeleaf 등
    • 스파르타 강의를 통해 Thymeleaf를 사용해 보았으니 예시는 생략
    • build.gradle에 Thymeleaf를 추가하면
      ThymeleafViewResolver와 필요한 Spring Bean들이 등록된다.
      - 기본 설정 예시(아래 내용을 자동으로 Spring Boot가 등록해준다.)
spring.thymeleaf.prefix=classpath:/templates/ 
spring.thymeleaf.suffix=.html // or jsp
  • Controller의 응답으로 String을 반환하는 경우
    • @ResponseBody 가 없으면 View Resolver가 실행되며 View를 찾고 Rendering한다.
    • @ResponseBody 가 있으면 HTTP Message Body에 return 문자열 값이 입력됨.
@RequestMapping("/response/view")
public String responseView(Model model) {
	// model.addAttribute("{viewTemplate에서 사용할 key값}", "{Value값}")
	return "string";
}
  • Return Type이 void인 경우 (잘 사용하지 않음)
    • @Controller를 사용 +
      @ResponseBody, HttpServletResponse, OutputStream과 같은
      HTTP Message Body를 처리하는 파라미터가 없으면
      RequestMapping URL을 참고하여 View Name으로 사용한다.
// /response/view.html 과 Mapping됨
@RequestMapping("/response/view")
public void responseView(Model model) {
	model.addAttribute("{viewTemplate에서 사용할 key값}", "{Value값}")
}
  • 예시와 같은 경우에는 viewTemplate(viewName)을 RequestMapping URL 주소로 찾는다.
  1. HTTP Message Body

    💡 HTTP API(Rest API)를 만드는 경우에는 HTML이 아닌 Data를 전달한다. → HTTP Message Body에 Data를 JSON 형식으로 담아서 통신한다
    • 참고 : 정적 HTML, View Template 또한 HTTP Message Body에 담겨서 전달된다.
      현재 설명하는 Response의 경우는 정적 HTML, View Template을 거치지 않고
      직접 HTTP Response Message를 만들어 전달하는 경우를 말하는것.

    • 예시코드1 (HttpServletResponse)

@Controller
public class ResponseBodyController {
	
	@GetMapping("/response/body")
	public void responseBody(
														HttpServletResponse response
													) throws IOException {
		
		response.getWriter().write("data");
	
	}
}
  • Response Body에 data 라는 문자열이 입력된채 응답한다.

  • 기존 Servlet을 다룰 때 코드와 형태가 같다.

  • 예시코드2(ResponseEntity<>)

@Controller
public class ResponseBodyController {
	
	@GetMapping("/response/body")
	public ResponseEntity<String> responseBody() {
		
		return new ResponseEntity<>("data", HttpStatus.OK);
	}
}
  • Response Body에 data라는 문자열과 HttpStatus.OK에 해당하는 상태코드를 반환한다.

  • ResponseEntityHttpEntity 를 상속받았다.

    • HttpEntity는 HTTP Message의 Header, Body 모두 가지고있다.
  • 예시코드3(@ResponseBody- TEXT, JSON)

// TEXT 데이터 통신
@Controller
public class ResponseBodyController {
	
	@ResponseBody
	@GetMapping("/response/body")
	public String responseBody() {
		
		return "data"; // HTTP Message Body에 "data"
	}
}


// JSON 데이터 통신
@Controller
public class ResponseBodyController {
	
	@ResponseBody
	@GetMapping("/response/body")
	public Data responseBody() {
		
		Data data = new Data("field1", "field2");
		
		return data; // HTTP Message Body에 JSON Member Object -> JSON
	}
}
  • View를 사용하는것이 아닌 HTTP Message Converter를 통해
    HTTP Message Body를 직접 입력할 수 있다. → ResponseEntity와 같음
  • @ResponseStatus 를 사용하여 상태코드를 지정할 수 있다.
@Controller
public class ResponseBodyController {
	
	@ResponseStatus(HttpStatus.OK)
	@ResponseBody
	@GetMapping("/response/body")
	public Data responseBody() {
		
		Data data = new Data("field1", "field2");		
		
		return data;
	}
}
  • 단, 응답 코드를 조건에 따라서 동적으로 변경할 수는 없다.
    ex) 1번의 경우 OK, 2번의 경우 Created

  • 예시코드4(ResponseEntity - JSON)

@ResponseBody
@Controller
public class ResponseBodyController {
	

	@GetMapping("/response/body")
	public ResponseEntity<Data> responseBody() {
		
		Data data = new Data("field1", "field2");

		return new ResponseEntity<>(Data, HttpStatus.OK);
	}
}
  • 두번째 파라미터에 Enum을 사용하여 상태코드를 원하는대로 바꿀 수 있다.
  • HTTP Message Converter를 통하여 JSON 형태로 변환되어 반환된다.
  • 동적으로 응답 코드를 변경할 수 있다.
  • HttpEntity를 상속받았다.

@RestController

💡 `@ResponseBody`가 아닌 `@RestController`를 사용할 수 있는 이유.
  1. @Controller 대신에 @RestController를 사용하면
    해당 Controller에 모두 메서드 단위에 @ResponseBody가 적용된다
  2. 즉, @RestController를 사용하는것은 View Template을 사용하는것이 아니다.
  3. HTTP Message Body에 HTTP Message Converter를 통해 Data를 직접 입력하는것이다.
  4. Rest API(HTTP API)를 만들 때 사용하는것이 바로 @RestController이다.
  • @ResponseBody 내부구조

  • @Target : @Target이 선언된 하위 어노테이션이 어떤 범위에 적용되는지 설정하는 어노테이션

  • ElementType Enum

    • 위 어노테이션들 덕분에 Controller 메소드 단위에 @ResponseBody를 직접 적어주지 않는다

정리


Client에서 Server로 Data를 전달하는 세가지 방법

  1. GET - Query Param ex) url?name=wonuk&age=100
  2. POST - HTML Form(x-www-form-urlencoded)
  3. HTTP Request Body - 데이터(JSON, TEXT, XML 등)를 직접 담아서 사용

Spring(Server)에서 HTTP 응답을 Client에 전달하는 세가지 방법

  1. 정적 리소스
  2. View Template
  3. HTTP Message Body

오늘의 코드카타

class Solution {

    public String solution(String rsp) {
        String answer = "";
        String[] arr = rsp.split("");

        for (int i = 0; i < arr.length; i++) {
            if (arr[i].equals("2")) {
                answer += "0";
            } else if (arr[i].equals("0")) {
                answer += "5";
            } else {
                answer += "2";
            }
        }
        return answer;
    }
}
public class Main {

    public static void main(String[] args) {

        Solution solution = new Solution();

        System.out.println(solution.solution("2"));
        System.out.println(solution.solution("205"));
    }
}

가위 바위 보

profile
새로운 시작~!

0개의 댓글