From a747bcf6d43e2c78afc8b3de6f131698ba4cdf8a Mon Sep 17 00:00:00 2001 From: puppy4c Date: Sat, 8 Feb 2025 21:13:54 +0800 Subject: [PATCH 1/2] Add support for MVC router functions to mappings endpoint See gh-44163 Signed-off-by: puppy4c --- ...ingsEndpointServletDocumentationTests.java | 18 ++++- .../DispatcherServletMappingDetails.java | 12 +++- ...herServletsMappingDescriptionProvider.java | 68 ++++++++++++++++++- .../servlet/HandlerFunctionDescription.java | 46 +++++++++++++ .../web/mappings/MappingsEndpointTests.java | 21 ++++-- 5 files changed, 156 insertions(+), 9 deletions(-) create mode 100644 spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/mappings/servlet/HandlerFunctionDescription.java diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/MappingsEndpointServletDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/MappingsEndpointServletDocumentationTests.java index b11040e97f..f3838b16b3 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/MappingsEndpointServletDocumentationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/MappingsEndpointServletDocumentationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -47,6 +47,9 @@ import org.springframework.restdocs.payload.ResponseFieldsSnippet; import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.function.RouterFunction; +import org.springframework.web.servlet.function.RouterFunctions; +import org.springframework.web.servlet.function.ServerResponse; import static org.springframework.restdocs.payload.PayloadDocumentation.beneathPath; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; @@ -54,6 +57,7 @@ import static org.springframework.restdocs.payload.PayloadDocumentation.response import static org.springframework.restdocs.payload.PayloadDocumentation.subsectionWithPath; import static org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation.document; import static org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation.documentationConfiguration; +import static org.springframework.web.servlet.function.RequestPredicates.GET; /** * Tests for generating documentation describing {@link MappingsEndpoint}. @@ -130,6 +134,13 @@ class MappingsEndpointServletDocumentationTests extends AbstractEndpointDocument fieldWithPath("*.[].details.handlerMethod.name").description("Name of the method."), fieldWithPath("*.[].details.handlerMethod.descriptor") .description("Descriptor of the method as specified in the Java Language Specification.")); + List handlerFunction = List.of( + fieldWithPath("*.[].details.handlerFunction").optional() + .type(JsonFieldType.OBJECT) + .description("Details of the function, if any, that will handle requests to this mapping."), + fieldWithPath("*.[].details.handlerFunction.className").type(JsonFieldType.STRING) + .description("Fully qualified name of the class of the function.")); + dispatcherServletFields.addAll(handlerFunction); dispatcherServletFields.addAll(handlerMethod); dispatcherServletFields.addAll(requestMappingConditions); this.client.get() @@ -192,6 +203,11 @@ class MappingsEndpointServletDocumentationTests extends AbstractEndpointDocument return new ExampleController(); } + @Bean + RouterFunction exampleRouter() { + return RouterFunctions.route(GET("/foo"), (request) -> ServerResponse.ok().build()); + } + } @RestController diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/mappings/servlet/DispatcherServletMappingDetails.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/mappings/servlet/DispatcherServletMappingDetails.java index 699ff05b46..05326ea505 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/mappings/servlet/DispatcherServletMappingDetails.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/mappings/servlet/DispatcherServletMappingDetails.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,6 +29,8 @@ public class DispatcherServletMappingDetails { private HandlerMethodDescription handlerMethod; + private HandlerFunctionDescription handlerFunction; + private RequestMappingConditionsDescription requestMappingConditions; public HandlerMethodDescription getHandlerMethod() { @@ -39,6 +41,14 @@ public class DispatcherServletMappingDetails { this.handlerMethod = handlerMethod; } + public HandlerFunctionDescription getHandlerFunction() { + return this.handlerFunction; + } + + void setHandlerFunction(HandlerFunctionDescription handlerFunction) { + this.handlerFunction = handlerFunction; + } + public RequestMappingConditionsDescription getRequestMappingConditions() { return this.requestMappingConditions; } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/mappings/servlet/DispatcherServletsMappingDescriptionProvider.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/mappings/servlet/DispatcherServletsMappingDescriptionProvider.java index ffb48304ee..452ca506f7 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/mappings/servlet/DispatcherServletsMappingDescriptionProvider.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/mappings/servlet/DispatcherServletsMappingDescriptionProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,8 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Optional; +import java.util.function.Function; import java.util.stream.Stream; import jakarta.servlet.Servlet; @@ -36,10 +38,17 @@ import org.springframework.boot.actuate.web.mappings.servlet.DispatcherServletsM import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.ImportRuntimeHints; +import org.springframework.core.io.Resource; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.DispatcherServlet; import org.springframework.web.servlet.HandlerMapping; +import org.springframework.web.servlet.function.HandlerFunction; +import org.springframework.web.servlet.function.RequestPredicate; +import org.springframework.web.servlet.function.RouterFunction; +import org.springframework.web.servlet.function.RouterFunctions.Visitor; +import org.springframework.web.servlet.function.ServerRequest; +import org.springframework.web.servlet.function.support.RouterFunctionMapping; import org.springframework.web.servlet.handler.AbstractUrlHandlerMapping; import org.springframework.web.servlet.mvc.method.RequestMappingInfo; import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping; @@ -63,6 +72,7 @@ public class DispatcherServletsMappingDescriptionProvider implements MappingDesc providers.add(new RequestMappingInfoHandlerMappingDescriptionProvider()); providers.add(new UrlHandlerMappingDescriptionProvider()); providers.add(new IterableDelegatesHandlerMappingDescriptionProvider(new ArrayList<>(providers))); + providers.add(new RouterFunctionMappingDescriptionProvider()); descriptionProviders = Collections.unmodifiableList(providers); } @@ -200,6 +210,62 @@ public class DispatcherServletsMappingDescriptionProvider implements MappingDesc } + private static final class RouterFunctionMappingDescriptionProvider + implements HandlerMappingDescriptionProvider { + + @Override + public Class getMappingClass() { + return RouterFunctionMapping.class; + } + + @Override + public List describe(RouterFunctionMapping handlerMapping) { + MappingDescriptionVisitor visitor = new MappingDescriptionVisitor(); + RouterFunction routerFunction = handlerMapping.getRouterFunction(); + if (routerFunction != null) { + routerFunction.accept(visitor); + } + return visitor.descriptions; + } + + } + + private static final class MappingDescriptionVisitor implements Visitor { + + private final List descriptions = new ArrayList<>(); + + @Override + public void startNested(RequestPredicate predicate) { + } + + @Override + public void endNested(RequestPredicate predicate) { + } + + @Override + public void route(RequestPredicate predicate, HandlerFunction handlerFunction) { + DispatcherServletMappingDetails details = new DispatcherServletMappingDetails(); + details.setHandlerFunction(new HandlerFunctionDescription(handlerFunction)); + this.descriptions.add( + new DispatcherServletMappingDescription(predicate.toString(), handlerFunction.toString(), details)); + } + + @Override + public void resources(Function> lookupFunction) { + + } + + @Override + public void attributes(Map attributes) { + } + + @Override + public void unknown(RouterFunction routerFunction) { + + } + + } + static class DispatcherServletsMappingDescriptionProviderRuntimeHints implements RuntimeHintsRegistrar { private final BindingReflectionHintsRegistrar bindingRegistrar = new BindingReflectionHintsRegistrar(); diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/mappings/servlet/HandlerFunctionDescription.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/mappings/servlet/HandlerFunctionDescription.java new file mode 100644 index 0000000000..26df168bae --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/mappings/servlet/HandlerFunctionDescription.java @@ -0,0 +1,46 @@ +/* + * Copyright 2012-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.web.mappings.servlet; + + +import org.springframework.web.servlet.function.HandlerFunction; + +/** + * Description of a {@link HandlerFunction}. + * + * @author Xiong Tang + * @since 3.5.0 + */ +public class HandlerFunctionDescription { + + private final String className; + + HandlerFunctionDescription(HandlerFunction handlerFunction) { + this.className = getHandlerFunctionClassName(handlerFunction); + } + + private static String getHandlerFunctionClassName(HandlerFunction handlerFunction) { + Class functionClass = handlerFunction.getClass(); + String canonicalName = functionClass.getCanonicalName(); + return (canonicalName != null) ? canonicalName : functionClass.getName(); + } + + public String getClassName() { + return this.className; + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/web/mappings/MappingsEndpointTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/web/mappings/MappingsEndpointTests.java index 929b7a9e1f..f99745216b 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/web/mappings/MappingsEndpointTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/web/mappings/MappingsEndpointTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -88,7 +88,7 @@ class MappingsEndpointTests { "dispatcherServlets"); assertThat(dispatcherServlets).containsOnlyKeys("dispatcherServlet"); List handlerMappings = dispatcherServlets.get("dispatcherServlet"); - assertThat(handlerMappings).hasSize(1); + assertThat(handlerMappings).hasSize(3); List servlets = mappings(contextMappings, "servlets"); assertThat(servlets).hasSize(1); List filters = mappings(contextMappings, "servletFilters"); @@ -111,7 +111,7 @@ class MappingsEndpointTests { "dispatcherServlets"); assertThat(dispatcherServlets).containsOnlyKeys("dispatcherServlet"); List handlerMappings = dispatcherServlets.get("dispatcherServlet"); - assertThat(handlerMappings).hasSize(1); + assertThat(handlerMappings).hasSize(3); List servlets = mappings(contextMappings, "servlets"); assertThat(servlets).hasSize(1); List filters = mappings(contextMappings, "servletFilters"); @@ -131,9 +131,9 @@ class MappingsEndpointTests { "dispatcherServlets"); assertThat(dispatcherServlets).containsOnlyKeys("dispatcherServlet", "customDispatcherServletRegistration", "anotherDispatcherServletRegistration"); - assertThat(dispatcherServlets.get("dispatcherServlet")).hasSize(1); - assertThat(dispatcherServlets.get("customDispatcherServletRegistration")).hasSize(1); - assertThat(dispatcherServlets.get("anotherDispatcherServletRegistration")).hasSize(1); + assertThat(dispatcherServlets.get("dispatcherServlet")).hasSize(3); + assertThat(dispatcherServlets.get("customDispatcherServletRegistration")).hasSize(3); + assertThat(dispatcherServlets.get("anotherDispatcherServletRegistration")).hasSize(3); }); } @@ -253,6 +253,15 @@ class MappingsEndpointTests { } + @Bean + org.springframework.web.servlet.function.RouterFunction routerFunction() { + return org.springframework.web.servlet.function.RouterFunctions + .route(org.springframework.web.servlet.function.RequestPredicates.GET("/one"), + (request) -> org.springframework.web.servlet.function.ServerResponse.ok().build()) + .andRoute(org.springframework.web.servlet.function.RequestPredicates.POST("/two"), + (request) -> org.springframework.web.servlet.function.ServerResponse.ok().build()); + } + } @Configuration From b2ba2a53fe43f9204aa8d50ae1a5e327b1c5ba61 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 13 Feb 2025 11:15:41 +0000 Subject: [PATCH 2/2] Polish "Add support for MVC router functions to mappings endpoint" See gh-44163 --- ...ingsEndpointServletDocumentationTests.java | 7 ++-- .../DispatcherServletMappingDetails.java | 1 + ...herServletsMappingDescriptionProvider.java | 1 + .../servlet/HandlerFunctionDescription.java | 1 - .../web/mappings/MappingsEndpointTests.java | 33 ++++++++++++------- 5 files changed, 28 insertions(+), 15 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/MappingsEndpointServletDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/MappingsEndpointServletDocumentationTests.java index f3838b16b3..6ed5e3598c 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/MappingsEndpointServletDocumentationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/MappingsEndpointServletDocumentationTests.java @@ -63,6 +63,7 @@ import static org.springframework.web.servlet.function.RequestPredicates.GET; * Tests for generating documentation describing {@link MappingsEndpoint}. * * @author Andy Wilkinson + * @author Xiong Tang */ @ExtendWith(RestDocumentationExtension.class) @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) @@ -136,10 +137,10 @@ class MappingsEndpointServletDocumentationTests extends AbstractEndpointDocument .description("Descriptor of the method as specified in the Java Language Specification.")); List handlerFunction = List.of( fieldWithPath("*.[].details.handlerFunction").optional() - .type(JsonFieldType.OBJECT) - .description("Details of the function, if any, that will handle requests to this mapping."), + .type(JsonFieldType.OBJECT) + .description("Details of the function, if any, that will handle requests to this mapping."), fieldWithPath("*.[].details.handlerFunction.className").type(JsonFieldType.STRING) - .description("Fully qualified name of the class of the function.")); + .description("Fully qualified name of the class of the function.")); dispatcherServletFields.addAll(handlerFunction); dispatcherServletFields.addAll(handlerMethod); dispatcherServletFields.addAll(requestMappingConditions); diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/mappings/servlet/DispatcherServletMappingDetails.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/mappings/servlet/DispatcherServletMappingDetails.java index 05326ea505..786ddc5519 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/mappings/servlet/DispatcherServletMappingDetails.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/mappings/servlet/DispatcherServletMappingDetails.java @@ -23,6 +23,7 @@ import org.springframework.web.servlet.DispatcherServlet; * Details of a {@link DispatcherServlet} mapping. * * @author Andy Wilkinson + * @author Xiong Tang * @since 2.0.0 */ public class DispatcherServletMappingDetails { diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/mappings/servlet/DispatcherServletsMappingDescriptionProvider.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/mappings/servlet/DispatcherServletsMappingDescriptionProvider.java index 452ca506f7..2225550c1b 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/mappings/servlet/DispatcherServletsMappingDescriptionProvider.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/mappings/servlet/DispatcherServletsMappingDescriptionProvider.java @@ -60,6 +60,7 @@ import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMappi * * @author Andy Wilkinson * @author Stephane Nicoll + * @author Xiong Tang * @since 2.0.0 */ @ImportRuntimeHints(DispatcherServletsMappingDescriptionProviderRuntimeHints.class) diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/mappings/servlet/HandlerFunctionDescription.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/mappings/servlet/HandlerFunctionDescription.java index 26df168bae..f62a4f55f4 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/mappings/servlet/HandlerFunctionDescription.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/mappings/servlet/HandlerFunctionDescription.java @@ -16,7 +16,6 @@ package org.springframework.boot.actuate.web.mappings.servlet; - import org.springframework.web.servlet.function.HandlerFunction; /** diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/web/mappings/MappingsEndpointTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/web/mappings/MappingsEndpointTests.java index f99745216b..5516fa69f5 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/web/mappings/MappingsEndpointTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/web/mappings/MappingsEndpointTests.java @@ -57,6 +57,8 @@ import org.springframework.web.servlet.DispatcherServlet; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.PathMatchConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import org.springframework.web.servlet.function.RequestPredicates; +import org.springframework.web.servlet.function.RouterFunctions; import org.springframework.web.util.pattern.PathPatternParser; import static org.assertj.core.api.Assertions.assertThat; @@ -71,6 +73,7 @@ import static org.springframework.web.reactive.function.server.RouterFunctions.r * * @author Andy Wilkinson * @author Stephane Nicoll + * @author Xiong Tang */ class MappingsEndpointTests { @@ -88,7 +91,7 @@ class MappingsEndpointTests { "dispatcherServlets"); assertThat(dispatcherServlets).containsOnlyKeys("dispatcherServlet"); List handlerMappings = dispatcherServlets.get("dispatcherServlet"); - assertThat(handlerMappings).hasSize(3); + assertThat(handlerMappings).hasSize(4); List servlets = mappings(contextMappings, "servlets"); assertThat(servlets).hasSize(1); List filters = mappings(contextMappings, "servletFilters"); @@ -111,7 +114,7 @@ class MappingsEndpointTests { "dispatcherServlets"); assertThat(dispatcherServlets).containsOnlyKeys("dispatcherServlet"); List handlerMappings = dispatcherServlets.get("dispatcherServlet"); - assertThat(handlerMappings).hasSize(3); + assertThat(handlerMappings).hasSize(4); List servlets = mappings(contextMappings, "servlets"); assertThat(servlets).hasSize(1); List filters = mappings(contextMappings, "servletFilters"); @@ -131,9 +134,9 @@ class MappingsEndpointTests { "dispatcherServlets"); assertThat(dispatcherServlets).containsOnlyKeys("dispatcherServlet", "customDispatcherServletRegistration", "anotherDispatcherServletRegistration"); - assertThat(dispatcherServlets.get("dispatcherServlet")).hasSize(3); - assertThat(dispatcherServlets.get("customDispatcherServletRegistration")).hasSize(3); - assertThat(dispatcherServlets.get("anotherDispatcherServletRegistration")).hasSize(3); + assertThat(dispatcherServlets.get("dispatcherServlet")).hasSize(4); + assertThat(dispatcherServlets.get("customDispatcherServletRegistration")).hasSize(4); + assertThat(dispatcherServlets.get("anotherDispatcherServletRegistration")).hasSize(4); }); } @@ -248,18 +251,26 @@ class MappingsEndpointTests { return dispatcherServlet; } + @Bean + org.springframework.web.servlet.function.RouterFunction routerFunction() { + return RouterFunctions + .route(RequestPredicates.GET("/one"), + (request) -> org.springframework.web.servlet.function.ServerResponse.ok().build()) + .andRoute(RequestPredicates.POST("/two"), + (request) -> org.springframework.web.servlet.function.ServerResponse.ok().build()); + } + @RequestMapping("/three") void three() { } @Bean - org.springframework.web.servlet.function.RouterFunction routerFunction() { - return org.springframework.web.servlet.function.RouterFunctions - .route(org.springframework.web.servlet.function.RequestPredicates.GET("/one"), - (request) -> org.springframework.web.servlet.function.ServerResponse.ok().build()) - .andRoute(org.springframework.web.servlet.function.RequestPredicates.POST("/two"), - (request) -> org.springframework.web.servlet.function.ServerResponse.ok().build()); + org.springframework.web.servlet.function.RouterFunction routerFunctionWithAttributes() { + return RouterFunctions + .route(RequestPredicates.GET("/four"), + (request) -> org.springframework.web.servlet.function.ServerResponse.ok().build()) + .withAttribute("test", "test"); } }