Loading...
Spring Framework Reference Documentation 7.0.2의 Functional Endpoints의 한국어 번역본입니다.
아래의 경우에 피드백에서 신고해주신다면 반영하겠습니다.
감사합니다 :)
See equivalent in the Reactive stack
Spring Web MVC는 WebMvc.fn을 포함합니다. WebMvc.fn은 함수를 사용하여 요청을 라우팅하고 처리하며, contract가 immutability를 위해 설계된 경량의 함수형 프로그래밍 모델입니다. 이는 어노테이션 기반 프로그래밍 모델의 대안이지만, 그 외에는 동일한 DispatcherServlet에서 실행됩니다.
See equivalent in the Reactive stack
WebMvc.fn에서 HTTP 요청은 HandlerFunction으로 처리됩니다. HandlerFunction은 ServerRequest를 받고 ServerResponse를 반환하는 함수입니다.
요청과 응답 객체 모두 HTTP 요청과 응답에 대한 JDK 8 친화적인 접근을 제공하는 immutable contract를 가집니다.
HandlerFunction은 어노테이션 기반 프로그래밍 모델에서 @RequestMapping 메서드 본문에 해당합니다.
들어오는 요청은 RouterFunction을 사용하여 핸들러 함수로 라우팅됩니다. RouterFunction은
ServerRequest를 받고 optional HandlerFunction(즉, Optional<HandlerFunction>)을 반환하는 함수입니다.
라우터 함수가 일치하면 핸들러 함수가 반환되고, 그렇지 않으면 비어 있는 Optional이 반환됩니다.
RouterFunction은 @RequestMapping 어노테이션에 해당하지만, 라우터 함수가 데이터뿐 아니라 동작도 제공한다는 중요한 차이가 있습니다.
RouterFunctions.route()는 다음 예제와 같이 라우터 생성을 용이하게 하는 라우터 빌더를 제공합니다:
1import static org.springframework.http.MediaType.APPLICATION_JSON; 2import static org.springframework.web.servlet.function.RequestPredicates.*; 3import static org.springframework.web.servlet.function.RouterFunctions.route; 4 5PersonRepository repository = ... 6PersonHandler handler = new PersonHandler(repository); 7 8RouterFunction<ServerResponse> route = route() // (1) 9 .GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson) 10 .GET("/person", accept(APPLICATION_JSON), handler::listPeople) 11 .POST("/person", handler::createPerson) 12 .build(); 13 14public class PersonHandler { 15 16 // ... 17 18 public ServerResponse listPeople(ServerRequest request) { 19 // ... 20 } 21 22 public ServerResponse createPerson(ServerRequest request) { 23 // ... 24 } 25 26 public ServerResponse getPerson(ServerRequest request) { 27 // ... 28 } 29}
| 1 | route()를 사용하여 라우터를 생성합니다. |
1import org.springframework.web.servlet.function.router 2 3val repository: PersonRepository = ... 4val handler = PersonHandler(repository) 5 6val route = router { // (1) 7 accept(APPLICATION_JSON).nest { 8 GET("/person/{id}", handler::getPerson) 9 GET("/person", handler::listPeople) 10 } 11 POST("/person", handler::createPerson) 12} 13 14class PersonHandler(private val repository: PersonRepository) { 15 16 // ... 17 18 fun listPeople(request: ServerRequest): ServerResponse { 19 // ... 20 } 21 22 fun createPerson(request: ServerRequest): ServerResponse { 23 // ... 24 } 25 26 fun getPerson(request: ServerRequest): ServerResponse { 27 // ... 28 } 29}
| 1 | 라우터 DSL을 사용하여 라우터를 생성합니다. |
RouterFunction을 빈으로 등록하면, 예를 들어 @Configuration 클래스에서 노출함으로써 서블릿에 의해 자동으로 감지되며,
이는 Running a Server에서 설명한 대로입니다.
See equivalent in the Reactive stack
ServerRequest와 ServerResponse는 immutable 인터페이스이며, 헤더, 바디, 메서드, 상태 코드를 포함한
HTTP 요청과 응답에 대한 JDK 8 친화적인 접근을 제공합니다.
ServerRequest는 HTTP 메서드, URI, 헤더, 쿼리 파라미터에 대한 접근을 제공하며,
바디에 대한 접근은 body 메서드를 통해 제공됩니다.
다음 예제는 요청 바디를 String으로 추출합니다:
1String string = request.body(String.class);
1val string = request.body<String>()
다음 예제는 바디를 List<Person>으로 추출하는 방법을 보여줍니다.
여기서 Person 객체는 JSON 또는 XML과 같은 직렬화된 형태에서 디코딩됩니다:
1List<Person> people = request.body(new ParameterizedTypeReference<List<Person>>() {});
1val people = request.body<Person>()
다음 예제는 파라미터에 접근하는 방법을 보여줍니다:
1MultiValueMap<String, String> params = request.params();
1val map = request.params()
다음은 DataBinder를 통해 요청 파라미터, URI 변수, 헤더를 바인딩하는 방법과
DataBinder를 커스터마이징하는 방법을 보여줍니다:
1Pet pet = request.bind(Pet.class, dataBinder -> dataBinder.setAllowedFields("name"));
1val pet = request.bind(Pet::class.java) { dataBinder -> dataBinder.setAllowedFields("name") }
ServerResponse는 HTTP 응답에 대한 접근을 제공하며, immutable이므로 build 메서드를 사용하여 생성할 수 있습니다.
빌더를 사용하여 응답 상태를 설정하고, 응답 헤더를 추가하거나, 바디를 제공할 수 있습니다.
다음 예제는 JSON 콘텐츠를 가진 200 (OK) 응답을 생성합니다:
1Person person = ... 2ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person);
1val person: Person = ... 2ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person)
다음 예제는 Location 헤더와 바디가 없는 201 (CREATED) 응답을 빌드하는 방법을 보여줍니다:
1URI location = ... 2ServerResponse.created(location).build();
1val location: URI = ... 2ServerResponse.created(location).build()
또한 바디로 CompletableFuture, Publisher 또는 ReactiveAdapterRegistry에서 지원하는 기타 타입 형태의
비동기 결과를 사용할 수도 있습니다. 예를 들면 다음과 같습니다:
1Mono<Person> person = webClient.get().retrieve().bodyToMono(Person.class); 2ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person);
1val person = webClient.get().retrieve().awaitBody<Person>() 2ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person)
바디뿐만 아니라 상태나 헤더도 비동기 타입에 기반하는 경우,
ServerResponse의 static async 메서드를 사용할 수 있으며,
이는 CompletableFuture<ServerResponse>, Publisher<ServerResponse> 또는
ReactiveAdapterRegistry에서 지원하는 기타 비동기 타입을 받습니다. 예를 들면 다음과 같습니다:
1Mono<ServerResponse> asyncResponse = webClient.get().retrieve().bodyToMono(Person.class) 2 .map(p -> ServerResponse.ok().header("Name", p.name()).body(p)); 3ServerResponse.async(asyncResponse);
Server-Sent Events는
ServerResponse의 static sse 메서드를 통해 제공할 수 있습니다.
해당 메서드가 제공하는 빌더를 사용하면 String 또는 JSON으로 변환될 다른 객체를 전송할 수 있습니다.
예를 들면 다음과 같습니다:
1public RouterFunction<ServerResponse> sse() { 2 return route(GET("/sse"), request -> ServerResponse.sse(sseBuilder -> { 3 // Save the sseBuilder object somewhere.. 4 })); 5} 6 7// In some other thread, sending a String 8sseBuilder.send("Hello world"); 9 10// Or an object, which will be transformed into JSON 11Person person = ... 12sseBuilder.send(person); 13 14// Customize the event by using the other methods 15sseBuilder.id("42") 16 .event("sse event") 17 .data(person); 18 19// and done at some point 20sseBuilder.complete();
1fun sse(): RouterFunction<ServerResponse> = router { 2 GET("/sse") { request -> ServerResponse.sse { sseBuilder -> 3 // Save the sseBuilder object somewhere.. 4 } } 5} 6 7// In some other thread, sending a String 8sseBuilder.send("Hello world") 9 10// Or an object, which will be transformed into JSON 11val person = ... 12sseBuilder.send(person) 13 14// Customize the event by using the other methods 15sseBuilder.id("42") 16 .event("sse event") 17 .data(person) 18 19// and done at some point 20sseBuilder.complete()
다음 예제와 같이 핸들러 함수를 람다로 작성할 수 있습니다:
1HandlerFunction<ServerResponse> helloWorld = 2 request -> ServerResponse.ok().body("Hello World");
1val helloWorld: (ServerRequest) -> ServerResponse = 2 { ServerResponse.ok().body("Hello World") }
이는 편리하지만, 애플리케이션에서는 여러 함수가 필요하며, 여러 개의 인라인 람다는 복잡해질 수 있습니다.
따라서 관련 핸들러 함수를 핸들러 클래스에 함께 그룹화하는 것이 유용하며,
이는 어노테이션 기반 애플리케이션에서 @Controller와 유사한 역할을 합니다.
예를 들어, 다음 클래스는 리액티브 Person 리포지토리를 노출합니다:
1import static org.springframework.http.MediaType.APPLICATION_JSON; 2import static org.springframework.web.reactive.function.server.ServerResponse.ok; 3 4public class PersonHandler { 5 6 private final PersonRepository repository; 7 8 public PersonHandler(PersonRepository repository) { 9 this.repository = repository; 10 } 11 12 public ServerResponse listPeople(ServerRequest request) { // (1) 13 List<Person> people = repository.allPeople(); 14 return ok().contentType(APPLICATION_JSON).body(people); 15 } 16 17 public ServerResponse createPerson(ServerRequest request) throws Exception { // (2) 18 Person person = request.body(Person.class); 19 repository.savePerson(person); 20 return ok().build(); 21 } 22 23 public ServerResponse getPerson(ServerRequest request) { // (3) 24 int personId = Integer.parseInt(request.pathVariable("id")); 25 Person person = repository.getPerson(personId); 26 if (person != null) { 27 return ok().contentType(APPLICATION_JSON).body(person); 28 } 29 else { 30 return ServerResponse.notFound().build(); 31 } 32 } 33 34}
| 1 | listPeople는 리포지토리에서 발견된 모든 Person 객체를 JSON으로 반환하는 핸들러 함수입니다. |
| 2 | createPerson은 요청 바디에 포함된 새 Person을 저장하는 핸들러 함수입니다. |
| 3 | getPerson은 id 경로 변수로 식별되는 단일 person을 반환하는 핸들러 함수입니다.<br>해당 Person을 리포지토리에서 조회하고, 발견되면 JSON 응답을 생성합니다.<br>발견되지 않으면 404 Not Found 응답을 반환합니다. |
1class PersonHandler(private val repository: PersonRepository) { 2 3 fun listPeople(request: ServerRequest): ServerResponse { // (1) 4 val people: List<Person> = repository.allPeople() 5 return ok().contentType(APPLICATION_JSON).body(people) 6 } 7 8 fun createPerson(request: ServerRequest): ServerResponse { // (2) 9 val person = request.body<Person>() 10 repository.savePerson(person) 11 return ok().build() 12 } 13 14 fun getPerson(request: ServerRequest): ServerResponse { // (3) 15 val personId = request.pathVariable("id").toInt() 16 return repository.getPerson(personId)?.let { ok().contentType(APPLICATION_JSON).body(it) } 17 ?: ServerResponse.notFound().build() 18 19 } 20}
| 1 | listPeople는 리포지토리에서 발견된 모든 Person 객체를 JSON으로 반환하는 핸들러 함수입니다. |
| 2 | createPerson은 요청 바디에 포함된 새 Person을 저장하는 핸들러 함수입니다. |
| 3 | getPerson은 id 경로 변수로 식별되는 단일 person을 반환하는 핸들러 함수입니다.<br>해당 Person을 리포지토리에서 조회하고, 발견되면 JSON 응답을 생성합니다.<br>발견되지 않으면 404 Not Found 응답을 반환합니다. |
함수형 엔드포인트는 Spring의 validation facilities를 사용하여
요청 바디에 검증을 적용할 수 있습니다. 예를 들어, Person에 대한 custom Spring
Validator 구현이 주어졌다고 가정합니다:
1public class PersonHandler { 2 3 private final Validator validator = new PersonValidator(); // (1) 4 5 // ... 6 7 public ServerResponse createPerson(ServerRequest request) { 8 Person person = request.body(Person.class); 9 validate(person); // (2) 10 repository.savePerson(person); 11 return ok().build(); 12 } 13 14 private void validate(Person person) { 15 Errors errors = new BeanPropertyBindingResult(person, "person"); 16 validator.validate(person, errors); 17 if (errors.hasErrors()) { 18 throw new ServerWebInputException(errors.toString()); // (3) 19 } 20 } 21}
| 1 | Validator 인스턴스를 생성합니다. |
| 2 | 검증을 적용합니다. |
| 3 | 400 응답을 위한 예외를 발생시킵니다. |
1class PersonHandler(private val repository: PersonRepository) { 2 3 private val validator = PersonValidator() // (1) 4 5 // ... 6 7 fun createPerson(request: ServerRequest): ServerResponse { 8 val person = request.body<Person>() 9 validate(person) // (2) 10 repository.savePerson(person) 11 return ok().build() 12 } 13 14 private fun validate(person: Person) { 15 val errors: Errors = BeanPropertyBindingResult(person, "person") 16 validator.validate(person, errors) 17 if (errors.hasErrors()) { 18 throw ServerWebInputException(errors.toString()) // (3) 19 } 20 } 21}
| 1 | Validator 인스턴스를 생성합니다. |
| 2 | 검증을 적용합니다. |
| 3 | 400 응답을 위한 예외를 발생시킵니다. |
Handler는 또한 LocalValidatorFactoryBean을 기반으로 한 global Validator 인스턴스를 생성하고 주입함으로써
표준 빈 검증 API(JSR-303)를 사용할 수도 있습니다.
Spring Validation을 참조하십시오.
RouterFunctionSee equivalent in the Reactive stack
라우터 함수는 요청을 해당하는 HandlerFunction으로 라우팅하는 데 사용됩니다.
일반적으로 라우터 함수를 직접 작성하지 않고, RouterFunctions 유틸리티 클래스의 메서드를 사용하여 생성합니다.
RouterFunctions.route()(파라미터 없음)는 라우터 함수를 생성하기 위한 fluent 빌더를 제공하며,
RouterFunctions.route(RequestPredicate, HandlerFunction)는 라우터를 직접 생성하는 방법을 제공합니다.
일반적으로 route() 빌더를 사용하는 것이 권장되며,
이는 발견하기 어려운 static import 없이 typical 매핑 시나리오를 위한 편리한 shortcut을 제공합니다.
예를 들어, 라우터 함수 빌더는 GET 요청에 대한 매핑을 생성하기 위해
GET(String, HandlerFunction) 메서드를 제공하며, POST에 대해서는 POST(String, HandlerFunction)을 제공합니다.
HTTP 메서드 기반 매핑 외에도, 라우트 빌더는 요청을 매핑할 때 추가 프레디케이트를 도입하는 방법을 제공합니다.
각 HTTP 메서드에 대해 RequestPredicate를 파라미터로 받는 오버로드된 변형이 있으며,
이를 통해 추가 제약 조건을 표현할 수 있습니다.
자신만의 RequestPredicate를 작성할 수 있지만, RequestPredicates 유틸리티 클래스는
HTTP 메서드, 요청 경로, 헤더, API version 등에 기반한 매칭을 위한
공통 필요 사항에 대해 built-in 옵션을 제공합니다.
다음 예제는 Accept 헤더 요청 프레디케이트를 사용합니다:
1RouterFunction<ServerResponse> route = RouterFunctions.route() 2 .GET("/hello-world", accept(MediaType.TEXT_PLAIN), 3 request -> ServerResponse.ok().body("Hello World")).build();
1import org.springframework.web.servlet.function.router 2 3val route = router { 4 GET("/hello-world", accept(TEXT_PLAIN)) { 5 ServerResponse.ok().body("Hello World") 6 } 7}
여러 요청 프레디케이트를 다음을 사용하여 함께 조합할 수 있습니다:
RequestPredicate.and(RequestPredicate) — 두 프레디케이트 모두 일치해야 합니다.RequestPredicate.or(RequestPredicate) — 둘 중 하나가 일치할 수 있습니다.RequestPredicates의 많은 프레디케이트는 합성되어 있습니다.
예를 들어, RequestPredicates.GET(String)은 RequestPredicates.method(HttpMethod)와
RequestPredicates.path(String)에서 합성됩니다.
위에 표시된 예제 역시 두 개의 요청 프레디케이트를 사용하며, 빌더는 내부적으로
RequestPredicates.GET을 사용하고 이를 accept 프레디케이트와 합성합니다.
라우터 함수는 순서대로 평가됩니다. 첫 번째 라우트가 일치하지 않으면 두 번째가 평가되는 식입니다. 따라서 보다 구체적인 라우트를 일반적인 라우트보다 먼저 선언하는 것이 좋습니다. 이는 나중에 설명할 Spring 빈으로 라우터 함수를 등록할 때도 중요합니다. 이는 "가장 구체적인" 컨트롤러 메서드가 자동으로 선택되는 어노테이션 기반 프로그래밍 모델과는 다른 동작입니다.
라우터 함수 빌더를 사용할 때, 정의된 모든 라우트는 하나의 RouterFunction으로 합성되며,
이는 build()에서 반환됩니다.
여러 라우터 함수를 함께 합성하는 다른 방법도 있습니다:
RouterFunctions.route() 빌더의 add(RouterFunction)RouterFunction.and(RouterFunction)RouterFunction.andRoute(RequestPredicate, HandlerFunction) — 중첩된
RouterFunctions.route()와 함께 사용하는 RouterFunction.and()의 shortcut입니다.다음 예제는 네 개의 라우트를 합성하는 방법을 보여줍니다:
1import static org.springframework.http.MediaType.APPLICATION_JSON; 2import static org.springframework.web.servlet.function.RequestPredicates.*; 3 4PersonRepository repository = ... 5PersonHandler handler = new PersonHandler(repository); 6 7RouterFunction<ServerResponse> otherRoute = ... 8 9RouterFunction<ServerResponse> route = route() 10 .GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson) // (1) 11 .GET("/person", accept(APPLICATION_JSON), handler::listPeople) // (2) 12 .POST("/person", handler::createPerson) // (3) 13 .add(otherRoute) // (4) 14 .build();
| 1 | JSON과 일치하는 Accept 헤더를 가진 GET /person/{id}는<br>PersonHandler.getPerson으로 라우팅됩니다. |
| 2 | JSON과 일치하는 Accept 헤더를 가진 GET /person는<br>PersonHandler.listPeople으로 라우팅됩니다. |
| 3 | 추가 프레디케이트 없이 POST /person는<br>PersonHandler.createPerson에 매핑되며, |
| 4 | otherRoute는 다른 곳에서 생성된 라우터 함수이며, 빌드된 라우트에 추가됩니다. |
1import org.springframework.http.MediaType.APPLICATION_JSON 2import org.springframework.web.servlet.function.router 3 4val repository: PersonRepository = ... 5val handler = PersonHandler(repository) 6 7val otherRoute = router { } 8 9val route = router { 10 GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson) // (1) 11 GET("/person", accept(APPLICATION_JSON), handler::listPeople) // (2) 12 POST("/person", handler::createPerson) // (3) 13}.and(otherRoute) // (4)
| 1 | JSON과 일치하는 Accept 헤더를 가진 GET /person/{id}는<br>PersonHandler.getPerson으로 라우팅됩니다. |
| 2 | JSON과 일치하는 Accept 헤더를 가진 GET /person는<br>PersonHandler.listPeople으로 라우팅됩니다. |
| 3 | 추가 프레디케이트 없이 POST /person는<br>PersonHandler.createPerson에 매핑되며, |
| 4 | otherRoute는 다른 곳에서 생성된 라우터 함수이며, 빌드된 라우트에 추가됩니다. |
라우터 함수 그룹이 공통 프레디케이트(예: 공통 경로)를 가지는 경우가 일반적입니다.
위 예제에서 공통 프레디케이트는 /person과 일치하는 경로 프레디케이트이며,
세 개의 라우트에서 사용됩니다.
어노테이션을 사용할 때는 type-level @RequestMapping 어노테이션을 사용하여 /person에 매핑함으로써
이 중복을 제거할 수 있습니다.
WebMvc.fn에서는 라우터 함수 빌더의 path 메서드를 통해 경로 프레디케이트를 공유할 수 있습니다.
예를 들어, 위 예제의 마지막 몇 줄은 중첩 라우트를 사용하여 다음과 같이 개선할 수 있습니다:
1RouterFunction<ServerResponse> route = route() 2 .path("/person", builder -> builder // (1) 3 .GET("/{id}", accept(APPLICATION_JSON), handler::getPerson) 4 .GET(accept(APPLICATION_JSON), handler::listPeople) 5 .POST(handler::createPerson)) 6 .build();
| 1 | path의 두 번째 파라미터가 라우터 빌더를 받는 consumer임에 주의하십시오. |
1import org.springframework.web.servlet.function.router 2 3val route = router { 4 "/person".nest { // (1) 5 GET("/{id}", accept(APPLICATION_JSON), handler::getPerson) 6 GET(accept(APPLICATION_JSON), handler::listPeople) 7 POST(handler::createPerson) 8 } 9}
| 1 | nest DSL을 사용합니다. |
경로 기반 네스팅이 가장 일반적이지만, 빌더의 nest 메서드를 사용하여
어떤 종류의 프레디케이트에도 네스팅할 수 있습니다.
위 예제에는 여전히 공통 Accept 헤더 프레디케이트 형태의 중복이 포함되어 있습니다.
nest 메서드를 accept와 함께 사용하여 더 개선할 수 있습니다:
1RouterFunction<ServerResponse> route = route() 2 .path("/person", b1 -> b1 3 .nest(accept(APPLICATION_JSON), b2 -> b2 4 .GET("/{id}", handler::getPerson) 5 .GET(handler::listPeople)) 6 .POST(handler::createPerson)) 7 .build();
1import org.springframework.web.servlet.function.router 2 3val route = router { 4 "/person".nest { 5 accept(APPLICATION_JSON).nest { 6 GET("/{id}", handler::getPerson) 7 GET("", handler::listPeople) 8 POST(handler::createPerson) 9 } 10 } 11}
라우터 함수는 API 버전에 의한 매칭을 지원합니다.
먼저
MVC Config에서 API 버저닝을 활성화한 다음,
version predicate를 다음과 같이 사용할 수 있습니다:
1RouterFunction<ServerResponse> route = RouterFunctions.route() 2 .GET("/hello-world", version("1.2"), 3 request -> ServerResponse.ok().body("Hello World")).build();
1val route = router { 2 GET("/hello-world", version("1.2")) { 3 ServerResponse.ok().body("Hello World") 4 } 5}
version 프레디케이트는 다음과 같을 수 있습니다:
기반 인프라스트럭처와 API 버저닝에 대한 지원에 대한 자세한 내용은 API Versioning을 참조하십시오.
WebMvc.fn은 리소스 제공에 대한 built-in 지원을 제공합니다.
아래에 설명된 기능 외에도, 보다 유연한 리소스 처리를 구현하는 것이 가능합니다.<br>이는<br>
RouterFunctions#resource(java.util.function.Function)덕분입니다.
지정된 프레디케이트와 일치하는 요청을 리소스로 리다이렉트하는 것이 가능합니다. 이는 예를 들어 Single Page Application에서 리다이렉트를 처리하는 데 유용할 수 있습니다.
1ClassPathResource index = new ClassPathResource("static/index.html"); 2List<String> extensions = List.of("js", "css", "ico", "png", "jpg", "gif"); 3RequestPredicate spaPredicate = path("/api/**").or(path("/error")).negate(); 4RouterFunction<ServerResponse> redirectToIndex = route() 5 .resource(spaPredicate, index) 6 .build();
1val redirectToIndex = router { 2 val index = ClassPathResource("static/index.html") 3 val spaPredicate = !(path("/api/**") or path("/error")) 4 resource(spaPredicate, index) 5}
주어진 패턴과 일치하는 요청을 주어진 루트 위치를 기준으로 하는 리소스로 라우팅하는 것도 가능합니다.
1Resource location = new FileUrlResource("public-resources/"); 2RouterFunction<ServerResponse> resources = RouterFunctions.resources("/resources/**", location);
1val location = FileUrlResource("public-resources/") 2val resources = router { resources("/resources/**", location) }
See equivalent in the Reactive stack
일반적으로 라우터 함수는
MVC Config를 통해
DispatcherHandler 기반 설정에서 실행되며,
이는 요청을 처리하는 데 필요한 컴포넌트를 선언하기 위해 Spring 설정을 사용합니다.
MVC Java 설정은 함수형 엔드포인트를 지원하기 위해 다음 인프라스트럭처 컴포넌트를 선언합니다:
RouterFunctionMapping: Spring 설정에서 하나 이상의 RouterFunction<?> 빈을
감지하고, 이를
정렬한 다음,
RouterFunction.andOther를 통해 결합하고, 요청을 결과로 합성된 RouterFunction으로 라우팅합니다.HandlerFunctionAdapter: DispatcherHandler가 요청에 매핑된 HandlerFunction을 호출할 수 있도록 하는 simple 어댑터입니다.위 컴포넌트는 함수형 엔드포인트가 DispatcherServlet 요청 처리 라이프사이클 내에
적합하도록 하며, 선언된 어노테이션 컨트롤러가 있는 경우 잠재적으로 함께 실행되도록 합니다.
또한 이는 Spring Boot Web starter에서 함수형 엔드포인트가 활성화되는 방식이기도 합니다.
다음 예제는 WebMvc Java 설정을 보여줍니다:
1@Configuration 2@EnableMvc 3public class WebConfig implements WebMvcConfigurer { 4 5 @Bean 6 public RouterFunction<?> routerFunctionA() { 7 // ... 8 } 9 10 @Bean 11 public RouterFunction<?> routerFunctionB() { 12 // ... 13 } 14 15 // ... 16 17 @Override 18 public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { 19 // configure message conversion... 20 } 21 22 @Override 23 public void addCorsMappings(CorsRegistry registry) { 24 // configure CORS... 25 } 26 27 @Override 28 public void configureViewResolvers(ViewResolverRegistry registry) { 29 // configure view resolution for HTML rendering... 30 } 31}
1@Configuration 2@EnableMvc 3class WebConfig : WebMvcConfigurer { 4 5 @Bean 6 fun routerFunctionA(): RouterFunction<*> { 7 // ... 8 } 9 10 @Bean 11 fun routerFunctionB(): RouterFunction<*> { 12 // ... 13 } 14 15 // ... 16 17 override fun configureMessageConverters(converters: List<HttpMessageConverter<*>>) { 18 // configure message conversion... 19 } 20 21 override fun addCorsMappings(registry: CorsRegistry) { 22 // configure CORS... 23 } 24 25 override fun configureViewResolvers(registry: ViewResolverRegistry) { 26 // configure view resolution for HTML rendering... 27 } 28}
See equivalent in the Reactive stack
라우팅 함수 빌더의 before, after, filter 메서드를 사용하여 핸들러 함수를 필터링할 수 있습니다.
어노테이션을 사용할 때는 @ControllerAdvice, ServletFilter 또는 둘 다를 사용하여
유사한 기능을 구현할 수 있습니다.
필터는 빌더에 의해 빌드된 모든 라우트에 적용됩니다.
이는 중첩 라우트에 정의된 필터가 "top-level" 라우트에는 적용되지 않는다는 것을 의미합니다.
예를 들어 다음 예제를 고려해 보십시오:
1RouterFunction<ServerResponse> route = route() 2 .path("/person", b1 -> b1 3 .nest(accept(APPLICATION_JSON), b2 -> b2 4 .GET("/{id}", handler::getPerson) 5 .GET(handler::listPeople) 6 .before(request -> ServerRequest.from(request) // (1) 7 .header("X-RequestHeader", "Value") 8 .build())) 9 .POST(handler::createPerson)) 10 .after((request, response) -> logResponse(response)) // (2) 11 .build();
| 1 | custom 요청 헤더를 추가하는 before 필터는 두 개의 GET 라우트에만 적용됩니다. |
| 2 | 응답을 로그하는 after 필터는 중첩 라우트를 포함한 모든 라우트에 적용됩니다. |
1import org.springframework.web.servlet.function.router 2 3val route = router { 4 "/person".nest { 5 GET("/{id}", handler::getPerson) 6 GET(handler::listPeople) 7 before { // (1) 8 ServerRequest.from(it) 9 .header("X-RequestHeader", "Value").build() 10 } 11 } 12 POST(handler::createPerson) 13 after { _, response -> // (2) 14 logResponse(response) 15 } 16}
| 1 | custom 요청 헤더를 추가하는 before 필터는 두 개의 GET 라우트에만 적용됩니다. |
| 2 | 응답을 로그하는 after 필터는 중첩 라우트를 포함한 모든 라우트에 적용됩니다. |
라우터 빌더의 filter 메서드는 HandlerFilterFunction을 받습니다.
이는 ServerRequest와 HandlerFunction을 받고 ServerResponse를 반환하는 함수입니다.
핸들러 함수 파라미터는 체인에서 다음 요소를 나타냅니다.
이는 일반적으로 라우팅되는 핸들러이지만, 여러 필터가 적용된 경우 다른 필터일 수도 있습니다.
이제 특정 경로가 허용되는지 여부를 결정할 수 있는 SecurityManager가 있다고 가정하면,
라우트에 simple 시큐리티 필터를 추가할 수 있습니다.
다음 예제는 이를 수행하는 방법을 보여줍니다:
1SecurityManager securityManager = ... 2 3RouterFunction<ServerResponse> route = route() 4 .path("/person", b1 -> b1 5 .nest(accept(APPLICATION_JSON), b2 -> b2 6 .GET("/{id}", handler::getPerson) 7 .GET(handler::listPeople)) 8 .POST(handler::createPerson)) 9 .filter((request, next) -> { 10 if (securityManager.allowAccessTo(request.path())) { 11 return next.handle(request); 12 } 13 else { 14 return ServerResponse.status(UNAUTHORIZED).build(); 15 } 16 }) 17 .build();
1import org.springframework.web.servlet.function.router 2 3val securityManager: SecurityManager = ... 4 5val route = router { 6 ("/person" and accept(APPLICATION_JSON)).nest { 7 GET("/{id}", handler::getPerson) 8 GET("", handler::listPeople) 9 POST(handler::createPerson) 10 filter { request, next -> 11 if (securityManager.allowAccessTo(request.path())) { 12 next(request) 13 } 14 else { 15 status(UNAUTHORIZED).build() 16 } 17 } 18 } 19}
위 예제는 next.handle(ServerRequest) 호출이 optional임을 보여줍니다.
access가 허용되는 경우에만 핸들러 함수이 실행되도록 합니다.
라우터 함수 빌더의 filter 메서드를 사용하는 것 외에도,
RouterFunction.filter(HandlerFilterFunction)을 통해 기존 라우터 함수에 필터를 적용할 수 있습니다.
함수형 엔드포인트에 대한 CORS 지원은 전용<br>
CorsFilter를 통해 제공됩니다.
Controller Advice
URI Links