@RequestMapping
- 요청을 컨트롤러 메서드에 매핑하는데 사용된다.
- URL, HTTP method, request parameters, headers, media types 등을 매칭하기 위한 다양한 attribute를 가지고 있다.
- 클래스 레벨에서 shared mapping을 표현하거나 메서드 레벨에서 특정 엔드포인트 매핑을 좁히는 데 사용될 수 있다.
HTTP 메서드 별로 단축된 형태의 @RequestMapping 어노테이션이 있다.
- @GetMapping
- @PostMapping
- @PutMapping
- @DeleteMapping
- @PatchMapping
대부분의 컨트롤러 메서드는 특정 HTTP 메서드에 매핑되어야 하기 때문에 @RequestMapping 대신 사용될 수 있다. @RequestMapping는 기본적으로 모든 HTTP 메서드에 매칭되지만, 특정 HTTP 메서드에 대해서 별도로 명시하는 것이 권장된다. 그럼에도 불구하고 shared mappings을 표현하기 위해 클래스 레벨에서는 여전히 @RequestMapping이 필요하다.
동일한 요소(클래스, 인터페이스 또는 메서드)에 선언된 여러 @RequestMapping 어노테이션들은 함께 사용할 수 없다. 동일한 요소에 여러 @RequestMapping 어노테이션이 발견되면 경고가 기록되며, 첫 번째 매핑만 사용된다. 이는 @GetMapping, @PostMapping 등과 같이 조합된 @RequestMapping 어노테이션에도 적용된다.
예시
@RestController
@RequestMapping("/persons")
class PersonController {
@GetMapping("/{id}")
public Person getPerson(@PathVariable Long id) {
// ...
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public void add(@RequestBody Person person) {
// ...
}
}
URL Patterns
global pattern과 wildcard를 사용하여 요청을 매핑할 수 있다.
Pattern | Description | Example |
? | 한 문자와 일치 | "/pages/t?st.html" - "/pages/test.html" 일치 - "/pages/t3st.html" 일치 |
* | path segment 내에서 0개 이상의 문자와 일치 | "/resources/*.png" - "/resources/file.png" 일치 "/projects/*/versions" - "/projects/spring/versions" 일치 - "/projects/spring/boot/versions" 일치X |
** | path 끝까지 0개 이상의 path segment와 일치 | "/resources/**" - "/resources/file.png" 일치 - "/resources/images/file.png" 일치 "/resources/**/file.png"는 경로의 끝에서만 **가 허용되므로 유효하지 않다. |
{name} | path segment와 일치하고 "name"이라는 변수로 capture | "/projects/{project}/versions" - "/projects/spring/versions"와 project=spring을 일치시킨다. |
{name:[a-z]}+ | 정규 표현식 "[a-z]+"와 일치하고 "name"이라는 변수로 capture | "/projects/{project:[a-z]}/versions" - "/projects/spring/versions"와 일치 - "/projects/spring1/versions"에는 일치X |
{*path} | path의 끝까지 0개 이상의 path segment를 capture하고 "path"라는 변수로 capture |
"/resources/{*file}" - "/resources/images/file.png"과 file=/images/file.png을 capture |
클래스 및 메서드 레벨에서 @PathVariable을 사용하여 URL 변수를 선언할 수 있다.
@Controller
@RequestMapping("/owners/{ownerId}") // Class-level URI mapping
public class OwnerController {
@GetMapping("/pets/{petId}") // Method-level URI mapping
public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
// ...
}
}
URL 변수의 자동 변환
- URL 변수는 자동으로 메서드의 배개변수 타입에 맞게 변환된다. (e.g Long, Integer, Date)
- 만약 변환이 실패하면 TypeMismatchException이 발생한다.
명시적인 변수 이름 지정
- URL 변수를 선언할 때 {name}과 같이 변수 이름을 지정할 수 있다.
- e.g) @PathVariable("customId")
- 변수 이름을 생략하면 메서드의 매개변수 이름과 일치하게 사용할 수 있다.
{*varName}
- 나머지 path segment와 일치하는 URI 변수 선언
{varName:regex}
- 정규 표현식을 사용하여 URL 변수를 선언한다.
- e.g) URL이 /spring-web-3.0.5.jar인 경우 다음 메서드는 이름, 버전 및 파일 확장자를 추출한다.
@GetMapping("/{name:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{ext:\\.[a-z]+}")
public void handle(@PathVariable String version, @PathVariable String ext) {
// ...
}
version, ext 변수를 @PathVariable로 선언하여 URI 경로에서 해당하는 부분을 추출한다.
Pattern Comparison
여러 패턴이 URL에 매칭될 경우, 가장 적합한 매칭을 찾기 위해 PathPattern.SPECIFICITY_COMPARATOR을 사용한다.
각 패턴마다 URL 변수와 와일드카드의 개수를 기준으로 점수가 계산된다. URL 변수는 와일드카드보다 낮은 점수를 받는다. 총 점수가 낮은 패턴이 이기고, 점수가 동일한 경우에는 더 긴 패턴을 선택한다.
Catch-all 패턴(**, {*varName} 등)은 점수 계산에서 제외되고, 항상 가장 마지막에 정렬된다. 만약 두 패턴이 모두 Catch-all이면, 더 긴 패턴이 선택된다.
Consumable Media Types
요청의 Content-Type에 따라서 request mapping을 좁힐 수 있다.
@PostMapping(path = "/pets", consumes = "application/json")
public void addPet(@RequestBody Pet pet) {
// ...
}
→ consumes = "application/json"을 사용하여 JSON 형식의 요청만 처리할 수 있게 함
consumes 속성은 부정 표현식도 지원한다. 예를 들어 text/plain은 text/plain 이외의 모든 콘텐츠 타입을 의미한다.
Producible Media Types
특정 컨트롤러 메서드가 생성할 수 있는 콘텐츠 타입을 명시적으로 지정하여 클라이언트가 원하는 미디어 타입에 맞춰 응답을 생성할 수 있다.
@GetMapping(path = "/pets/{petId}", produces = "application/json")
@ResponseBody
public Pet getPet(@PathVariable String petId) {
// ...
}
→ "/pets/{petId}" 경로에 대한 GET 요청이 application/json 타입의 콘텐츠를 생성
produces 속성은 문자 집합을 지정할 수도 있으며, 부정 표현식도 지원된다.
Parameters and Headers
쿼리 파라미터 조건에 따라 request mapping을 좁힐 수 있다. 즉 쿼리 파라미터의 존재 여부를 확인할 수 있고(myParam), 존재하지 않음을 확인할 수도 있다(!myParam), 또는 특정 값(myParam=myValue)의 여부를 테스트할 수 있다.
쿼리 매개변수 조건 : myParam과 myValue가 같은지 확인
@GetMapping(path = "/pets/{petId}", params = "myParam=myValue")
public void findPet(@PathVariable String petId) {
// ...
}
/pets/{petId} 경로에 대한 GET 요청이 myParam=myValue라는 특정 쿼리 매개변수를 가지고 있어야 findPet 메서드가 처리된다. 즉, 요청 URL에 myParam=myValue가 포함되어야 한다.
요청 헤더 조건 : myHeader과 myValue가 같은지 확인
@GetMapping(path = "/pets/{petId}", headers = "myHeader=myValue")
public void findPet(@PathVariable String petId) {
// ...
}
/pets/{petId} 경로에 대한 GET 요청이 myHeader=myValue라는 특정 요청 헤더를 가지고 있어야 findPet 메서드가 처리된다. 즉, 요청 헤더에 myHeader: myValue가 포함되어야 한다.
HTTP HEAD, OPTIONS
HTTP HEAD
@GetMapping과 @RequestMapping(method=HttpMethod.GET)은 HTTP HEAD를 요청 매핑 목적으로 투명하게 지원한다. 컨트롤러 메서드는 변경할 필요가 없다. HttpHandler 서버 어댑터에서 적용된 응답 래퍼는 실제로 응답을 기록하지 않고도 Content-Length 헤더를 응답에 쓰여진 바이트 수로 설정한다.
HTTP OPTIONS
기본적으로 HTTP OPTIONS는 모든 @RequestMapping 메서드의 URL 패턴에 맞는 HTTP 메서드 목록을 나열하여 Allow 응답 헤더를 설정함으로써 처리된다.
예를 들어, HTTP 메서드가 선언되지 않은 @RequestMapping에 대해 Allow 헤더는 GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS와 같이 설정된다. 컨트롤러 메서드는 항상 지원하는 HTTP 메서드를 명시적으로 선언해야 한다. 예를 들어, @GetMapping, @PostMapping 등의 HTTP 메서드별 단축 어노테이션을 사용할 수 있다.
HTTP HEAD와 OPTIONS를 명시적으로 매핑하는 것은 일반적으로 필요하지 않지만, 특정 상황에서는 가능하다.
Explicit Registrations
동적 등록이나 동일한 핸들러의 다른 인스턴스를 다른 URL에서 사용하는 등의 advanced 사례를 위해 핸들러 메서드를 프로그래밍 방식으로 등록할 수 있다.
@Configuration
public class MyConfig {
@Autowired
public void setHandlerMapping(RequestMappingHandlerMapping mapping, UserHandler handler) // 1
throws NoSuchMethodException {
RequestMappingInfo info = RequestMappingInfo
.paths("/user/{id}").methods(RequestMethod.GET).build(); // 2
Method method = UserHandler.class.getMethod("getUser", Long.class); //3
mapping.registerMapping(info, handler, method); // 4
}
}
- 대상 핸들러 및 컨트롤러의 핸들러 매핑 주입 : 설정 클래스 내에서 @Autowired를 사용하여 대상 핸들러와 요청 매핑 핸들러 매핑을 주입
- request mapping metadata 준비 : RequestMappingInfo 객체를 생성하여 경로와 메서드 정보를 설정
- 핸들러 메서드 가져오기 : UserHandler 클래스에서 특정 메서드를 가져온다.
- 등록 추가 : 매핑에 요청 매핑 정보와 핸들러, 메서드를 등록
HttpExchange
목적
- @HttpExchange의 주요 목적은 생성된 프록시를 사용하여 HTTP 클라이언트 코드를 추상화하는 것이다.
- HTTP 인터페이스는 클라이언트와 서버 사용을 구분하지 않는 계약으로, 클라이언트 코드 단순화와 서버 API 노출을 편리하게 한다.
- 특히 Spring Cloud에서 많이 사용되며, 서버 측 처리를 위한 @RequestMapping의 대안으로 활용된다.
@HttpExchange("/persons")
interface PersonService {
@GetExchange("/{id}")
Person getPerson(@PathVariable Long id);
@PostExchange
void add(@RequestBody Person person);
}
@RestController
class PersonController implements PersonService {
public Person getPerson(@PathVariable Long id) {
// ...
}
@ResponseStatus(HttpStatus.CREATED)
public void add(@RequestBody Person person) {
// ...
}
}
차이점
- @RequestMapping: 경로 패턴, HTTP 메서드 등 다양한 요청에 매핑될 수 있다.
- @HttpExchange: 구체적인 HTTP 메서드, 경로, 콘텐츠 유형을 가진 단일 엔드포인트를 선언한다.
메서드 매개변수 및 반환 값
- @HttpExchange는 @RequestMapping이 지원하는 메서드 매개변수의 하위 집합을 지원한다.
- 특히 서버 측 특정 매개변수 유형을 제외한다.
사용 시 유의 사항
- 클라이언트와 서버 간의 결합을 증가시킬 수 있어 공개 API에는 적합하지 않을 수 있지만, 내부 API에는 유용할 수 있다.
- 클라이언트와 서버가 동일한 인터페이스를 공유하여 코드 중복을 줄일 수 있다.
잡담
Parameters and Headers에서 myParam, myHeader, myValue 부분은 더 찾아봐도 이해가 잘 되지 않는데 좀 더 공부하고 나중에 돌아보면 이해가 될 수도 있지 않을까 싶다. 이 뒷부분부터는 솔직히 외계어로 들린다...
'Spring' 카테고리의 다른 글
Validation in Spring Boot (0) | 2024.07.02 |
---|---|
Validating Form Input (0) | 2024.07.02 |
Serving Web Content with Spring MVC (0) | 2024.06.28 |
REST APIs Explained - 4 Components (0) | 2024.06.26 |
Spring MVC (0) | 2024.05.20 |