Add support for MVC router functions to mappings endpoint
See gh-44163 Signed-off-by: puppy4c <puppy4c@foxmail.com>
This commit is contained in:
@@ -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<FieldDescriptor> 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<ServerResponse> exampleRouter() {
|
||||
return RouterFunctions.route(GET("/foo"), (request) -> ServerResponse.ok().build());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@RestController
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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<RouterFunctionMapping> {
|
||||
|
||||
@Override
|
||||
public Class<RouterFunctionMapping> getMappingClass() {
|
||||
return RouterFunctionMapping.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DispatcherServletMappingDescription> 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<DispatcherServletMappingDescription> 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<ServerRequest, Optional<Resource>> lookupFunction) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attributes(Map<String, Object> attributes) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unknown(RouterFunction<?> routerFunction) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class DispatcherServletsMappingDescriptionProviderRuntimeHints implements RuntimeHintsRegistrar {
|
||||
|
||||
private final BindingReflectionHintsRegistrar bindingRegistrar = new BindingReflectionHintsRegistrar();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<DispatcherServletMappingDescription> handlerMappings = dispatcherServlets.get("dispatcherServlet");
|
||||
assertThat(handlerMappings).hasSize(1);
|
||||
assertThat(handlerMappings).hasSize(3);
|
||||
List<ServletRegistrationMappingDescription> servlets = mappings(contextMappings, "servlets");
|
||||
assertThat(servlets).hasSize(1);
|
||||
List<FilterRegistrationMappingDescription> filters = mappings(contextMappings, "servletFilters");
|
||||
@@ -111,7 +111,7 @@ class MappingsEndpointTests {
|
||||
"dispatcherServlets");
|
||||
assertThat(dispatcherServlets).containsOnlyKeys("dispatcherServlet");
|
||||
List<DispatcherServletMappingDescription> handlerMappings = dispatcherServlets.get("dispatcherServlet");
|
||||
assertThat(handlerMappings).hasSize(1);
|
||||
assertThat(handlerMappings).hasSize(3);
|
||||
List<ServletRegistrationMappingDescription> servlets = mappings(contextMappings, "servlets");
|
||||
assertThat(servlets).hasSize(1);
|
||||
List<FilterRegistrationMappingDescription> 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<org.springframework.web.servlet.function.ServerResponse> 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
|
||||
|
||||
Reference in New Issue
Block a user