https://sundaland.tistory.com/314
[ ▶ Annotated Controllers ]
스프링 MVC는 @Controller와 @RestController 컴포넌트가 요청 매핑, 요청 압력, 예외 처리 등을 애노테이션을 통해 표현할 수 있는 애노테이션 기반 프로그래밍 모델을 제공한다. 애노테이션 컨트롤러는 유연한 메서드 시그니처를 가지며, 기본 클래스를 확장하거나 특정 인터페이스를 구현할 필요가 없다.
▼ 애노테이션으로 정의된 컨트롤러
@Controller
public class HelloController {
@GetMapping("/hello")
public String handle(Model model) {
model.addAttribute("message", "Hello World!");
return "index";
}
}
[ ▷ Declaration ]
스프링 MVC에서 컨트롤러 빈 (Controller Bean)을 정의하려면 Servlet의 WebApplicationContext에서 표준 스프링 빈 정의를 사용할 수 있다. @Controller 애노테이션은 컨트롤러 클래스에 사용되며, 이 클래스가 웹 컴포넌트임을 나타내는 역할을 한다.
또한, 스프링의 일반적인 @Component 클래스를 클래스 경로에서 자동으로 감지하고, 이를 빈 정의로 자동 등록하는 방식과 동일하게 @Controller 애노테이션도 자동 감지도 가능하다.
[ ▷ 자동 감지 설정 ]
@Controller 빈을 자동으로 감지하고 등록하려면, Java 설정에 컴포넌트 스캔을 추가할 수 있다.
▼ @ComponentScan을 사용하여 @Controller 애노테이션이 붙은 클래스를 스캔하는 방법
@Configuration
@ComponentScan("org.example.web")
public class WebConfig {
// 추가 설정 가능
}
org.example.web 패키지를 스캔하여 해당 패키지 내의 @Controller 클래스들을 자동으로 감지하고 빈으로 등록한다.
[ ▷ XML 설정 방식 ]
▼ 위와 동일한 기능을 하는 XML 설정
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="org.example.web"/>
</beans>
이 XML 설정은 Java 설정과 마찬가지로 org.example.web 패키지를 스캔하여 @Controller 클래스를 감지하고 등록한다.
[ ▷ @RestController ]
@RestController는 복합 애노테이션으로, 이 자체가 @Controller와 @ResponseBody로 메타 애노테이션되어 있다. 즉, @RestController가 붙은 컨트롤러 클래스는 모든 메서드가 기본적으로 @ResponseBody를 상속받아 메서드의 반환 값을 HTML 템플릿을 통한 뷰 렌더링 대신 HTTP 응답의 본문으로 직접 작성하게 된다. RESTful API를 개발할 때 많이 사용된다.
[ ▷ AOP 프록시 (AOP Proxies) ]
컨트롤러에 AOP 프록시를 런타임에 적용해야 하는 경우가 있다. 예를 들어, 컨트롤러에 직접 @Transactional 애노테이션을 사용하는 경우가 있다. 이 경우 컨트롤러에 대한 프록시는 클래스 기반 프록시(class-based proxy)를 사용하는 것이 권장된다. 이는 애노테이션이 컨트롤러에 직접적으로 적용된 경우 자동으로 적용된다.
[ ▷ 인터페이스 기반 프록시 ]
만약 컨트롤러가 인터페이스를 구현하고 있고, AOP 프록시가 필요하다면 클래스 기반 프록시를 명시적으로 설정해야 할 수 있다. 예를 들어 @EnableTransactionManagement 설정에서 @EnableTransactionManagement(proxyTargetClass = true)로 변경하거나, XML 설정의 <tx:annotation-driven/>을 <tx:annotation-driven proxy-target-class="true"/>로 변경할 수 있다.
Spring Framework 6.0부터는 인터페이스 프록시를 사용할 때, 인터페이스에만 @RequestMapping이 적용된 경우 Spring MVC는 이를 컨트롤러로 인식하지 않는다. 이 문제를 해결하려면 클래스 기반 프록시를 활성화하거나, 인터페이스에도 @Controller 애노테이션을 명시적으로 추가해야한다.
이러한 내용을 바탕으로 Spring MVC에서 컨트롤러를 정의하고 사용하는 방법은 매우 유연하며, 상황에 맞는 다양한 설정 방법을 제공한다.
[ ▷ Mapping Requests ]
[ ▷ @RequestMapping ]
△ @RequestMapping
@RequestMapping 애너테이션은 클라이언트 요청을 특정 컨트롤러 메서드에 매핑하는 역할을한다. 서버로 들어오는 요청의 경로, HTTP 메서드(GET, POST 등), 요청 파라미터, 헤더, 그리고 미디어 타입 등을 기준으로 요청을 특정 컨트롤러의 메서드와 연결한다.
- value 또는 path: 요청을 매핑할 URL 경로를 지정한다.
- method: 요청을 처리할 HTTP 메서드(예: GET, POST 등)를 지정한다.
- params: 특정 요청 파라미터에 따라 요청을 매핑할 수 있다.
- headers: 특정 요청 헤더를 기준으로 매핑할 수 있다.
- consumes: 요청 본문의 콘텐츠 타입을 지정한다. (application/json)
- produces: 응답의 콘텐츠 타입을 지정한다. (application/json)
@RequestMapping은 클래스 레벨과 메서드 레벨에서 모두 사용할 수 있다.
△ 클래스 레벨 사용
클래스 레벨에 @RequestMapping을 사용하면 그 클래스에 포함된 모든 메서드에 대해 공통된 경로를 정의할 수 있다.
예를 들어, 모든 메서드가 /persons로 시작하는 요청 경로에 대해 응답하도록 설정할 수 있다.
@RestController
@RequestMapping("/persons")
class PersonController {
// 클래스 레벨에서 공통된 경로 매핑
}
△ HTTP 메서드별 단축 애너테이션
Spring은 HTTP 메서드에 따라 요청을 처리할 수 있는 단축 애너테이션을 제공한다.
@RequestMapping과 동일한 기능을 제공하지만, 메서드에 매핑되는 HTTP 메서드를 명시적으로 지정하여 사용하기 편리하도록 만든 애너테이션들이다.
- @GetMapping: GET 요청을 처리
- @PostMapping: POST 요청을 처리
- @PutMapping: PUT 요청을 처리
- @DeleteMapping: DELETE 요청을 처리
- @PatchMapping: PATCH 요청을 처리
@GetMapping("/{id}")
public Person getPerson(@PathVariable Long id) {
// GET 요청에 대해 응답
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public void addPerson(@RequestBody Person person) {
// POST 요청에 대해 응답
}
여기서 @GetMapping("/{id}")은 URL 경로 /persons/{id}로 들어오는 GET 요청을 처리하며, @PostMapping은 /persons 경로로 들어오는 POST 요청을 처리한다.
△ 클래스 레벨 @RequestMapping과 메서드 레벨 @RequestMapping의 결합
클래스 레벨에서 @RequestMapping을 사용하여 공통 경로를 설정한 후, 메서드 레벨에서 세부 경로와 HTTP 메서드를 정의할 수 있다. 이 방식은 중복되는 URL 경로를 줄이고, 각 메서드가 처리할 구체적인 요청을 정의하는 데 유용하다.
@RestController
@RequestMapping("/persons")
class PersonController {
@GetMapping("/{id}")
public Person getPerson(@PathVariable Long id) {
// GET 요청 처리
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public void add(@RequestBody Person person) {
// POST 요청 처리
}
}
이 코드에서는 클래스 레벨에서 /persons라는 경로를 설정했기 때문에, 각 메서드는 /persons 경로를 공유하면서 각기 다른 경로와 HTTP 메서드를 처리하게된다.
- /persons/{id} 경로로 GET 요청을 보내면 getPerson() 메서드가 실행된다.
- /persons 경로로 POST 요청을 보내면 add() 메서드가 실행된다.
△ 다중 @RequestMapping 애너테이션 경고
@RequestMapping 또는 @GetMapping, @PostMapping 등과 같은 단축 애너테이션은 동일한 클래스나 메서드에 중복으로 사용될 수 없다. 하나의 클래스, 인터페이스, 또는 메서드에 여러 개의 @RequestMapping 애너테이션을 적용하면 경고가 발생하며, 첫 번째 매핑만 사용된다.
△ 정리
- @RequestMapping은 URL, HTTP 메서드, 파라미터, 헤더 등을 기준으로 요청을 컨트롤러 메서드에 매핑하는 강력한 도구이다.
- HTTP 메서드별 단축 애너테이션(@GetMapping, @PostMapping 등)은 사용이 간편하고 가독성을 높여주므로, 자주 사용된다.
- 클래스 레벨과 메서드 레벨에서 각각 요청 매핑을 설정할 수 있으며, 클래스 레벨에서는 공통 경로를, 메서드 레벨에서는 세부적인 매핑을 정의한다.
- 동일한 요소에 여러 @RequestMapping 애너테이션을 적용하면 경고가 발생하므로 주의해야 다.
이러한 방식으로 Spring MVC에서는 애너테이션을 사용해 클라이언트의 요청을 효과적으로 처리하고, 서버의 엔드포인트를 간결하게 관리할 수 있다.
[ ▷ URI patterns ]
Spring MVC의 @RequestMapping 메서드에서 URI 패턴을 어떻게 사용하는지 설명한다. @RequestMapping 애너테이션으로 경로를 매핑할 때, 두 가지 주요 패턴 매칭 방법을 사용할 수 있으며, URI 패턴에서 변수를 캡처하는 방법, 정규 표현식을 사용하는 방법 등을 상세히 다룬다.
△ URI 패턴 매핑 방법
◎ PathPattern
- 설명: PathPattern은 사전 처리된 패턴을 URL 경로와 매칭하는 방식이다. URL 경로는 PathContainer로 사전 처리되며, 이 방식은 웹 애플리케이션에서의 인코딩 처리와 경로 파라미터 처리에 적합하고 효율적이다.
- 특징: 경로와 패턴이 미리 파싱되기 때문에 효율적인 매칭을 제공하며, Spring WebFlux에서는 유일한 선택이다.
- 사용 권장: Spring MVC 5.3부터 사용 가능하며, Spring MVC 6.0부터는 기본적으로 사용되도록 설정되었다.
◎ AntPathMatcher
- 설명: AntPathMatcher는 문자열 패턴을 문자열 경로와 매칭하는 방식이다. 기존에 Spring에서 파일 시스템, 클래스패스 등에서 리소스를 선택할 때 사용되던 방식이다.
- 특징: 이 방식은 인코딩이나 URL의 복잡한 구조를 처리하는 데 있어서 다소 비효율적일 수 있다.
- 비교: PathPattern이 성능이나 사용성 측면에서 더 나은 결과를 제공하므로, 웹 애플리케이션에서는 PathPattern이 더 적합하고 권장된다.
△ PathPattern의 패턴 문법
PathPattern은 AntPathMatcher와 동일한 패턴 문법을 지원하지만, 몇 가지 추가 기능과 제한 사항이 있다.
◎ 패턴 문법 예시
- "/resources/ima?e.png": 경로의 한 문자와 매칭. 예를 들어 /resources/image.png와 /resources/imaae.png와 매칭된다.
- "/resources/*.png": 경로 세그먼트에서 0개 이상의 문자를 매칭. .png 확장자로 끝나는 파일을 찾는다.
- "/resources/**": 여러 개의 경로 세그먼트를 매칭. 예를 들어 /resources/dir1/dir2/file.png와 매칭된다.
- "/projects/{project}/versions": 경로 세그먼트를 변수로 매칭. {project}는 경로에서 특정 값을 캡처하여 변수로 저장한다.
- "/projects/{project:[a-z]}/versions": 정규 표현식으로 변수를 매칭. {project}는 소문자 알파벳 하나로만 이루어진 값을 캡처한다.
◎ PathPattern만의 특징
- 캡처 패턴: "{*spring}"처럼 경로 끝에서 0개 이상의 경로 세그먼트를 캡처할 수 있다. 이는 경로의 끝에서 여러 경로를 매칭해야 할 때 유용하다.
- 제약: PathPattern에서는를 사용하여 여러 경로 세그먼트를 매칭할 수 있지만, 이는 경로 끝에서만 허용된다. 이를 통해 요청 경로에서 최적의 패턴을 선택할 때 발생할 수 있는 모호성을 줄인다.
△ URI 변수 캡처와 사용
URI 패턴에서 경로 세그먼트를 변수로 캡처하는 방법을 제공한다. 이를 통해 클라이언트가 보낸 경로에서 변수를 추출하고, 해당 변수를 메서드에서 사용할 수 있다.
◎ @PathVariable 애너테이션
URI에서 캡처된 변수는 메서드 파라미터로 전달되며, 이때 @PathVariable 애너테이션을 사용한다.
@GetMapping("/owners/{ownerId}/pets/{petId}")
public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
// 요청 경로에서 ownerId와 petId 변수를 추출하여 사용
}
위 코드에서는 /owners/{ownerId}/pets/{petId} 경로로 들어온 요청에서 {ownerId}와 {petId}에 해당하는 값을 추출하여 메서드의 파라미터 ownerId와 petId에 전달한다.
◎ 클래스와 메서드 레벨에서 변수 선언
래스 레벨과 메서드 레벨에서 모두 URI 변수를 선언할 수 있다. 클래스 레벨에서 선언된 변수는 해당 클래스의 모든 메서드에서 사용할 수 있다.
@Controller
@RequestMapping("/owners/{ownerId}")
public class OwnerController {
@GetMapping("/pets/{petId}")
public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
// 클래스 레벨에서 선언한 ownerId와 메서드 레벨에서 선언한 petId를 사용
}
}
위 코드에서는 /owners/{ownerId}/pets/{petId}와 같은 경로로 들어온 요청에서 ownerId와 petId를 각각 추출하여 사용한다. 클래스 레벨에서 선언된 ownerId는 모든 메서드에서 사용 가능하며, 메서드에서 선언한 petId는 해당 메서드에서만 사용된다.
◎ 타입 변환
URI 변수는 자동으로 해당하는 타입으로 변환된다. 예를 들어, @PathVariable Long ownerId는 URI에서 추출한 값을 Long 타입으로 변환한다. 만약 변환에 실패하면 TypeMismatchException이 발생한다. 기본적으로 지원되는 타입은 int, long, Date 등이 있으며, 추가적인 타입 지원이 필요할 경우 커스텀 변환기를 등록할 수 있다.
△ 정규 표현식으로 URI 변수 매칭
정규 표현식을 사용하여 더 정밀한 URI 패턴 매칭을 할 수 있다. varName:regex} 문법을 사용하면 URI 변수에 대해 정규 표현식 기반으로 조건을 정의할 수 있다.
▼ 정규 표현식 예시
@GetMapping("/{name:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{ext:\\.[a-z]+}")
public void handle(@PathVariable String name, @PathVariable String version,
@PathVariable String ext) {
// 요청 경로에서 name, version, ext 변수를 추출하여 사용
}
위 코드에서는 경로가 /spring-web-3.0.5.jar일 때 name은 spring-web, version은 3.0.5, ext는 .jar로 추출된다.다.
정규 표현식을 사용하면 보다 복잡한 패턴도 처리할 수 있어 다양한 경로 구조에 유연하게 대응할 수 있다.
△ 프로퍼티 기반 경로 매칭
URI 패턴에서 ${…} 형식을 사용하여 플레이스홀더를 정의할 수 있다. 이 플레이스홀더는 애플리케이션이 시작될 때 외부 설정 파일이나 시스템 환경 변수 등의 값을 기반으로 동적으로 치환된다. 이를 통해 애플리케이션 구성에 따라 경로를 유연하게 설정할 수 있다.
▼ 플레이스홀더 예시
@RequestMapping("${base.url}/owners")
public class OwnerController {
// ...
}
위 코드에서 ${base.url}은 외부 설정에 따라 실제 URL 경로로 치환된다. 이를 통해 설정에 따라 동적으로 경로를 변경할 수 있다.
△ 정리
Spring MVC에서 URI 패턴을 활용하면 매우 유연한 요청 매핑이 가능하나. PathPattern을 사용하면 더 효율적인 경로 매칭이 가능하며, 정규 표현식과 플레이스홀더를 사용해 다양한 경로 구조를 처리할 수 있다. @PathVariable 애너테이션을 통해 경로에서 변수를 쉽게 추출하고 사용할 수 있으며, URI 패턴 문법은 단순한 매칭부터 복잡한 패턴 처리까지 널리 활용된다.
[ ▷ Pattern Comparison ]
여러 패턴이 동일한 URL과 매칭될 때 가장 적합한 패턴을 선택하는 방법에 대해 설명하고 있다. 이는 PathPattern 또는 AntPathMatcher를 사용할 때 발생할 수 있는 문제로, 두 가지 방법 중 하나를 사용하여 패턴을 정렬하고, 더 구체적인 패턴을 선택하게 된다.
△ 패턴 비교의 필요성
Spring MVC에서 @RequestMapping 애너테이션을 통해 URL을 컨트롤러 메서드에 매핑할 때, 여러 가지 패턴이 동일한 URL과 매칭될 수 있다. 예를 들어, /resources/*와 /resources/file.png가 동일한 URL에 매칭될 수 있다. 이런 상황에서는 가장 적합한 (가장 구체적인) 패턴이 선택되어야 한다.
- PathPattern.SPECIFICITY_COMPARATOR
- AntPathMatcher.getPatternComparator(String path)
△ 패턴 비교 방식
위 두 가지 방식 모두 패턴을 정렬하여, 더 구체적인 패턴이 상위에 오도록 한다. 이때 패턴의 구체성을 판단하는 기준은 다음과 같다.
◎ URI 변수
- 패턴에 포함된 URI 변수는 그 패턴을 덜 구체적으로 만든다. 예를 들어 /users/{id}는 /users/admin보다 덜 구체적이다.
- URI 변수의 수가 적을수록 패턴은 더 구체적이다. 예를 들어, /users/{id}/orders/{orderId}는 /users/{id}보다 덜 구체적이다.
◎ 단일 와일드 카드 (*)
- *는 한개의 경로 세그먼트를 매칭하는 와일드카드이다. 단일 와일드 카드는 UIR 변수보다는 덜 구체적이며, 이는 /users/*가 /users/{id}보다 덜 구체적이다.
◎ 이중 와일드 카드 (``)**
- **는 여러 경로 세그먼트를 매칭하는 와일드카이다. 이는 경로 전체에 걸쳐 여러 세그먼트를 매칭할 수 있으므로, 패턴을 덜 구체적으로 만든다. 예를 들어, /users/**는 /users/* 보다 덜 구체적이다.
◎ 길이
- 구체성 점수가 같은 경우, 패턴의 길이가 더 긴쪽이 더 구체적으로 간주된다. 예를 들어, /users/profile은 /users 보다 더 구체적이다.
◎ URI 변수 vs 와일드카드
- 구체성 점수와 길이가 같은 경우, URI 변수가 와일드 카드보다 더 구체적으로 간주된다. 즉, /users/{id}는 /users/* 보다 더 구체적이다.
△ 패턴 구체성 평가
패턴을 평가할 때는 다음과 같은 규칙이 적용된다.
- URI 변수는 1점으로 계산된다.
- 단일 와일드카드 (*)는 1점으로 계산된다.
- 이중 와일드카드 (``)**는 2점으로 계산된다.
- 점수가 같을 경우 더 긴 패턴이 우선된다.
- 길이가 같고 점수가 같을 경우 URI 변수가 와일드카드보다 우선된다.
예를 들어, 다음과 같은 두 패턴이 있다고 가정한다면 (추천되지 않는 작성 방법)
- /users/{id} (URI 변수 1개 = 1점)
- /users/* (단일 와일드카드 1개 = 1점)
두 패턴은 점수가 동일하지만, URI 변수가 와일드카드보다 구체적이므로 /users/{id}가 선택된다.
△ 특수한 경우: 기본 매핑 패턴
특정 패턴은 항상 우선 순위가 낮게 설정된다.
- /**: 기본 매핑 패턴으로, 모든 경로를 매칭하는 패턴이다. 이 패턴은 구체성 평가에서 제외되며, 항상 마지막에 정렬된다.
- prefix 패턴: 예를 들어 /public/**와 같은 접두사 패턴은 구체성 측면에서 덜 구체적인 패턴으로 간주된다. 따라서 더 구체적은 패턴보다 후순위로 매칭된다.
△ 패턴 비교 예시
- /users/{id}/orders (URI 변수 1개 = 1점)
- /users/*/orders (단일 와일드카드 1개 = 1점)
- /users/** (이중 와일드카드 1개 = 2점)
이 세 패턴은 모두 같은 경로 /users/123/orders와 매칭될 수 있다. 이를 구체성 순서로 정렬하면 아래와 같다.
- /users/{id}/orders (URI 변수 1개)
- /users/*/orders (단일 와일드카드 1개)
- /users/** (이중 와일드카드 1개)
따라서 가장 구체적인 패턴인 /users/{id}/orders 가 선택된다.
△ 패턴 비교 시 주의 사항
- Spring은 여러 패턴이 동일한 경로와 매칭될 수 있을 때, 가장 구체적인 패턴을 선택해야 한다. 이를 위해 패턴 비교 알고리즘을 사용하여 패턴을 정렬하고, 가장 구체적인 패턴이 선택되도록 보장한다.
- 이중 와일드카드는 구체성이 낮으며, 특정 경로의 일부만을 매칭하는 접두사 패턴도 구체성이 낮게 평가된다.
- 기본 매핑 패턴 (/**)은 항상 최후에 매칭된다.
△ 정리
Spring MVC에서 여러 패턴이 동일한 경로에 매핑될 수 있을 때, 패턴 비교 알고리즘을 통해 가장 구체적인 패턴이 선택된다. PathPattern.SPECIFICITY_COMPARATOR 또는 AntPathMatcher.getPatternComparator 가 사용되며, URI 변수, 와일드카드, 패턴 길이 등을 기준으로 구체성을 평가한다. /**와 같은 기본 패턴은 항상 후순위로 처리되며, 이중 와일드카드나 접두사 패턴은 구체성이 낮게 평가된다.
이러한 매커니즘을 통해 Spring MVC보다 효율저깅고 직관적인 URL 매칭을 제공한다.
[ ▷ Suffix Match ]
△ Suffix 패턴 매칭이란?
기존 Spring MVC에서는 @RequestMapping("/person")과 같은 컨트롤러 매핑이 있을 때, 이 매핑이 /person.* 형식의 URL에도 암묵적으로 적용되었다. 예를 들어, /person.pdf, /person.xml과 같은 URL도 매칭되어서, 요청된 파일 확장자에 따라 콘텐츠 타입(Content Type)이 결정되었다.
그러나 Spring 5.3 이후부터는 이런 suffix 패턴 매칭이 기본적으로 비활성화되었다. 즉, /person으로 매핑된 컨트롤러가 더 이상 /person.pdf나 /person.xml과 같은 URL을 자동으로 매핑하지 않는다.
△ 왜 더 이상 suffix 패턴 매칭을 사용하지 않는 이유
◎ Accept 헤더와의 충돌
과거에는 브라우저가 Accept 헤더를 일관되게 해석하기 어려웠기 때문에, 파일 확장자를 사용해 콘텐츠 타입을 결정하는 방식이 필요했다. 예를 들어, /person.pdf는 PDF 형식으로 응답을 보내고, /person.xml은 XML 형식으로 응답하는 방식이었다.
그러나 최근에는 브라우저들이 Accept 헤더를 더 잘 해석하게 되었고, 따라서 더 이상 파일 확장자를 이용해 콘텐츠 타입을 결정할 필요가 없어졌다. 이제는 Accept 헤더를 통해 응답 형식을 결정하는 것이 더 권장다.
◎ URI 변수, 경로 파라미터와의 충돌
파일 확장자를 URL에 사용하는 것은 URI 변수나 경로 파라미터와 충돌할 수 있다. 예를 들어, /person/{id}.xml 같은 경로에서 {id}가 변수로 사용되면서 파일 확장자와 충돌이 발생할 수 있다. 이런 경우에는 URL을 파싱하거나 보안 규칙을 적용하는 데 혼란이 생길 수 있다.
◎ 보안 문제
파일 확장자를 이용한 패턴 매칭은 URL 기반 인증 및 보안에 문제를 일으킬 수 있다. 특정 파일 확장자가 포함된 URL에 대해 구체적으로 보안 규칙을 설정하기 어렵거나, 예상치 못한 경로가 매칭되는 경우가 발생할 수 있다.
△ Suffix 패턴 매칭 비활성화 방법
만약 5.3 이전 버전을 사용하는 경우, 명시적으로 suffix 패턴 매칭을 비활성화할 수 있다. 이를 위해서는 다음 두 가지 설정을 사용할 수 있다.
- useSuffixPatternMatching(false): PathMatchConfigurer를 사용해 suffix 패턴 매칭을 비활성화한다.
- favorPathExtension(false): ContentNegotiationConfigurer를 사용해 콘텐츠 타입 결정 시 파일 확장자를 사용하지 않도록 설정한다.
이 두 설정을 통해 Spring MVC는 더 이상 파일 확장자를 사용해 콘텐츠 타입을 결정하지 않으며, URL을 더 명확하고 안전하게 처리할 수 있다.
△ 파일 확장자를 대신할 대안
파일 확장자를 사용하는 대신, URL에서 다른 방법을 통해 콘텐츠 타입을 요청하는 것이 더 안전하고 유연한 방법이다.
◎ 쿼리 파라미터 사용
파일 확장자를 사용하는 대신, 쿼리 파라미터를 사용하여 콘텐츠 타입을 요청하는 방법이 있다. 예를 들어, /person?format=pdf와 같은 방식으로 콘텐츠 타입을 명시하는 것이 가능하다. 이렇게 하면 파일 확장자가 아닌 쿼리 파라미터를 통해 응답 형식을 명확하게 지정할 수 있다.
◎ 파일 확장자의 제한적 사용
만약 파일 확장자를 반드시 사용해야 한다면, 이를 명시적으로 등록된 확장자 목록으로 제한할 수 있다. ContentNegotiationConfigurer의 mediaTypes 속성을 통해 허용할 파일 확장자와 그에 대응하는 미디어 타입을 등록할 수 있다. 예를 들어, .pdf, .xml 등의 확장자를 명시적으로 허용하고, 그 외 확장자는 차단할 수 있다.
△ 정리
Spring MVC 5.3 이후부터는 기본적으로 suffix 패턴 매칭이 비활성화되었다. 이는 파일 확장자를 통한 콘텐츠 타입 결정 방식이 과거에는 필요했지만, 현재는 Accept 헤더를 통한 방식이 더 선호되고 있기 때문이다. 또한 파일 확장자를 사용하는 방식은 URI 변수, 경로 파라미터, 보안 문제 등 여러 가지 충돌을 일으킬 수 있기 때문에, 이를 비활성화하고 쿼리 파라미터 같은 대안 방식을 사용하는 것이 권장된다.
따라서, 파일 확장자를 통한 콘텐츠 타입 요청은 가능한 피하고, 현대적인 웹 애플리케이션에서는 Accept 헤더를 활용하는 방식으로 전환하는 것이 바람직하다.
[ ▷ Suffix Match and RFD ]
△ 반사된 파일 다운로드(RFD) 공격이란?
반사된 파일 다운로드(reflected file download, RFD) 공격은 클라이언트의 요청 입력(예: 쿼리 파라미터나 URI 변수)이 응답에 그대로 반영되는 경우 발생한다. 일반적으로 XSS 공격에서는 JavaScript 코드를 HTML에 삽입하여 공격하지만, RFD 공격에서는 브라우저가 응답을 다운로드 가능하게 처리하고, 사용자가 이 파일을 실행하는 방식으로 공격이 이루어진다.
- 사용자가 악성 URL을 클릭하거나 직접 입력한다.
- 서버는 요청을 처리하고, 요청에 포함된 입력을 그대로 반영한 파일을 생성한다.
- 이 파일은 브라우저가 다운로드 가능하게 응답한다.
- 사용자가 이 파일을 더블 클릭하여 실행하면, 의도하지 않은 스크립트나 코드가 실행될 수 있다.
△ Spring MVC와 RFD 공격의 관계
Spring MVC에서는 @ResponseBody 또는 ResponseEntity 메소드를 사용하여 다양한 콘텐츠 타입을 렌더링할 수 있다. 사용자가 요청하는 URL 경로 확장자에 따라 다른 콘텐츠 타입을 요청할 수 있기 때문에, 이는 RFD 공격에 노출될 위험이 존재다.
suffix 패턴 매칭을 비활성화하고 경로 확장을 콘텐츠 협상에 사용하는 것은 RFD 공격의 위험을 줄이지만, 이러한 조치만으로는 완전히 방지할 수 없다.
△ RFD 공격 방지 방법
◎ Content-Disposition 헤더 추가
Spring MVC에서는 응답 본문을 렌더링하기 전에 Content-Disposition 헤더를 추가하여 다운로드 파일에 대한 제안을 한다.
Content-Disposition: inline; filename=f.txt
이 헤더는 URL 경로에 파일 확장자가 포함되어 있으나, 안전한 확장자로 간주되지 않거나 콘텐츠 협상에 명시적으로 등록되지 않은 경우에 추가된다.
이렇게 설정된 Content-Disposition 헤더는 사용자가 응답을 다운로드할 때 안전한 파일 이름을 제안한다.
◎ 안전한 확장자
Spring MVC는 기본적으로 몇 가지 일반적인 파일 확장자를 안전한 것으로 간주한다. 예를 들어, .pdf, .jpg, .txt 등의 확장자가 안전하다고 판단한다. 따라서 이러한 확장자는 Content-Disposition 헤더를 추가하지 않고 정상적으로 응답될 수 있다.
◎ 사용자 정의 HttpMessageConverter
애플리케이션에서 사용자 정의 HttpMessageConverter를 구현하는 경우, 콘텐츠 협상을 위해 특정 파일 확장자를 명시적으로 등록할 수 있다. 이를 통해 Content-Disposition 헤더가 추가되지 않도록 설정할 수 있다. 이러한 등록은 콘텐츠 타입에 따라 동적으로 처리되므로, 개발자는 응답의 타입에 따라 적절한 처리를 할 수 있다.
△ 주의사항
◎ 직접 URL 입력의 부작용
URL을 직접 브라우저에 입력하는 경우, 공격자가 의도하지 않은 결과를 초래할 수 있는 URL을 사용하여 RFD 공격을 수행할 수 있다. 이와 관련하여, Spring MVC는 Content-Disposition 헤더를 추가함으로써 사용자가 다운로드한 파일의 안전성을 높이려고 하지만, 사용자가 직접 URL을 입력할 때는 여전히 주의가 필요하다.
◎ CVE-2015-5211
RFD 공격과 관련하여, 특정 취약점(CVE-2015-5211)에 대한 추가 권장 사항이 있다. 이 CVE는 RFD 공격에 대한 보안 권장 사항을 제시하며, 개발자는 이 정보를 참고하여 애플리케이션의 보안을 강화해야 한다.
△ 정리
반사된 파일 다운로드(RFD) 공격은 요청 입력이 응답에 반영되는 경우 발생하며, Spring MVC의 다양한 응답 처리 방식에서 취약점이 존재한다. RFD 공격을 방지하기 위해 Spring MVC는 Content-Disposition 헤더를 사용하여 안전한 파일 다운로드를 제안하고, 기본적으로 몇 가지 확장자를 안전하다고 간주한다. 그러나 사용자 정의 HttpMessageConverter를 통해 특정 파일 확장자를 명시적으로 등록하는 등의 추가 조치가 필요하며, URL을 직접 입력할 때 발생할 수 있는 위험에 대해 개발자들은 항상 주의해야 한다.
[ ▷ Consumable Media Types ]
Consumable Media Types는 Spring MVC에서 요청을 처리할 때, 클라이언트가 전송하는 데이터의 콘텐츠 유형(Content-Type)에 따라 매핑을 세분화할 수 있도록 해주는 기능이다. 이 기능을 통해 개발자는 특정 미디어 타입에 대한 요청만 처리할 수 있도록 컨트롤러 메소드를 구성할 수 있다.
△ Content-Type 기반 요청 매핑
Content-Type은 HTTP 요청의 헤더 중 하나로, 클라이언트가 서버에 보내는 데이터의 유형을 나타낸다. 예를 들어, JSON 데이터를 보내는 경우 Content-Type은 application/json이 된다. Spring MVC에서는 이 Content-Type을 기반으로 요청을 매핑할 수 있다.
▼ Content-Type이 application/json인 요청만 처리하는 메소드 예시
@PostMapping(path = "/pets", consumes = "application/json")
public void addPet(@RequestBody Pet pet) {
// ...
}
POST 요청이 /pets 경로로 들어오고, Content-Type이 application/json일 때만 addPet 메소드를 호출한다.
△ consumes 속성
◎ 속성의 역할
- consumes 속성은 요청 매핑에서 특정 미디어 타입을 명시적으로 지정한다.
- 요청의 Content-Type 헤더가 이 값과 일치하는 경우에만 해당 메소드가 실행된다.
◎ 부정 표현식
- consumes 속성은 부정 표현식을 지원한다. 예를 들어 !text/plain는 text/plain 외의 모든 콘텐츠 유형을 의미한다.
@PostMapping(path = "/pets", consumes = "!text/plain")
public void addPet(@RequestBody Pet pet) {
// ...
}
Content-Type이 text/plain이 아닌 요청만 처리한다.
△ 클래스 레벨의 consumes : @Consumes
◎ 공유 속성
- consumes 속성을 클래스 레벨에 선언하여 모든 메소드에서 사용할 수 있다. 이렇게 하면 클래스 내의 모든 메소드가 동일한 Content-Type을 사용할 수 있다.
@RestController
@RequestMapping("/pets")
@Consumes("application/json")
public class PetController {
@PostMapping
public void addPet(@RequestBody Pet pet) {
// ...
}
@PutMapping
public void updatePet(@RequestBody Pet pet) {
// ...
}
}
PetController 클래스의 모든 메소드는 Content-Type이 application/json일 때만 호출된다.
◎ 메서드 레벨의 오버라이드
- 클래스 레벨에서 설정한 consumes 속성은 메소드 레벨에서 오버라이드된다. 즉, 특정 메소드에서 consumes 속성을 설정하면 클래스 레벨의 설정이 무시되고 해당 메소드에서 지정한 설정이 적용된다.
@RestController
@RequestMapping("/pets")
@Consumes("application/json")
public class PetController {
@PostMapping(consumes = "application/xml")
public void addPet(@RequestBody Pet pet) {
// 이 메소드는 application/xml을 소비합니다.
}
}
addPet 메소드는 application/xml 콘텐츠 유형을 소비하므로 클래스 레벨에서 설정한 application/json은 무시된다.
◎ MediaType 상수
- Spring MVC에서는 자주 사용되는 미디어 타입에 대한 상수를 제공하는 MediaType 클래스를 제공한다. 이를 통해 코드의 가독성을 높이고 오타를 방지할 수 있다.
import org.springframework.http.MediaType;
@PostMapping(path = "/pets", consumes = MediaType.APPLICATION_JSON_VALUE)
public void addPet(@RequestBody Pet pet) {
// ...
}
MediaType.APPLICATION_JSON_VALUE를 사용하여 미디어 타입을 지정할 수 있다.
◎ 정리
Spring MVC에서 Consumable Media Types는 클라이언트의 요청 데이터 타입에 따라 컨트롤러 메소드를 세분화하여 매핑할 수 있도록 해준다. consumes 속성을 사용하여 특정 Content-Type에 대한 요청만 처리할 수 있으며, 부정 표현식도 지원하여 더 유연한 매핑이 가능하다. 클래스 레벨과 메소드 레벨에서 설정을 통해 요구사항에 맞게 효과적으로 요청을 처리할 수 있습니다. MediaType 상수를 활용하면 코드의 가독성과 유지보수성을 높일 수 있다.
[ ▷ Producible Media Types ]
Spring에서는 produces 속성을 사용하여 메서드가 HTTP 요청에 대한 응답으로 생성할 수 있는 미디어 유형을 지정한다. 이는 REST API에서 클라이언트가 데이터의 다양한 형식을 요청할 수 있기 때문에 특히 중요하다(JSON, XML).
△ 주요 포인트
◎ Request Mapping
@GetMapping 주석은 @RequestMapping(method = RequestMethod.GET)의 단축형이다. HTTP GET 요청을 컨트롤러의 특정 핸들러 메서드에 매핑하는 데 사용된다.
◎ Path Variable
@PathVariable 주석은 URI에서 값을 추출하는 데 사용된다. 제공된 예에서 petId는 URL 경로 /pets/{petId}에서 추출된 변수이다.
◎ Produces 속성
- 예제에서 @GetMapping 주석의 produces 속성은 "application/json"으로 설정되어 있다. 이는 이 메서드가 HTTP 요청의 Accept 헤더에 application/json이 포함된 경우에만 처리된다는 것을 의미한다.
- 만약 이 미디어 유형과 일치하지 않는 Accept 헤더가 있는 요청이 들어오면 Spring은 이 메서드를 사용하지 않으며 406 Not Acceptable 응답을 반환한다.
◎ 문자 세트
미디어 유형의 일부로 문자 세트를 지정할 수 있다. 예를 들어, produces = "application/json; charset=UTF-8"은 이 메서드가 UTF-8로 인코딩된 JSON 데이터를 생성함을 나타낸다.
◎ Negated Expressions
특정 미디어 유형을 제외하기 위해 부정 표현을 사용할 수 있다. 예를 들어, produces = "!text/plain"은 이 메서드가 text/plain이 아닌 모든 콘텐츠 유형을 처리함을 의미다.
◎ Class-Level vs. Method-Level
- produces 속성을 클래스 수준에서 선언할 수 있으며, 이는 해당 클래스의 모든 메서드에 적용된다.
- 그러나 메서드 수준에서 produces 속성을 지정하면 클래스 수준 선언을 무시다. 이는 각 메서드가 생성할 수 있는 내용을 보다 세밀하게 제어할 수 있게 해준다.
◎ MediaType 상수
- Spring은 MediaType 클래스에서 일반적으로 사용되는 미디어 유형에 대한 상수를 제공한다.
- ediaType.APPLICATION_JSON_VALUE: "application/json"에 해당 한다.
- MediaType.APPLICATION_XML_VALUE: "application/xml"에 해당 한다.
이러한 상수를 사용하면 오타를 방지하고 코드의 가독성을 높일 수 있다.
△ 코드 예제 설명
@GetMapping(path = "/pets/{petId}", produces = "application/json")
@ResponseBody
public Pet getPet(@PathVariable String petId) {
// ...
}
- @GetMapping: 이 메서드는 /pets/{petId} 엔드포인트에서 GET 요청을 처리한다.
- produces = "application/json": 이 메서드는 JSON을 수신할 수 있는 요청에 대해서만 응답한다.
- @ResponseBody: 이는 메서드의 반환 값이 HTTP 응답 본문에 직접 작성됨을 나타내며, 주어진 produces 속성에 따라 일반적으로 JSON으로 반환된다.
- @PathVariable String petId: 이는 URL의 {petId} 부분을 캡처하여 메서드 매개변수로 사용 가능하게 한다.
△ 리액티브 스택에서의 동등한 예
리액티브 스택(스프링 WebFlux 사용)에서는 동일한 개념이 적용된다. 일반적으로 @GetMapping 주석과 유사한 produces 속성을 사용한다. 그러나 반환 유형은 구체적인 유형 대신 Mono<Pet> 또는 Flux<Pet>과 같은 리액티브 유형이 된다.
▼ 유사한 메서드를 리액티브 컨트롤러에서 정의하는 방법
@GetMapping(path = "/pets/{petId}", produces = MediaType.APPLICATION_JSON_VALUE)
public Mono<Pet> getPet(@PathVariable String petId) {
// ... Mono를 반환하여 Pet 포함
}
△ 정리
- Producible Media Types은 API가 응답할 수 있는 형식을 제어할 수 있게 해주어, API를 유연하고 클라이언트의 요구에 응답할 수 있도록 한다.
- produces 속성을 효과적으로 사용하면 콘텐츠 협상을 관리하여 클라이언트가 기대하는 형식으로 데이터를 받을 수 있도록 한다.
- 이러한 개념이 전통적인 MVC와 리액티브 스택 모두에 어떻게 적용되는지를 이해하는 것은 강력한 Spring 애플리케이션을 구축하는데 중요하다.
[ ▷ Parameters, headers ]
Spring MVC에서 요청 매핑을 파라미터 및 헤더에 기반하여 세분화할 수 있다. 이를 통해 특정 파라미터의 존재 여부(myParam), 부재 여부(!myParam), 또는 특정 값(myParam=myValue)에 따라 어떤 요청을 처리할지 제어할 수 있다.
△ 주요 개념
◎ 요청 파라미터
요청 매핑을 요청 파라미터와 관련된 조건으로 세분화할 수 있습니다.
조건으로 아래 내용을 지정할 수 있다.
- 존재: 파라미터가 존재하는지 확인 (myParam)
- 부재: 파라미터가 존재하지 않는지 확인 (!myParam)
- 특정 값: 파라미터가 특정 값과 일치하는지 확인 (myParam=myValue)
/pets/123?myParam=myValue
@GetMapping(path = "/pets/{petId}", params = "myParam=myValue")
public void findPet(@PathVariable String petId) { // ... }
이 경우 findPet 메서드는 요청에 myParam이라는 쿼리 파라미터가 myValue라는 값으로 포함되어 있는 경우에만 호출된다.
여기서 URL '123'은 petId의 값이며, myParam은 myValue로 포함된다.
◎ 요청 헤더
요청 파라미터와 유사하게, 요청 헤더와 관련된 조건으로 요청 매핑을 세분화할 수 있다.
조건으로 아래 내용을 지정할 수 있다.
- 특정 헤더 값: 헤더가 특정 값과 동일한지 확인 (myHeader=myValue)
/pets/123 (헤더: myHeader: myValue 포함)
@GetMapping(path = "/pets/{petId}", headers = "myHeader=myValue")
public void findPet(@PathVariable String petId) { // ... }
이 경우 findPet 메서드는 요청에 myHeader라는 헤더가 myValue라는 값으로 포함되어 있을 때만 호출된다.
여기서 URL은 헤더를 표시하지 않지만, 브라우저 또는 클라이언트는 요청과 함께 myHeader 헤더를 전송해야 한다.
◎ Content-Type 및 Accept 매칭
- headers 조건을 사용하여 Content-Type 및 Accept 헤더와 일치시킬 수도 있다.
- 그러나 일반적으로 콘텐츠 협상을 위해 consumes 및 produces 속성을 사용하는 것이 더 좋다. 이는 서버가 수용할 수 있는 데이터 유형과 생성할 수 있는 데이터 유형에 중점을 둔다.
△ 리액티브 스택에서의 동등한 예
리액티브 스택(스프링 WebFlux 사용)에서는 유사한 방식으로 @GetMapping 주석을 사용하여 동일한 동작을 수행할 수 있습니다. 요청 파라미터와 헤더를 다루는 방식은 개념적으로 동일하지만, 반환 유형은 일반적으로 리액티브 유형(Mono 또는 Flux)이 된다.
▼ 리액티브 컨트롤러에서 유사한 메서드를 정의하는 방법
@GetMapping(path = "/pets/{petId}", params = "myParam=myValue")
public Mono<Void> findPet(@PathVariable String petId) {
// ... 리액티브 구현
}
@GetMapping(path = "/pets/{petId}", headers = "myHeader=myValue")
public Mono<Void> findPet(@PathVariable String petId) {
// ... 리액티브 구현
}
△ 정리
- 요청 파라미터: 파라미터에 기반하여 요청을 세분화하면 메서드가 처리할 요청을 보다 정밀하게 제어할 수 있다.
- 요청 헤더: 파라미터와 유사하게 헤더를 사용하여 요청 처리를 제어할 수 있다.
- 모범 사례: 콘텐츠 유형을 처리할 때는 Content-Type 및 Accept 헤더를 직접 매칭하기보다는 consumes 및 produces 속성을 사용하는 것이 좋다.
- 리액티브 스택: 리액티브 스택에서도 유사한 접근 방식이 유지되며, 반환 값으로는 리액티브 유형을 사용한다.
[ ▷ HTTP HEAD, OPTIONS ]
△ HTTP HEAD 요청
◎ 목적
HTTP HEAD 메서드는 GET 메서드와 유사하지만, 응답의 본문이 아닌 헤더만 요청한다. 이는 전체 리소스를 다운로드하지 않고도 메타데이터 (Content-Length 또는 Content-Type)를 가져오는 데 유용하다.
◎ 자동 지원
- Spring MVC는 @GetMapping 및 @RequestMapping(method = RequestMethod.GET)을 통해 HEAD 메서드를 투명하게 지원한다.
- 이는 GET 요청을 처리하기 위해 정의된 컨트롤러 메서드가 HEAD 요청에도 자동으로 응답한다는 것을 의미한다. 추가적인 코드나 별도의 메서드 정의가 필요하지 않다.
◎ 응답 처리
- HEAD 요청을 수신하면, Spring MVC는 jakarta.servlet.http.HttpServlet 기반의 응답 래퍼를 사용하여 응답의 Content-Length 헤더가 GET 응답에서 전송될 바이트 수로 올바르게 설정되도록 보장한다.
- 중요하게도, 실제로는 본문이 전송되지 않고 헤더만 포함된다.
@GetMapping("/pets/{petId}")
public Pet getPet(@PathVariable String petId) {
// ... Pet 객체 반환
}
/pets/123에 대한 HEAD 요청은 헤더(포함된 Content-Length)를 반환하지만 Pet 객체 자체는 반환하지 않는다.
△ HTTP OPTIONS 요청
◎ 목적
HTTP OPTIONS 메서드는 대상 리소스에 대한 통신 옵션을 설명하는 데 사용된다. 이를 통해 클라이언트는 특정 엔드포인트에 대해 서버가 지원하는 HTTP 메서드를 확인할 수 있다.
◎ 기본 처리
- OPTIONS 요청을 수신하면, Spring MVC는 Allow 응답 헤더를 설정한다. 이 헤더는 컨트롤러 메서드에서 매칭된 URL 패턴에 대해 지원되는 HTTP 메서드를 나열한다.
- 다양한 @RequestMapping 메서드가 정의되어 있다면, 해당 메서드에 따라 Allow 헤더에 이 메서드들이 반영된다.
◎ 메서드 선언 없음
HTTP 메서드 선언이 없는 @RequestMapping의 경우 (@RequestMapping("/pets/{petId}")), 기본 Allow 헤더는 다음과 같이 설정된다.
GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS
◎ 모범 사례
컨트롤러 메서드는 항상 지원하는 HTTP 메서드를 명시적으로 선언하는 것이 좋다. 이를 통해 코드 가독성이 향상되고, 엔드포인트의 의도가 명확해진다.
@RequestMapping("/pets/{petId}")
public Pet getPet(@PathVariable String petId) {
// ... Pet 객체 반환
}
/pets/123에 대한 OPTIONS 요청은 지원되는 메서드를 나타내는 Allow 헤더와 함께 응답한다.
△ 명시적 매핑
@RequestMapping 메서드를 HTTP HEAD 및 OPTIONS 요청에 명시적으로 매핑할 수 있지만, 일반적인 경우에는 필요하지 않다. 위에서 설명한 기본 동작이 대부분의 경우 효과적으로 처리된다.
▼ HEAD 및 OPTIONS 요청을 명시적으로 처리하는 예시
@RequestMapping(path = "/pets/{petId}", method = RequestMethod.OPTIONS)
public ResponseEntity<?> optionsPet() {
return ResponseEntity.ok()
.allow(HttpMethod.GET, HttpMethod.HEAD, HttpMethod.POST)
.build();
}
@RequestMapping(path = "/pets/{petId}", method = RequestMethod.HEAD)
public ResponseEntity<?> headPet(@PathVariable String petId) {
// 일반적으로 응답 본문의 길이를 가져옵니다.
return ResponseEntity.ok()
.contentLength(123) // 실제 콘텐츠 길이로 대체
.build();
}
△ 정리
◎ HTTP HEAD
- @GetMapping 및 @RequestMapping(method = RequestMethod.GET)에 의해 자동으로 지원된다.
- 본문 없이 헤더만 반환하며, Content-Length를 포함한다.
◎ HTTP OPTIONS
- 요청된 URL 패턴에 대해 지원되는 메서드를 나열하는 Allow 헤더를 자동으로 제공한다.
- 기본 동작이 대부분의 경우를 처리하므로, 명시적 매핑이 필요하지 않다.
◎ 모범 사례
명확성과 유지 관리를 위해 항상 지원하는 HTTP 메서드를 명시적으로 선언하는 것이 좋다
Spring MVC가 HTTP HEAD 및 OPTIONS 요청을 처리하는 방식을 이해하면, 보다 유익하고 효율적인 API를 생성할 수 있다. 또한 클라이언트가 HTTP 표준을 준수하면서 API와 상호작용할 수 있도록 도와준다.
[ ▷ Custom Annotations ]
Spring MVC는 요청 매핑을 위한 구성 어노테이션(composed annotations)의 사용을 지원한다. 이러한 어노테이션은 @RequestMapping으로 메타 어노테이션되어 있으며, 보다 좁고 구체적인 목적을 가진 @RequestMapping 속성의 하위 집합(또는 전체)을 재선언하기 위해 구성된다.
@GetMapping, @PostMapping, @PutMapping, @DeleteMapping, @PatchMapping이 구성 어노테이션의 예시이다. 이들은 대부분의 컨트롤러 메서드가 @RequestMapping을 사용하는 대신 특정 HTTP 메서드에 매핑되어야 한다고 주장할 수 있기 때문에 제공된다. 기본적으로 @RequestMapping은 모든 HTTP 메서드와 매칭된다. 구성 어노테이션을 구현하는 방법에 대한 예가 필요하다면, 이러한 어노테이션이 어떻게 선언되는지를 살펴보면 된다.
참고: @RequestMapping은 동일한 요소(클래스, 인터페이스 또는 메서드)에서 선언된 다른 @RequestMapping 어노테이션과 함께 사용할 수 없다. 동일한 요소에서 여러 개의 @RequestMapping 어노테이션이 감지되면 경고가 로그에 기록되며, 첫 번째 매핑만 사용된다. 이는 @GetMapping, @PostMapping 등과 같은 구성된 @RequestMapping 어노테이션에도 적용된다.
Spring MVC는 또한 사용자 정의 요청 매핑 속성과 사용자 정의 요청 매칭 로직을 지원한다. 이는 더 고급 옵션으로, RequestMappingHandlerMapping을 서브클래싱하고 getCustomMethodCondition 메서드를 오버라이드하여 사용자 정의 속성을 확인하고 자체 RequestCondition을 반환할 수 있다.
[ 출처 ]: https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-controller.html
'Web on Servlet Stack' 카테고리의 다른 글
Path Matching (0) | 2024.10.15 |
---|---|
View Resolvers (0) | 2024.10.15 |
View Controllers (0) | 2024.10.15 |
Content Types (1) | 2024.10.15 |
Validation (0) | 2024.10.15 |