[Spring] 요청 URI 매칭


요청 URI 매칭


클라이언트가 요청한 URL과 @RequestMapping 어노테이션의 값을 이용해서 클라이언트의 요청을 어떤 컨트롤러의 어떤 메서드가 처리할 지가 결정된다.

매칭이 어떻게 이루어지는 지 알지 못할 경우 클라이언트의 요청 URL과 매칭되는 메서드를 설정할 수 없기 때문에, 어떻게 매칭이 이루어지는 지 아는 것은 작지만 매우 중요하다.

본 절에서는 매칭과 관련된 기본 내용인 경로와 @RequestMapping 어노테이션 간의 관계 그리고 @PathVariable을 이용한 URI 템플릿에 대해서 살펴보자.


1. 전체 경로와 서블릿 기반 경로 매칭 설정

    <servlet>

        <servlet-name>dispatcher</servlet-name>

        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

        <load-on-startup>1</load-on-startup>

    </servlet>

    

    <servlet-mapping>

        <servlet-name>dispatcher</servlet-name>

        <url-pattern>*.do</url-pattern>

        <url-pattern>/game/*</url-pattern>

    </servlet-mapping>

위 코드는 *.do로 오는 요청과 /game/으로 오는 요청을 dispatcher 서블릿이 처리하도록 설정하고 있다. 이 경우 다음의 두 @RequestMapping은 어떻게 할까?


@RequestMapping("/search/game.do")

public String search(...){

...

}

 /search/game.do 요청은 <url-pattern>이 ".do"인 확장자 기반의 매칭이 되므로 서블릿 경로가 ""가 된다. 따라서, /search/game.do"이므로 요청은 값이 "search/game.do"인 @RequestMapping 어노테이션과 매칭된다.


@RequestMapping("/game/info")

public String info(...){

...

}

info() 메서드는 /game/info요청을 처리하지 않는다. 서블릿 매핑 설정에서 <url-pattern> 의 값을 "/game/*" 으로 설정했기 때문이다.

<url-pattern>의 값으로 디렉터리를 포함한 패턴을 지정하게 되면 서블릿 경로는 "/game"이 되며, 서블릿 경로를 제외한 나머지  경로를 이용해서 @RequestMapping 어노테이션의 값과 매칭 여부를 판단하게 되므로,

실제 비교할 때 사용되는 요청 URI는 "/info"가 된다. @RequestMapping 어노테이션의 값은 "/game/info" 이므로 비교할 요청 URI인 "info"와는 매칭되지 않는다.


코드만 보면 /search/game.do에 대한 요청은 search()메서드가 처리하고 /game/info에 대한 요청은 info() 메서드가 처리한다고 생각하기 쉽다.

하지만 search() 메서드만 /search/game.do에 대한 요청을 처리하고, info() 메서드는 /game/info요청을 처리하지 않는다.


서블릿 경로를 제외한 나머지 경로를 사용하는 이유는 DispatcherServlet이 기본적으로 사용하는 HandlerMapping 구현체(DefalutAnnotationHandlerMapping)와 HandlerAdapter 구현체(AnnotationMethodAdapter)

가 전체 경로를 사용하지 않도록 설정되어 있기 때문이다. 

DispatherServlet은 DispatherServlet이 사용하는 스프링 설정 파일에 HandlerMapping 구현 클래스와 HandlerAdapter 구현 클래스가 빈으로 등록되어 있으면, 기본 구현체를 사용하지 않고 설정 파일에 등록된 구현체를 사용하게 된다.(기본구현체: DefaultAnnotationServlet 과 AnnotationMethodHandlerAdapter)


2. @PathVariable 어노테이션을 이용한 URI 템플릿


RESTful 서비스가 유행하면서 URI를 다음과 같이 REST 방식으로 구성하는 경우가 늘어나고 있다.

- http://somehost/users/madvius

- http://somehost/games

- http://somehost/forun/board1/10

기본에는 http://somehost/users/userinfo?id=madvius와 같이 파라미터를 이용해서 아이디나 이름을 전달받았다면, 이제는 URI에 아이디나 이름 등이 포함되도록 URL을 구성하고 있다.

URI 템플릿을 이용하면 REST 방식의 URL 매칭을 쉽게 처리할 수 있다. URI 템플릿을 사용하는 방법은 매우 간단하며, 다음과 같이 두 가지만 추가로 작업해주면 된다.


- @RequestMapping: 어노테이션의 값으로(템플릿 변수)

- @PathVariable: 어노테이션을 이용해서{템플릿 변수}와 동일한 이름을 갖는 파라미터를 추가한다.


import org.springframework.web.bind.annotation.PathVariable;

import org.springframework.web.bind.annotation.RequestMapping;


@Controller

public class CharacterInfoController {

@RequestMapping("/game/users/{userId}/chracters/{chracterId}")

public String characterInfo(@PathVariable String userId/*madvirus*/, @PathVariable int characterId /*1*/, ModelMap model) {

model.addAttribute("userId", userId);

model.addAttribute("characterId", characterId);

return "game/character/info";

}

}

위 코드를 보면 @RequestMapping 어노테이션은 이름이 uesrId와 characterId인 변수를 포함하고 있다. 이들 변수는 @PathVariable 어노테이션이 적용된 동일한 이름을 갖는 파라미터에 매칭된다. 따라서 요청 URI가 "/game/users/madivirus/character/1"이면, characterInfo() 메서드의 userId 파라미터와 chracterId 파라미터의 값은 각각 "madvirus"와 1이 된다.


만약 파라미터 이름과 URI 템플릿의 변수 이름이 동일하지 않다면, 다음과 같이 @PathVariable에 매칭될 템플릿 변수 이름을 지정해주면 된다.

@RequestMapping("/game/users/{userId}")

public String userInfo(@PathVariable("userId") String id, ModelMap model){ //userId != id

...

}


3. @RequestMapping 어노테이션의 추가 설정 방법

@RequestMapping 어노테이션을 클래스와 메서드에 함께 적용할 경우, 메서드에 적용한 @RequestMapping 어노테이션의 값은 클래스에 적용한 @RequestMapping 어노테이션의 값은 클래스에 적용한 @RequestMapping 어노테이션의 값을 기본 경로로 사용하게 된다.


@Controller

@RequestMapping("/game/users/{userId}")

public class CharacterInfoController {


@RequestMapping("/characters/{characterId}")

public String characterInfo(@PathVariable String userId,

@PathVariable int characterId, ModelMap model) {

model.addAttribute("userId", userId);

model.addAttribute("characterId", characterId);

return "game/character/info";

}

}

위 코드에서 characterInfo() 메서드에 적용된 @RequestMapping 어노테이션의 값은 "/chracters/{characterId}"인데, 실제로 매칭되는 값은 클래스에 적용된 @RequestMapping 어노테이션의 값을 포함한

"/game/users/{userId}/chracters/{chracterId}"가 된다.


cf.) @RequestMapping 어노테이션은 Ant 스타일의 패턴을 지원한다. 따라서, "*"나 "**"를 값으로 사용할 수 있다.

- ?: 1개의 문자와 매칭

- *: 0개 이상의 문자와 매칭

- **: 0개 이상의 디렉터리와 매칭