SPR-8859 Fix issue with prototype controllers in RequestMappingHandlerAdapter.
This commit is contained in:
parent
c03a950706
commit
e4fada56ab
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue