개요

2020년 3월 25일날 작성하는 글

해당 글을 작성한지 어언 2년이 지났다. 그 사이에 많은 분들이 @RequestMapping 을 검색해서 들어오고 있기 때문에 정리가 필요하다고 생각해서 작성하는 글.

 

소개

내가 처음 스프링을 접했을 시에는 컨트롤러가 뷰만 리턴하는 그런 존재로만 알고 있었지만 시간이 지남에 따라서 뷰리졸버의 역할도 하지만 근래들에 프론트와 백엔드의 분리로 api 역할의 비중도 커졌다. 그로 인해서 단순히 서로 다른 디바이스 간의 통신을 위해서 데이터 전달의 주고받음이 필요한데 이에 대한 통신규격으로 요즘은 json 을 많이 사용한다. 각설하고 진행.

 

사용

내가 첫 스프링을 접했을 당시에는 .jsp 페이지를 반환하는 역할로 컨트롤러를 자주 이용했다.

@Controller
public class DoubleController {
    
    @RequestMapping("/apple")
    private String showAppleView(){
        return "apple";
    }

    @RequestMapping("/banana")
    private String showBananaView(){
        return "banana";
    }
    
    @RequestMapping("/grape")
    private String showGrapeView(){
        return "grape";
    }   
}

// 아래는 JSP 페이지의 내용
<p><a href="./apple">apple view 이동</a>
<p><a href="./banana">banana view 이동</a></p>
<p><a href="./grape">grape view 이동</a></p>

컨트롤러 애노테이션을 이용하고, 내부 메소드에 @RequestMapping("~~") 를 붙여 해당 URL 로 클라이언트의 요청이 들어오면 이후에 .jsp 페이지를 반환한다.

 

@PathVarible

url path 로 들어오는 값을 해석 그대로 변수로 사용하겠다는 의미이다.

@Controller
@RequestMapping("v3")
public class FruitController {
    
    @ResponseBody
    @RequestMapping(value = "fruit/{name}", method = RequestMethod.GET)
    public String getFruitName(@PathVariable("name") String name){
        return name;
    }
}

위의 내용을 살펴보면, fruit/{name} 이 있다. 이는 fruit 하위의 패스까지 해당 RequestMapping 에서 받겠다는 의미이다.

  • fruit/apple
  • fruit/banana
  • fruit/grape 
  • fruit/{name}

또한 상단 FruitController 를 보면, @RequestMapping("v3") 으로 되어있는걸 확인할 수 있는데 이는 url path 가 계층적으로 동작할 수 있음을 말해준다. 예를 들어 아래와 같이 이용할 수 있는 것이다.

  • v3/fruit/apple
  • v3/fruit/banana
  • v3/fruit/grape 
  • v3/fruit/{name}

여기서 만약에 PathVariable 로 받는 타입이 enum 이라면, 아래와 같나면 어떻게 하는것이 좋을까? 이럴때 클라이언트 요청에 대한 url path 를 가지고 컨버팅이 필요한데 여기 관련링크를 참고하자.

@Controller
@RequestMapping("v3")
public class FruitController {

    @ResponseBody
    @RequestMapping(value = "fruit/{name}", method = RequestMethod.GET)
    public String getFruitName(@PathVariable("name") Type type){
        return type.name();
    }

    enum Type{
        APPLE, BANANA, GRAPE
    }
}

사용시 유의점.

  • @PathVariable 을 사용한다는 것은 URL 값을 변수명으로 사용한다는 것이다. 따라서 해당 특정 경로로 진입을 수행한 뒤 @PathVariable 을 이용해야 한다는 것이다. 
    • @RequestMapping(value = "/{id}") ( X )
    • @RequestMapping(value = "/user/{id}") : user 도메인의 특정 id 를 기준으로 작업을 수행하겠다는 의미
    • @RequestMapping(value = "/post/{id}") : post 도메인의 특정 id 를 기준으로 작업을 수행하겠다는 의미

이렇게 쓰는 이유는 하나의 서비스가 비대해지면서 패키지 구성을 Domain 기준으로 나누게 되었을 때 계층적으로 분리할 수 있다는 장점이 있다. 분류 및 url 구분의 효율성이 좋기 때문이다.

 

+) @PathVariable 을 Map 으로 사용해보자.

PathVariable 을 받는 값을 단순 변수로서 컨트롤러에서 받지 않고, 맵으로도 형식을 받아서 사용할 수 있다. 예제를 보자.

 

1. 일반적으로 @PathVariable 애노테이션을 사용해서 변수를 받아오는 경우

@GetMapping("/student/{id}/{name}/{age}")
public ResponseEntity<String> createStudent(@PathVariable("id") String id,
                                            @PathVariable("name") String name,
                                            @PathVariable("age") int age) {

    log.info("id : {}, name : {}, age : {}", id, name, age);

    return ResponseEntity.ok().body("student");
}

  • 위의 형식처럼 작성하게 되면, id=1, name=pasudo123, age=25 로 찍히게 된다.

 

2. Map 으로 받아오는 경우

@GetMapping("/opt-student/{id}/{name}/{age}/{gender}")
public ResponseEntity<String> optCreateStudent(@PathVariable final Map<String, Object> variables) {

    variables.entrySet().stream()
            .forEach(entry -> log.info("key : {}, value : {}", entry.getKey(), entry.getValue()));

    return ResponseEntity.ok().body("opt-student");
}

  • 맵 형식의 내용을 앞 단에 @PathVariable 애노테이션을 붙임으로써, 가져올 수 있다. 이러한 경우는 해당 url 에 포함되어야 하는 변수가 많을 경우 사용하면 좋다. (하지만 나는 이렇게 진행해본 적은 없다. 이번에 이 글을 작성하면서 처음 알았기 때문)

@RequestParam

url path 의 query string 을 이용하겠다는 의미이다.

query 스트링이란 localhost:8080/user?name=PARK&age=100&gender=MAN 을 의미한다. 쿼리 스트링은 key/value 값을 가지고 조회 및 획득할 수 있다.

@Controller
@RequestMapping("v1")
public class UserController {

    private final UserService userService;

    public UserController(final UserService userService){
        this.userService = userService;
    }

    @ResponseBody
    @RequestMapping(value = "user/{id}", method = RequestMethod.GET)
    public User findById(@PathVariable("id") int id){
        return userService.findById(id);
    }

    @ResponseBody
    @RequestMapping(value = "/user", method = RequestMethod.POST)
    public User createUser(@RequestParam("name") String name,
                           @RequestParam("age") Integer age,
                           @RequestParam("gender") String gender){
        return userService.create(name,age, gender);
    }
}
위의 코드에서 createUser() 메소드를 살펴보면, @RequestParam 을 통해서 쿼리스트링을 받겠다고 되어있다.
 

@RequestBody & @ResponseBody

요즘에는 클라이언트와 서버의 통신은 서로 다른 디바이스의 형태를 띄고 있다. 그리고 api 형태도 각양각색이고 프론트에서 이용하는 프레임워크도 다양하고 클라이언트 또한 웹브라우저만 있는 것이 아니고 모바일도 있고 end to server 형태로 되어있다.

 

그러기엔 통신하려고 하는 디바이스 간의 규격을 통일시키는게 무엇보다 중요한데, 내가 아는 선에서는 JSON 형식을 많이 이용하는 것으로 알고 있다. 예전에는 XML 도 이용할 수 있다는데, 나는 우선적으로 JSON 으로만 설명하려고 하겠다.

  • @RequestBody
    • http body 를 POJO 로 매핑
  • @ResponseBody
    • POJO 를 http body 로 매핑

앞선 코드를 다시 보자. 
추가적으로 나는 스프링부트를 이용하고 있는데 스프링부트를 이용하면 자동으로 Jackson lib 이라는 의존성이 추가된다. 해당 lib 은 http 요청에 대해서 내부적으로 디스패처 서블릿을 거치는 단계에서 메세지 컨버터 역할을 수행하는데 이 메시지 컨버터가 실질적으로 들어오는 요청에 대해서 json 화를 하고 있다. 

자세한 내용은 여기를 클릭

@Controller
@RequestMapping("v1")
public class UserController {

    private final UserService userService;

    public UserController(final UserService userService){
        this.userService = userService;
    }

    @ResponseBody
    @RequestMapping(value = "user/{id}", method = RequestMethod.GET)
    public User findById(@PathVariable("id") int id){
        return userService.findById(id);
    }

    @ResponseBody
    @RequestMapping(value = "/user", method = RequestMethod.POST)
    public User createUser(@RequestParam("name") String name,
                           @RequestParam("age") Integer age,
                           @RequestParam("gender") String gender){
        return userService.create(name,age, gender);
    }
}
@RestController
@RequestMapping("v2")
public class UserRestController {

    private final UserService userService;

    public UserRestController(final UserService userService){
        this.userService = userService;
    }

    @GetMapping(value = "user/{id}")
    public User findById(@PathVariable("id") int id){
        return userService.findById(id);
    }

    @PostMapping(value = "/user")
    public User createUser(@RequestParam("name") String name,
                           @RequestParam("age") Integer age,
                           @RequestParam("gender") String gender){
        return userService.create(name,age, gender);
    }
}
 

위의 두 Controller 는 동일하다. 다만 애노테이션이 다르다. 스프링의 버전이 올라감에 따라서 새로운 애노테이션이 생기고 좀 더 직관적으로 변했다.

  • @RestController  는 @Controller + @ReponseBody 가 합쳐진 형태이다.
  • @GetMapping 은 @RequestMapping 에 method = RequestMethod 가 합쳐진 형태이다.

@RestController 는 실제 스프링 프로젝트에서 아래와 같이 명시되어있다.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {
	... 생략
}

 

@GetMapping 은 실제 스프링 프로젝트에서 아래와 같이 명시되어있다.

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(method = RequestMethod.GET)
public @interface GetMapping {
	... 생략
}

 

관련링크

Posted by doubler
,