SPR-8859 Fix issue with prototype controllers in RequestMappingHandlerAdapter.

This commit is contained in:
Rossen Stoyanchev 2011-11-18 11:32:01 +00:00
parent c03a950706
commit e4fada56ab
4 changed files with 83 additions and 39 deletions

View File

@ -24,6 +24,7 @@ Changes in version 3.1 RC2 (2011-11-15)
* added methods to UriComponentsBuilder for replacing the path or the query * added methods to UriComponentsBuilder for replacing the path or the query
* added ServletUriComponentsBuilder to build a UriComponents instance starting with a ServletRequest * added ServletUriComponentsBuilder to build a UriComponents instance starting with a ServletRequest
* MockHttpServletRequest and MockHttpServletResponse now keep contentType field and Content-Type header in sync * MockHttpServletRequest and MockHttpServletResponse now keep contentType field and Content-Type header in sync
* Fix issue with cache ignoring prototype-scoped controllers in RequestMappingHandlerAdapter
Changes in version 3.1 RC1 (2011-10-11) Changes in version 3.1 RC1 (2011-10-11)

View File

@ -20,6 +20,7 @@ import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
@ -143,10 +144,9 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
private HandlerMethodReturnValueHandlerComposite returnValueHandlers; private HandlerMethodReturnValueHandlerComposite returnValueHandlers;
private final Map<Class<?>, WebDataBinderFactory> dataBinderFactoryCache = private final Map<Class<?>, Set<Method>> dataBinderFactoryCache = new ConcurrentHashMap<Class<?>, Set<Method>>();
new ConcurrentHashMap<Class<?>, WebDataBinderFactory>();
private final Map<Class<?>, ModelFactory> modelFactoryCache = new ConcurrentHashMap<Class<?>, ModelFactory>(); private final Map<Class<?>, Set<Method>> modelFactoryCache = new ConcurrentHashMap<Class<?>, Set<Method>>();
/** /**
* Default constructor. * Default constructor.
@ -660,38 +660,38 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
private ModelFactory getModelFactory(HandlerMethod handlerMethod, WebDataBinderFactory binderFactory) { private ModelFactory getModelFactory(HandlerMethod handlerMethod, WebDataBinderFactory binderFactory) {
SessionAttributesHandler sessionAttrHandler = getSessionAttributesHandler(handlerMethod); SessionAttributesHandler sessionAttrHandler = getSessionAttributesHandler(handlerMethod);
Class<?> handlerType = handlerMethod.getBeanType(); Class<?> handlerType = handlerMethod.getBeanType();
ModelFactory modelFactory = this.modelFactoryCache.get(handlerType); Set<Method> methods = this.modelFactoryCache.get(handlerType);
if (modelFactory == null) { if (methods == null) {
methods = HandlerMethodSelector.selectMethods(handlerType, MODEL_ATTRIBUTE_METHODS);
this.modelFactoryCache.put(handlerType, methods);
}
List<InvocableHandlerMethod> attrMethods = new ArrayList<InvocableHandlerMethod>(); List<InvocableHandlerMethod> attrMethods = new ArrayList<InvocableHandlerMethod>();
for (Method method : HandlerMethodSelector.selectMethods(handlerType, MODEL_ATTRIBUTE_METHODS)) { for (Method method : methods) {
InvocableHandlerMethod attrMethod = new InvocableHandlerMethod(handlerMethod.getBean(), method); InvocableHandlerMethod attrMethod = new InvocableHandlerMethod(handlerMethod.getBean(), method);
attrMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers); attrMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
attrMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer); attrMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
attrMethod.setDataBinderFactory(binderFactory); attrMethod.setDataBinderFactory(binderFactory);
attrMethods.add(attrMethod); attrMethods.add(attrMethod);
} }
modelFactory = new ModelFactory(attrMethods, binderFactory, sessionAttrHandler); return new ModelFactory(attrMethods, binderFactory, sessionAttrHandler);
this.modelFactoryCache.put(handlerType, modelFactory);
}
return modelFactory;
} }
private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) throws Exception { private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) throws Exception {
Class<?> handlerType = handlerMethod.getBeanType(); Class<?> handlerType = handlerMethod.getBeanType();
WebDataBinderFactory binderFactory = this.dataBinderFactoryCache.get(handlerType); Set<Method> methods = this.dataBinderFactoryCache.get(handlerType);
if (binderFactory == null) { if (methods == null) {
methods = HandlerMethodSelector.selectMethods(handlerType, INIT_BINDER_METHODS);
this.dataBinderFactoryCache.put(handlerType, methods);
}
List<InvocableHandlerMethod> binderMethods = new ArrayList<InvocableHandlerMethod>(); List<InvocableHandlerMethod> binderMethods = new ArrayList<InvocableHandlerMethod>();
for (Method method : HandlerMethodSelector.selectMethods(handlerType, INIT_BINDER_METHODS)) { for (Method method : methods) {
InvocableHandlerMethod binderMethod = new InvocableHandlerMethod(handlerMethod.getBean(), method); InvocableHandlerMethod binderMethod = new InvocableHandlerMethod(handlerMethod.getBean(), method);
binderMethod.setHandlerMethodArgumentResolvers(this.initBinderArgumentResolvers); binderMethod.setHandlerMethodArgumentResolvers(this.initBinderArgumentResolvers);
binderMethod.setDataBinderFactory(new DefaultDataBinderFactory(this.webBindingInitializer)); binderMethod.setDataBinderFactory(new DefaultDataBinderFactory(this.webBindingInitializer));
binderMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer); binderMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
binderMethods.add(binderMethod); binderMethods.add(binderMethod);
} }
binderFactory = createDataBinderFactory(binderMethods); return createDataBinderFactory(binderMethods);
this.dataBinderFactoryCache.put(handlerType, binderFactory);
}
return binderFactory;
} }
/** /**

View File

@ -87,17 +87,14 @@ import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.method.annotation.support.ServletWebArgumentResolverAdapter; import org.springframework.web.servlet.mvc.method.annotation.support.ServletWebArgumentResolverAdapter;
/** /**
* Serves as a sandbox to invoke all types of controller methods using all features except for the kitchen sink. * A test fixture with a controller with all supported method signature styles
* Once a problem has been debugged and understood, tests demonstrating the issue are preferably added to the * and arguments. A convenient place to test or confirm a problem with a
* appropriate, more fine-grained test fixture. * specific argument or return value type.
*
* <p>If you wish to add high-level tests, consider the following other "integration"-style tests:
* <ul>
* <li>{@link HandlerMethodAnnotationDetectionTests}
* <li>{@link ServletAnnotationControllerHandlerMethodTests}
* </ul>
* *
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
*
* @see HandlerMethodAnnotationDetectionTests
* @see ServletAnnotationControllerHandlerMethodTests
*/ */
public class RequestMappingHandlerAdapterIntegrationTests { public class RequestMappingHandlerAdapterIntegrationTests {
@ -261,7 +258,7 @@ public class RequestMappingHandlerAdapterIntegrationTests {
@Test @Test
public void handleAndCompleteSession() throws Exception { public void handleAndCompleteSession() throws Exception {
HandlerMethod handlerMethod = handlerMethod("handleAndCompleteSession", SessionStatus.class); HandlerMethod handlerMethod = handlerMethod("handleAndCompleteSession", SessionStatus.class);
ModelAndView mav = handlerAdapter.handle(request, response, handlerMethod); handlerAdapter.handle(request, response, handlerMethod);
assertFalse(request.getSession().getAttributeNames().hasMoreElements()); assertFalse(request.getSession().getAttributeNames().hasMoreElements());
} }

View File

@ -71,6 +71,7 @@ import org.springframework.beans.TestBean;
import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer; import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.propertyeditors.CustomDateEditor; import org.springframework.beans.propertyeditors.CustomDateEditor;
@ -1494,6 +1495,29 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
assertTrue(RequestContextUtils.getOutputFlashMap(request).isEmpty()); assertTrue(RequestContextUtils.getOutputFlashMap(request).isEmpty());
} }
@Test
public void prototypeController() throws Exception {
initServlet(new ApplicationContextInitializer<GenericWebApplicationContext>() {
public void initialize(GenericWebApplicationContext context) {
RootBeanDefinition beanDef = new RootBeanDefinition(PrototypeController.class);
beanDef.setScope(BeanDefinition.SCOPE_PROTOTYPE);
context.registerBeanDefinition("controller", beanDef);
}
});
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
request.addParameter("param", "1");
MockHttpServletResponse response = new MockHttpServletResponse();
getServlet().service(request, response);
assertEquals("count:3", response.getContentAsString());
response = new MockHttpServletResponse();
getServlet().service(request, response);
assertEquals("count:3", response.getContentAsString());
}
/* /*
* Controllers * Controllers
*/ */
@ -2828,6 +2852,28 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
} }
} }
@Controller
static class PrototypeController {
private int count;
@InitBinder
public void initBinder(WebDataBinder dataBinder) {
this.count++;
}
@ModelAttribute
public void populate(Model model) {
this.count++;
}
@RequestMapping("/")
public void message(int param, Writer writer) throws IOException {
this.count++;
writer.write("count:" + this.count);
}
}
// Test cases deleted from the original SevletAnnotationControllerTests: // Test cases deleted from the original SevletAnnotationControllerTests:
// @Ignore("Controller interface => no method-level @RequestMapping annotation") // @Ignore("Controller interface => no method-level @RequestMapping annotation")