提交 8d0ab1d2 编写于 作者: R Rossen Stoyanchev

Refine HandlerMethod registration to allow detection by handler instance as well as by bean name

上级 d0c31ad8
......@@ -96,6 +96,14 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
return urlPathHelper;
}
/**
* Return the map with all {@link HandlerMethod}s. The key of the map is the generic type
* <strong>{@code <T>}</strong> containing request mapping conditions.
*/
public Map<T, HandlerMethod> getHandlerMethods() {
return Collections.unmodifiableMap(handlerMethods);
}
/**
* Calls the initialization of the superclass and detects handlers.
*/
......@@ -115,35 +123,36 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
logger.debug("Looking for request mappings in application context: " + getApplicationContext());
}
for (String beanName : getApplicationContext().getBeanNamesForType(Object.class)) {
if (isHandler(beanName)){
if (isHandler(getApplicationContext().getType(beanName))){
detectHandlerMethods(beanName);
}
}
}
/**
* Determines if the given bean is a handler that should be introspected for handler methods.
* @param beanName the name of the bean to check
* @return true if the bean is a handler and may contain handler methods, false otherwise.
* Determines if the given type could contain handler methods.
* @param beanType the type to check
* @return true if this a type that could contain handler methods, false otherwise.
*/
protected abstract boolean isHandler(String beanName);
protected abstract boolean isHandler(Class<?> beanType);
/**
* Detect and register handler methods for the specified handler.
* @param handler the bean name of a handler or a handler instance
*/
private void detectHandlerMethods(final String beanName) {
Class<?> handlerType = getApplicationContext().getType(beanName);
protected void detectHandlerMethods(final Object handler) {
final Class<?> handlerType = (handler instanceof String) ?
getApplicationContext().getType((String) handler) : handler.getClass();
Set<Method> methods = HandlerMethodSelector.selectMethods(handlerType, new MethodFilter() {
public boolean matches(Method method) {
return getMappingForMethod(beanName, method) != null;
return getMappingForMethod(method, handlerType) != null;
}
});
for (Method method : methods) {
HandlerMethod handlerMethod = new HandlerMethod(beanName, getApplicationContext(), method);
T mapping = getMappingForMethod(beanName, method);
Set<String> paths = getMappingPaths(mapping);
registerHandlerMethod(paths, mapping, handlerMethod);
T mapping = getMappingForMethod(method, handlerType);
registerHandlerMethod(handler, method, mapping);
}
}
......@@ -151,40 +160,50 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
* Provides a request mapping for the given bean method. A method for which no request mapping can be determined
* is not considered a handler method.
*
* @param beanName the name of the bean the method belongs to
* @param method the method to create a mapping for
* @param handlerType the actual handler type (possibly a subtype of {@code method.getDeclaringClass()})
* @return the mapping, or {@code null} if the method is not mapped
*/
protected abstract T getMappingForMethod(String beanName, Method method);
protected abstract T getMappingForMethod(Method method, Class<?> handlerType);
/**
* Registers a {@link HandlerMethod} with the given mapping.
*
* @param paths URL paths mapped to this method
* @param mapping the mapping for the method
* @param handlerMethod the handler method to register
* @param handler the bean name of the handler or the actual handler instance
* @param method the method to register
* @param mapping the mapping conditions associated with the handler method
* @throws IllegalStateException if another method was already register under the same mapping
*/
protected void registerHandlerMethod(Set<String> paths, T mapping, HandlerMethod handlerMethod) {
Assert.notNull(mapping, "'mapping' must not be null");
Assert.notNull(handlerMethod, "'handlerMethod' must not be null");
HandlerMethod mappedHandlerMethod = handlerMethods.get(mapping);
if (mappedHandlerMethod != null && !mappedHandlerMethod.equals(handlerMethod)) {
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
HandlerMethod handlerMethod;
if (handler instanceof String) {
String beanName = (String) handler;
handlerMethod = new HandlerMethod(beanName, getApplicationContext(), method);
}
else {
handlerMethod = new HandlerMethod(handler, method);
}
HandlerMethod oldHandlerMethod = handlerMethods.get(mapping);
if (oldHandlerMethod != null && !oldHandlerMethod.equals(handlerMethod)) {
throw new IllegalStateException("Ambiguous mapping found. Cannot map '" + handlerMethod.getBean()
+ "' bean method \n" + handlerMethod + "\nto " + mapping + ": There is already '"
+ mappedHandlerMethod.getBean() + "' bean method\n" + mappedHandlerMethod + " mapped.");
+ oldHandlerMethod.getBean() + "' bean method\n" + oldHandlerMethod + " mapped.");
}
handlerMethods.put(mapping, handlerMethod);
if (logger.isInfoEnabled()) {
logger.info("Mapped \"" + mapping + "\" onto " + handlerMethod);
}
Set<String> paths = getMappingPaths(mapping);
for (String path : paths) {
urlMap.add(path, mapping);
}
}
/**
* Get the URL paths for the given mapping.
* Get the URL paths associated with the given mapping.
*/
protected abstract Set<String> getMappingPaths(T mapping);
......
......@@ -46,12 +46,12 @@ import org.springframework.web.servlet.handler.MappedInterceptors;
import org.springframework.web.servlet.mvc.method.condition.RequestConditionFactory;
/**
* An {@link AbstractHandlerMethodMapping} variant that uses {@link RequestMappingInfo}s for the registration and
* An {@link AbstractHandlerMethodMapping} variant that uses {@link RequestMappingInfo}s for the registration and
* the lookup of {@link HandlerMethod}s.
*
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
* @since 3.1
* @since 3.1.0
*/
public class RequestMappingHandlerMapping extends AbstractHandlerMethodMapping<RequestMappingInfo> {
......@@ -87,32 +87,30 @@ public class RequestMappingHandlerMapping extends AbstractHandlerMethodMapping<R
/**
* {@inheritDoc}
* The handler determination is made based on the presence of a type-level {@link Controller} or
* a type-level {@link RequestMapping} annotation.
* The handler determination in this method is made based on the presence of a type-level {@link Controller} annotation.
*/
@Override
protected boolean isHandler(String beanName) {
return ((getApplicationContext().findAnnotationOnBean(beanName, RequestMapping.class) != null) ||
(getApplicationContext().findAnnotationOnBean(beanName, Controller.class) != null));
protected boolean isHandler(Class<?> beanType) {
return AnnotationUtils.findAnnotation(beanType, Controller.class) != null;
}
/**
* Provides a {@link RequestMappingInfo} for the given method.
* <p>Only {@link RequestMapping @RequestMapping}-annotated methods are considered.
* Type-level {@link RequestMapping @RequestMapping} annotations are also detected and their
* Provides a {@link RequestMappingInfo} for the given method.
* <p>Only {@link RequestMapping @RequestMapping}-annotated methods are considered.
* Type-level {@link RequestMapping @RequestMapping} annotations are also detected and their
* attributes combined with method-level {@link RequestMapping @RequestMapping} attributes.
*
* @param beanName the name of the bean the method belongs to
* @param method the method to create a mapping for
* @param handlerType the actual handler type, possibly a sub-type of {@code method.getDeclaringClass()}
* @return the mapping, or {@code null}
* @see RequestMappingInfo#combine(RequestMappingInfo, PathMatcher)
*/
@Override
protected RequestMappingInfo getMappingForMethod(String beanName, Method method) {
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
RequestMapping annotation = AnnotationUtils.findAnnotation(method, RequestMapping.class);
if (annotation != null) {
RequestMappingInfo methodMapping = createFromRequestMapping(annotation);
RequestMapping typeAnnot = getApplicationContext().findAnnotationOnBean(beanName, RequestMapping.class);
RequestMapping typeAnnot = AnnotationUtils.findAnnotation(handlerType, RequestMapping.class);
if (typeAnnot != null) {
RequestMappingInfo typeMapping = createFromRequestMapping(typeAnnot);
return typeMapping.combine(methodMapping, pathMatcher);
......@@ -206,12 +204,12 @@ public class RequestMappingHandlerMapping extends AbstractHandlerMethodMapping<R
}
/**
* A comparator for {@link RequestMappingInfo}s. Effective comparison can only be done in the context of a
* specific request. For example not all {@link RequestMappingInfo} patterns may apply to the current request.
* A comparator for {@link RequestMappingInfo}s. Effective comparison can only be done in the context of a
* specific request. For example not all {@link RequestMappingInfo} patterns may apply to the current request.
* Therefore an HttpServletRequest is required as input.
*
* <p>Furthermore, the following assumptions are made about the input RequestMappings:
* <ul><li>Each RequestMappingInfo has been fully matched to the request <li>The RequestMappingInfo contains
* <p>Furthermore, the following assumptions are made about the input RequestMappings:
* <ul><li>Each RequestMappingInfo has been fully matched to the request <li>The RequestMappingInfo contains
* matched patterns only <li>Patterns are ordered with the best matching pattern at the top </ul>
*
* @see RequestMappingHandlerMapping#getMatchingMapping(RequestMappingInfo, String, HttpServletRequest)
......
......@@ -41,46 +41,48 @@ public class HandlerMethodMappingTests {
private AbstractHandlerMethodMapping<String> mapping;
private HandlerMethod handlerMethod1;
private MyHandler handler;
private HandlerMethod handlerMethod2;
private Method method1;
private Method method2;
@Before
public void setUp() throws Exception {
mapping = new MyHandlerMethodMapping();
MyHandler handler = new MyHandler();
handlerMethod1 = new HandlerMethod(handler, "handlerMethod1");
handlerMethod2 = new HandlerMethod(handler, "handlerMethod2");
handler = new MyHandler();
method1 = handler.getClass().getMethod("handlerMethod1");
method2 = handler.getClass().getMethod("handlerMethod2");
}
@Test(expected = IllegalStateException.class)
public void registerDuplicates() {
mapping.registerHandlerMethod(new HashSet<String>(), "foo", handlerMethod1);
mapping.registerHandlerMethod(new HashSet<String>(), "foo", handlerMethod2);
mapping.registerHandlerMethod(handler, method1, "foo");
mapping.registerHandlerMethod(handler, method2, "foo");
}
@Test
public void directMatch() throws Exception {
String key = "foo";
mapping.registerHandlerMethod(new HashSet<String>(), key, handlerMethod1);
mapping.registerHandlerMethod(handler, method1, key);
HandlerMethod result = mapping.getHandlerInternal(new MockHttpServletRequest("GET", key));
assertEquals(handlerMethod1, result);
assertEquals(method1, result.getMethod());
}
@Test
public void patternMatch() throws Exception {
mapping.registerHandlerMethod(new HashSet<String>(), "/fo*", handlerMethod1);
mapping.registerHandlerMethod(new HashSet<String>(), "/f*", handlerMethod1);
mapping.registerHandlerMethod(handler, method1, "/fo*");
mapping.registerHandlerMethod(handler, method1, "/f*");
HandlerMethod result = mapping.getHandlerInternal(new MockHttpServletRequest("GET", "/foo"));
assertEquals(handlerMethod1, result);
assertEquals(method1, result.getMethod());
}
@Test(expected = IllegalStateException.class)
public void ambiguousMatch() throws Exception {
mapping.registerHandlerMethod(new HashSet<String>(), "/f?o", handlerMethod1);
mapping.registerHandlerMethod(new HashSet<String>(), "/fo?", handlerMethod2);
mapping.registerHandlerMethod(handler, method1, "/f?o");
mapping.registerHandlerMethod(handler, method2, "/fo?");
mapping.getHandlerInternal(new MockHttpServletRequest("GET", "/foo"));
}
......@@ -95,7 +97,7 @@ public class HandlerMethodMappingTests {
}
@Override
protected String getMappingForMethod(String beanName, Method method) {
protected String getMappingForMethod(Method method, Class<?> handlerType) {
String methodName = method.getName();
return methodName.startsWith("handler") ? methodName : null;
}
......@@ -106,7 +108,7 @@ public class HandlerMethodMappingTests {
}
@Override
protected boolean isHandler(String beanName) {
protected boolean isHandler(Class<?> beanType) {
return true;
}
......
......@@ -1531,6 +1531,7 @@ public class ServletHandlerMethodTests {
@RequestMapping("/myPage")
@SessionAttributes({"object1", "object2"})
@Controller
public interface MySessionAttributesControllerIfc {
@RequestMapping(method = RequestMethod.GET)
......@@ -1540,7 +1541,6 @@ public class ServletHandlerMethodTests {
String post(@ModelAttribute("object1") Object object1);
}
@Controller
public static class MySessionAttributesControllerImpl implements MySessionAttributesControllerIfc {
public String get(Model model) {
......
......@@ -542,6 +542,7 @@ public class UriTemplateServletHandlerMethodTests {
}
@Controller
@RequestMapping("/*/menu/**")
public static class MenuTreeController {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册