@RequestMapping은 들어온 요청을 특정 메소드와 매핑하기 위해 사용되는 메소드다. 이번 포스팅에서는 @RequestMapping을 자세하게 알아보자.
1. DefaultAnnotationHandlerMapping
- 디폴트 핸들러 매핑 전략으로, @RequestMapping 어노테이션을 활용한다는 특징이 있다.
- 다른 핸들러 매핑 빈을 등록했다면 디폴트 핸들러 매핑이 전략이 적용되지 않기 때문에 DefaultAnnotationHandlerMapping을 직접 bean으로 등록해줘야 한다.
2. @RequestMapping 속성
- 모든 @RequestMapping 속성은 생략이 가능하다.
- 속성이 배열타입이기 때문에 {}를 사용해서 여러 개의 값을 넣을 수 있다.
- 모든 속성의 조건이 맞아야 매핑이 이루어진다.
- 매핑 조건을 만족하는 경우가 여러 개 있을 때는 구분이 좀 더 상세한 쪽에 매핑된다.
1) String[] value
- 디폴트 엘리먼트로, URL 패턴들을 지정할 수 있다.
- URL 패턴에 *(와일드카드)를 적용할 수도 있다.
@RequestMapping("/home")
@RequestMapping("/*community")
@RequestMapping("/view.*")
@RequestMapping("/admin/**/config")
- URL 패턴에 중괄호를 이용하여 변수를 지정할 수도 있다.
@RequestMapping("/userinfo/{userId}")
- 중괄호로 여러 URL 패턴을 지정할 수도 있다. 이들 중 한 가지 패턴이라도 만족하면 된다.
@RequestMapping({"/main", "/home"})
- 디폴트 접미어 패턴이 적용되기 때문에 URL 패턴의 뒤에 확장자가 붙지 않고 /로 끝나지 않더라도 이들을 모두 반영한다. 예를 들면, 아래 두 줄은 같은 표현이다.
@RequestMapping("/home")
@RequestMapping({"/home", "/home/", "/home.*"})
2) RequestMethod[] method
- 특정 HTTP 메소드에 대한 요청만 받아야 할 때 쓰인다.
- RequestMethod는 enum 타입으로, 내부에 GET, HEAD, POST, PUT, DELETE, OPTIONS, TRACE 총 7개의 요소가 정의되어 있다.
@RequestMapping(value="/user/signup", method=RequestMethod.POST) // POST 요청이 아니면 매핑되지 않는다.
※ form을 처리할 때 동일한 URL에 대하여 GET, POST로 요청을 구분해서 각각 form을 출력하는 용도와 form을 submit하는 용도로 구분하면 편리하다.
- HTML의 <form>에서는 GET, POST만 지원하기 때문에, 그 외의 요청 메소드를 사용하려면 javascript를 이용하거나 <form:form> 커스텀 태그를 이용해서 히든 필드를 통해 HTTP 메소드를 전달해야 한다.
- 여러 값을 넣은 경우에는 한 가지 메소드라도 일치하면 된다.
3) String[] params
- 특정 요청 파라미터 값을 확인해서 매핑 여부를 결정할 때 쓰인다.
@RequestMapping(value="/user/edit", params="type=admin") // type 속성값이 admin이 아니면 매핑되지 않는다.
※ 예를 들어, GET 요청의 경우에는 "/user/edit?type=admin"로 요청 시 매핑에 성공한다.
- !를 이용하여 특정 파라미터가 존재하지 않아야 한다는 조건을 지정할 수도 있다.
@RequestMapping(value="/user/edit", params="!type") // type 속성이 존재하면 매핑되지 않는다.
- 여러 값을 넣은 경우에는 모든 요청 파라미터 조건을 만족해야 한다.
4) String[] headers
- 특정 HTTP 헤더 정보를 가진 요청만 받아야 할 때 쓰인다.
@RequestMapping(value="/info", headers="content-type=text/*") // content-type 헤더 값이 text/로 시작하지 않으면 매핑되지 않는다.
- Spring 3.1에서 Content-Type과 Accept는 요청 조건으로 따로 분리되었다. Spring은 구버전 호환성을 잘 보장해주기 때문에 버전을 올려도 잘 동작할 것이다. 자세한 내용은 @RequestMapping 심화(2)에서 살펴볼 것이다.
- 여러 값을 넣은 경우에는 모든 헤더 조건을 만족해야 한다.
3. 클래스와 메소드를 결합한 매핑
- @RequestMapping은 클래스 레벨과 메소드 레벨 모두에 적용해서 매핑 정보를 결합시킬 수도 있다. 매핑 정보 결합은 URL뿐만 아니라, HTTP 메소드, 파라미터에도 적용된다.
- 클래스 레벨에 적용하는 경우는 메소드 레벨 @RequestMapping의 공통 정보를 지정하려는 목적이고, 메소드 레벨에 적용하는 경우는 클래스 레벨의 매핑을 더 세분화하려는 목적이다.
※ 간단한 예시는 https://kimcoder.tistory.com/235의 [2. @RequestMapping을 클래스에 설정하는 방법]을 참고하자.
- 다음과 같이 클래스 레벨의 URL 패턴이 /*로 끝나는 경우에는 메소드 이름을 이용한 매핑도 가능하다. 예를 들어, 아래 예시에서는 메소드 이름을 따라 각각 /user/add와 /user/edit으로 매핑된다.
@RequestMapping("/user/*")
public class UserController {
@RequestMapping public String signup(...) {...}
@RequestMapping public String edit(...) {...}
}
- 참고로, @RequestMapping을 클래스 레벨에 단독으로 사용할 수도 있다. 다른 타입 컨트롤러에 대한 매핑을 위한 용도가 대표적이다.
@RequestMapping("/home")
public class HomeController implements Controller {...}
4. @RequestMapping 클래스 상속
1) @RequestMapping 정보의 상속
- @RequestMapping 클래스를 상속해서 컨트롤러로 사용하는 경우에는 상위 클래스와 메소드에 붙은 모든 @RequestMapping 정보가 상속되지만, 하위 클래스나 메소드에서 @RequestMapping을 재정의하면 무시된다. 즉, @RequestMapping 어노테이션을 재정의하지 않는 한 정보를 상속받을 수 있는 것이다.
※ 한 가지 예외가 있긴 한데 잘 쓰이지는 않는 방식이다. 잠시 후에 살펴볼 [2)-(5) 하위 클래스 메소드에서 value 속성 없는 @RequestMapping 재정의]를 참고하자.
- @RequestMapping 인터페이스 구현의 경우에도 동일하게 동작한다.
2) 매핑 정보 상속의 종류
(1) 상위 클래스와 메소드의 @RequestMapping 상속
- 메소드를 오버라이드했더라도 @RequestMapping을 붙이지 않았다면 상위 클래스 메소드의 @RequestMapping 정보는 그대로 상속된다.
- 상속이 몇 단계에 걸쳐 진행되더라도 상관 없다.
@RequestMapping("/user")
public class ParentController {
@RequestMapping("/list")
public String list() {...}
}
public class ChildController extends ParentController {
public String list() {...} // 슈퍼클래스 메소드를 재정의 했더라도 "/user/list"에 매핑된다.
}
- 인터페이스 구현의 경우에도 동일하게 동작한다.
(2) 상위 클래스의 @RequestMapping + 하위 클래스 메소드의 @RequestMapping
@RequestMapping("/user")
public class ParentController {...}
public class ChildController extends ParentController {
@RequestMapping("/list")
public String list() {...} // @RequestMapping의 value가 결합되어 "/user/list"에 매핑된다.
}
- 인터페이스 구현의 경우에도 동일하게 동작한다.
(3) 상위 클래스 메소드의 @RequestMapping + 하위 클래스의 @RequestMapping
public class ParentController {
@RequestMapping("/list")
public String list() {...} // 자식 클래스의 @RequestMapping 정보부터 적용되어 "/user/list"에 매핑된다.
}
@RequestMapping("/user")
public class ChildController extends ParentController {...}
- 자식 클래스에서 메소드를 오버라이드했다면 오버라이딩한 메소드가 실행된다.
- 인터페이스 구현의 경우에도 동일하게 동작한다.
(4) 하위 클래스와 메소드에서 모두 @RequestMapping 재정의
- 상위 클래스에 있는 모든 @RequestMapping 정보는 무시되고, 재정의한 @RequestMapping 정보만 반영된다.
- 상위 클래스에서 정의한 정보와 결합되지도 않는다.
- 인터페이스 구현의 경우에도 동일하게 동작한다.
(5) 하위 클래스 메소드에서 value 속성 없는 @RequestMapping 재정의
- 희한하게도 상위 클래스를 상속받아서 오버라이드한 하위 메소드에 한해서는 URL 조건이 없는 @RequestMapping을 붙였을 경우에는 상위 메소드의 @RequestMapping의 URL 조건이 그대로 상속된다.
- @RequestMapping의 다른 속성들을 이용해 조건을 추가로 부여하여 조건 결합이 가능하다.
- 너무 복잡하므로 권장되지 않는 방식이다.
★ 정리
- 매핑 정보의 결합과 대체를 잘 구분하자. 클래스와 메소드간에는 매핑 정보를 결합할 수 있고, 상위 클래스를 상속받아서 @RequestMapping을 재정의하면 재정의한 매핑 정보로 대체된다.
- 상속이나 구현을 활용하여 컨트롤러마다 들어가는 공통적인 매핑 작업을 줄이도록 노력하자.
3) 제네릭스와 매핑정보 상속을 이용한 컨트롤러 작성
- 개별 컨트롤러를 작성할 때 기본적인 CRUD처럼 타입만 달라지는 중복 코드가 발생하는 것을 막기 위해, 제네릭스의 타입 파라미터를 가진 슈퍼 클래스로 공통적인 코드를 뽑아내는 것이다. 개별 컨트롤러는 이 슈퍼 클래스를 상속받아 사용하면 된다.
public abstract class GenericCrudController<T, K, S> {
S service;
@RequestMapping("/add") public void add(T entity) {...}
@RequestMapping("/view") public T view(K id) {...}
@RequestMapping("/list") public List<T> list() {...}
@RequestMapping("/update") public update(T entity) {...}
@RequestMapping("/delete") public void delete(K id) {...}
}
※ T는 도메인 오브젝트 타입, K는 기본키 타입, S는 서비스 오브젝트 타입이다.
슈퍼 클래스에 정의된 메소드만 필요하다면 개별 컨트롤러는 한 줄의 코드도 필요하지 않다. 다음과 같이 슈퍼 클래스를 상속 받아서 타입 파라미터만 지정해주면 된다.
@RequestMapping("/user")
public class UserController extends GenericCrudController<User, Integer, UserService> {}
- 정리하자면, 위 방법은 2)-(3)에서 살펴본 "상위 클래스 메소드의 @RequestMapping + 하위 클래스의 @RequestMapping" 방법을 응용한 것으로, 제네릭스를 활용하여 상위 타입에는 타입 파라미터와 공통 매핑 정보를 지정해놓고, 하위 타입(개별 컨트롤러)에는 구체적인 타입과 클래스 레벨의 기준 매핑정보를 지정해준 것이다.
● 참고 자료 : 토비의 스프링 3.1 Vol.2
'Spring Series > Spring Framework' 카테고리의 다른 글
[Spring] @Controller의 리턴 방식 (0) | 2022.09.13 |
---|---|
[Spring] @Controller의 메소드 파라미터의 종류 (0) | 2022.09.11 |
[Spring] web.xml 대신에 WebApplicationInitializer 사용하기 (0) | 2022.09.07 |
[Spring] View의 종류와 다양한 전략 (0) | 2022.07.22 |
[Spring] Controller의 종류와 다양한 전략 (0) | 2022.07.16 |
댓글