Refine HandlerMethod registration to allow detection by handler instance as well as by bean name
This commit is contained in:
parent
d0c31ad84c
commit
8d0ab1d2e5
|
|
@ -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 {
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue