From 3f7c46e16db3668147ae92c2a68065bc6ec1e919 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Sun, 15 Aug 2010 21:12:54 +0000 Subject: [PATCH] revised handler method resolution, in particular with respect to generic interfaces (SPR-7355) git-svn-id: https://src.springframework.org/svn/spring-framework/trunk@3579 50f2f4bb-b051-0410-bef5-90022cba6387 --- .../springframework/util/ReflectionUtils.java | 12 + .../AnnotationMethodHandlerAdapter.java | 10 +- .../DefaultAnnotationHandlerMapping.java | 118 +++++----- .../AnnotationMethodHandlerAdapter.java | 45 ++-- .../DefaultAnnotationHandlerMapping.java | 9 +- .../ServletAnnotationControllerTests.java | 221 +++++++++++++++--- .../support/HandlerMethodResolver.java | 21 +- 7 files changed, 322 insertions(+), 114 deletions(-) diff --git a/org.springframework.core/src/main/java/org/springframework/util/ReflectionUtils.java b/org.springframework.core/src/main/java/org/springframework/util/ReflectionUtils.java index 0c6e97cb09a..f7ab95038a5 100644 --- a/org.springframework.core/src/main/java/org/springframework/util/ReflectionUtils.java +++ b/org.springframework.core/src/main/java/org/springframework/util/ReflectionUtils.java @@ -622,4 +622,16 @@ public abstract class ReflectionUtils { } }; + + /** + * Pre-built MethodFilter that matches all non-bridge methods + * which are not declared on java.lang.Object. + */ + public static MethodFilter USER_DECLARED_METHODS = new MethodFilter() { + + public boolean matches(Method method) { + return (!method.isBridge() && method.getDeclaringClass() != Object.class); + } + }; + } diff --git a/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/mvc/annotation/AnnotationMethodHandlerAdapter.java b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/mvc/annotation/AnnotationMethodHandlerAdapter.java index 46246b5bd2f..ab346c666fa 100644 --- a/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/mvc/annotation/AnnotationMethodHandlerAdapter.java +++ b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/mvc/annotation/AnnotationMethodHandlerAdapter.java @@ -435,6 +435,9 @@ public class AnnotationMethodHandlerAdapter extends PortletContentGenerator @Override protected boolean isHandlerMethod(Method method) { + if (this.mappings.containsKey(method)) { + return true; + } RequestMappingInfo mappingInfo = new RequestMappingInfo(); RequestMapping requestMapping = AnnotationUtils.findAnnotation(method, RequestMapping.class); ActionMapping actionMapping = AnnotationUtils.findAnnotation(method, ActionMapping.class); @@ -460,8 +463,11 @@ public class AnnotationMethodHandlerAdapter extends PortletContentGenerator mappingInfo.phase = determineDefaultPhase(method); } } - this.mappings.put(method, mappingInfo); - return (mappingInfo.phase != null); + if (mappingInfo.phase != null) { + this.mappings.put(method, mappingInfo); + return true; + } + return false; } public Method resolveHandlerMethod(PortletRequest request) throws PortletException { diff --git a/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/mvc/annotation/DefaultAnnotationHandlerMapping.java b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/mvc/annotation/DefaultAnnotationHandlerMapping.java index 11a40405257..dcf47d2b8db 100644 --- a/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/mvc/annotation/DefaultAnnotationHandlerMapping.java +++ b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/mvc/annotation/DefaultAnnotationHandlerMapping.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2010 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,7 @@ import java.lang.reflect.Method; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import javax.portlet.ClientDataRequest; @@ -145,71 +146,76 @@ public class DefaultAnnotationHandlerMapping extends AbstractMapBasedHandlerMapp * @return true if at least 1 handler method has been registered; * false otherwise */ - protected boolean detectHandlerMethods(Class handlerType, final String beanName, final RequestMapping typeMapping) { + protected boolean detectHandlerMethods(Class handlerType, final String beanName, final RequestMapping typeMapping) { final Set handlersRegistered = new HashSet(1); - ReflectionUtils.doWithMethods(handlerType, new ReflectionUtils.MethodCallback() { - public void doWith(Method method) { - boolean mappingFound = false; - String[] modeKeys = new String[0]; - String[] params = new String[0]; - String resourceId = null; - String eventName = null; - for (Annotation ann : method.getAnnotations()) { - if (AnnotationUtils.findAnnotation(ann.getClass(), Mapping.class) != null) { - mappingFound = true; - if (ann instanceof RequestMapping) { - RequestMapping rm = (RequestMapping) ann; - modeKeys = rm.value(); - params = StringUtils.mergeStringArrays(params, rm.params()); - } - else if (ann instanceof ResourceMapping) { - ResourceMapping rm = (ResourceMapping) ann; - resourceId = rm.value(); - } - else if (ann instanceof EventMapping) { - EventMapping em = (EventMapping) ann; - eventName = em.value(); - } - else { - String[] specificParams = (String[]) AnnotationUtils.getValue(ann, "params"); - params = StringUtils.mergeStringArrays(params, specificParams); + Set> handlerTypes = new LinkedHashSet>(); + handlerTypes.add(handlerType); + handlerTypes.addAll(Arrays.asList(handlerType.getInterfaces())); + for (Class currentHandlerType : handlerTypes) { + ReflectionUtils.doWithMethods(currentHandlerType, new ReflectionUtils.MethodCallback() { + public void doWith(Method method) { + boolean mappingFound = false; + String[] modeKeys = new String[0]; + String[] params = new String[0]; + String resourceId = null; + String eventName = null; + for (Annotation ann : method.getAnnotations()) { + if (AnnotationUtils.findAnnotation(ann.getClass(), Mapping.class) != null) { + mappingFound = true; + if (ann instanceof RequestMapping) { + RequestMapping rm = (RequestMapping) ann; + modeKeys = rm.value(); + params = StringUtils.mergeStringArrays(params, rm.params()); + } + else if (ann instanceof ResourceMapping) { + ResourceMapping rm = (ResourceMapping) ann; + resourceId = rm.value(); + } + else if (ann instanceof EventMapping) { + EventMapping em = (EventMapping) ann; + eventName = em.value(); + } + else { + String[] specificParams = (String[]) AnnotationUtils.getValue(ann, "params"); + params = StringUtils.mergeStringArrays(params, specificParams); + } } } - } - if (mappingFound) { - if (modeKeys.length == 0) { + if (mappingFound) { + if (modeKeys.length == 0) { + if (typeMapping != null) { + modeKeys = typeMapping.value(); + } + else { + throw new IllegalStateException( + "No portlet mode mappings specified - neither at type nor at method level"); + } + } if (typeMapping != null) { - modeKeys = typeMapping.value(); + if (!PortletAnnotationMappingUtils.validateModeMapping(modeKeys, typeMapping.value())) { + throw new IllegalStateException("Mode mappings conflict between method and type level: " + + Arrays.asList(modeKeys) + " versus " + Arrays.asList(typeMapping.value())); + } + params = StringUtils.mergeStringArrays(typeMapping.params(), params); + } + PortletRequestMappingPredicate predicate; + if (resourceId != null) { + predicate = new ResourceMappingPredicate(resourceId); + } + else if (eventName != null) { + predicate = new EventMappingPredicate(eventName); } else { - throw new IllegalStateException( - "No portlet mode mappings specified - neither at type nor at method level"); + predicate = new ParameterMappingPredicate(params); } - } - if (typeMapping != null) { - if (!PortletAnnotationMappingUtils.validateModeMapping(modeKeys, typeMapping.value())) { - throw new IllegalStateException("Mode mappings conflict between method and type level: " + - Arrays.asList(modeKeys) + " versus " + Arrays.asList(typeMapping.value())); + for (String modeKey : modeKeys) { + registerHandler(new PortletMode(modeKey), beanName, predicate); + handlersRegistered.add(Boolean.TRUE); } - params = StringUtils.mergeStringArrays(typeMapping.params(), params); - } - PortletRequestMappingPredicate predicate; - if (resourceId != null) { - predicate = new ResourceMappingPredicate(resourceId); - } - else if (eventName != null) { - predicate = new EventMappingPredicate(eventName); - } - else { - predicate = new ParameterMappingPredicate(params); - } - for (String modeKey : modeKeys) { - registerHandler(new PortletMode(modeKey), beanName, predicate); - handlersRegistered.add(Boolean.TRUE); } } - } - }); + }, ReflectionUtils.USER_DECLARED_METHODS); + } return !handlersRegistered.isEmpty(); } diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter.java index fe6f4848a25..f7154662ee8 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter.java @@ -27,6 +27,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; @@ -496,10 +497,36 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator */ private class ServletHandlerMethodResolver extends HandlerMethodResolver { + private final Map mappings = new HashMap(); + private ServletHandlerMethodResolver(Class handlerType) { init(handlerType); } + @Override + protected boolean isHandlerMethod(Method method) { + if (this.mappings.containsKey(method)) { + return true; + } + RequestMapping mapping = AnnotationUtils.findAnnotation(method, RequestMapping.class); + if (mapping != null) { + RequestMappingInfo mappingInfo = new RequestMappingInfo(); + mappingInfo.patterns = mapping.value(); + if (!hasTypeLevelMapping() || !Arrays.equals(mapping.method(), getTypeLevelMapping().method())) { + mappingInfo.methods = mapping.method(); + } + if (!hasTypeLevelMapping() || !Arrays.equals(mapping.params(), getTypeLevelMapping().params())) { + mappingInfo.params = mapping.params(); + } + if (!hasTypeLevelMapping() || !Arrays.equals(mapping.headers(), getTypeLevelMapping().headers())) { + mappingInfo.headers = mapping.headers(); + } + this.mappings.put(method, mappingInfo); + return true; + } + return false; + } + public Method resolveHandlerMethod(HttpServletRequest request) throws ServletException { String lookupPath = urlPathHelper.getLookupPathForRequest(request); Comparator pathComparator = pathMatcher.getPatternComparator(lookupPath); @@ -507,7 +534,7 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator Set allowedMethods = new LinkedHashSet(7); String resolvedMethodName = null; for (Method handlerMethod : getHandlerMethods()) { - RequestMappingInfo mappingInfo = createRequestMappingInfo(handlerMethod); + RequestMappingInfo mappingInfo = this.mappings.get(handlerMethod); boolean match = false; if (mappingInfo.hasPatterns()) { List matchingPatterns = new ArrayList(mappingInfo.patterns.length); @@ -599,22 +626,6 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator } } - private RequestMappingInfo createRequestMappingInfo(Method handlerMethod) { - RequestMappingInfo mappingInfo = new RequestMappingInfo(); - RequestMapping mapping = AnnotationUtils.findAnnotation(handlerMethod, RequestMapping.class); - mappingInfo.patterns = mapping.value(); - if (!hasTypeLevelMapping() || !Arrays.equals(mapping.method(), getTypeLevelMapping().method())) { - mappingInfo.methods = mapping.method(); - } - if (!hasTypeLevelMapping() || !Arrays.equals(mapping.params(), getTypeLevelMapping().params())) { - mappingInfo.params = mapping.params(); - } - if (!hasTypeLevelMapping() || !Arrays.equals(mapping.headers(), getTypeLevelMapping().headers())) { - mappingInfo.headers = mapping.headers(); - } - return mappingInfo; - } - /** * Determines the combined pattern for the given methodLevelPattern and path. *

Uses the following algorithm:

    diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/annotation/DefaultAnnotationHandlerMapping.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/annotation/DefaultAnnotationHandlerMapping.java index 7e883855428..b67f84fec9c 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/annotation/DefaultAnnotationHandlerMapping.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/annotation/DefaultAnnotationHandlerMapping.java @@ -17,7 +17,7 @@ package org.springframework.web.servlet.mvc.annotation; import java.lang.reflect.Method; -import java.lang.reflect.Proxy; +import java.util.Arrays; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.Map; @@ -165,8 +165,9 @@ public class DefaultAnnotationHandlerMapping extends AbstractDetectingUrlHandler } final Set urls = new LinkedHashSet(); - Class[] handlerTypes = - Proxy.isProxyClass(handlerType) ? handlerType.getInterfaces() : new Class[]{handlerType}; + Set> handlerTypes = new LinkedHashSet>(); + handlerTypes.add(handlerType); + handlerTypes.addAll(Arrays.asList(handlerType.getInterfaces())); for (Class currentHandlerType : handlerTypes) { ReflectionUtils.doWithMethods(currentHandlerType, new ReflectionUtils.MethodCallback() { public void doWith(Method method) { @@ -187,7 +188,7 @@ public class DefaultAnnotationHandlerMapping extends AbstractDetectingUrlHandler } } } - }, ReflectionUtils.NON_BRIDGED_METHODS); + }, ReflectionUtils.USER_DECLARED_METHODS); } return StringUtils.toStringArray(urls); } diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationControllerTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationControllerTests.java index 0087ed29ec9..f17a5c9eabe 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationControllerTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationControllerTests.java @@ -174,6 +174,27 @@ public class ServletAnnotationControllerTests { assertEquals("test", response.getContentAsString()); } + @Test + public void emptyValueMapping() throws Exception { + servlet = new DispatcherServlet() { + @Override + protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) { + GenericWebApplicationContext wac = new GenericWebApplicationContext(); + wac.registerBeanDefinition("controller", new RootBeanDefinition(ControllerWithEmptyValueMapping.class)); + wac.refresh(); + return wac; + } + }; + servlet.init(new MockServletConfig()); + + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo"); + request.setContextPath("/foo"); + request.setServletPath(""); + MockHttpServletResponse response = new MockHttpServletResponse(); + servlet.service(request, response); + assertEquals("test", response.getContentAsString()); + } + @Test public void customAnnotationController() throws Exception { initServlet(CustomAnnotationController.class); @@ -382,6 +403,7 @@ public class ServletAnnotationControllerTests { MockHttpServletRequest request = new MockHttpServletRequest("GET", "/myPage"); MockHttpServletResponse response = new MockHttpServletResponse(); servlet.service(request, response); + assertEquals("page1", request.getAttribute("viewName")); HttpSession session = request.getSession(); assertTrue(session.getAttribute("object1") != null); assertTrue(session.getAttribute("object2") != null); @@ -392,6 +414,7 @@ public class ServletAnnotationControllerTests { request.setSession(session); response = new MockHttpServletResponse(); servlet.service(request, response); + assertEquals("page2", request.getAttribute("viewName")); assertTrue(session.getAttribute("object1") != null); assertTrue(session.getAttribute("object2") != null); assertTrue(((Map) session.getAttribute("model")).containsKey("object1")); @@ -419,6 +442,7 @@ public class ServletAnnotationControllerTests { MockHttpServletRequest request = new MockHttpServletRequest("GET", "/myPage"); MockHttpServletResponse response = new MockHttpServletResponse(); servlet.service(request, response); + assertEquals("page1", request.getAttribute("viewName")); HttpSession session = request.getSession(); assertTrue(session.getAttribute("object1") != null); assertTrue(session.getAttribute("object2") != null); @@ -429,12 +453,88 @@ public class ServletAnnotationControllerTests { request.setSession(session); response = new MockHttpServletResponse(); servlet.service(request, response); + assertEquals("page2", request.getAttribute("viewName")); assertTrue(session.getAttribute("object1") != null); assertTrue(session.getAttribute("object2") != null); assertTrue(((Map) session.getAttribute("model")).containsKey("object1")); assertTrue(((Map) session.getAttribute("model")).containsKey("object2")); } + @Test + public void parameterizedAnnotatedInterface() throws Exception { + @SuppressWarnings("serial") DispatcherServlet servlet = new DispatcherServlet() { + @Override + protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) { + GenericWebApplicationContext wac = new GenericWebApplicationContext(); + wac.registerBeanDefinition("controller", new RootBeanDefinition(MyParameterizedControllerImpl.class)); + wac.registerBeanDefinition("viewResolver", new RootBeanDefinition(ModelExposingViewResolver.class)); + wac.refresh(); + return wac; + } + }; + servlet.init(new MockServletConfig()); + + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/myPage"); + MockHttpServletResponse response = new MockHttpServletResponse(); + servlet.service(request, response); + assertEquals("page1", request.getAttribute("viewName")); + HttpSession session = request.getSession(); + assertTrue(session.getAttribute("object1") != null); + assertTrue(session.getAttribute("object2") != null); + assertTrue(((Map) session.getAttribute("model")).containsKey("object1")); + assertTrue(((Map) session.getAttribute("model")).containsKey("object2")); + assertTrue(((Map) session.getAttribute("model")).containsKey("testBeanList")); + + request = new MockHttpServletRequest("POST", "/myPage"); + request.setSession(session); + response = new MockHttpServletResponse(); + servlet.service(request, response); + assertEquals("page2", request.getAttribute("viewName")); + assertTrue(session.getAttribute("object1") != null); + assertTrue(session.getAttribute("object2") != null); + assertTrue(((Map) session.getAttribute("model")).containsKey("object1")); + assertTrue(((Map) session.getAttribute("model")).containsKey("object2")); + assertTrue(((Map) session.getAttribute("model")).containsKey("testBeanList")); + } + + @Test + public void parameterizedAnnotatedInterfaceWithOverriddenMappingsInImpl() throws Exception { + @SuppressWarnings("serial") DispatcherServlet servlet = new DispatcherServlet() { + @Override + protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) { + GenericWebApplicationContext wac = new GenericWebApplicationContext(); + wac.registerBeanDefinition("controller", + new RootBeanDefinition(MyParameterizedControllerImplWithOverriddenMappings.class)); + wac.registerBeanDefinition("viewResolver", new RootBeanDefinition(ModelExposingViewResolver.class)); + wac.refresh(); + return wac; + } + }; + servlet.init(new MockServletConfig()); + + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/myPage"); + MockHttpServletResponse response = new MockHttpServletResponse(); + servlet.service(request, response); + assertEquals("page1", request.getAttribute("viewName")); + HttpSession session = request.getSession(); + assertTrue(session.getAttribute("object1") != null); + assertTrue(session.getAttribute("object2") != null); + assertTrue(((Map) session.getAttribute("model")).containsKey("object1")); + assertTrue(((Map) session.getAttribute("model")).containsKey("object2")); + assertTrue(((Map) session.getAttribute("model")).containsKey("testBeanList")); + + request = new MockHttpServletRequest("POST", "/myPage"); + request.setSession(session); + response = new MockHttpServletResponse(); + servlet.service(request, response); + assertEquals("page2", request.getAttribute("viewName")); + assertTrue(session.getAttribute("object1") != null); + assertTrue(session.getAttribute("object2") != null); + assertTrue(((Map) session.getAttribute("model")).containsKey("object1")); + assertTrue(((Map) session.getAttribute("model")).containsKey("object2")); + assertTrue(((Map) session.getAttribute("model")).containsKey("testBeanList")); + } + @Test public void adaptedHandleMethods() throws Exception { doTestAdaptedHandleMethods(MyAdaptedController.class); @@ -1599,6 +1699,7 @@ public class ServletAnnotationControllerTests { assertEquals("test-{foo=bar}", response.getContentAsString()); } + /* * Controllers */ @@ -1623,6 +1724,20 @@ public class ServletAnnotationControllerTests { } } + @Controller + private static class ControllerWithEmptyValueMapping { + + @RequestMapping("") + public void myPath2(HttpServletResponse response) throws IOException { + response.getWriter().write("test"); + } + + @RequestMapping("/bar") + public void myPath3(HttpServletResponse response) throws IOException { + response.getWriter().write("testX"); + } + } + @Controller private static class MyAdaptedController { @@ -1632,10 +1747,8 @@ public class ServletAnnotationControllerTests { } @RequestMapping("/myPath2.do") - public void myHandle(@RequestParam("param1") String p1, - @RequestParam("param2") int p2, - @RequestHeader("header1") long h1, - @CookieValue("cookie1") Cookie c1, + public void myHandle(@RequestParam("param1") String p1, @RequestParam("param2") int p2, + @RequestHeader("header1") long h1, @CookieValue("cookie1") Cookie c1, HttpServletResponse response) throws IOException { response.getWriter().write("test-" + p1 + "-" + p2 + "-" + h1 + "-" + c1.getValue()); } @@ -1661,11 +1774,8 @@ public class ServletAnnotationControllerTests { } @RequestMapping("/myPath2.do") - public void myHandle(@RequestParam("param1") String p1, - int param2, - HttpServletResponse response, - @RequestHeader("header1") String h1, - @CookieValue("cookie1") String c1) throws IOException { + public void myHandle(@RequestParam("param1") String p1, int param2, HttpServletResponse response, + @RequestHeader("header1") String h1, @CookieValue("cookie1") String c1) throws IOException { response.getWriter().write("test-" + p1 + "-" + param2 + "-" + h1 + "-" + c1); } @@ -1684,11 +1794,8 @@ public class ServletAnnotationControllerTests { private static class MyAdaptedControllerBase { @RequestMapping("/myPath2.do") - public void myHandle(@RequestParam("param1") T p1, - int param2, - @RequestHeader Integer header1, - @CookieValue int cookie1, - HttpServletResponse response) throws IOException { + public void myHandle(@RequestParam("param1") T p1, int param2, @RequestHeader Integer header1, + @CookieValue int cookie1, HttpServletResponse response) throws IOException { response.getWriter().write("test-" + p1 + "-" + param2 + "-" + header1 + "-" + cookie1); } @@ -1712,11 +1819,8 @@ public class ServletAnnotationControllerTests { } @Override - public void myHandle(@RequestParam("param1") String p1, - int param2, - @RequestHeader Integer header1, - @CookieValue int cookie1, - HttpServletResponse response) throws IOException { + public void myHandle(@RequestParam("param1") String p1, int param2, @RequestHeader Integer header1, + @CookieValue int cookie1, HttpServletResponse response) throws IOException { response.getWriter().write("test-" + p1 + "-" + param2 + "-" + header1 + "-" + cookie1); } @@ -1768,13 +1872,13 @@ public class ServletAnnotationControllerTests { public String get(Model model) { model.addAttribute("object1", new Object()); model.addAttribute("object2", new Object()); - return "myPage"; + return "page1"; } @RequestMapping(method = RequestMethod.POST) public String post(@ModelAttribute("object1") Object object1) { //do something with object1 - return "myPage"; + return "page2"; } } @@ -1796,14 +1900,79 @@ public class ServletAnnotationControllerTests { public String get(Model model) { model.addAttribute("object1", new Object()); model.addAttribute("object2", new Object()); - return "myPage"; + return "page1"; } public String post(@ModelAttribute("object1") Object object1) { //do something with object1 - return "myPage"; + return "page2"; } } + + @RequestMapping("/myPage") + @SessionAttributes({"object1", "object2"}) + public interface MyParameterizedControllerIfc { + + @ModelAttribute("testBeanList") + List getTestBeans(); + + @RequestMapping(method = RequestMethod.GET) + String get(Model model); + } + + public interface MyEditableParameterizedControllerIfc extends MyParameterizedControllerIfc { + + @RequestMapping(method = RequestMethod.POST) + String post(@ModelAttribute("object1") T object); + } + + @Controller + public static class MyParameterizedControllerImpl implements MyEditableParameterizedControllerIfc { + + public List getTestBeans() { + List list = new LinkedList(); + list.add(new TestBean("tb1")); + list.add(new TestBean("tb2")); + return list; + } + + public String get(Model model) { + model.addAttribute("object1", new TestBean()); + model.addAttribute("object2", new TestBean()); + return "page1"; + } + + public String post(TestBean object) { + //do something with object1 + return "page2"; + } + } + + @Controller + public static class MyParameterizedControllerImplWithOverriddenMappings implements MyEditableParameterizedControllerIfc { + + @ModelAttribute("testBeanList") + public List getTestBeans() { + List list = new LinkedList(); + list.add(new TestBean("tb1")); + list.add(new TestBean("tb2")); + return list; + } + + @RequestMapping(method = RequestMethod.GET) + public String get(Model model) { + model.addAttribute("object1", new TestBean()); + model.addAttribute("object2", new TestBean()); + return "page1"; + } + + @RequestMapping(method = RequestMethod.POST) + public String post(@ModelAttribute("object1") TestBean object1) { + //do something with object1 + return "page2"; + } + } + @Controller public static class MyFormController { @@ -2191,15 +2360,15 @@ public class ServletAnnotationControllerTests { } } - private static class ModelExposingViewResolver implements ViewResolver { + public static class ModelExposingViewResolver implements ViewResolver { - public View resolveViewName(String viewName, Locale locale) throws Exception { + public View resolveViewName(final String viewName, Locale locale) throws Exception { return new View() { public String getContentType() { return null; } - public void render(Map model, HttpServletRequest request, HttpServletResponse response) { + request.setAttribute("viewName", viewName); request.getSession().setAttribute("model", model); } }; diff --git a/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/support/HandlerMethodResolver.java b/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/support/HandlerMethodResolver.java index dcb3b579faa..7ab3385893a 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/support/HandlerMethodResolver.java +++ b/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/support/HandlerMethodResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2010 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,13 +17,13 @@ package org.springframework.web.bind.annotation.support; import java.lang.reflect.Method; -import java.lang.reflect.Proxy; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Set; +import org.springframework.core.BridgeMethodResolver; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; @@ -70,14 +70,17 @@ public class HandlerMethodResolver { * Initialize a new HandlerMethodResolver for the specified handler type. * @param handlerType the handler class to introspect */ - public void init(Class handlerType) { - Class[] handlerTypes = - Proxy.isProxyClass(handlerType) ? handlerType.getInterfaces() : new Class[] {handlerType}; - for (final Class currentHandlerType : handlerTypes) { + public void init(final Class handlerType) { + Set> handlerTypes = new LinkedHashSet>(); + handlerTypes.add(handlerType); + handlerTypes.addAll(Arrays.asList(handlerType.getInterfaces())); + for (Class currentHandlerType : handlerTypes) { ReflectionUtils.doWithMethods(currentHandlerType, new ReflectionUtils.MethodCallback() { public void doWith(Method method) { - Method specificMethod = ClassUtils.getMostSpecificMethod(method, currentHandlerType); - if (isHandlerMethod(method)) { + Method specificMethod = ClassUtils.getMostSpecificMethod(method, handlerType); + Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(specificMethod); + if (isHandlerMethod(specificMethod) && + (bridgedMethod == specificMethod || !isHandlerMethod(bridgedMethod))) { handlerMethods.add(specificMethod); } else if (method.isAnnotationPresent(InitBinder.class)) { @@ -87,7 +90,7 @@ public class HandlerMethodResolver { modelAttributeMethods.add(specificMethod); } } - }, ReflectionUtils.NON_BRIDGED_METHODS); + }, ReflectionUtils.USER_DECLARED_METHODS); } this.typeLevelMapping = AnnotationUtils.findAnnotation(handlerType, RequestMapping.class); SessionAttributes sessionAttributes = AnnotationUtils.findAnnotation(handlerType, SessionAttributes.class);