Skip to content

Mappings Endpoint support web-servlet router funtion #44172

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -47,13 +47,17 @@
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;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
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}.
Expand Down Expand Up @@ -130,6 +134,13 @@ void mappings() {
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()
Expand Down Expand Up @@ -192,6 +203,11 @@ ExampleController exampleController() {
return new ExampleController();
}

@Bean
RouterFunction<ServerResponse> exampleRouter() {
return RouterFunctions.route(GET("/foo"), (request) -> ServerResponse.ok().build());
}

}

@RestController
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -29,6 +29,8 @@ public class DispatcherServletMappingDetails {

private HandlerMethodDescription handlerMethod;

private HandlerFunctionDescription handlerFunction;

private RequestMappingConditionsDescription requestMappingConditions;

public HandlerMethodDescription getHandlerMethod() {
Expand All @@ -39,6 +41,14 @@ void setHandlerMethod(HandlerMethodDescription handlerMethod) {
this.handlerMethod = handlerMethod;
}

public HandlerFunctionDescription getHandlerFunction() {
return this.handlerFunction;
}

void setHandlerFunction(HandlerFunctionDescription handlerFunction) {
this.handlerFunction = handlerFunction;
}

public RequestMappingConditionsDescription getRequestMappingConditions() {
return this.requestMappingConditions;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -23,6 +23,8 @@
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;
Expand All @@ -36,10 +38,17 @@
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;
Expand All @@ -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);
}

Expand Down Expand Up @@ -200,6 +210,62 @@ public List<DispatcherServletMappingDescription> describe(Iterable handlerMappin

}

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();
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}

}
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -88,7 +88,7 @@ void servletWebMappings() {
"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");
Expand All @@ -111,7 +111,7 @@ void servletWebMappingsWithPathPatternParser() {
"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");
Expand All @@ -131,9 +131,9 @@ void servletWebMappingsWithAdditionalDispatcherServlets() {
"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);
});
}

Expand Down Expand Up @@ -253,6 +253,15 @@ void three() {

}

@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
Expand Down