스프링 부트 핵심 가이드 - Controller

이건희·2024년 2월 27일
0

오늘 작성해 볼 것은 Controller에 대한 이야기이다. Controller는 클라이언트로부터 요청이 오면 해당 요청을 받고 Service 계층에서 처리 후, 다시 응답을 반환하는 계층이다.

이미 프로젝트를 하면서 요청을 받고 응답을 보내는 방법들을 알고 있지만 이번 기회에 정확하게 정리해 보려고 한다.

@RestController와 @RequestMapping

@RestController

보통은 Controller 계층의 클래스에 @Controller를 붙여 작성한다. 그럼 @RestController는 무엇일까?

@RestController는 @ResponseBody + @Controller 라고 생각하면 될 것 같다. @Controller는 ViewResolver가 작동해 뷰를 반환하지만 @RestController는 클라이언트 측에 데이터를 전송할 때 사용하므로 MessageConverter가 작동한다.

@RequestMapping

@RequestMapping 어노테이션을 별 설정 없이 선언하면 HTTP의 모든 요청을 받는다.

하지만 클래스에 다음과 같이 설정하면 클래스에 작성하는 메서드 앞에 자동적으로 해당 URL이 붙는다.

@RestController
@RequestMapping("/api/v1/get-api")
public class GetController {
	
    //http://localhost:8080/api/v1/get-api/hello
    @RequestMapping(value = "hello", method = RequestMethod.GET)
    public String getHello() {
    	return "Hello";
    }
}

근데 사실 대부분에 상황에서는 이렇게 작성하진 않고 아래와 같이 작성한다.

@RestController
@RequestMapping("/api/v1/get-api")
public class GetController {

	//http://localhost:8080/api/v1/get-api/hello
    @GetMapping("hello")
    public String getHello() {
    	return "Hello";
    }
}

URI와 URL의 차이

URL은 우리가 흔히 말하는 웹 주소를 의미하며 리소스가 어디에 있는지 알려주기 위한 경로를 의미한다.

URI는 특정 리소스를 식별할 수 있는 식별자를 의미한다.

웹에서는 URL을 통해 리소스가 어느 서버에 위치해 있는지 알 수 있으며, 그 서버에 접근해서 리소스를 접근하기 위해서는 대부분 URI가 필요하다.


@GetMapping

HTTP Method 중 GET은 서버에 있는 자원을 요청할 때 사용한다. 이때 중요한 것은 GET은 Body가 없지만 여러가지 파라미터를 전송할 수 있다.

여러가지 파라미터는 URL에 작성하여 전송하게 되는데, 크게 2가지 방법이 있다.

@PathVariable을 활용

아래 예시 코드를 보자.

//http://localhost:8080/api/v1/get-api/variable1/{String값}
@GetMapping("/variable1/{variable}")
public String getVariable(@PathVariable String variable) {
	return variable;
}

위와 같은 방식으로 URL에 직접 값을 넣어서 전송할 수 있다. localhost:8080/api/v1/get-api/variable1/hello 와 같은 형태로 전송한다면 메서드의 variable에는 hello라는 값이 들어가게 된다.

하지만 위와 같이 작성할 땐 지켜야 할 규칙이 있다.

  1. @GetMapping 어노테이션의 값으로 URL을 입력할 때 중괄호를 사용해 어느 위치에서 값을 받을지 정해야 한다.

  2. 메서드 파라미터를 받는 위치에 @PathVariable을 명시하며 @GetMapping 어노테이션과 @PathVariable에 저장된 변수의 이름을 동일하게 맞춰야 한다.

만약 @GetMapping 어노테이션에 지정한 변수의 이름과 @PathVariable 변수 이름을 동일하게 맞추기 어렵다면 아래와 같이 @PathVariable 뒤에 괄호를 열어 @GetMapping 어노테이션의 변수명을 지정해야 한다.

@GetMapping(value = "/variable2/{variable}")
public String getVariable2(@PathVariable("variable") String var) {
	return var;
}

@RequestParam을 활용

@PathVariable과 같이 직접적으로 값을 담을 수도 있지만 쿼리 파라미터 형식으로 값을 담을 수도 있다.

쿼리 파라미터란 '?'을 기준으로 우측에 {키}={값} 형태로 구성된 요청이다. 이러한 쿼리 파라미터를 활용한 요청에서 @RequestParam을 사용한다.

@GetMapping("/request1")
public String getReqeustParam1(
	@RequestParam String name,
    @RequestParam String email,
    @RequestParam String organization) {
    
    return name + " " + email + " " + organization;
}

위와 같이 @RequestParm이 지정된 변수는 쿼리 파라미터에서 Key에 해당한다. 따라서 위 코드의 요청 예시는 아래와 같다.

localhost:8080/api/v1/get-api/request1?name=value1&email=value2&organization=value3

기존 URL 뒤에 '?'로 시작하여 key=value 형태로 작성하고, 여러 개가 있을 땐 '&'로 연결한다.

또한 @RequestParam 뒤에 적는 이름을 동일하게 설정하기 어렵다면 @PathVariable처럼 value 요소로 매핑하면 된다.

쿼리스트링에 값을 특정할 수 없을 때

만약에 쿼리스트링에 어떤 값이 들어올지 모른다면 아래와 같이 Map 객체를 활용할 수 있다.

@GetMapping(value = "/request2")
public String getRequestParam2(@RequestParam Map<String, String> param) {
	StringBuilder sb = new StringBuilder();
    
    param.entrySet().forEach(map -> {
    	sb.append(map.getKey() + " : " + map.getValue() + "\n");
    });
    
    return sb.toString();
}

위와 같이 Map 객체를 활용하면 값에 상관없이 요청을 받을 수 있다.

DTO 객체를 활용한 GET 메서드 구현

DTO란 Data Transfer Object의 약자로 다른 레이어 간 데이터 교환에 활용된다. 쉽게 설명하면 각 클래스 및 인터페이스를 호출하면서 전달하는 매개변수로 사용되는 데이터 객체이다.

DTO는 데이터를 교환하는 요도로만 사용되는 객체이기 때문에 별도의 로직이 포함되지 않는다.

MemberDto

@Getter
@Setter
@ToString
public class MemberDto {
	
    private String name;
    private String email;
    private String organization;
}

DTO 객체는 POST 요청에서 객체로 받기 위해 사용하는 건줄 알았는데 GET 요청에서도 사용할 수 있다는 걸 처음 알았다.

이렇게 선언하면 DTO 클래스에 선언된 필드는 컨트롤러의 메서드에서 쿼리 파라미터의 키와 매핑된다. 즉, 쿼리스트링의 키가 정해져 있지만 받아야 할 파라미터가 많을 경우에는 DTO 객체를 활용할 수 있다.

@GetMapping("/request3")
public String getRequestparam3(MemberDto memberDto) {
	return memberDto.toString();
}

@PostMapping

POST API는 웹 에플리케이션을 통해 데이터베이스 등의 저장소에 리소스를 저장할 때 사용되는 API이다.

GET API에서는 URL 경로나 파라미터에 변수를 넣어 요청을 보냈지만 POST API에서는 저장하고자 하는 리소스나 값을 HTTP Body에 담아 서버에 전달한다.

@RequestBody를 활용

Body 영역에 작성되는 값은 일정한 형태를 취한다. 일반적으로 JSON 형태로 전송된다.

Map 객체에 Mapping

@PostMapping("/member")
public String postMember(@RequestBody Map<String, Object> postData) {
	StringBuilder ssb = new StringBuilder();
    
    postData.entrySet().forEach(map -> {
    	sb.append(map.getKey() + " : " + map.getvalue() + "\n");
    });
    
    return sb.toString();
}

위에는 @RequestBody라는 어노테이션을 사용하였는데, 이는 HTTP의 Body 내용을 해당 어노테이션이 지정된 객체에 매핑하는 역할을 한다.

Map 객체는 요청을 통해 어떤 값이 들어올지 특정하기 어려울 때 주로 사용한다.

이전까지는 DTO 객체와 같은 객체에 매핑 했는데 Map 형식에도 매핑이 된다는걸 처음 알았다..

DTO 객체에 Mapping

요청 메세지가 들어갈 값이 정해져 있다면 DTO 객체를 매개변수로 삼아 작성할 수 있다.

@PostMapping("/member2")
public String postMemberDto(@RequestBody MemberDto memberDto) {
	return memberDto.toString();
}

@PutMapping

PUT API는 웹 어플리케이션 서버를 통해 데이터베이스 같은 저장소에 존재하는 리소스 값을 업데이트 하는데 사용한다.

기본적으로 요청하는 방삭은 POST와 거의 동일하기 때문에 응답을 어떻게 구성하는지 살펴보겠다.(응답도 POST와 거의 동일하다)

ResponseEntity를 활용

스프링 프레임워크에는 HttpEntity라는 클래스가 있다. HttpEntity는 Header와 Body로 구성된 HTTP 요청과 응답을 구성하는 역할을 수행한다.

HttpEntity

public class HttpEntity<T> {

	private final HttpHeaders headers;
    
    @Nullable
    private final T body;
    
    
    ...
}

RequestEntity와 ResponseEntity는 HttpEntity를 상속받아 구현한 클래스이다. 그중 ResponseEntity는 서버에 들어온 요청에 대해 응답 데이터를 구성해서 전달할 수 있게 한다.

아래와 같이 ResponseEntity는 HttpEntity로부터 HttpHeaders와 Body를 가지고 자체적으로 HttpStatus를 구현한다.

ResponseEntity

public class ResponseEntity<T> extends HttpEntity<T> {
	
    private final Object status;
    
    ...
}

위 클래스를 활용하면 응답 코드 변경, Header와 Body를 쉽게 구성할 수 있다. 이는 PUT 이외에도 다른 메서드들에서도 모두 사용 가능하다.

ResponseEntity를 활용한 PUT 구현

@PutMapping("/member3")
public ResponseEntity<MemberDto> postMemberDto3(@RequestBody MemberDto memberDto) {
	return ResponseEntity
    	.status(HttpStatus.ACCEPTED)
        .body(memberDto);
}

@DeleteMapping

DELETE API는 웹 어플리케이션 서버를 거쳐 데이터베이스 등의 저장소에 있는 리소스를 삭제할 때 사용한다. 컨트롤러를 통해 값을 받는 단계에서는 간단한 값을 받기 때문에 GET 메서드와 같이 URI에 값을 넣어 요청을 받는 형식으로 구현된다.

방식은 GET과 거의 유사하기 때문에 생략하겠다.

profile
백엔드 개발자가 되겠어요

0개의 댓글