Move ApiVersionStrategy up to AbstractHandlerMapping
Similar to CorProcessor, ApiVersionStrategy is now supported at the AbstractHandlerMapping level. See gh-35113
This commit is contained in:
parent
93a9c9b8bf
commit
86f50b20f2
|
@ -29,7 +29,6 @@ import org.springframework.http.codec.HttpMessageReader;
|
||||||
import org.springframework.http.codec.ServerCodecConfigurer;
|
import org.springframework.http.codec.ServerCodecConfigurer;
|
||||||
import org.springframework.http.server.reactive.observation.ServerRequestObservationContext;
|
import org.springframework.http.server.reactive.observation.ServerRequestObservationContext;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
import org.springframework.web.reactive.accept.ApiVersionStrategy;
|
|
||||||
import org.springframework.web.reactive.accept.DefaultApiVersionStrategy;
|
import org.springframework.web.reactive.accept.DefaultApiVersionStrategy;
|
||||||
import org.springframework.web.reactive.function.server.HandlerFunction;
|
import org.springframework.web.reactive.function.server.HandlerFunction;
|
||||||
import org.springframework.web.reactive.function.server.RouterFunction;
|
import org.springframework.web.reactive.function.server.RouterFunction;
|
||||||
|
@ -56,8 +55,6 @@ public class RouterFunctionMapping extends AbstractHandlerMapping implements Ini
|
||||||
|
|
||||||
private List<HttpMessageReader<?>> messageReaders = Collections.emptyList();
|
private List<HttpMessageReader<?>> messageReaders = Collections.emptyList();
|
||||||
|
|
||||||
private @Nullable ApiVersionStrategy versionStrategy;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an empty {@code RouterFunctionMapping}.
|
* Create an empty {@code RouterFunctionMapping}.
|
||||||
|
@ -96,15 +93,6 @@ public class RouterFunctionMapping extends AbstractHandlerMapping implements Ini
|
||||||
this.messageReaders = messageReaders;
|
this.messageReaders = messageReaders;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Configure a strategy to manage API versioning.
|
|
||||||
* @param strategy the strategy to use
|
|
||||||
* @since 7.0
|
|
||||||
*/
|
|
||||||
public void setApiVersionStrategy(@Nullable ApiVersionStrategy strategy) {
|
|
||||||
this.versionStrategy = strategy;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void afterPropertiesSet() throws Exception {
|
public void afterPropertiesSet() throws Exception {
|
||||||
|
@ -119,7 +107,7 @@ public class RouterFunctionMapping extends AbstractHandlerMapping implements Ini
|
||||||
|
|
||||||
if (this.routerFunction != null) {
|
if (this.routerFunction != null) {
|
||||||
RouterFunctions.changeParser(this.routerFunction, getPathPatternParser());
|
RouterFunctions.changeParser(this.routerFunction, getPathPatternParser());
|
||||||
if (this.versionStrategy instanceof DefaultApiVersionStrategy davs) {
|
if (getApiVersionStrategy() instanceof DefaultApiVersionStrategy davs) {
|
||||||
if (davs.detectSupportedVersions()) {
|
if (davs.detectSupportedVersions()) {
|
||||||
this.routerFunction.accept(new SupportedVersionVisitor(davs));
|
this.routerFunction.accept(new SupportedVersionVisitor(davs));
|
||||||
}
|
}
|
||||||
|
@ -169,24 +157,10 @@ public class RouterFunctionMapping extends AbstractHandlerMapping implements Ini
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Mono<?> getHandlerInternal(ServerWebExchange exchange) {
|
protected Mono<?> getHandlerInternal(ServerWebExchange exchange) {
|
||||||
|
|
||||||
if (this.routerFunction == null) {
|
if (this.routerFunction == null) {
|
||||||
return Mono.empty();
|
return Mono.empty();
|
||||||
}
|
}
|
||||||
|
ServerRequest request = ServerRequest.create(exchange, this.messageReaders, getApiVersionStrategy());
|
||||||
if (this.versionStrategy != null) {
|
|
||||||
Comparable<?> version = exchange.getAttribute(API_VERSION_ATTRIBUTE);
|
|
||||||
if (version == null) {
|
|
||||||
version = this.versionStrategy.resolveParseAndValidateVersion(exchange);
|
|
||||||
if (version != null) {
|
|
||||||
exchange.getAttributes().put(API_VERSION_ATTRIBUTE, version);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ServerRequest request = ServerRequest.create(
|
|
||||||
exchange, this.messageReaders, this.versionStrategy);
|
|
||||||
|
|
||||||
return this.routerFunction.route(request)
|
return this.routerFunction.route(request)
|
||||||
.doOnNext(handler -> setAttributes(exchange.getAttributes(), request, handler));
|
.doOnNext(handler -> setAttributes(exchange.getAttributes(), request, handler));
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,7 @@ import org.springframework.web.cors.reactive.CorsUtils;
|
||||||
import org.springframework.web.cors.reactive.DefaultCorsProcessor;
|
import org.springframework.web.cors.reactive.DefaultCorsProcessor;
|
||||||
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
|
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
|
||||||
import org.springframework.web.reactive.HandlerMapping;
|
import org.springframework.web.reactive.HandlerMapping;
|
||||||
|
import org.springframework.web.reactive.accept.ApiVersionStrategy;
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
import org.springframework.web.server.WebHandler;
|
import org.springframework.web.server.WebHandler;
|
||||||
import org.springframework.web.util.pattern.PathPatternParser;
|
import org.springframework.web.util.pattern.PathPatternParser;
|
||||||
|
@ -64,6 +65,8 @@ public abstract class AbstractHandlerMapping extends ApplicationObjectSupport
|
||||||
|
|
||||||
private CorsProcessor corsProcessor = new DefaultCorsProcessor();
|
private CorsProcessor corsProcessor = new DefaultCorsProcessor();
|
||||||
|
|
||||||
|
private @Nullable ApiVersionStrategy apiVersionStrategy;
|
||||||
|
|
||||||
private int order = Ordered.LOWEST_PRECEDENCE; // default: same as non-Ordered
|
private int order = Ordered.LOWEST_PRECEDENCE; // default: same as non-Ordered
|
||||||
|
|
||||||
private @Nullable String beanName;
|
private @Nullable String beanName;
|
||||||
|
@ -138,6 +141,23 @@ public abstract class AbstractHandlerMapping extends ApplicationObjectSupport
|
||||||
return this.corsProcessor;
|
return this.corsProcessor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure a strategy to manage API versioning.
|
||||||
|
* @param strategy the strategy to use
|
||||||
|
* @since 7.0
|
||||||
|
*/
|
||||||
|
public void setApiVersionStrategy(@Nullable ApiVersionStrategy strategy) {
|
||||||
|
this.apiVersionStrategy = strategy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the configured {@link ApiVersionStrategy} strategy.
|
||||||
|
* @since 7.0
|
||||||
|
*/
|
||||||
|
public @Nullable ApiVersionStrategy getApiVersionStrategy() {
|
||||||
|
return this.apiVersionStrategy;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specify the order value for this HandlerMapping bean.
|
* Specify the order value for this HandlerMapping bean.
|
||||||
* <p>The default value is {@code Ordered.LOWEST_PRECEDENCE}, meaning non-ordered.
|
* <p>The default value is {@code Ordered.LOWEST_PRECEDENCE}, meaning non-ordered.
|
||||||
|
@ -164,6 +184,7 @@ public abstract class AbstractHandlerMapping extends ApplicationObjectSupport
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<Object> getHandler(ServerWebExchange exchange) {
|
public Mono<Object> getHandler(ServerWebExchange exchange) {
|
||||||
|
initApiVersion(exchange);
|
||||||
return getHandlerInternal(exchange).map(handler -> {
|
return getHandlerInternal(exchange).map(handler -> {
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
logger.debug(exchange.getLogPrefix() + "Mapped to " + handler);
|
logger.debug(exchange.getLogPrefix() + "Mapped to " + handler);
|
||||||
|
@ -182,10 +203,28 @@ public abstract class AbstractHandlerMapping extends ApplicationObjectSupport
|
||||||
return NO_OP_HANDLER;
|
return NO_OP_HANDLER;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (getApiVersionStrategy() != null) {
|
||||||
|
Comparable<?> version = exchange.getAttribute(API_VERSION_ATTRIBUTE);
|
||||||
|
if (version != null) {
|
||||||
|
getApiVersionStrategy().handleDeprecations(version, exchange);
|
||||||
|
}
|
||||||
|
}
|
||||||
return handler;
|
return handler;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void initApiVersion(ServerWebExchange exchange) {
|
||||||
|
if (this.apiVersionStrategy != null) {
|
||||||
|
Comparable<?> version = exchange.getAttribute(API_VERSION_ATTRIBUTE);
|
||||||
|
if (version == null) {
|
||||||
|
version = this.apiVersionStrategy.resolveParseAndValidateVersion(exchange);
|
||||||
|
if (version != null) {
|
||||||
|
exchange.getAttributes().put(API_VERSION_ATTRIBUTE, version);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Look up a handler for the given request, returning an empty {@code Mono}
|
* Look up a handler for the given request, returning an empty {@code Mono}
|
||||||
* if no specific one is found. This method is called by {@link #getHandler}.
|
* if no specific one is found. This method is called by {@link #getHandler}.
|
||||||
|
|
|
@ -28,7 +28,6 @@ import java.util.function.Predicate;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import org.jspecify.annotations.Nullable;
|
import org.jspecify.annotations.Nullable;
|
||||||
import reactor.core.publisher.Mono;
|
|
||||||
|
|
||||||
import org.springframework.context.EmbeddedValueResolverAware;
|
import org.springframework.context.EmbeddedValueResolverAware;
|
||||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||||
|
@ -48,7 +47,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMethod;
|
import org.springframework.web.bind.annotation.RequestMethod;
|
||||||
import org.springframework.web.cors.CorsConfiguration;
|
import org.springframework.web.cors.CorsConfiguration;
|
||||||
import org.springframework.web.method.HandlerMethod;
|
import org.springframework.web.method.HandlerMethod;
|
||||||
import org.springframework.web.reactive.accept.ApiVersionStrategy;
|
|
||||||
import org.springframework.web.reactive.accept.DefaultApiVersionStrategy;
|
import org.springframework.web.reactive.accept.DefaultApiVersionStrategy;
|
||||||
import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
|
import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
|
||||||
import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder;
|
import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder;
|
||||||
|
@ -56,7 +54,6 @@ import org.springframework.web.reactive.result.condition.ConsumesRequestConditio
|
||||||
import org.springframework.web.reactive.result.condition.RequestCondition;
|
import org.springframework.web.reactive.result.condition.RequestCondition;
|
||||||
import org.springframework.web.reactive.result.method.RequestMappingInfo;
|
import org.springframework.web.reactive.result.method.RequestMappingInfo;
|
||||||
import org.springframework.web.reactive.result.method.RequestMappingInfoHandlerMapping;
|
import org.springframework.web.reactive.result.method.RequestMappingInfoHandlerMapping;
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
|
||||||
import org.springframework.web.service.annotation.HttpExchange;
|
import org.springframework.web.service.annotation.HttpExchange;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -82,8 +79,6 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
|
||||||
|
|
||||||
private RequestedContentTypeResolver contentTypeResolver = new RequestedContentTypeResolverBuilder().build();
|
private RequestedContentTypeResolver contentTypeResolver = new RequestedContentTypeResolverBuilder().build();
|
||||||
|
|
||||||
private @Nullable ApiVersionStrategy apiVersionStrategy;
|
|
||||||
|
|
||||||
private @Nullable StringValueResolver embeddedValueResolver;
|
private @Nullable StringValueResolver embeddedValueResolver;
|
||||||
|
|
||||||
private RequestMappingInfo.BuilderConfiguration config = new RequestMappingInfo.BuilderConfiguration();
|
private RequestMappingInfo.BuilderConfiguration config = new RequestMappingInfo.BuilderConfiguration();
|
||||||
|
@ -132,23 +127,6 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
|
||||||
return this.contentTypeResolver;
|
return this.contentTypeResolver;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Configure a strategy to manage API versioning.
|
|
||||||
* @param strategy the strategy to use
|
|
||||||
* @since 7.0
|
|
||||||
*/
|
|
||||||
public void setApiVersionStrategy(@Nullable ApiVersionStrategy strategy) {
|
|
||||||
this.apiVersionStrategy = strategy;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the configured {@link ApiVersionStrategy} strategy.
|
|
||||||
* @since 7.0
|
|
||||||
*/
|
|
||||||
public @Nullable ApiVersionStrategy getApiVersionStrategy() {
|
|
||||||
return this.apiVersionStrategy;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setEmbeddedValueResolver(StringValueResolver resolver) {
|
public void setEmbeddedValueResolver(StringValueResolver resolver) {
|
||||||
this.embeddedValueResolver = resolver;
|
this.embeddedValueResolver = resolver;
|
||||||
|
@ -174,20 +152,6 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
|
||||||
return AnnotatedElementUtils.hasAnnotation(beanType, Controller.class);
|
return AnnotatedElementUtils.hasAnnotation(beanType, Controller.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Mono<HandlerMethod> getHandlerInternal(ServerWebExchange exchange) {
|
|
||||||
if (this.apiVersionStrategy != null) {
|
|
||||||
Comparable<?> version = exchange.getAttribute(API_VERSION_ATTRIBUTE);
|
|
||||||
if (version == null) {
|
|
||||||
version = this.apiVersionStrategy.resolveParseAndValidateVersion(exchange);
|
|
||||||
if (version != null) {
|
|
||||||
exchange.getAttributes().put(API_VERSION_ATTRIBUTE, version);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return super.getHandlerInternal(exchange);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Uses type-level and method-level {@link RequestMapping @RequestMapping}
|
* Uses type-level and method-level {@link RequestMapping @RequestMapping}
|
||||||
* and {@link HttpExchange @HttpExchange} annotations to create the
|
* and {@link HttpExchange @HttpExchange} annotations to create the
|
||||||
|
@ -253,7 +217,7 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
|
||||||
info = createRequestMappingInfo((HttpExchange) exchangeDescriptors.get(0).annotation, customCondition);
|
info = createRequestMappingInfo((HttpExchange) exchangeDescriptors.get(0).annotation, customCondition);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (info != null && this.apiVersionStrategy instanceof DefaultApiVersionStrategy davs) {
|
if (info != null && getApiVersionStrategy() instanceof DefaultApiVersionStrategy davs) {
|
||||||
String version = info.getVersionCondition().getVersion();
|
String version = info.getVersionCondition().getVersion();
|
||||||
if (version != null) {
|
if (version != null) {
|
||||||
davs.addMappedVersion(version);
|
davs.addMappedVersion(version);
|
||||||
|
@ -397,17 +361,6 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
|
||||||
new RequestMethod[] {RequestMethod.valueOf(method)} : EMPTY_REQUEST_METHOD_ARRAY);
|
new RequestMethod[] {RequestMethod.valueOf(method)} : EMPTY_REQUEST_METHOD_ARRAY);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void handleMatch(RequestMappingInfo info, HandlerMethod handlerMethod, ServerWebExchange exchange) {
|
|
||||||
super.handleMatch(info, handlerMethod, exchange);
|
|
||||||
|
|
||||||
Comparable<?> version = exchange.getAttribute(API_VERSION_ATTRIBUTE);
|
|
||||||
if (version != null) {
|
|
||||||
Assert.state(this.apiVersionStrategy != null, "No ApiVersionStrategy");
|
|
||||||
this.apiVersionStrategy.handleDeprecations(version, exchange);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void registerMapping(RequestMappingInfo mapping, Object handler, Method method) {
|
public void registerMapping(RequestMappingInfo mapping, Object handler, Method method) {
|
||||||
super.registerMapping(mapping, handler, method);
|
super.registerMapping(mapping, handler, method);
|
||||||
|
|
|
@ -31,7 +31,6 @@ import org.springframework.http.converter.HttpMessageConverter;
|
||||||
import org.springframework.http.converter.StringHttpMessageConverter;
|
import org.springframework.http.converter.StringHttpMessageConverter;
|
||||||
import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter;
|
import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
import org.springframework.web.accept.ApiVersionStrategy;
|
|
||||||
import org.springframework.web.accept.DefaultApiVersionStrategy;
|
import org.springframework.web.accept.DefaultApiVersionStrategy;
|
||||||
import org.springframework.web.filter.ServerHttpObservationFilter;
|
import org.springframework.web.filter.ServerHttpObservationFilter;
|
||||||
import org.springframework.web.servlet.function.HandlerFunction;
|
import org.springframework.web.servlet.function.HandlerFunction;
|
||||||
|
@ -64,8 +63,6 @@ public class RouterFunctionMapping extends AbstractHandlerMapping implements Ini
|
||||||
|
|
||||||
private List<HttpMessageConverter<?>> messageConverters = Collections.emptyList();
|
private List<HttpMessageConverter<?>> messageConverters = Collections.emptyList();
|
||||||
|
|
||||||
private @Nullable ApiVersionStrategy versionStrategy;
|
|
||||||
|
|
||||||
private boolean detectHandlerFunctionsInAncestorContexts = false;
|
private boolean detectHandlerFunctionsInAncestorContexts = false;
|
||||||
|
|
||||||
|
|
||||||
|
@ -114,15 +111,6 @@ public class RouterFunctionMapping extends AbstractHandlerMapping implements Ini
|
||||||
this.messageConverters = messageConverters;
|
this.messageConverters = messageConverters;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Configure a strategy to manage API versioning.
|
|
||||||
* @param strategy the strategy to use
|
|
||||||
* @since 7.0
|
|
||||||
*/
|
|
||||||
public void setApiVersionStrategy(@Nullable ApiVersionStrategy strategy) {
|
|
||||||
this.versionStrategy = strategy;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set whether to detect handler functions in ancestor ApplicationContexts.
|
* Set whether to detect handler functions in ancestor ApplicationContexts.
|
||||||
* <p>Default is "false": Only handler functions in the current ApplicationContext
|
* <p>Default is "false": Only handler functions in the current ApplicationContext
|
||||||
|
@ -154,7 +142,7 @@ public class RouterFunctionMapping extends AbstractHandlerMapping implements Ini
|
||||||
}
|
}
|
||||||
RouterFunctions.changeParser(this.routerFunction, patternParser);
|
RouterFunctions.changeParser(this.routerFunction, patternParser);
|
||||||
|
|
||||||
if (this.versionStrategy instanceof DefaultApiVersionStrategy davs) {
|
if (getApiVersionStrategy() instanceof DefaultApiVersionStrategy davs) {
|
||||||
if (davs.detectSupportedVersions()) {
|
if (davs.detectSupportedVersions()) {
|
||||||
this.routerFunction.accept(new SupportedVersionVisitor(davs));
|
this.routerFunction.accept(new SupportedVersionVisitor(davs));
|
||||||
}
|
}
|
||||||
|
@ -218,24 +206,10 @@ public class RouterFunctionMapping extends AbstractHandlerMapping implements Ini
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected @Nullable Object getHandlerInternal(HttpServletRequest servletRequest) throws Exception {
|
protected @Nullable Object getHandlerInternal(HttpServletRequest servletRequest) throws Exception {
|
||||||
|
|
||||||
if (this.routerFunction == null) {
|
if (this.routerFunction == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
ServerRequest request = ServerRequest.create(servletRequest, this.messageConverters, getApiVersionStrategy());
|
||||||
if (this.versionStrategy != null) {
|
|
||||||
Comparable<?> version = (Comparable<?>) servletRequest.getAttribute(API_VERSION_ATTRIBUTE);
|
|
||||||
if (version == null) {
|
|
||||||
version = this.versionStrategy.resolveParseAndValidateVersion(servletRequest);
|
|
||||||
if (version != null) {
|
|
||||||
servletRequest.setAttribute(API_VERSION_ATTRIBUTE, version);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ServerRequest request =
|
|
||||||
ServerRequest.create(servletRequest, this.messageConverters, this.versionStrategy);
|
|
||||||
|
|
||||||
HandlerFunction<?> handlerFunction = this.routerFunction.route(request).orElse(null);
|
HandlerFunction<?> handlerFunction = this.routerFunction.route(request).orElse(null);
|
||||||
setAttributes(servletRequest, request, handlerFunction);
|
setAttributes(servletRequest, request, handlerFunction);
|
||||||
return handlerFunction;
|
return handlerFunction;
|
||||||
|
|
|
@ -39,6 +39,7 @@ import org.springframework.util.Assert;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
import org.springframework.util.PathMatcher;
|
import org.springframework.util.PathMatcher;
|
||||||
import org.springframework.web.HttpRequestHandler;
|
import org.springframework.web.HttpRequestHandler;
|
||||||
|
import org.springframework.web.accept.ApiVersionStrategy;
|
||||||
import org.springframework.web.context.request.WebRequestInterceptor;
|
import org.springframework.web.context.request.WebRequestInterceptor;
|
||||||
import org.springframework.web.context.request.async.WebAsyncManager;
|
import org.springframework.web.context.request.async.WebAsyncManager;
|
||||||
import org.springframework.web.context.request.async.WebAsyncUtils;
|
import org.springframework.web.context.request.async.WebAsyncUtils;
|
||||||
|
@ -104,6 +105,8 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
|
||||||
|
|
||||||
private CorsProcessor corsProcessor = new DefaultCorsProcessor();
|
private CorsProcessor corsProcessor = new DefaultCorsProcessor();
|
||||||
|
|
||||||
|
private @Nullable ApiVersionStrategy versionStrategy;
|
||||||
|
|
||||||
private int order = Ordered.LOWEST_PRECEDENCE; // default: same as non-Ordered
|
private int order = Ordered.LOWEST_PRECEDENCE; // default: same as non-Ordered
|
||||||
|
|
||||||
private @Nullable String beanName;
|
private @Nullable String beanName;
|
||||||
|
@ -397,6 +400,23 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
|
||||||
return this.corsProcessor;
|
return this.corsProcessor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure a strategy to manage API versioning.
|
||||||
|
* @param strategy the strategy to use
|
||||||
|
* @since 7.0
|
||||||
|
*/
|
||||||
|
public void setApiVersionStrategy(@Nullable ApiVersionStrategy strategy) {
|
||||||
|
this.versionStrategy = strategy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the configured {@link ApiVersionStrategy} strategy.
|
||||||
|
* @since 7.0
|
||||||
|
*/
|
||||||
|
public @Nullable ApiVersionStrategy getApiVersionStrategy() {
|
||||||
|
return this.versionStrategy;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specify the order value for this HandlerMapping bean.
|
* Specify the order value for this HandlerMapping bean.
|
||||||
* <p>The default value is {@code Ordered.LOWEST_PRECEDENCE}, meaning non-ordered.
|
* <p>The default value is {@code Ordered.LOWEST_PRECEDENCE}, meaning non-ordered.
|
||||||
|
@ -519,6 +539,7 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public final @Nullable HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
|
public final @Nullable HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
|
||||||
|
initApiVersion(request);
|
||||||
Object handler = getHandlerInternal(request);
|
Object handler = getHandlerInternal(request);
|
||||||
if (handler == null) {
|
if (handler == null) {
|
||||||
handler = getDefaultHandler();
|
handler = getDefaultHandler();
|
||||||
|
@ -563,6 +584,18 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
|
||||||
return executionChain;
|
return executionChain;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void initApiVersion(HttpServletRequest request) {
|
||||||
|
if (this.versionStrategy != null) {
|
||||||
|
Comparable<?> version = (Comparable<?>) request.getAttribute(API_VERSION_ATTRIBUTE);
|
||||||
|
if (version == null) {
|
||||||
|
version = this.versionStrategy.resolveParseAndValidateVersion(request);
|
||||||
|
if (version != null) {
|
||||||
|
request.setAttribute(API_VERSION_ATTRIBUTE, version);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Look up a handler for the given request, returning {@code null} if no
|
* Look up a handler for the given request, returning {@code null} if no
|
||||||
* specific one is found. This method is called by {@link #getHandler};
|
* specific one is found. This method is called by {@link #getHandler};
|
||||||
|
@ -634,6 +667,7 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
|
||||||
* @see #getAdaptedInterceptors()
|
* @see #getAdaptedInterceptors()
|
||||||
*/
|
*/
|
||||||
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
|
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
|
||||||
|
|
||||||
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain handlerExecutionChain ?
|
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain handlerExecutionChain ?
|
||||||
handlerExecutionChain : new HandlerExecutionChain(handler));
|
handlerExecutionChain : new HandlerExecutionChain(handler));
|
||||||
|
|
||||||
|
@ -647,6 +681,14 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
|
||||||
chain.addInterceptor(interceptor);
|
chain.addInterceptor(interceptor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.versionStrategy != null) {
|
||||||
|
Comparable<?> version = (Comparable<?>) request.getAttribute(API_VERSION_ATTRIBUTE);
|
||||||
|
if (version != null) {
|
||||||
|
chain.addInterceptor(new ApiVersionDeprecationHandlerInterceptor(this.versionStrategy, version));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return chain;
|
return chain;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -755,4 +797,15 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private record ApiVersionDeprecationHandlerInterceptor(
|
||||||
|
ApiVersionStrategy versionStrategy, Comparable<?> version) implements HandlerInterceptor {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
||||||
|
this.versionStrategy.handleDeprecations(this.version, request, response);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,6 @@ import java.util.function.Predicate;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
import org.jspecify.annotations.Nullable;
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import org.springframework.context.EmbeddedValueResolverAware;
|
import org.springframework.context.EmbeddedValueResolverAware;
|
||||||
|
@ -43,7 +42,6 @@ import org.springframework.util.Assert;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.util.StringValueResolver;
|
import org.springframework.util.StringValueResolver;
|
||||||
import org.springframework.web.accept.ApiVersionStrategy;
|
|
||||||
import org.springframework.web.accept.ContentNegotiationManager;
|
import org.springframework.web.accept.ContentNegotiationManager;
|
||||||
import org.springframework.web.accept.DefaultApiVersionStrategy;
|
import org.springframework.web.accept.DefaultApiVersionStrategy;
|
||||||
import org.springframework.web.bind.annotation.CrossOrigin;
|
import org.springframework.web.bind.annotation.CrossOrigin;
|
||||||
|
@ -53,8 +51,6 @@ import org.springframework.web.bind.annotation.RequestMethod;
|
||||||
import org.springframework.web.cors.CorsConfiguration;
|
import org.springframework.web.cors.CorsConfiguration;
|
||||||
import org.springframework.web.method.HandlerMethod;
|
import org.springframework.web.method.HandlerMethod;
|
||||||
import org.springframework.web.service.annotation.HttpExchange;
|
import org.springframework.web.service.annotation.HttpExchange;
|
||||||
import org.springframework.web.servlet.HandlerExecutionChain;
|
|
||||||
import org.springframework.web.servlet.HandlerInterceptor;
|
|
||||||
import org.springframework.web.servlet.handler.MatchableHandlerMapping;
|
import org.springframework.web.servlet.handler.MatchableHandlerMapping;
|
||||||
import org.springframework.web.servlet.handler.RequestMatchResult;
|
import org.springframework.web.servlet.handler.RequestMatchResult;
|
||||||
import org.springframework.web.servlet.mvc.condition.AbstractRequestCondition;
|
import org.springframework.web.servlet.mvc.condition.AbstractRequestCondition;
|
||||||
|
@ -89,8 +85,6 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
|
||||||
|
|
||||||
private ContentNegotiationManager contentNegotiationManager = new ContentNegotiationManager();
|
private ContentNegotiationManager contentNegotiationManager = new ContentNegotiationManager();
|
||||||
|
|
||||||
private @Nullable ApiVersionStrategy apiVersionStrategy;
|
|
||||||
|
|
||||||
private @Nullable StringValueResolver embeddedValueResolver;
|
private @Nullable StringValueResolver embeddedValueResolver;
|
||||||
|
|
||||||
private RequestMappingInfo.BuilderConfiguration config = new RequestMappingInfo.BuilderConfiguration();
|
private RequestMappingInfo.BuilderConfiguration config = new RequestMappingInfo.BuilderConfiguration();
|
||||||
|
@ -137,23 +131,6 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
|
||||||
return this.contentNegotiationManager;
|
return this.contentNegotiationManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Configure a strategy to manage API versioning.
|
|
||||||
* @param strategy the strategy to use
|
|
||||||
* @since 7.0
|
|
||||||
*/
|
|
||||||
public void setApiVersionStrategy(@Nullable ApiVersionStrategy strategy) {
|
|
||||||
this.apiVersionStrategy = strategy;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the configured {@link ApiVersionStrategy} strategy.
|
|
||||||
* @since 7.0
|
|
||||||
*/
|
|
||||||
public @Nullable ApiVersionStrategy getApiVersionStrategy() {
|
|
||||||
return this.apiVersionStrategy;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setEmbeddedValueResolver(StringValueResolver resolver) {
|
public void setEmbeddedValueResolver(StringValueResolver resolver) {
|
||||||
this.embeddedValueResolver = resolver;
|
this.embeddedValueResolver = resolver;
|
||||||
|
@ -201,20 +178,6 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected @Nullable HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
|
|
||||||
if (this.apiVersionStrategy != null) {
|
|
||||||
Comparable<?> version = (Comparable<?>) request.getAttribute(API_VERSION_ATTRIBUTE);
|
|
||||||
if (version == null) {
|
|
||||||
version = this.apiVersionStrategy.resolveParseAndValidateVersion(request);
|
|
||||||
if (version != null) {
|
|
||||||
request.setAttribute(API_VERSION_ATTRIBUTE, version);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return super.getHandlerInternal(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Uses type-level and method-level {@link RequestMapping @RequestMapping}
|
* Uses type-level and method-level {@link RequestMapping @RequestMapping}
|
||||||
* and {@link HttpExchange @HttpExchange} annotations to create the
|
* and {@link HttpExchange @HttpExchange} annotations to create the
|
||||||
|
@ -287,7 +250,7 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
|
||||||
info = createRequestMappingInfo((HttpExchange) exchangeDescriptors.get(0).annotation, customCondition);
|
info = createRequestMappingInfo((HttpExchange) exchangeDescriptors.get(0).annotation, customCondition);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (info != null && this.apiVersionStrategy instanceof DefaultApiVersionStrategy davs) {
|
if (info != null && getApiVersionStrategy() instanceof DefaultApiVersionStrategy davs) {
|
||||||
String version = info.getVersionCondition().getVersion();
|
String version = info.getVersionCondition().getVersion();
|
||||||
if (version != null) {
|
if (version != null) {
|
||||||
davs.addMappedVersion(version);
|
davs.addMappedVersion(version);
|
||||||
|
@ -425,16 +388,6 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
|
||||||
new RequestMethod[] {RequestMethod.valueOf(method)} : EMPTY_REQUEST_METHOD_ARRAY);
|
new RequestMethod[] {RequestMethod.valueOf(method)} : EMPTY_REQUEST_METHOD_ARRAY);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
|
|
||||||
HandlerExecutionChain executionChain = super.getHandlerExecutionChain(handler, request);
|
|
||||||
Comparable<?> version = (Comparable<?>) request.getAttribute(API_VERSION_ATTRIBUTE);
|
|
||||||
if (version != null) {
|
|
||||||
executionChain.addInterceptor(new DeprecationInterceptor(version));
|
|
||||||
}
|
|
||||||
return executionChain;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void registerMapping(RequestMappingInfo mapping, Object handler, Method method) {
|
public void registerMapping(RequestMappingInfo mapping, Object handler, Method method) {
|
||||||
super.registerMapping(mapping, handler, method);
|
super.registerMapping(mapping, handler, method);
|
||||||
|
@ -597,21 +550,4 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private final class DeprecationInterceptor implements HandlerInterceptor {
|
|
||||||
|
|
||||||
private final Comparable<?> version;
|
|
||||||
|
|
||||||
private DeprecationInterceptor(Comparable<?> version) {
|
|
||||||
this.version = version;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
|
||||||
Assert.state(apiVersionStrategy != null, "No ApiVersionStrategy");
|
|
||||||
apiVersionStrategy.handleDeprecations(this.version, request, response);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue