Skip to content

Commit 682dbe9

Browse files
committed
Merge pull request #44163 from null
* gh-44173: Polish "Add support for MVC router functions to mappings endpoint" Add support for MVC router functions to mappings endpoint Closes gh-44163
2 parents 4c097b9 + b2ba2a5 commit 682dbe9

File tree

5 files changed

+169
-9
lines changed

5 files changed

+169
-9
lines changed

spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/MappingsEndpointServletDocumentationTests.java

+18-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2024 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -47,18 +47,23 @@
4747
import org.springframework.test.web.reactive.server.WebTestClient;
4848
import org.springframework.web.bind.annotation.PostMapping;
4949
import org.springframework.web.bind.annotation.RestController;
50+
import org.springframework.web.servlet.function.RouterFunction;
51+
import org.springframework.web.servlet.function.RouterFunctions;
52+
import org.springframework.web.servlet.function.ServerResponse;
5053

5154
import static org.springframework.restdocs.payload.PayloadDocumentation.beneathPath;
5255
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
5356
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
5457
import static org.springframework.restdocs.payload.PayloadDocumentation.subsectionWithPath;
5558
import static org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation.document;
5659
import static org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation.documentationConfiguration;
60+
import static org.springframework.web.servlet.function.RequestPredicates.GET;
5761

5862
/**
5963
* Tests for generating documentation describing {@link MappingsEndpoint}.
6064
*
6165
* @author Andy Wilkinson
66+
* @author Xiong Tang
6267
*/
6368
@ExtendWith(RestDocumentationExtension.class)
6469
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@@ -130,6 +135,13 @@ void mappings() {
130135
fieldWithPath("*.[].details.handlerMethod.name").description("Name of the method."),
131136
fieldWithPath("*.[].details.handlerMethod.descriptor")
132137
.description("Descriptor of the method as specified in the Java Language Specification."));
138+
List<FieldDescriptor> handlerFunction = List.of(
139+
fieldWithPath("*.[].details.handlerFunction").optional()
140+
.type(JsonFieldType.OBJECT)
141+
.description("Details of the function, if any, that will handle requests to this mapping."),
142+
fieldWithPath("*.[].details.handlerFunction.className").type(JsonFieldType.STRING)
143+
.description("Fully qualified name of the class of the function."));
144+
dispatcherServletFields.addAll(handlerFunction);
133145
dispatcherServletFields.addAll(handlerMethod);
134146
dispatcherServletFields.addAll(requestMappingConditions);
135147
this.client.get()
@@ -192,6 +204,11 @@ ExampleController exampleController() {
192204
return new ExampleController();
193205
}
194206

207+
@Bean
208+
RouterFunction<ServerResponse> exampleRouter() {
209+
return RouterFunctions.route(GET("/foo"), (request) -> ServerResponse.ok().build());
210+
}
211+
195212
}
196213

197214
@RestController

spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/mappings/servlet/DispatcherServletMappingDetails.java

+12-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -23,12 +23,15 @@
2323
* Details of a {@link DispatcherServlet} mapping.
2424
*
2525
* @author Andy Wilkinson
26+
* @author Xiong Tang
2627
* @since 2.0.0
2728
*/
2829
public class DispatcherServletMappingDetails {
2930

3031
private HandlerMethodDescription handlerMethod;
3132

33+
private HandlerFunctionDescription handlerFunction;
34+
3235
private RequestMappingConditionsDescription requestMappingConditions;
3336

3437
public HandlerMethodDescription getHandlerMethod() {
@@ -39,6 +42,14 @@ void setHandlerMethod(HandlerMethodDescription handlerMethod) {
3942
this.handlerMethod = handlerMethod;
4043
}
4144

45+
public HandlerFunctionDescription getHandlerFunction() {
46+
return this.handlerFunction;
47+
}
48+
49+
void setHandlerFunction(HandlerFunctionDescription handlerFunction) {
50+
this.handlerFunction = handlerFunction;
51+
}
52+
4253
public RequestMappingConditionsDescription getRequestMappingConditions() {
4354
return this.requestMappingConditions;
4455
}

spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/mappings/servlet/DispatcherServletsMappingDescriptionProvider.java

+68-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2023 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -23,6 +23,8 @@
2323
import java.util.List;
2424
import java.util.Map;
2525
import java.util.Map.Entry;
26+
import java.util.Optional;
27+
import java.util.function.Function;
2628
import java.util.stream.Stream;
2729

2830
import jakarta.servlet.Servlet;
@@ -36,10 +38,17 @@
3638
import org.springframework.boot.web.servlet.ServletRegistrationBean;
3739
import org.springframework.context.ApplicationContext;
3840
import org.springframework.context.annotation.ImportRuntimeHints;
41+
import org.springframework.core.io.Resource;
3942
import org.springframework.web.context.WebApplicationContext;
4043
import org.springframework.web.method.HandlerMethod;
4144
import org.springframework.web.servlet.DispatcherServlet;
4245
import org.springframework.web.servlet.HandlerMapping;
46+
import org.springframework.web.servlet.function.HandlerFunction;
47+
import org.springframework.web.servlet.function.RequestPredicate;
48+
import org.springframework.web.servlet.function.RouterFunction;
49+
import org.springframework.web.servlet.function.RouterFunctions.Visitor;
50+
import org.springframework.web.servlet.function.ServerRequest;
51+
import org.springframework.web.servlet.function.support.RouterFunctionMapping;
4352
import org.springframework.web.servlet.handler.AbstractUrlHandlerMapping;
4453
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
4554
import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
@@ -51,6 +60,7 @@
5160
*
5261
* @author Andy Wilkinson
5362
* @author Stephane Nicoll
63+
* @author Xiong Tang
5464
* @since 2.0.0
5565
*/
5666
@ImportRuntimeHints(DispatcherServletsMappingDescriptionProviderRuntimeHints.class)
@@ -63,6 +73,7 @@ public class DispatcherServletsMappingDescriptionProvider implements MappingDesc
6373
providers.add(new RequestMappingInfoHandlerMappingDescriptionProvider());
6474
providers.add(new UrlHandlerMappingDescriptionProvider());
6575
providers.add(new IterableDelegatesHandlerMappingDescriptionProvider(new ArrayList<>(providers)));
76+
providers.add(new RouterFunctionMappingDescriptionProvider());
6677
descriptionProviders = Collections.unmodifiableList(providers);
6778
}
6879

@@ -200,6 +211,62 @@ public List<DispatcherServletMappingDescription> describe(Iterable handlerMappin
200211

201212
}
202213

214+
private static final class RouterFunctionMappingDescriptionProvider
215+
implements HandlerMappingDescriptionProvider<RouterFunctionMapping> {
216+
217+
@Override
218+
public Class<RouterFunctionMapping> getMappingClass() {
219+
return RouterFunctionMapping.class;
220+
}
221+
222+
@Override
223+
public List<DispatcherServletMappingDescription> describe(RouterFunctionMapping handlerMapping) {
224+
MappingDescriptionVisitor visitor = new MappingDescriptionVisitor();
225+
RouterFunction<?> routerFunction = handlerMapping.getRouterFunction();
226+
if (routerFunction != null) {
227+
routerFunction.accept(visitor);
228+
}
229+
return visitor.descriptions;
230+
}
231+
232+
}
233+
234+
private static final class MappingDescriptionVisitor implements Visitor {
235+
236+
private final List<DispatcherServletMappingDescription> descriptions = new ArrayList<>();
237+
238+
@Override
239+
public void startNested(RequestPredicate predicate) {
240+
}
241+
242+
@Override
243+
public void endNested(RequestPredicate predicate) {
244+
}
245+
246+
@Override
247+
public void route(RequestPredicate predicate, HandlerFunction<?> handlerFunction) {
248+
DispatcherServletMappingDetails details = new DispatcherServletMappingDetails();
249+
details.setHandlerFunction(new HandlerFunctionDescription(handlerFunction));
250+
this.descriptions.add(
251+
new DispatcherServletMappingDescription(predicate.toString(), handlerFunction.toString(), details));
252+
}
253+
254+
@Override
255+
public void resources(Function<ServerRequest, Optional<Resource>> lookupFunction) {
256+
257+
}
258+
259+
@Override
260+
public void attributes(Map<String, Object> attributes) {
261+
}
262+
263+
@Override
264+
public void unknown(RouterFunction<?> routerFunction) {
265+
266+
}
267+
268+
}
269+
203270
static class DispatcherServletsMappingDescriptionProviderRuntimeHints implements RuntimeHintsRegistrar {
204271

205272
private final BindingReflectionHintsRegistrar bindingRegistrar = new BindingReflectionHintsRegistrar();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright 2012-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.actuate.web.mappings.servlet;
18+
19+
import org.springframework.web.servlet.function.HandlerFunction;
20+
21+
/**
22+
* Description of a {@link HandlerFunction}.
23+
*
24+
* @author Xiong Tang
25+
* @since 3.5.0
26+
*/
27+
public class HandlerFunctionDescription {
28+
29+
private final String className;
30+
31+
HandlerFunctionDescription(HandlerFunction<?> handlerFunction) {
32+
this.className = getHandlerFunctionClassName(handlerFunction);
33+
}
34+
35+
private static String getHandlerFunctionClassName(HandlerFunction<?> handlerFunction) {
36+
Class<?> functionClass = handlerFunction.getClass();
37+
String canonicalName = functionClass.getCanonicalName();
38+
return (canonicalName != null) ? canonicalName : functionClass.getName();
39+
}
40+
41+
public String getClassName() {
42+
return this.className;
43+
}
44+
45+
}

spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/web/mappings/MappingsEndpointTests.java

+26-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2023 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -57,6 +57,8 @@
5757
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
5858
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
5959
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
60+
import org.springframework.web.servlet.function.RequestPredicates;
61+
import org.springframework.web.servlet.function.RouterFunctions;
6062
import org.springframework.web.util.pattern.PathPatternParser;
6163

6264
import static org.assertj.core.api.Assertions.assertThat;
@@ -71,6 +73,7 @@
7173
*
7274
* @author Andy Wilkinson
7375
* @author Stephane Nicoll
76+
* @author Xiong Tang
7477
*/
7578
class MappingsEndpointTests {
7679

@@ -88,7 +91,7 @@ void servletWebMappings() {
8891
"dispatcherServlets");
8992
assertThat(dispatcherServlets).containsOnlyKeys("dispatcherServlet");
9093
List<DispatcherServletMappingDescription> handlerMappings = dispatcherServlets.get("dispatcherServlet");
91-
assertThat(handlerMappings).hasSize(1);
94+
assertThat(handlerMappings).hasSize(4);
9295
List<ServletRegistrationMappingDescription> servlets = mappings(contextMappings, "servlets");
9396
assertThat(servlets).hasSize(1);
9497
List<FilterRegistrationMappingDescription> filters = mappings(contextMappings, "servletFilters");
@@ -111,7 +114,7 @@ void servletWebMappingsWithPathPatternParser() {
111114
"dispatcherServlets");
112115
assertThat(dispatcherServlets).containsOnlyKeys("dispatcherServlet");
113116
List<DispatcherServletMappingDescription> handlerMappings = dispatcherServlets.get("dispatcherServlet");
114-
assertThat(handlerMappings).hasSize(1);
117+
assertThat(handlerMappings).hasSize(4);
115118
List<ServletRegistrationMappingDescription> servlets = mappings(contextMappings, "servlets");
116119
assertThat(servlets).hasSize(1);
117120
List<FilterRegistrationMappingDescription> filters = mappings(contextMappings, "servletFilters");
@@ -131,9 +134,9 @@ void servletWebMappingsWithAdditionalDispatcherServlets() {
131134
"dispatcherServlets");
132135
assertThat(dispatcherServlets).containsOnlyKeys("dispatcherServlet",
133136
"customDispatcherServletRegistration", "anotherDispatcherServletRegistration");
134-
assertThat(dispatcherServlets.get("dispatcherServlet")).hasSize(1);
135-
assertThat(dispatcherServlets.get("customDispatcherServletRegistration")).hasSize(1);
136-
assertThat(dispatcherServlets.get("anotherDispatcherServletRegistration")).hasSize(1);
137+
assertThat(dispatcherServlets.get("dispatcherServlet")).hasSize(4);
138+
assertThat(dispatcherServlets.get("customDispatcherServletRegistration")).hasSize(4);
139+
assertThat(dispatcherServlets.get("anotherDispatcherServletRegistration")).hasSize(4);
137140
});
138141
}
139142

@@ -248,11 +251,28 @@ DispatcherServlet dispatcherServlet(WebApplicationContext context) throws Servle
248251
return dispatcherServlet;
249252
}
250253

254+
@Bean
255+
org.springframework.web.servlet.function.RouterFunction<org.springframework.web.servlet.function.ServerResponse> routerFunction() {
256+
return RouterFunctions
257+
.route(RequestPredicates.GET("/one"),
258+
(request) -> org.springframework.web.servlet.function.ServerResponse.ok().build())
259+
.andRoute(RequestPredicates.POST("/two"),
260+
(request) -> org.springframework.web.servlet.function.ServerResponse.ok().build());
261+
}
262+
251263
@RequestMapping("/three")
252264
void three() {
253265

254266
}
255267

268+
@Bean
269+
org.springframework.web.servlet.function.RouterFunction<org.springframework.web.servlet.function.ServerResponse> routerFunctionWithAttributes() {
270+
return RouterFunctions
271+
.route(RequestPredicates.GET("/four"),
272+
(request) -> org.springframework.web.servlet.function.ServerResponse.ok().build())
273+
.withAttribute("test", "test");
274+
}
275+
256276
}
257277

258278
@Configuration

0 commit comments

Comments
 (0)