Deprecate path extension strategies

This commit deprecates PathExtensionContentNegotiationStrategy and
ServletPathExtensionContentNegotiationStrategy and also updates code
that depends on them internally to remove that dependence.

See gh-24179
This commit is contained in:
Rossen Stoyanchev 2020-01-22 13:34:27 +00:00
parent 542e187831
commit c69703ffdb
8 changed files with 138 additions and 73 deletions

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2019 the original author or authors. * Copyright 2002-2020 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -310,7 +310,11 @@ public class StandaloneMockMvcBuilder extends AbstractMockMvcBuilder<StandaloneM
* Whether to use suffix pattern match (".*") when matching patterns to * Whether to use suffix pattern match (".*") when matching patterns to
* requests. If enabled a method mapped to "/users" also matches to "/users.*". * requests. If enabled a method mapped to "/users" also matches to "/users.*".
* <p>The default value is {@code true}. * <p>The default value is {@code true}.
* @deprecated as of 5.2.4. See class-level note in
* {@link RequestMappingHandlerMapping} on the deprecation of path extension
* config options.
*/ */
@Deprecated
public StandaloneMockMvcBuilder setUseSuffixPatternMatch(boolean useSuffixPatternMatch) { public StandaloneMockMvcBuilder setUseSuffixPatternMatch(boolean useSuffixPatternMatch) {
this.useSuffixPatternMatch = useSuffixPatternMatch; this.useSuffixPatternMatch = useSuffixPatternMatch;
return this; return this;
@ -442,6 +446,7 @@ public class StandaloneMockMvcBuilder extends AbstractMockMvcBuilder<StandaloneM
/** Using the MVC Java configuration as the starting point for the "standalone" setup. */ /** Using the MVC Java configuration as the starting point for the "standalone" setup. */
private class StandaloneConfiguration extends WebMvcConfigurationSupport { private class StandaloneConfiguration extends WebMvcConfigurationSupport {
@SuppressWarnings("deprecation")
public RequestMappingHandlerMapping getHandlerMapping( public RequestMappingHandlerMapping getHandlerMapping(
FormattingConversionService mvcConversionService, FormattingConversionService mvcConversionService,
ResourceUrlProvider mvcResourceUrlProvider) { ResourceUrlProvider mvcResourceUrlProvider) {

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2019 the original author or authors. * Copyright 2002-2020 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -41,7 +41,11 @@ import org.springframework.web.util.UrlPathHelper;
* *
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
* @since 3.2 * @since 3.2
* @deprecated as of 5.2.4. See class-level note in
* {@link ContentNegotiationManagerFactoryBean} on the deprecation of path
* extension config options.
*/ */
@Deprecated
public class PathExtensionContentNegotiationStrategy extends AbstractMappingContentNegotiationStrategy { public class PathExtensionContentNegotiationStrategy extends AbstractMappingContentNegotiationStrategy {
private UrlPathHelper urlPathHelper = new UrlPathHelper(); private UrlPathHelper urlPathHelper = new UrlPathHelper();

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2017 the original author or authors. * Copyright 2002-2020 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -34,7 +34,11 @@ import org.springframework.web.context.request.NativeWebRequest;
* *
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
* @since 3.2 * @since 3.2
* @deprecated as of 5.2.4. See class-level note in
* {@link ContentNegotiationManagerFactoryBean} on the deprecation of path
* extension config options.
*/ */
@Deprecated
public class ServletPathExtensionContentNegotiationStrategy extends PathExtensionContentNegotiationStrategy { public class ServletPathExtensionContentNegotiationStrategy extends PathExtensionContentNegotiationStrategy {
private final ServletContext servletContext; private final ServletContext servletContext;

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2019 the original author or authors. * Copyright 2002-2020 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -151,6 +151,7 @@ public class ResourceHandlerRegistry {
* of no registrations. * of no registrations.
*/ */
@Nullable @Nullable
@SuppressWarnings("deprecation")
protected AbstractHandlerMapping getHandlerMapping() { protected AbstractHandlerMapping getHandlerMapping() {
if (this.registrations.isEmpty()) { if (this.registrations.isEmpty()) {
return null; return null;

View File

@ -26,6 +26,7 @@ import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Set; import java.util.Set;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
@ -43,6 +44,7 @@ import org.springframework.http.HttpOutputMessage;
import org.springframework.http.HttpRange; import org.springframework.http.HttpRange;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.MediaTypeFactory;
import org.springframework.http.converter.GenericHttpMessageConverter; import org.springframework.http.converter.GenericHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotWritableException; import org.springframework.http.converter.HttpMessageNotWritableException;
@ -54,7 +56,6 @@ import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.web.HttpMediaTypeNotAcceptableException; import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.accept.ContentNegotiationManager; import org.springframework.web.accept.ContentNegotiationManager;
import org.springframework.web.accept.PathExtensionContentNegotiationStrategy;
import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.ServletWebRequest; import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler; import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
@ -102,8 +103,6 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe
private final ContentNegotiationManager contentNegotiationManager; private final ContentNegotiationManager contentNegotiationManager;
private final PathExtensionContentNegotiationStrategy pathStrategy;
private final Set<String> safeExtensions = new HashSet<>(); private final Set<String> safeExtensions = new HashSet<>();
@ -133,17 +132,10 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe
super(converters, requestResponseBodyAdvice); super(converters, requestResponseBodyAdvice);
this.contentNegotiationManager = (manager != null ? manager : new ContentNegotiationManager()); this.contentNegotiationManager = (manager != null ? manager : new ContentNegotiationManager());
this.pathStrategy = initPathStrategy(this.contentNegotiationManager);
this.safeExtensions.addAll(this.contentNegotiationManager.getAllFileExtensions()); this.safeExtensions.addAll(this.contentNegotiationManager.getAllFileExtensions());
this.safeExtensions.addAll(WHITELISTED_EXTENSIONS); this.safeExtensions.addAll(WHITELISTED_EXTENSIONS);
} }
private static PathExtensionContentNegotiationStrategy initPathStrategy(ContentNegotiationManager manager) {
Class<PathExtensionContentNegotiationStrategy> clazz = PathExtensionContentNegotiationStrategy.class;
PathExtensionContentNegotiationStrategy strategy = manager.getStrategy(clazz);
return (strategy != null ? strategy : new PathExtensionContentNegotiationStrategy());
}
/** /**
* Creates a new {@link HttpOutputMessage} from the given {@link NativeWebRequest}. * Creates a new {@link HttpOutputMessage} from the given {@link NativeWebRequest}.
@ -481,26 +473,21 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe
return true; return true;
} }
} }
return safeMediaTypesForExtension(new ServletWebRequest(request), extension); MediaType mediaType = resolveMediaType(request, extension);
return (mediaType != null && (safeMediaType(mediaType)));
} }
private boolean safeMediaTypesForExtension(NativeWebRequest request, String extension) { @Nullable
List<MediaType> mediaTypes = null; private MediaType resolveMediaType(ServletRequest request, String extension) {
try { MediaType result = null;
mediaTypes = this.pathStrategy.resolveMediaTypeKey(request, extension); String rawMimeType = request.getServletContext().getMimeType("file." + extension);
if (StringUtils.hasText(rawMimeType)) {
result = MediaType.parseMediaType(rawMimeType);
} }
catch (HttpMediaTypeNotAcceptableException ex) { if (result == null || MediaType.APPLICATION_OCTET_STREAM.equals(result)) {
// Ignore result = MediaTypeFactory.getMediaType("file." + extension).orElse(null);
} }
if (CollectionUtils.isEmpty(mediaTypes)) { return result;
return false;
}
for (MediaType mediaType : mediaTypes) {
if (!safeMediaType(mediaType)) {
return false;
}
}
return true;
} }
private boolean safeMediaType(MediaType mediaType) { private boolean safeMediaType(MediaType mediaType) {

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2019 the original author or authors. * Copyright 2002-2020 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -24,6 +24,7 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -43,6 +44,7 @@ import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.http.HttpRange; import org.springframework.http.HttpRange;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.MediaTypeFactory;
import org.springframework.http.converter.ResourceHttpMessageConverter; import org.springframework.http.converter.ResourceHttpMessageConverter;
import org.springframework.http.converter.ResourceRegionHttpMessageConverter; import org.springframework.http.converter.ResourceRegionHttpMessageConverter;
import org.springframework.http.server.ServletServerHttpRequest; import org.springframework.http.server.ServletServerHttpRequest;
@ -56,8 +58,6 @@ import org.springframework.util.StringUtils;
import org.springframework.util.StringValueResolver; import org.springframework.util.StringValueResolver;
import org.springframework.web.HttpRequestHandler; import org.springframework.web.HttpRequestHandler;
import org.springframework.web.accept.ContentNegotiationManager; import org.springframework.web.accept.ContentNegotiationManager;
import org.springframework.web.accept.PathExtensionContentNegotiationStrategy;
import org.springframework.web.accept.ServletPathExtensionContentNegotiationStrategy;
import org.springframework.web.context.request.ServletWebRequest; import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.CorsConfigurationSource;
@ -129,8 +129,7 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
@Nullable @Nullable
private ContentNegotiationManager contentNegotiationManager; private ContentNegotiationManager contentNegotiationManager;
@Nullable private final Map<String, MediaType> mediaTypes = new HashMap<>(4);
private PathExtensionContentNegotiationStrategy contentNegotiationStrategy;
@Nullable @Nullable
private CorsConfiguration corsConfiguration; private CorsConfiguration corsConfiguration;
@ -262,7 +261,11 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
* media types for resources being served. If the manager contains a path * media types for resources being served. If the manager contains a path
* extension strategy it will be checked for registered file extension. * extension strategy it will be checked for registered file extension.
* @since 4.3 * @since 4.3
* @deprecated as of 5.2.4 in favor of using {@link #setMediaTypes(Map)}
* with mappings possibly obtained from
* {@link ContentNegotiationManager#getMediaTypeMappings()}.
*/ */
@Deprecated
public void setContentNegotiationManager(@Nullable ContentNegotiationManager contentNegotiationManager) { public void setContentNegotiationManager(@Nullable ContentNegotiationManager contentNegotiationManager) {
this.contentNegotiationManager = contentNegotiationManager; this.contentNegotiationManager = contentNegotiationManager;
} }
@ -270,12 +273,38 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
/** /**
* Return the configured content negotiation manager. * Return the configured content negotiation manager.
* @since 4.3 * @since 4.3
* @deprecated as of 5.2.4.
*/ */
@Nullable @Nullable
@Deprecated
public ContentNegotiationManager getContentNegotiationManager() { public ContentNegotiationManager getContentNegotiationManager() {
return this.contentNegotiationManager; return this.contentNegotiationManager;
} }
/**
* Add mappings between file extensions, extracted from the filename of a
* static {@link Resource}, and corresponding media type to set on the
* response.
* <p>Use of this method is typically not necessary since mappings are
* otherwise determined via
* {@link javax.servlet.ServletContext#getMimeType(String)} or via
* {@link MediaTypeFactory#getMediaType(Resource)}.
* @param mediaTypes media type mappings
* @since 5.2.4
*/
public void setMediaTypes(Map<String, MediaType> mediaTypes) {
mediaTypes.forEach((ext, mediaType) ->
this.mediaTypes.put(ext.toLowerCase(Locale.ENGLISH), mediaType));
}
/**
* Return the {@link #setMediaTypes(Map) configured} media types.
* @since 5.2.4
*/
public Map<String, MediaType> getMediaTypes() {
return this.mediaTypes;
}
/** /**
* Specify the CORS configuration for resources served by this handler. * Specify the CORS configuration for resources served by this handler.
* <p>By default this is not set in which allows cross-origin requests. * <p>By default this is not set in which allows cross-origin requests.
@ -344,7 +373,17 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
this.resourceRegionHttpMessageConverter = new ResourceRegionHttpMessageConverter(); this.resourceRegionHttpMessageConverter = new ResourceRegionHttpMessageConverter();
} }
this.contentNegotiationStrategy = initContentNegotiationStrategy(); ContentNegotiationManager manager = getContentNegotiationManager();
if (manager != null) {
setMediaTypes(manager.getMediaTypeMappings());
}
@SuppressWarnings("deprecation")
org.springframework.web.accept.PathExtensionContentNegotiationStrategy strategy =
initContentNegotiationStrategy();
if (strategy != null) {
setMediaTypes(strategy.getMediaTypes());
}
} }
private void resolveResourceLocations() { private void resolveResourceLocations() {
@ -412,26 +451,20 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
} }
/** /**
* Initialize the content negotiation strategy depending on the {@code ContentNegotiationManager} * Initialize the strategy to use to determine the media type for a resource.
* setup and the availability of a {@code ServletContext}. * @deprecated as of 5.2.4 this method returns {@code null}, and if a
* @see ServletPathExtensionContentNegotiationStrategy * sub-class returns an actual instance,the instance is used only as a
* @see PathExtensionContentNegotiationStrategy * source of media type mappings, if it contains any. Please, use
* {@link #setMediaTypes(Map)} instead, or if you need to change behavior,
* you can override {@link #getMediaType(HttpServletRequest, Resource)}.
*/ */
protected PathExtensionContentNegotiationStrategy initContentNegotiationStrategy() { @Nullable
Map<String, MediaType> mediaTypes = null; @Deprecated
if (getContentNegotiationManager() != null) { @SuppressWarnings("deprecation")
PathExtensionContentNegotiationStrategy strategy = protected org.springframework.web.accept.PathExtensionContentNegotiationStrategy initContentNegotiationStrategy() {
getContentNegotiationManager().getStrategy(PathExtensionContentNegotiationStrategy.class); return null;
if (strategy != null) {
mediaTypes = new HashMap<>(strategy.getMediaTypes());
}
}
return (getServletContext() != null ?
new ServletPathExtensionContentNegotiationStrategy(getServletContext(), mediaTypes) :
new PathExtensionContentNegotiationStrategy(mediaTypes));
} }
/** /**
* Processes a resource request. * Processes a resource request.
* <p>Checks for the existence of the requested resource in the configured list of locations. * <p>Checks for the existence of the requested resource in the configured list of locations.
@ -659,17 +692,40 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
/** /**
* Determine the media type for the given request and the resource matched * Determine the media type for the given request and the resource matched
* to it. This implementation tries to determine the MediaType based on the * to it. This implementation tries to determine the MediaType using one of
* file extension of the Resource via * the following lookups based on the resource filename and its path
* {@link ServletPathExtensionContentNegotiationStrategy#getMediaTypeForResource}. * extension:
* <ol>
* <li>{@link javax.servlet.ServletContext#getMimeType(String)}
* <li>{@link #getMediaTypes()}
* <li>{@link MediaTypeFactory#getMediaType(String)}
* </ol>
* @param request the current request * @param request the current request
* @param resource the resource to check * @param resource the resource to check
* @return the corresponding media type, or {@code null} if none found * @return the corresponding media type, or {@code null} if none found
*/ */
@Nullable @Nullable
protected MediaType getMediaType(HttpServletRequest request, Resource resource) { protected MediaType getMediaType(HttpServletRequest request, Resource resource) {
return (this.contentNegotiationStrategy != null ? MediaType result = null;
this.contentNegotiationStrategy.getMediaTypeForResource(resource) : null); String mimeType = request.getServletContext().getMimeType(resource.getFilename());
if (StringUtils.hasText(mimeType)) {
result = MediaType.parseMediaType(mimeType);
}
if (result == null || MediaType.APPLICATION_OCTET_STREAM.equals(result)) {
MediaType mediaType = null;
String filename = resource.getFilename();
String ext = StringUtils.getFilenameExtension(filename);
if (ext != null) {
mediaType = this.mediaTypes.get(ext.toLowerCase(Locale.ENGLISH));
}
if (mediaType == null) {
mediaType = MediaTypeFactory.getMediaType(filename).orElse(null);
}
if (mediaType != null) {
result = mediaType;
}
}
return result;
} }
/** /**

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2019 the original author or authors. * Copyright 2002-2020 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -74,13 +74,15 @@ public class ResourceHttpRequestHandlerTests {
paths.add(new ClassPathResource("testalternatepath/", getClass())); paths.add(new ClassPathResource("testalternatepath/", getClass()));
paths.add(new ClassPathResource("META-INF/resources/webjars/")); paths.add(new ClassPathResource("META-INF/resources/webjars/"));
TestServletContext servletContext = new TestServletContext();
this.handler = new ResourceHttpRequestHandler(); this.handler = new ResourceHttpRequestHandler();
this.handler.setLocations(paths); this.handler.setLocations(paths);
this.handler.setCacheSeconds(3600); this.handler.setCacheSeconds(3600);
this.handler.setServletContext(new TestServletContext()); this.handler.setServletContext(servletContext);
this.handler.afterPropertiesSet(); this.handler.afterPropertiesSet();
this.request = new MockHttpServletRequest("GET", ""); this.request = new MockHttpServletRequest(servletContext, "GET", "");
this.response = new MockHttpServletResponse(); this.response = new MockHttpServletResponse();
} }
@ -283,15 +285,12 @@ public class ResourceHttpRequestHandlerTests {
@Test // SPR-14368 @Test // SPR-14368
public void getResourceWithMediaTypeResolvedThroughServletContext() throws Exception { public void getResourceWithMediaTypeResolvedThroughServletContext() throws Exception {
MockServletContext servletContext = new MockServletContext() { MockServletContext servletContext = new MockServletContext() {
@Override @Override
public String getMimeType(String filePath) { public String getMimeType(String filePath) {
return "foo/bar"; return "foo/bar";
} }
@Override
public String getVirtualServerName() {
return "";
}
}; };
List<Resource> paths = Collections.singletonList(new ClassPathResource("test/", getClass())); List<Resource> paths = Collections.singletonList(new ClassPathResource("test/", getClass()));
@ -300,8 +299,9 @@ public class ResourceHttpRequestHandlerTests {
handler.setLocations(paths); handler.setLocations(paths);
handler.afterPropertiesSet(); handler.afterPropertiesSet();
this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.css"); MockHttpServletRequest request = new MockHttpServletRequest(servletContext, "GET", "");
handler.handleRequest(this.request, this.response); request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.css");
handler.handleRequest(request, this.response);
assertThat(this.response.getContentType()).isEqualTo("foo/bar"); assertThat(this.response.getContentType()).isEqualTo("foo/bar");
assertThat(this.response.getContentAsString()).isEqualTo("h1 { color:red; }"); assertThat(this.response.getContentAsString()).isEqualTo("h1 { color:red; }");

View File

@ -1683,6 +1683,18 @@ the issues that come with file extensions. Alternatively, if you must use file e
restricting them to a list of explicitly registered extensions through the restricting them to a list of explicitly registered extensions through the
`mediaTypes` property of <<mvc-config-content-negotiation,ContentNegotiationConfigurer>>. `mediaTypes` property of <<mvc-config-content-negotiation,ContentNegotiationConfigurer>>.
[INFO]
====
Starting in 5.2.4, path extension related options for request mapping in
{api-spring-framework}/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java[RequestMappingHandlerMapping]
and for content negotiation in
{api-spring-framework}/org.springframework.web.accept/ContentNegotiationManagerFactoryBean.java[ContentNegotiationManagerFactoryBean]
are deprecated. See Spring Framework issue
https://github.com/spring-projects/spring-framework/issues/24179[#24179] and related
issues for further plans.
====
[[mvc-ann-requestmapping-rfd]] [[mvc-ann-requestmapping-rfd]]
==== Suffix Match and RFD ==== Suffix Match and RFD
@ -5779,13 +5791,11 @@ The following example shows how to customize path matching in Java configuration
@Override @Override
public void configurePathMatch(PathMatchConfigurer configurer) { public void configurePathMatch(PathMatchConfigurer configurer) {
configurer configurer
.setUseSuffixPatternMatch(true)
.setUseTrailingSlashMatch(false) .setUseTrailingSlashMatch(false)
.setUseRegisteredSuffixPatternMatch(true) .setUseRegisteredSuffixPatternMatch(true)
.setPathMatcher(antPathMatcher()) .setPathMatcher(antPathMatcher())
.setUrlPathHelper(urlPathHelper()) .setUrlPathHelper(urlPathHelper())
.addPathPrefix("/api", .addPathPrefix("/api", HandlerTypePredicate.forAnnotation(RestController.class));
HandlerTypePredicate.forAnnotation(RestController.class));
} }
@Bean @Bean
@ -5813,8 +5823,7 @@ The following example shows how to customize path matching in Java configuration
.setUseRegisteredSuffixPatternMatch(true) .setUseRegisteredSuffixPatternMatch(true)
.setPathMatcher(antPathMatcher()) .setPathMatcher(antPathMatcher())
.setUrlPathHelper(urlPathHelper()) .setUrlPathHelper(urlPathHelper())
.addPathPrefix("/api", .addPathPrefix("/api", HandlerTypePredicate.forAnnotation(RestController::class.java))
HandlerTypePredicate.forAnnotation(RestController::class.java))
} }
@Bean @Bean
@ -5835,7 +5844,6 @@ The following example shows how to achieve the same configuration in XML:
---- ----
<mvc:annotation-driven> <mvc:annotation-driven>
<mvc:path-matching <mvc:path-matching
suffix-pattern="true"
trailing-slash="false" trailing-slash="false"
registered-suffixes-only="true" registered-suffixes-only="true"
path-helper="pathHelper" path-helper="pathHelper"