Browse Source

Add support for MVC router functions to mappings endpoint

See gh-44163

Signed-off-by: puppy4c <puppy4c@foxmail.com>
pull/44267/head
puppy4c 12 months ago committed by Andy Wilkinson
parent
commit
a747bcf6d4
  1. 18
      spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/MappingsEndpointServletDocumentationTests.java
  2. 12
      spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/mappings/servlet/DispatcherServletMappingDetails.java
  3. 68
      spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/mappings/servlet/DispatcherServletsMappingDescriptionProvider.java
  4. 46
      spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/mappings/servlet/HandlerFunctionDescription.java
  5. 21
      spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/web/mappings/MappingsEndpointTests.java

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

@ -1,5 +1,5 @@ @@ -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; @@ -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 @@ -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 @@ -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 @@ -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

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

@ -1,5 +1,5 @@ @@ -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 { @@ -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 { @@ -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;
}

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

@ -1,5 +1,5 @@ @@ -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; @@ -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 @@ -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 @@ -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 @@ -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();

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

@ -0,0 +1,46 @@ @@ -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;
}
}

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

@ -1,5 +1,5 @@ @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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

Loading…
Cancel
Save