SPR-7707 - Unexpected behavior with class-level @RequestMappings
git-svn-id: https://src.springframework.org/svn/spring-framework/trunk@3793 50f2f4bb-b051-0410-bef5-90022cba6387
This commit is contained in:
parent
810bb4a441
commit
bcad0df063
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2007 the original author or authors.
|
* Copyright 2002-2010 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,6 +74,14 @@ public interface HandlerMapping {
|
||||||
*/
|
*/
|
||||||
String BEST_MATCHING_PATTERN_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingPattern";
|
String BEST_MATCHING_PATTERN_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingPattern";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name of the boolean {@link HttpServletRequest} attribute that indicates
|
||||||
|
* whether type-level mappings should be inspected.
|
||||||
|
* <p>Note: This attribute is not required to be supported by all
|
||||||
|
* HandlerMapping implementations.
|
||||||
|
*/
|
||||||
|
String INTROSPECT_TYPE_LEVEL_MAPPING = HandlerMapping.class.getName() + ".introspectTypeLevelMapping";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Name of the {@link HttpServletRequest} attribute that contains the URI
|
* Name of the {@link HttpServletRequest} attribute that contains the URI
|
||||||
* templates map, mapping variable names to values.
|
* templates map, mapping variable names to values.
|
||||||
|
|
|
||||||
|
|
@ -429,6 +429,13 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
|
||||||
return Collections.unmodifiableMap(this.handlerMap);
|
return Collections.unmodifiableMap(this.handlerMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether this handler mapping support type-level mappings. Default to {@code false}.
|
||||||
|
*/
|
||||||
|
protected boolean supportsTypeLevelMappings() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Special interceptor for exposing the
|
* Special interceptor for exposing the
|
||||||
|
|
@ -449,10 +456,11 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
|
||||||
@Override
|
@Override
|
||||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
||||||
exposePathWithinMapping(this.bestMatchingPattern, this.pathWithinMapping, request);
|
exposePathWithinMapping(this.bestMatchingPattern, this.pathWithinMapping, request);
|
||||||
|
request.setAttribute(HandlerMapping.INTROSPECT_TYPE_LEVEL_MAPPING, supportsTypeLevelMappings());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Special interceptor for exposing the
|
* Special interceptor for exposing the
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,6 @@ import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
import javax.servlet.ServletRequest;
|
import javax.servlet.ServletRequest;
|
||||||
import javax.servlet.ServletResponse;
|
import javax.servlet.ServletResponse;
|
||||||
|
|
@ -45,6 +44,7 @@ import javax.servlet.http.HttpSession;
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
import org.springframework.beans.BeanUtils;
|
import org.springframework.beans.BeanUtils;
|
||||||
import org.springframework.beans.factory.BeanFactory;
|
import org.springframework.beans.factory.BeanFactory;
|
||||||
import org.springframework.beans.factory.BeanFactoryAware;
|
import org.springframework.beans.factory.BeanFactoryAware;
|
||||||
|
|
@ -571,6 +571,27 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator
|
||||||
}
|
}
|
||||||
mappingInfo.sortMatchedPatterns(pathComparator);
|
mappingInfo.sortMatchedPatterns(pathComparator);
|
||||||
}
|
}
|
||||||
|
else if (useTypeLevelMapping(request)) {
|
||||||
|
String[] typeLevelPatterns = getTypeLevelMapping().value();
|
||||||
|
for (String typeLevelPattern : typeLevelPatterns) {
|
||||||
|
if (!typeLevelPattern.startsWith("/")) {
|
||||||
|
typeLevelPattern = "/" + typeLevelPattern;
|
||||||
|
}
|
||||||
|
if (isPathMatchInternal(typeLevelPattern, lookupPath)) {
|
||||||
|
if (mappingInfo.matches(request)) {
|
||||||
|
match = true;
|
||||||
|
mappingInfo.addMatchedPattern(typeLevelPattern);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (!mappingInfo.matchesRequestMethod(request)) {
|
||||||
|
allowedMethods.addAll(mappingInfo.methodNames());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mappingInfo.sortMatchedPatterns(pathComparator);
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
// No paths specified: parameter match sufficient.
|
// No paths specified: parameter match sufficient.
|
||||||
match = mappingInfo.matches(request);
|
match = mappingInfo.matches(request);
|
||||||
|
|
@ -638,6 +659,14 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean useTypeLevelMapping(HttpServletRequest request) {
|
||||||
|
if (!hasTypeLevelMapping() || ObjectUtils.isEmpty(getTypeLevelMapping().value())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return (Boolean) request.getAttribute(
|
||||||
|
HandlerMapping.INTROSPECT_TYPE_LEVEL_MAPPING);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines the combined pattern for the given methodLevelPattern and path.
|
* Determines the combined pattern for the given methodLevelPattern and path.
|
||||||
* <p>Uses the following algorithm: <ol>
|
* <p>Uses the following algorithm: <ol>
|
||||||
|
|
@ -649,7 +678,7 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator
|
||||||
* </ol>
|
* </ol>
|
||||||
*/
|
*/
|
||||||
private String getCombinedPattern(String methodLevelPattern, String lookupPath, HttpServletRequest request) {
|
private String getCombinedPattern(String methodLevelPattern, String lookupPath, HttpServletRequest request) {
|
||||||
if (hasTypeLevelMapping() && (!ObjectUtils.isEmpty(getTypeLevelMapping().value()))) {
|
if (useTypeLevelMapping(request)) {
|
||||||
String[] typeLevelPatterns = getTypeLevelMapping().value();
|
String[] typeLevelPatterns = getTypeLevelMapping().value();
|
||||||
for (String typeLevelPattern : typeLevelPatterns) {
|
for (String typeLevelPattern : typeLevelPatterns) {
|
||||||
if (!typeLevelPattern.startsWith("/")) {
|
if (!typeLevelPattern.startsWith("/")) {
|
||||||
|
|
|
||||||
|
|
@ -261,4 +261,8 @@ public class DefaultAnnotationHandlerMapping extends AbstractDetectingUrlHandler
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean supportsTypeLevelMappings() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,6 @@ import javax.validation.Valid;
|
||||||
import javax.validation.constraints.NotNull;
|
import javax.validation.constraints.NotNull;
|
||||||
import javax.xml.bind.annotation.XmlRootElement;
|
import javax.xml.bind.annotation.XmlRootElement;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
|
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
|
||||||
|
|
@ -134,6 +133,8 @@ import org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMap
|
||||||
import org.springframework.web.servlet.view.InternalResourceViewResolver;
|
import org.springframework.web.servlet.view.InternalResourceViewResolver;
|
||||||
import org.springframework.web.util.NestedServletException;
|
import org.springframework.web.util.NestedServletException;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Juergen Hoeller
|
* @author Juergen Hoeller
|
||||||
* @author Sam Brannen
|
* @author Sam Brannen
|
||||||
|
|
@ -1842,6 +1843,16 @@ public class ServletAnnotationControllerTests {
|
||||||
assertEquals("1-2", response.getContentAsString());
|
assertEquals("1-2", response.getContentAsString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMatchWithoutMethodLevelPath() throws Exception {
|
||||||
|
initServlet(NoPathGetAndM2PostController.class);
|
||||||
|
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/t1/m2");
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
servlet.service(request, response);
|
||||||
|
assertEquals(405, response.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Controllers
|
* Controllers
|
||||||
|
|
@ -3104,4 +3115,20 @@ public class ServletAnnotationControllerTests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Controller
|
||||||
|
@RequestMapping("/t1")
|
||||||
|
protected static class NoPathGetAndM2PostController {
|
||||||
|
@RequestMapping(method = RequestMethod.GET)
|
||||||
|
public void handle1(Writer writer) throws IOException {
|
||||||
|
writer.write("handle1");
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequestMapping(value = "/m2", method = RequestMethod.POST)
|
||||||
|
public void handle2(Writer writer) throws IOException {
|
||||||
|
writer.write("handle2");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue