728x90

 

웹 서비스를 만들기 위해서는 클라이언트와 서버 간에 데이터를 주고받을 필요가 있습니다.

그리고 REST API같은 HTTP API를 제작하기 위해서도 데이터의 처리방법에 대해 알 필요가 있습니다.

이전에 스프링을 사용하지 않고 이클립스 IDE만 사용해서 컨트롤러를 만들어 봤었습니다.

이때에는 매번 메서드에 HttpServletRequest를 매개변수로 사용하고 .getParameter()를 사용해

데이터를 처리하느라 좀 귀찮은 점이 있었습니다. 하지만 스프링을 사용하면 HTTP 요청과 응답 데이터를

쉽게 처리할 수 있습니다. 

 

우선 요청 데이터를 처리하는 것에 대해 정리하고 응답 데이터 처리에 대해 정리해보겠습니다.

응답 데이터 처리는 요청 데이터 처리와 중복되는 것이 많기 때문에 간략하게 해보겠습니다.

 

HTTP 요청 데이터 조회

 

HTTP 요청 메시지를 통해 클라이언트에서 서버로 데이터를 전달하는 방법은 주로 세 가지 방법을 사용합니다.

  • GET - 쿼리 파라미터
  • POST - HTML Form
  • HTTP message body

GET과 POST의 HTML Form 데이터를 처리하는 방법은 같습니다.

그전에 GET과 POST의 데이터 전달 방법에 대해 먼저 알아보겠습니다.

일단 GET의 데이터는 메시지 바디 없이 URL의 ?뒤에 Key=Value의 형식으로 전달합니다.

ex) localhost:8080/user?username=kim&userage=22

POST의  Form 데이터는 메시지 바디로 전달하지만 형식은 똑같이 쿼리 파라미터 형식으로 전달합니다.

username=kim&userage=22

이때 HTTP 헤더의 content-type은 application/x-www-form-urlencoded입니다.

 

이렇게 쿼리 파라미터 형식으로 전달하는 두 데이터를 처리하는 방법에는 크게 세 가지가 있습니다.

앞서 이야기했던 HttpServletRequest의 .getParameter를 사용하는 방법과

@RequestParam을 사용하는 방법 그리고 @ModelAttribute를 사용하는 방법이 있습니다.

 

@RequestParam

 

해당 어노테이션은 해당 어노테이션에 값으로 설정한 데이터가 들어오면 매핑해주는 역할을 합니다.

그리고 파라미터의 이름과 데이터의 Key값이 동일하면 어노테이션을 생략할 수 있습니다.

// URL -> http://localhost:8080/request-param?username=kim&age=24

// 1번 방법
@RequestMapping("/request-param")
public String requestParamV2(
	@RequestParam("username") String memberName,
	@RequestParam("age") int memberAge){...}

// 2번 방법 어노테이션 안의 값 생략
@RequestMapping("/request-param")
public String requestParamV2(
	@RequestParam String username,
 	@RequestParam int age){...}

// 3번 방법 어노테이션 자체를 생략
@RequestMapping("/request-param")
public String requestParamV2(String username,int age){...}

 

추가적으로 @RequestParam는 required 속성을 가지고 있습니다.

디폴트 값이 true인데 이럴 경우 해당하는 파라미터의 요청 데이터가 넘어오지 않으면 400 에러를 반환합니다.

하지만 ""처럼 빈 값을 넘겨도 아무런 에러를 반환하지 않습니다.

그리고 기본형 타입의 파라미터에 required=false를 설정했을 때는 값이 넘어오지 않으면 null값을 주는 것이 불가능하여

500 에러를 반환하기 때문에 이를 처리하는 것이 필요합니다.

이때에는 다른 속성인 defaultValue나 래퍼클래스를 사용하면 됩니다.

// required 속성 사용
@RequestMapping
public String requestParamDefault(
        @RequestParam(required = true) String username,
        @RequestParam(required = false) int age) {...} 
        // age 해당하는 값이 안넘어 올 경우 500에러를 반환한다.

// defaultValue 속성 사용
@RequestMapping
public String requestParamDefault(
		@RequestParam(defaultValue = "guest") String username,
		@RequestParam(defaultValue = "-1") int age){...}
        
// 래퍼클래스 사용
@RequestMapping
public String requestParamDefault(
		@RequestParam String username,
		@RequestParam(required = false) Integer age){...}
        // 래퍼클래스를 사용해 null값 오류를 해결할 수 있다.

 

@ModelAttribute

 

스프링은 객체에 요청 파라미터를 바인딩하는 기능도 가지고 있습니다.

이게 @ModelAttribute의 기능입니다. 해당 어노테이션도 생략이 가능합니다.

@RequestMapping
public String modelAttributeV1(@ModelAttribute HelloData helloData) {...}


// @ModelAttribute 생략
@RequestMapping
public String modelAttributeV1(HelloData helloData) {...}

위와 같이 작성 가능합니다.

스프링MVC는 @ModelAttribute가 있으면 다음을 실행합니다.

  • HelloData 객체를 생성합니다.
  • 요청 파라미터의 이름으로 HelloData 객체의 프로퍼티를 찾습니다.
  • 해당 프로퍼티의 setter를 호출해 파라미터의 값을 바인딩합니다.

때문에 객체로 사용할 클래스에는 setter 메서드를 만들어 둬야 합니다.

 

앞서 이야기한 @RequestParam과 @ModelAttribute는 둘 다 생략할 수 있습니다.

때문에 스프링은 생략 시 다음과 같은 규칙을 적용합니다.

  • String, int, Integer와 같은 단순 타입은  @RequestParam을 사용합니다.
  • argument resolver로 지정해둔 타입을 제외한 나머지는 @ModelAttribute 사용합니다.

 

HTTP Message Body

 

다음은 HTTP 메시지 바디에 담긴 단순 텍스트 데이터를 처리하는 방법입니다.

HTTP API에 주로 사용되고 JSON 형식이나 XML 형식을 주로 사용합니다.

 

이렇게 메시지 바디로 넘어오는 데이터는 앞서 이야기한 @RequestParam과 @ModelAttribute를 사용할 수 없습니다.

HTML form으로 넘어오는 데이터를 제외하고요.

요청 메시지 바디로 넘어오는 데이터를 조회하는 방법은 아래와 같습니다. 

  • InputStream
  • HttpEntity
  • RequestEntity - HttpEntity를 상속받은 클래스로 추가 기능을 제공합니다.
  • @RequestBody

 

이중에서는 @RequestBody를 사용하는 게 가장 편해 보였습니다.

스트림은 바이트 타입으로 넘어오기 때문에 다시 한번 타입에 맞춰 변경해주는 작업이 필요합니다.

그리고 HttpEntity나 @RequestBody는 HTTP 메시지 컨버터를 통해 바디의 내용을 문자나 객체 등으로 변환시켜줍니다.

하지만 HttpEntity는 getBody()로 한 번 더 꺼내는 과정이 필요합니다. 

// @RequestBody
@PostMapping("/request-body-json")
public HelloData requestBodyJsonV5(@RequestBody HelloData data) {...}

// HttpEntity
@PostMapping("/request-body-json")
public String requestBodyJsonV4(HttpEntity<HelloData> httpEntity) {...}

 

HTTP 응답

 

스프링에서 응답 데이터를 만드는 방법은 크게 세 가지입니다.

  • 정적 리소스
  • 뷰 템플릿 사용
  • HTTP 메시지 사용

 

 

HTTP 메시지 사용

 

HTTP API나 비동기 통신을 통해 데이터의 특정 값만 제공해야 하는 경우에는

HTTP 바디에 직접 데이터를 실어서 보낼 필요가 있습니다.

 

만약 컨트롤러의 메서드가 전부 특정 데이터만 전달해야 하는 경우에는 클래스에 

@RestController나 @ResponseBody 어노테이션을 달아 사용할 수 있습니다. 

@RestController는 @Controller와 @ResponseBody 합쳐진 것이기 때문에 둘 중 무엇을 사용하든 상관없습니다.

 

스프링의 경우 String으로 반환하면 뷰 리졸버가 실행되어서 뷰를 찾고 렌더링 합니다.

하지만 @ResponseBody가 달린 클래스나 메서드는 반환 값을 HTTP 응답 메시지 바디에 직접 실어 보냅니다.

그리고 해당 어노테이션을 쓰지 않고 HttpEntity나 ResponseEntity를 사용해서 응답할 수도 있습니다.

ResponseEntity를 사용할 경우 HTTP 응답 코드를 설정할 수 있는데 @ResponseBody는 따로 설정할 수 없습니다.

하지만 @ResponseStatus를 사용한다면 @ResponseBody로 반환하는 메서드도 응답 코드를 설정할 수 있습니다.

 

// @ResponseBody와 @ResponseStatus 사용
@ResponseStatus(HttpStatus.OK)
@ResponseBody
@GetMapping("/response-body-json")
public HelloData responseBodyJson() {
    HelloData helloData = new HelloData();
    helloData.setUsername("userA");
    helloData.setAge(20);
    
    return helloData;
}

// ResponseEntity 사용
@GetMapping("/response-body-json")
public ResponseEntity<HelloData> responseBodyJson() {
    HelloData helloData = new HelloData();
    helloData.setUsername("userA");
    helloData.setAge(20);
    
    return new ResponseEntity<>(helloData, HttpStatus.OK);
}

 

 

참조

HTTP 메시지 컨버터

argument resolver

728x90
728x90

최근에 스터디를 하면서 공부를 하고 있습니다.

그런데 금방 까먹는 듯한 기분이 들어 정리하는 시간을 가지려고 합니다.

이렇게 하면 더 오래 남지 않을까 하는 개인적인 기대가 있습니다.

이번 글은 인프런 김영한 님의 스프링 강의 - 스프링의 기본 기능 내용 정리입니다.

 

로깅(Logging)

제가 프로젝트를 수행할 때는 어떠한 버그가 발생했을 때 이유를 알아보기 위해서 

시스템 콘솔에 데이터를 출력하는 System.out.println()을 사용했습니다.

하지만 실무에서 위의 방법을 사용할 경우에는 로그관리를 할 수 없기 때문에 별도의 로깅 라이브러리를 사용합니다.

스프링 부트는 기본적으로 slf4j라는 라이브러리를 사용하는데,

이는 Logback, Log4j, Log4j2 등 수많은 라이브러리를 통합해서 제공하는 라이브러리입니다.

 

로그의 선언에는 

private Logger log = LoggerFactory.getLogger(getClass());
private static final Logger log = LoggerFactory.getLogger(Xxx.class);
@Slf4j

위와 같은 방법을 사용할 수 있습니다. Logger와 LoggerFactory은 slf4j의 클래스입니다.

그리고 @Slf4j 어노테이션은 Lombok 라이브러리의 어노테이션으로 위의 두줄과 같이 직접적인 선언 없이

로그를 호출할 수 있게 해 줍니다. 이외에도 Lombok은 DTO 클래스에서 setter, getter의 작성을 쉽게 해주기도 합니다.

Lombok에 대한 자세한 내용은 다음에 다뤄보도록 하겠습니다.

 

 호출은 아래와 같이 할 수 있습니다.

String name = "Spring";
 
 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);

로그의 출력은 개발 단계와 운영 단계에 맞춰 각각 출력하는 범위를 정할 수 있습니다.

레벨 순은 trace >> debug >> info >> warn >> error 순으로 디폴트 값은 info입니다.

그래서 출력을 info로 설정하면 하위 레벨인 debug와 trace는 출력되지 않습니다.

 

log.debug("String concat log=" + name);

그리고 로그의 출력을 위와 같이 + 연산자를 사용해서 출력할 수도 있습니다.

하지만 위와 같은 경우는 리소스를 낭비하여 사용하지 않아야 합니다.

출력이 info로 설정되어 있을 경우 debug는 대상이 아니라 그냥 넘어가야 하지만

그 작업이 이뤄지기 이전에 + 연산자의 로직이 실행돼 불필요한 자원을 소모하기 때문입니다.

 

로그 사용의 장점

  • 스레드 정보, 클래스 이름 같은 부가 정보를 함께 볼 수 있고, 출력 모양을 조정할 수 있습니다.
  • 로그를 상황에 맞게 레벨을 조절하여 출력할 수 있습니다.
  • 파일이나 네트워크 등, 로그를 별도의 위치에 남길 수 있습니다.
  • 파일로 남길 때에는 특정 기준에 맞춰 로그를 분할할 수 있습니다. 

 

매핑(Mapping)

스프링에서는 @Controller 어노테이션을 사용해 컨트롤러를 찾을 수 있습니다.

그리고 @RequestMapping 어노테이션을 사용해 해당하는 URL 호출이 오면 메서드가 실행되도록 매핑할 수 있습니다.

여기서는 @RequestMapping의 여러 매핑 방법에 대해 정리해보도록 하겠습니다.

 

@RequestMapping 방법 

  • HTTP 메서드 매핑
  • PathVariable 매핑
  • 특정 파라미터 조건 매핑
  • 특정 헤더 조건 매핑
  • 미디어 타입 조건 매핑

 

HTTP 메서드 매핑

 

HTTP 메서드 매핑은 @RequestMapping의 method 속성으로 매핑을 하는 방법입니다.

기본적으로 GET, POST, PUT, PATCH 등 모든 메서드에 대해 허용하게 됩니다.

method 속성을 사용하면 이를 특정 메서드의 요청에만 응답하도록 설정할 수 있습니다.

@RequestMapping(value = "/mapping", method = RequestMethod.GET)

위 URL에 POST 요청을 보내고 POST 요청에 해당하는 매핑이 없을 경우 

스프링 MVC는 HTTP 405(Method Not Allowed) 코드를 반환합니다.

덧붙여 스프링 부트는 해당 메서드 매핑의 축약형 어노테이션을 제공합니다.

@GetMapping(value="/mapping")
@PostMapping(value="/mapping")
@PutMapping(value="/mapping")

 

 

PathVariable 매핑

 

HTTP 리소스 경로에 식별자를 넣는 방법입니다.

@GetMapping("/mapping/{userId}")
public String mappingPath(@PathVariable("userId") String data) {}

@GetMapping("/mapping/{userId}")
public String mappingPath(@PathVariable String userId) {}

위와 같이 사용할 수 있으며, 경로 변수와 파라미터의 이름이 같다면 @PathVariable의 이름을 생략할 수 있습니다.

 

특정 파라미터 조건 매핑

 

/**
 * 파라미터로 추가 매핑
 * params="mode",
 * params="!mode"
 * params="mode=debug"
 * params="mode!=debug" (! = )
 * params = {"mode=debug","data=good"}
 */
@GetMapping(value = "/mapping-param", params = "mode=debug")

특정 파라미터가 있거나 없는 요청에 대해서 매핑하는 방법입니다.

하지만 잘 사용되지는 않는다고 합니다.

 

 

특정 헤더 조건 매핑

 

/**
 * 특정 헤더로 추가 매핑
 * headers="mode",
 * headers="!mode"
 * headers="mode=debug"
 * headers="mode!=debug" (! = )
 */
@GetMapping(value = "/mapping-header", headers = "mode=debug")

파라미터 매핑과 비슷하지만 HTTP 헤더를 이용합니다.

 

 

미디어 타입 조건 매핑

 

미디어 타입 조건 매핑에는 HTTP 요청의 Content-Type과 Accept 해당하는 매핑이 있습니다.

Content-Type은 클라이언트가 서버에게 전송하는 데이터의 원래 미디어 유형을 말합니다.

Accept는 클라이언트가 이해할 수 있는 미디어 유형의 HTTP 요청 헤더입니다. 

 

특정 Content-Type 요청에는 consumes 속성을 사용하고, Accept 요청에는 produces 속성을 사용합니다.

제가 이 속성들을 사용할 때에는 주로 jQuery로 Ajax 통신을 할 때 사용했습니다.

특정한 값으로 전달받거나 전달하기 위해서 그렇습니다.

 

Content-Type

/**
 * Content-Type 헤더 기반 추가 매핑 Media Type
 * consumes="application/json"
 * consumes="!application/json"
 * consumes="application/*"
 * consumes="*\/*"
 * MediaType.APPLICATION_JSON_VALUE
 */
@PostMapping(value = "/mapping-consume", consumes = "application/json")

/**
 *
 * consumes 예시
 */
consumes = "text/plain"
consumes = {"text/plain", "application/*"}
consumes = MediaType.TEXT_PLAIN_VALUE

맞지 않으면 HTTP 415(Unsupported Media Type) 상태 코드를 반환합니다.

 

Accept

/**
 * Accept 헤더 기반 Media Type
 * produces = "text/html"
 * produces = "!text/html"
 * produces = "text/*"
 * produces = "*\/*"
 */
@PostMapping(value = "/mapping-produce", produces = "text/html")

/**
 *
 * produces 예시
 */
produces = "text/plain"
produces = {"text/plain", "application/*"}
produces = MediaType.TEXT_PLAIN_VALUE
produces = "text/plain;charset=UTF-8"

맞지 않으면 HTTP 406(Not Acceptable) 상태코드를 반환합니다.

728x90

+ Recent posts