Baeldung - Spring RequestMapping

sycho·2024년 3월 30일
0

Baeldung - Spring MVC

목록 보기
3/7

Baeldung의 이 글을 정리 및 추가 정보를 넣은 글입니다.

1. Overview

  • 이 글에서는 @RequestMapping annotation을 집중적으로 다룬다.

2. @RequestMapping Basics

  • 그동안 몇개의 글에서 잠깐 다뤄서 알고 있겠지만, 웹 애플리케이션에 온 요청을 적절한 controller의 메서드에 mapping하는 것이 주된 역할이다.

  • ...라고 소개하고 있지만 사실 정확히 말하면 HTTP request만 처리가 가능하다. 다른 프로토콜 형태의 request에 대한 mapping은 다른 annotation을 사용해야 하는데 (@MessageMapping등) 어쨌든 @RequestMapping은 HTTP request만 처리가 가능하니 이 경우에 대해서만 생각하겠다.

  • 우리가 만든 애플리케이션이 기본적으로 /, 즉 root context path에서 콘텐츠를 제공한다고 해보자. 이는 Spring Boot의 기본 설정이기도 하다.

저 기본 root 경로를 바꾸는 방법을 알고 싶으면 이 글 참고.

2.1 @RequestMapping - by Path

  • 그러면 어떤 방식으로 mapping이 가능할까? 먼저 controller의 메서드가 어떤 '경로'에 대한 요청을 담당할지 지정함으로서 mapping하는 것이 가능하다.
@RequestMapping(value = "/ex/foos", method = RequestMethod.GET)
@ResponseBody
public String getFoosBySimplePath() {
    return "Get some Foos";
}

value

  • value, 그러니까 attribute를 지정하지 않고 설정하는 element 혹은 value를 써서 설정하는 element는 해당 메서드가 담당하는 경로(URI)를 나타낸다.

method

  • method는 해당 메서드가 담당하는 요청 종류를 나타낸다. 지원하는 HTTP 요청 종류는 밑과 같다.
    • GET / POST / HEAD / OPTIONS / PUT / PATCH / DELETE / TRACE

curl command

  • documentation

  • 위 예제가 잘 작동하는지 실험 할 때 밑과 같은 command를 쓰라고 하고 있다.

curl -i http://localhost:8080/ex/foos
  • 이는 리눅스 커맨드로, 서버에서 data를 전달하거나 받기 위한 요청을 보낼 때 사용하는 command이다. 위에 입력한 주소는 요청을 보내는 서버 및 관련 경로이며, 출력은 옵션으로 변경한게 아니면 서버로부터 받은 reply 결과물이다. 여러 option이 있는데 여기서 사용하는 옵션만 얘기해보자면

    • -i : HTTP header 정보도 출력하도록 한다. 기본은 내용물만 출력.
    • -X : 무슨 HTTP request를 할건지 명시할 때 쓰인다. 참고로 명시가 안되면 GET다.
    • -H : HTTP request때 추가로 더할 header을 명시할 때 쓰인다. 추가 header 개수만큼 -H를 쓴다.
    • -d : parameter 전달에 사용.
  • 위 command는 로컬상 돌아가는 웹애플리케이션의 /ex/foos에다가 GET 요청을 하는 것이다.

2.2 @RequestMapping - the HTTP Method

  • 각 HTTP request마다 처리할 controller method를 다르게 하고 싶으면 method부분을 각 controller method에다가 다르게 지정하면 된다. 밑처럼 메서드를 지정하면 위의 GET 메서드를 처리하는 메서드와 밑의 POST 메서드를 처리하는 메서드가 동일 경로에 대해 공존하게 된다.
@RequestMapping(value = "/ex/foos", method = POST)
@ResponseBody
public String postFoos() {
    return "Post some Foos";
}
  • 이게 잘 작동하는지는 밑의 command를 쓰도록 하자.
curl -i -X POST http://localhost:8080/ex/foos

3. RequestMapping and HTTP Headers

3.1 @RequestMapping With the headers Attribute

  • HTTP 요청에 header이 추가로 들어오는 경우는 자주 있다. 이것까지 고려해서 어떤 메서드가 요청을 처리할지 분기를 나누는 것이 가능하다. 이 때 사용하는 annotation의 element가 headers이다.

  • 밑의 경우 /ex/fooskey:val 형태의 header이 추가된 GET 요청이 오는 경우에 이를 처리하는 controller method를 정의한 것이다.

@RequestMapping(value = "/ex/foos", headers = "key=val", method = GET)
@ResponseBody
public String getFoosWithHeader() {
    return "Get some Foos with Header";
}
  • 이를 실험할거면 다음 command를 쓰자.
curl -i -H "key:val" http://localhost:8080/ex/foos
  • 알다시피 요청이 여러개의 header을 가지는 것도 가능하다. 이거를 전부 고려해서 분기를 나누는 것도 가능하다. 이 경우 중괄호를 사용한다.
@RequestMapping(
  value = "/ex/foos", 
  headers = { "key1=val1", "key2=val2" }, method = GET)
@ResponseBody
public String getFoosWithHeaders() {
    return "Get some Foos with Header";
}
  • 이를 실험할거면 다음 command를 쓰자.
curl -i -H "key1:val1" -H "key2:val2" http://localhost:8080/ex/foos
  • 위의 curl command, 그리고 사실 HTTP 요청도 보통 header의 형식이 :으로 나누도록 설정되어 있지만 Spring에서는 =을 사용한다는 점 유의

심화 응용

  • headers는 해당 header이 request에 '포함되어'있냐를 가지고 mapping을 한다. 즉 앞의 첫번째 예시 코드의 경우 key:val이라는 header이 포함되어 있으면 다른 header이 포함된 경우에도 저 controller method가 담당을 한다. 단, 현 요청의 header로 성립되는 더 구체적으로 조건이 지정된 controller method가 없다는 가정하에 말이다.

  • !=을 사용해서, 해당 header이 존재하고 해당 값을 가지지 '않아야' 한다는 조건을 넣는것도 가능하다. 예를들어 key!=val을 하면 val값을 가지지 않는 key header이 있는 경우에 그 controller method에 mapping한다는 것을 의미한다.

  • 거기에 header이름만 넣어서 해당 header이 존재해야 한다는 조건을 넣는것도 가능하다. 예를들어 key를 사용하면 key header이 뭔 값이든 존재만 하면 그 controller method에 mapping한다는 것을 의미한다.

  • 여기에 header 앞에 !을 사용해서 해당 header이 존재하지 않아야 한다는 조건을 넣는것도 가능하다. 예를들어 !key를 하면 key라는 header이 request에 존재하지 않는 경우에 그 controller method에 mapping한다는 것을 의미한다.

  • 마지막으로, type에도 이 annotation 사용이 가능하며 이 경우 메서드들이 전부 해당 조건을 계승하게 된다.

3.2 @RequestMapping Consumes and Produces

  • Header에 거의 무조건 있는 정보중의 하나가 바로 Accept header이다. 혹시나 모를까봐 설명하자면 요청한 측에서 받을 수 있는 형식들을 전부 명시한 것이다.

  • 때문에 이를 고려해가지고 controller method가 적절한 형식의 파일을 반환하도록 하는 것이 매우 중요하다. 이는 앞의 headers를 활용해서구현할 수 있다. 예제는 json 받는게 가능한 요청에 대해 처리하는 controller method를 정의한 것이다.

@RequestMapping(
  value = "/ex/foos", 
  method = GET, 
  headers = "Accept=application/json")
@ResponseBody
public String getFoosAsJsonFromBrowser() {
    return "Get some Foos with Header Old";
}
  • 이때 Accept의 경우 위 값을 '포함'하는 header이면 대응을 시켜주기 때문에 밑과 같은 request도 위의 controller method에 대응된다.
curl -H "Accept:application/json,text/html" http://localhost:8080/ex/foos
  • 이 형태의 header들에 한해서 (그러니까 AcceptContent-Type header 등의 미디어 관련 header에 한해서) wildcard pattern 사용이 가능하다. 모든 text형식에 대해서 mapping한다면 text/*으로 한다든가 등.

produces / consumes

  • Spring 3.1 이후의 경우 produces, consumes가 이 역할을 한다. 밑은 위와 동일한 효과를 내는 코드.
@RequestMapping(
  value = "/ex/foos", 
  method = RequestMethod.GET, 
  produces = "application/json"
)
@ResponseBody
public String getFoosAsJsonFromREST() {
    return "Get some Foos with Header New";
}

사실 앞의 옛날 코드를 Spring 3.1부터는 위와 같은 코드로 자동으로 변환시켜서 실행한다는 것도 유의.

consumesPOST와 같이 본인이 data를 '받는' 경우에 사용하는 element라는 것도 유의.

  • producesconsumes에 (headers와 마찬가지로) 여러개의 값을 넣는 것도 가능하다.
@RequestMapping(
  value = "/ex/foos", 
  method = GET,
  produces = { "application/json", "application/xml" }
)
...
  • produces / consumesAccept header을 넣은 headers와 같이 쓸 수 없다는 점 유의. 이유는 Spring 3.1 이후에서 이 둘을 사실상 같은 것으로 취급하기 때문이다.

4. RequestMapping With Path Variables

  • @PathVariable이라는 annotation을 활용해 URI의 일부를 변수에 대응시키는 것이 가능하다.

  • javadoc

4.1 Single @PathVariable

  • 단일 변수에 대해서 수행하는 예제
@RequestMapping(value = "/ex/foos/{id}", method = GET)
@ResponseBody
public String getFoosBySimplePathWithPathVariable(
  @PathVariable("id") long id) {
    return "Get a specific Foo with id=" + id;
}
  • 테스트에 사용할 수 있는 command
curl http://localhost:8080/ex/foos/1
  • 참고로 controller method의 parameter이름이 path에 있는 path variable의 이름과 정확히 같으면 @PathVariablevalue element를 지정할 필요가 없다. 그래서 밑은 위와 같은 효과를 낸다.
@RequestMapping(value = "/ex/foos/{id}", method = GET)
@ResponseBody
public String getFoosBySimplePathWithPathVariable(
  @PathVariable String id) {
    return "Get a specific Foo with id=" + id;
}
  • 또 자동으로 타입 변환을 해주기 때문에 밑과 같이 id의 type을 정해도 된다.
@PathVariable long id

4.2 Multiple @PathVariable

  • 여러개의 @PathVariable을 사용해도 된다.
@RequestMapping(value = "/ex/foos/{fooid}/bar/{barid}", method = GET)
@ResponseBody
public String getFoosBySimplePathWithPathVariables
  (@PathVariable long fooid, @PathVariable long barid) {
    return "Get a specific Bar with id=" + barid + 
      " from a Foo with id=" + fooid;
}
  • 테스트에 사용할 수 있는 command
curl http://localhost:8080/ex/foos/1/bar/2

4.3 @PathVariable With Regex

  • 정규 표현식을 사용하는 것도 가능하다. 예를들어 밑은 path의 numericId부분이 숫자인 경우에만 controller method가 mapping되도록 설정한다.
@RequestMapping(value = "/ex/bars/{numericId:[\\d]+}", method = GET)
@ResponseBody
public String getBarsBySimplePathWithPathVariable(
  @PathVariable long numericId) {
    return "Get a specific Bar with id=" + numericId;
}
  • http://localhost:8080/ex/bars/1는 위에 mapping되고, http://localhost:8080/ex/bars/abc는 안된다는 것이다.

5. RequestMapping With Request Parameters

  • URL의 parameter을 쉽게 추출해주도록 돕는 annotation으로 @RequestParam이 있다.

  • javadoc

  • 사용법은 그냥 특정 이름의 request parameter이 어디에 연결되어야 하는지를 표기하면 된다.

@RequestMapping(value = "/ex/bars", method = GET)
@ResponseBody
public String getBarBySimplePathWithRequestParam(
  @RequestParam("id") long id) {
    return "Get a specific Bar with id=" + id;
}
  • 테스트용 curl command
curl -i -d id=100 http://localhost:8080/ex/bars

params

  • 여기서 더 나아가 params를 사용할 경우, 해당 method가 해당 parameter을 가진 request를 받는 전용이라고 표시하는 것이 가능하다.

  • 이렇게 표기하면 해당 parameter을 가진 request만 controller method에서 처리한다는 것을 표기하는게 가능하다. 예를들어 밑의 경우 /ex/bars에 request하고, GET이고, id라는 parameter이 존재해야만 해당 controller method가 선택된다.

@RequestMapping(value = "/ex/bars", params = "id", method = GET)
@ResponseBody
public String getBarBySimplePathWithExplicitRequestParam(
  @RequestParam("id") long id) {
    return "Get a specific Bar with id=" + id;
}
  • 그리고 headers와 마찬가지로 여러개의 param을 설정하는 것도 가능하다. 이 경우에도 headers와 마찬가지로 하나라도 해당되면 선택되는 후보군이 된다.
@RequestMapping(
  value = "/ex/bars", 
  params = { "id", "second" }, 
  method = GET)
@ResponseBody
public String getBarBySimplePathWithExplicitRequestParams(
  @RequestParam("id") long id) {
    return "Narrow Get a specific Bar with id=" + id;
}
  • 물론 위와 같은 경우 밑처럼 두 parameter이 모두 주어지면 위의 method가 더 우선적으로 선택될 것이다.
http://localhost:8080/ex/bars?id=100&second=something

6. RequestMapping Corner Cases

  • 앞에서는 기본적인 용도, 그리고 대충 어떻게 mapping이 되는지에 대해 얘기를 했었다. 여기서는 실제로 여러개의 controller method가 정의 되어 있을 때 애매할 수 있는 선택에 대해 어떻게 결정되는지 알아보겠다.

6.1 @RequestMapping - Multiple Paths Mapped to the Same Controller Method

  • 보통 @RequestMapping의 path 부분은 하나의 controller method당 하나만 쓰는게 좋은 편이다. 다만 꼭 그게 좋다는건 아니고, 그 경우 여러개를 사용해도 가능하다.
@RequestMapping(
  value = { "/ex/advanced/bars", "/ex/advanced/foos" }, 
  method = GET)
@ResponseBody
public String getFoosOrBarsByPath() {
    return "Advanced - Get some Foos or Bars";
}

6.2 @RequestMapping - Multiple HTTP Request Methods to the Same Controller Method

  • 앞과 비슷하게, Request Method도 하나의 method가 여러개를 처리하는 것이 가능하다.
@RequestMapping(
  value = "/ex/foos/multiple", 
  method = { RequestMethod.PUT, RequestMethod.POST }
)
@ResponseBody
public String putAndPostFoos() {
    return "Advanced - PUT and POST within single method";
}

6.3 @RequestMapping - a Fallback for All Requests

  • value, 즉 경로는 wildcard 설정이 가능하며, method의 경우 유한하기 대문에 이를 전부 적으면 모든 request를 처리하는 controller method(...?)를 만드는 것이 가능하다.
@RequestMapping(
  value = "*", 
  method = { RequestMethod.GET, RequestMethod.POST ... })
@ResponseBody
public String allFallback() {
    return "Fallback for All Requests";
}

6.4 Ambiguous Mapping Error

  • 5번까지의 예제들을 보면서 조금 더 생각해보면 이래저래 짜다보면 하나의 request에 대해 두가지의 controller method가 동시에 처리할 수 있는 경우를 만드는 것이 가능하다.

  • 이 경우 보통은 더 많은 조건을 만족하는 controller method가 처리한다. 예를들어 밑의 경우 Accepts header을 기반으로 어떤 controller method를 사용할지 정하게 된다.

@GetMapping(value = "foos/duplicate", produces = MediaType.APPLICATION_XML_VALUE)
public String duplicateXml() {
    return "<message>Duplicate</message>";
}
    
@GetMapping(value = "foos/duplicate", produces = MediaType.APPLICATION_JSON_VALUE)
public String duplicateJson() {
    return "{\"message\":\"Duplicate\"}";
}
  • 반대로 이 경우에는 duplicate와 관련된 runtime error이 발생하게 된다.
@GetMapping(value = "foos/duplicate" )
public String duplicate() {
    return "Duplicate";
}

@GetMapping(value = "foos/duplicate" )
public String duplicateEx() {
    return "Duplicate";
}
  • 그러면 첫번째 경우에서 그 method들이 정의된 모든 controller method이고 Accepts header이 없는 foos/duplicate request가 들어오게 되면 어떻게 되냐고 할 수 있다. 오류? 아니다. Configuration에서 설정된 default media type에 지정된 type을 handle하는 controller method로 mapping된다. 구체적인 원리를 파악하는건 좀 복잡한데 이 글 참고

  • 다만 위 Accept가 아닌 다른 영역에서 약간 차이가 있는데 다른 element가 동일한 경우 보통은 runtime error을 일으킨다. 가끔 버전에 따라 아닌 경우가 있긴 하지만 일반적으로 그렇다. 그래서 가급적 중복되는 처리 조건이 나오지 않도록 path 등등을 잘 설정해주는게 좋다. 위의 produce, consume의 경우만 좀 예외가 있는거고.

7. New Request Mapping Shortcuts

  • Spring 4.3부터 @RequestMapping을 기반으로 새로운 몇개의 HTTP mapping annotation을 만들었다.
    @GetMapping
    @PostMapping
    @PutMapping
    @DeleteMapping
    @PatchMapping
  • 미리 method element가 지정되어 있는것이다. 나머지는 동일. 이를 활용해 CRUD operation을 지원하는 RESTful API를 간단하게 다음과 같이 만들 수 있다.
@GetMapping("/{id}")
public ResponseEntity<?> getBazz(@PathVariable String id){
    return new ResponseEntity<>(new Bazz(id, "Bazz"+id), HttpStatus.OK);
}

@PostMapping
public ResponseEntity<?> newBazz(@RequestParam("name") String name){
    return new ResponseEntity<>(new Bazz("5", name), HttpStatus.OK);
}

@PutMapping("/{id}")
public ResponseEntity<?> updateBazz(
  @PathVariable String id,
  @RequestParam("name") String name) {
    return new ResponseEntity<>(new Bazz(id, name), HttpStatus.OK);
}

@DeleteMapping("/{id}")
public ResponseEntity<?> deleteBazz(@PathVariable String id){
    return new ResponseEntity<>(new Bazz(id), HttpStatus.OK);
}

여기서 ResponseEntityHttpEntityHttpStatusCode가 추가된 녀석이며, 보통 Spring MVC의 @Controller의 return value로 자주 사용된다. new로 생성하는 객체 부분은 HTTP response의 body 부분을 담당하게 된다. HttpStatusCodeHttpStatus enum class를 활용하면 된다.

8. Spring Configuration

  • 마지막으로 이 @Controller달린 component를 web application의 controller로 활용하기 위해서는 해당 controller이 위치한 package에 대해 @ComponentScan을 하는것과, @EnableWebMvc를 해야 한다는 것을 잊지 말자.
@Configuration
@EnableWebMvc
@ComponentScan({ "org.baeldung.spring.web.controller" })
public class MvcConfig {
    //
}
profile
안 흔하고 싶은 개발자. 관심 분야 : 임베디드/컴퓨터 시스템 및 아키텍처/웹/AI

0개의 댓글