본문 바로가기
  • 실행력이 모든걸 결정한다
Spring Series/Spring Framework

[Spring] @RequestMapping 심화(1) - DefaultAnnotationHandlerMapping

by 김코더 김주역 2022. 9. 8.
반응형

@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

 

반응형

댓글