diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java index c2e030e9e6..3375322962 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -33,7 +33,6 @@ import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.core.LocalVariableTableParameterNameDiscoverer; -import org.springframework.core.MethodParameter; import org.springframework.core.ParameterNameDiscoverer; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.http.converter.ByteArrayHttpMessageConverter; @@ -57,17 +56,17 @@ import org.springframework.web.context.request.WebRequest; import org.springframework.web.method.HandlerMethod; import org.springframework.web.method.HandlerMethodSelector; import org.springframework.web.method.annotation.ErrorsMethodArgumentResolver; +import org.springframework.web.method.annotation.ExpressionValueMethodArgumentResolver; import org.springframework.web.method.annotation.MapMethodProcessor; import org.springframework.web.method.annotation.ModelAttributeMethodProcessor; import org.springframework.web.method.annotation.ModelFactory; import org.springframework.web.method.annotation.ModelMethodProcessor; import org.springframework.web.method.annotation.RequestHeaderMapMethodArgumentResolver; +import org.springframework.web.method.annotation.RequestHeaderMethodArgumentResolver; import org.springframework.web.method.annotation.RequestParamMapMethodArgumentResolver; +import org.springframework.web.method.annotation.RequestParamMethodArgumentResolver; import org.springframework.web.method.annotation.SessionAttributesHandler; import org.springframework.web.method.annotation.SessionStatusMethodArgumentResolver; -import org.springframework.web.method.annotation.ExpressionValueMethodArgumentResolver; -import org.springframework.web.method.annotation.RequestHeaderMethodArgumentResolver; -import org.springframework.web.method.annotation.RequestParamMethodArgumentResolver; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.HandlerMethodArgumentResolverComposite; import org.springframework.web.method.support.HandlerMethodReturnValueHandler; @@ -84,9 +83,9 @@ import org.springframework.web.util.WebUtils; /** * An {@link AbstractHandlerMethodAdapter} that supports {@link HandlerMethod}s - * with the signature -- method argument and return types, defined in + * with the signature -- method argument and return types, defined in * {@code @RequestMapping}. - * + * *
Support for custom argument and return value types can be added via
* {@link #setCustomArgumentResolvers} and {@link #setCustomReturnValueHandlers}.
* Or alternatively to re-configure all argument and return value types use
@@ -103,7 +102,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
private List Note: This method is available for backwards
- * compatibility only. However, it is recommended to re-write a
+ * Provide custom {@link ModelAndViewResolver}s.
+ * Note: This method is available for backwards
+ * compatibility only. However, it is recommended to re-write a
* {@code ModelAndViewResolver} as {@link HandlerMethodReturnValueHandler}.
- * An adapter between the two interfaces is not possible since the
+ * An adapter between the two interfaces is not possible since the
* {@link HandlerMethodReturnValueHandler#supportsReturnType} method
* cannot be implemented. Hence {@code ModelAndViewResolver}s are limited
- * to always being invoked at the end after all other return value
+ * to always being invoked at the end after all other return value
* handlers have been given a chance.
- * A {@code HandlerMethodReturnValueHandler} provides better access to
+ * A {@code HandlerMethodReturnValueHandler} provides better access to
* the return type and controller method information and can be ordered
* freely relative to other return value handlers.
*/
@@ -273,8 +272,8 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
}
/**
- * Provide the converters to use in argument resolvers and return value
- * handlers that support reading and/or writing to the body of the
+ * Provide the converters to use in argument resolvers and return value
+ * handlers that support reading and/or writing to the body of the
* request and response.
*/
public void setMessageConverters(List In contrast to the "cacheSeconds" property which will apply to all general
- * handlers (but not to In contrast to the "cacheSeconds" property which will apply to all general
+ * handlers (but not to Setting this flag to {@code true} guarantees the "default" model is
- * never used in a redirect scenario even if a RedirectAttributes argument
- * is not declared. Setting it to {@code false} means the "default" model
- * may be used in a redirect if the controller method doesn't declare a
+ * Setting this flag to {@code true} guarantees the "default" model is
+ * never used in a redirect scenario even if a RedirectAttributes argument
+ * is not declared. Setting it to {@code false} means the "default" model
+ * may be used in a redirect if the controller method doesn't declare a
* RedirectAttributes argument.
- * The default setting is {@code false} but new applications should
+ * The default setting is {@code false} but new applications should
* consider setting it to {@code true}.
* @see RedirectAttributes
*/
@@ -377,7 +376,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
/**
* {@inheritDoc}
- * A {@link ConfigurableBeanFactory} is expected for resolving
+ * A {@link ConfigurableBeanFactory} is expected for resolving
* expressions in method argument default values.
*/
public void setBeanFactory(BeanFactory beanFactory) {
@@ -387,7 +386,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
}
/**
- * Return the owning factory of this bean instance, or {@code null}.
+ * Return the owning factory of this bean instance, or {@code null}.
*/
protected ConfigurableBeanFactory getBeanFactory() {
return this.beanFactory;
@@ -451,7 +450,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
}
/**
- * Return the list of argument resolvers to use for {@code @InitBinder}
+ * Return the list of argument resolvers to use for {@code @InitBinder}
* methods including built-in and custom resolvers.
*/
private List If the underlying method has a {@link ResponseStatus} instruction, the status on the response is set
- * accordingly after the method is invoked but before the return value is handled.
+ * A {@code null} return value (including void) may be interpreted as the
+ * end of request processing in combination with a {@code @ResponseStatus}
+ * annotation, a not-modified check condition
+ * (see {@link ServletWebRequest#checkNotModified(long)}), or
+ * a method argument that provides access to the response stream.
*
* @author Rossen Stoyanchev
* @since 3.1
- * @see #invokeAndHandle(NativeWebRequest, ModelAndViewContainer, Object...)
*/
public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
@@ -51,64 +53,71 @@ public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
private HandlerMethodReturnValueHandlerComposite returnValueHandlers;
+
+ /**
+ * Creates an instance from the given handler and method.
+ */
+ public ServletInvocableHandlerMethod(Object handler, Method method) {
+ super(handler, method);
+ initResponseStatus();
+ }
+
+ /**
+ * Create an instance from a {@code HandlerMethod}.
+ */
+ public ServletInvocableHandlerMethod(HandlerMethod handlerMethod) {
+ super(handlerMethod);
+ initResponseStatus();
+ }
+
+ private void initResponseStatus() {
+ ResponseStatus annot = getMethodAnnotation(ResponseStatus.class);
+ if (annot != null) {
+ this.responseStatus = annot.value();
+ this.responseReason = annot.reason();
+ }
+ }
+
+ /**
+ * Register {@link HandlerMethodReturnValueHandler} instances to use to
+ * handle return values.
+ */
public void setHandlerMethodReturnValueHandlers(HandlerMethodReturnValueHandlerComposite returnValueHandlers) {
this.returnValueHandlers = returnValueHandlers;
}
/**
- * Creates a {@link ServletInvocableHandlerMethod} instance with the given bean and method.
- * @param handler the object handler
- * @param method the method
- */
- public ServletInvocableHandlerMethod(Object handler, Method method) {
- super(handler, method);
-
- ResponseStatus annotation = getMethodAnnotation(ResponseStatus.class);
- if (annotation != null) {
- this.responseStatus = annotation.value();
- this.responseReason = annotation.reason();
- }
- }
-
- /**
- * Invokes the method and handles the return value through a registered {@link HandlerMethodReturnValueHandler}.
- * Return value handling may be skipped entirely when the method returns {@code null} (also possibly due
- * to a {@code void} return type) and one of the following additional conditions is true:
- * After the return value is handled, callers of this method can use the {@link ModelAndViewContainer}
- * to gain access to model attributes, view selection choices, and to check if view resolution is even needed.
+ * Invokes the method and handles the return value through a registered
+ * {@link HandlerMethodReturnValueHandler}.
*
- * @param request the current request
- * @param mavContainer the {@link ModelAndViewContainer} for the current request
- * @param providedArgs argument values to try to use without the need for view resolution
+ * @param webRequest the current request
+ * @param mavContainer the ModelAndViewContainer for this request
+ * @param providedArgs "given" arguments matched by type, not resolved
*/
- public final void invokeAndHandle(
- NativeWebRequest request, ModelAndViewContainer mavContainer,
- Object... providedArgs) throws Exception {
+ public final void invokeAndHandle(ServletWebRequest webRequest,
+ ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
- Object returnValue = invokeForRequest(request, mavContainer, providedArgs);
+ Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
- setResponseStatus((ServletWebRequest) request);
+ setResponseStatus(webRequest);
if (returnValue == null) {
- if (isRequestNotModified(request) || hasResponseStatus() || mavContainer.isRequestHandled()) {
+ if (isRequestNotModified(webRequest) || hasResponseStatus() || mavContainer.isRequestHandled()) {
mavContainer.setRequestHandled(true);
return;
}
}
+ else if (StringUtils.hasText(this.responseReason)) {
+ mavContainer.setRequestHandled(true);
+ return;
+ }
mavContainer.setRequestHandled(false);
try {
- returnValueHandlers.handleReturnValue(returnValue, getReturnType(), mavContainer, request);
- } catch (Exception ex) {
+ this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
+ }
+ catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex);
}
@@ -116,6 +125,41 @@ public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
}
}
+ /**
+ * Set the response status according to the {@link ResponseStatus} annotation.
+ */
+ private void setResponseStatus(ServletWebRequest webRequest) throws IOException {
+ if (this.responseStatus == null) {
+ return;
+ }
+
+ if (StringUtils.hasText(this.responseReason)) {
+ webRequest.getResponse().sendError(this.responseStatus.value(), this.responseReason);
+ }
+ else {
+ webRequest.getResponse().setStatus(this.responseStatus.value());
+ }
+
+ // to be picked up by the RedirectView
+ webRequest.getRequest().setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, this.responseStatus);
+ }
+
+ /**
+ * Does the given request qualify as "not modified"?
+ * @see ServletWebRequest#checkNotModified(long)
+ * @see ServletWebRequest#checkNotModified(String)
+ */
+ private boolean isRequestNotModified(ServletWebRequest webRequest) {
+ return webRequest.isNotModified();
+ }
+
+ /**
+ * Does this method have the response status instruction?
+ */
+ private boolean hasResponseStatus() {
+ return responseStatus != null;
+ }
+
private String getReturnValueHandlingErrorMessage(String message, Object returnValue) {
StringBuilder sb = new StringBuilder(message);
if (returnValue != null) {
@@ -125,36 +169,4 @@ public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
return getDetailedErrorMessage(sb.toString());
}
- /**
- * Set the response status according to the {@link ResponseStatus} annotation.
- */
- private void setResponseStatus(ServletWebRequest webRequest) throws IOException {
- if (this.responseStatus != null) {
- if (StringUtils.hasText(this.responseReason)) {
- webRequest.getResponse().sendError(this.responseStatus.value(), this.responseReason);
- }
- else {
- webRequest.getResponse().setStatus(this.responseStatus.value());
- }
-
- // to be picked up by the RedirectView
- webRequest.getRequest().setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, this.responseStatus);
- }
- }
-
- /**
- * Does the given request qualify as "not modified"?
- * @see ServletWebRequest#checkNotModified(long)
- * @see ServletWebRequest#checkNotModified(String)
- */
- private boolean isRequestNotModified(NativeWebRequest request) {
- return ((ServletWebRequest) request).isNotModified();
- }
-
- /**
- * Does this method have the response status instruction?
- */
- private boolean hasResponseStatus() {
- return responseStatus != null;
- }
}
diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethodTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethodTests.java
index 00a7a3abf5..f92e8478fc 100644
--- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethodTests.java
+++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethodTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 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.
@@ -15,9 +15,10 @@
*/
package org.springframework.web.servlet.mvc.method.annotation;
-import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import java.lang.reflect.Method;
@@ -30,17 +31,20 @@ import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.ServletWebRequest;
+import org.springframework.web.method.annotation.RequestParamMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodArgumentResolverComposite;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite;
import org.springframework.web.method.support.ModelAndViewContainer;
+import org.springframework.web.servlet.view.RedirectView;
/**
* Test fixture with {@link ServletInvocableHandlerMethod}.
- *
+ *
* @author Rossen Stoyanchev
*/
public class ServletInvocableHandlerMethodTests {
@@ -53,6 +57,8 @@ public class ServletInvocableHandlerMethodTests {
private ServletWebRequest webRequest;
+ private MockHttpServletRequest request;
+
private MockHttpServletResponse response;
@Before
@@ -60,24 +66,23 @@ public class ServletInvocableHandlerMethodTests {
returnValueHandlers = new HandlerMethodReturnValueHandlerComposite();
argumentResolvers = new HandlerMethodArgumentResolverComposite();
mavContainer = new ModelAndViewContainer();
+ request = new MockHttpServletRequest();
response = new MockHttpServletResponse();
- webRequest = new ServletWebRequest(new MockHttpServletRequest(), response);
+ webRequest = new ServletWebRequest(request, response);
}
@Test
- public void nullReturnValueResponseStatus() throws Exception {
+ public void invokeAndHandle_VoidWithResponseStatus() throws Exception {
ServletInvocableHandlerMethod handlerMethod = getHandlerMethod("responseStatus");
handlerMethod.invokeAndHandle(webRequest, mavContainer);
assertTrue("Null return value + @ResponseStatus should result in 'request handled'",
mavContainer.isRequestHandled());
-
assertEquals(HttpStatus.BAD_REQUEST.value(), response.getStatus());
- assertEquals("400 Bad Request", response.getErrorMessage());
}
@Test
- public void nullReturnValueHttpServletResponseArg() throws Exception {
+ public void invokeAndHandle_VoidWithHttpServletResponseArgument() throws Exception {
argumentResolvers.addResolver(new ServletResponseMethodArgumentResolver());
ServletInvocableHandlerMethod handlerMethod = getHandlerMethod("httpServletResponse", HttpServletResponse.class);
@@ -88,33 +93,61 @@ public class ServletInvocableHandlerMethodTests {
}
@Test
- public void nullReturnValueRequestNotModified() throws Exception {
+ public void invokeAndHandle_VoidRequestNotModified() throws Exception {
webRequest.getNativeRequest(MockHttpServletRequest.class).addHeader("If-Modified-Since", 10 * 1000 * 1000);
int lastModifiedTimestamp = 1000 * 1000;
webRequest.checkNotModified(lastModifiedTimestamp);
-
+
ServletInvocableHandlerMethod handlerMethod = getHandlerMethod("notModified");
handlerMethod.invokeAndHandle(webRequest, mavContainer);
assertTrue("Null return value + 'not modified' request should result in 'request handled'",
mavContainer.isRequestHandled());
}
-
+
+ // SPR-9159
+
@Test
- public void exceptionWhileHandlingReturnValue() throws Exception {
+ public void invokeAndHandle_NotVoidWithResponseStatusAndReason() throws Exception {
+ ServletInvocableHandlerMethod handlerMethod = getHandlerMethod("responseStatusWithReason");
+ handlerMethod.invokeAndHandle(webRequest, mavContainer);
+
+ assertTrue("When a phrase is used, the response should not be used any more", mavContainer.isRequestHandled());
+ assertEquals(HttpStatus.BAD_REQUEST.value(), response.getStatus());
+ assertEquals("400 Bad Request", response.getErrorMessage());
+ }
+
+ @Test(expected=HttpMessageNotWritableException.class)
+ public void invokeAndHandle_Exception() throws Exception {
returnValueHandlers.addHandler(new ExceptionRaisingReturnValueHandler());
ServletInvocableHandlerMethod handlerMethod = getHandlerMethod("handle");
- try {
- handlerMethod.invokeAndHandle(webRequest, mavContainer);
- fail("Expected exception");
- } catch (HttpMessageNotWritableException ex) {
- // Expected..
- // Allow HandlerMethodArgumentResolver exceptions to propagate..
- }
+ handlerMethod.invokeAndHandle(webRequest, mavContainer);
+ fail("Expected exception");
}
- private ServletInvocableHandlerMethod getHandlerMethod(String methodName, Class>... argTypes)
+ @Test
+ public void invokeAndHandle_DynamicReturnValue() throws Exception {
+ argumentResolvers.addResolver(new RequestParamMethodArgumentResolver(null, false));
+ returnValueHandlers.addHandler(new ViewMethodReturnValueHandler());
+ returnValueHandlers.addHandler(new ViewNameMethodReturnValueHandler());
+
+ // Invoke without a request parameter (String return value)
+ ServletInvocableHandlerMethod handlerMethod = getHandlerMethod("dynamicReturnValue", String.class);
+ handlerMethod.invokeAndHandle(webRequest, mavContainer);
+
+ assertNotNull(mavContainer.getView());
+ assertEquals(RedirectView.class, mavContainer.getView().getClass());
+
+ // Invoke with a request parameter (RedirectView return value)
+ request.setParameter("param", "value");
+ handlerMethod.invokeAndHandle(webRequest, mavContainer);
+
+ assertEquals("view", mavContainer.getViewName());
+ }
+
+
+ private ServletInvocableHandlerMethod getHandlerMethod(String methodName, Class>... argTypes)
throws NoSuchMethodException {
Method method = Handler.class.getDeclaredMethod(methodName, argTypes);
ServletInvocableHandlerMethod handlerMethod = new ServletInvocableHandlerMethod(new Handler(), method);
@@ -130,16 +163,24 @@ public class ServletInvocableHandlerMethodTests {
return "view";
}
- @ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "400 Bad Request")
+ @ResponseStatus(value = HttpStatus.BAD_REQUEST)
public void responseStatus() {
}
-
+
+ @ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "400 Bad Request")
+ public String responseStatusWithReason() {
+ return "foo";
+ }
+
public void httpServletResponse(HttpServletResponse response) {
}
-
+
public void notModified() {
}
-
+
+ public Object dynamicReturnValue(@RequestParam(required=false) String param) {
+ return (param != null) ? "view" : new RedirectView("redirectView");
+ }
}
private static class ExceptionRaisingReturnValueHandler implements HandlerMethodReturnValueHandler {
diff --git a/org.springframework.web/src/main/java/org/springframework/web/method/HandlerMethod.java b/org.springframework.web/src/main/java/org/springframework/web/method/HandlerMethod.java
index cdf3f5f845..9ed46d9cf2 100644
--- a/org.springframework.web/src/main/java/org/springframework/web/method/HandlerMethod.java
+++ b/org.springframework.web/src/main/java/org/springframework/web/method/HandlerMethod.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 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.
@@ -29,13 +29,15 @@ import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
- * Encapsulates information about a bean method consisting of a {@linkplain #getMethod() method} and a
- * {@linkplain #getBean() bean}. Provides convenient access to method parameters, the method return value,
- * method annotations.
+ * Encapsulates information about a bean method consisting of a
+ * {@linkplain #getMethod() method} and a {@linkplain #getBean() bean}. Provides
+ * convenient access to method parameters, the method return value, method
+ * annotations.
*
- * The class may be created with a bean instance or with a bean name (e.g. lazy bean, prototype bean).
- * Use {@link #createWithResolvedBean()} to obtain an {@link HandlerMethod} instance with a bean instance
- * initialized through the bean factory.
+ * The class may be created with a bean instance or with a bean name (e.g. lazy
+ * bean, prototype bean). Use {@link #createWithResolvedBean()} to obtain an
+ * {@link HandlerMethod} instance with a bean instance initialized through the
+ * bean factory.
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
@@ -44,26 +46,25 @@ import org.springframework.util.ClassUtils;
public class HandlerMethod {
/** Logger that is available to subclasses */
- protected final Log logger = LogFactory.getLog(getClass());
+ protected final Log logger = LogFactory.getLog(HandlerMethod.class);
private final Object bean;
private final Method method;
-
+
private final BeanFactory beanFactory;
private MethodParameter[] parameters;
private final Method bridgedMethod;
+
/**
- * Constructs a new handler method with the given bean instance and method.
- * @param bean the object bean
- * @param method the method
+ * Create an instance from a bean instance and a method.
*/
public HandlerMethod(Object bean, Method method) {
- Assert.notNull(bean, "bean must not be null");
- Assert.notNull(method, "method must not be null");
+ Assert.notNull(bean, "bean is required");
+ Assert.notNull(method, "method is required");
this.bean = bean;
this.beanFactory = null;
this.method = method;
@@ -71,15 +72,12 @@ public class HandlerMethod {
}
/**
- * Constructs a new handler method with the given bean instance, method name and parameters.
- * @param bean the object bean
- * @param methodName the method name
- * @param parameterTypes the method parameter types
+ * Create an instance from a bean instance, method name, and parameter types.
* @throws NoSuchMethodException when the method cannot be found
*/
public HandlerMethod(Object bean, String methodName, Class>... parameterTypes) throws NoSuchMethodException {
- Assert.notNull(bean, "bean must not be null");
- Assert.notNull(methodName, "method must not be null");
+ Assert.notNull(bean, "bean is required");
+ Assert.notNull(methodName, "method is required");
this.bean = bean;
this.beanFactory = null;
this.method = bean.getClass().getMethod(methodName, parameterTypes);
@@ -87,24 +85,34 @@ public class HandlerMethod {
}
/**
- * Constructs a new handler method with the given bean name and method. The bean name will be lazily
- * initialized when {@link #createWithResolvedBean()} is called.
- * @param beanName the bean name
- * @param beanFactory the bean factory to use for bean initialization
- * @param method the method for the bean
+ * Create an instance from a bean name, a method, and a {@code BeanFactory}.
+ * The method {@link #createWithResolvedBean()} may be used later to
+ * re-create the {@code HandlerMethod} with an initialized the bean.
*/
public HandlerMethod(String beanName, BeanFactory beanFactory, Method method) {
- Assert.hasText(beanName, "'beanName' must not be null");
- Assert.notNull(beanFactory, "'beanFactory' must not be null");
- Assert.notNull(method, "'method' must not be null");
+ Assert.hasText(beanName, "beanName is required");
+ Assert.notNull(beanFactory, "beanFactory is required");
+ Assert.notNull(method, "method is required");
Assert.isTrue(beanFactory.containsBean(beanName),
- "Bean factory [" + beanFactory + "] does not contain bean " + "with name [" + beanName + "]");
+ "Bean factory [" + beanFactory + "] does not contain bean [" + beanName + "]");
this.bean = beanName;
this.beanFactory = beanFactory;
this.method = method;
this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
}
+ /**
+ * Create an instance from another {@code HandlerMethod}.
+ */
+ protected HandlerMethod(HandlerMethod handlerMethod) {
+ Assert.notNull(handlerMethod, "HandlerMethod is required");
+ this.bean = handlerMethod.bean;
+ this.beanFactory = handlerMethod.beanFactory;
+ this.method = handlerMethod.method;
+ this.bridgedMethod = handlerMethod.bridgedMethod;
+ this.parameters = handlerMethod.parameters;
+ }
+
/**
* Returns the bean for this handler method.
*/
@@ -120,25 +128,22 @@ public class HandlerMethod {
}
/**
- * Returns the type of the handler for this handler method.
+ * Returns the type of the handler for this handler method.
* Note that if the bean type is a CGLIB-generated class, the original, user-defined class is returned.
*/
public Class> getBeanType() {
- if (bean instanceof String) {
- String beanName = (String) bean;
- return beanFactory.getType(beanName);
- }
- else {
- return ClassUtils.getUserClass(bean.getClass());
- }
+ Class> clazz = (this.bean instanceof String)
+ ? this.beanFactory.getType((String) this.bean) : this.bean.getClass();
+
+ return ClassUtils.getUserClass(clazz);
}
-
+
/**
* If the bean method is a bridge method, this method returns the bridged (user-defined) method.
* Otherwise it returns the same method as {@link #getMethod()}.
*/
protected Method getBridgedMethod() {
- return bridgedMethod;
+ return this.bridgedMethod;
}
/**
@@ -147,20 +152,26 @@ public class HandlerMethod {
public MethodParameter[] getMethodParameters() {
if (this.parameters == null) {
int parameterCount = this.bridgedMethod.getParameterTypes().length;
- MethodParameter[] p = new MethodParameter[parameterCount];
+ this.parameters = new MethodParameter[parameterCount];
for (int i = 0; i < parameterCount; i++) {
- p[i] = new HandlerMethodParameter(this.bridgedMethod, i);
+ this.parameters[i] = new HandlerMethodParameter(i);
}
- this.parameters = p;
}
- return parameters;
+ return this.parameters;
}
/**
- * Returns the method return type, as {@code MethodParameter}.
+ * Return the HandlerMethod return type.
*/
public MethodParameter getReturnType() {
- return new HandlerMethodParameter(this.bridgedMethod, -1);
+ return new HandlerMethodParameter(-1);
+ }
+
+ /**
+ * Return the actual return value type.
+ */
+ public MethodParameter getReturnValueType(Object returnValue) {
+ return new ReturnValueMethodParameter(returnValue);
}
/**
@@ -171,8 +182,8 @@ public class HandlerMethod {
}
/**
- * Returns a single annotation on the underlying method traversing its super methods if no
- * annotation can be found on the given method itself.
+ * Returns a single annotation on the underlying method traversing its super methods if no
+ * annotation can be found on the given method itself.
* @param annotationType the type of annotation to introspect the method for.
* @return the annotation, or {@code null} if none found
*/
@@ -181,7 +192,7 @@ public class HandlerMethod {
}
/**
- * If the provided instance contains a bean name rather than an object instance, the bean name is resolved
+ * If the provided instance contains a bean name rather than an object instance, the bean name is resolved
* before a {@link HandlerMethod} is created and returned.
*/
public HandlerMethod createWithResolvedBean() {
@@ -190,9 +201,11 @@ public class HandlerMethod {
String beanName = (String) this.bean;
handler = this.beanFactory.getBean(beanName);
}
- return new HandlerMethod(handler, method);
+ HandlerMethod handlerMethod = new HandlerMethod(handler, this.method);
+ handlerMethod.parameters = getMethodParameters();
+ return handlerMethod;
}
-
+
@Override
public boolean equals(Object o) {
if (this == o) {
@@ -216,33 +229,41 @@ public class HandlerMethod {
}
/**
- * A {@link MethodParameter} that resolves method annotations even when the actual annotations
- * are on a bridge method rather than on the current method. Annotations on super types are
- * also returned via {@link AnnotationUtils#findAnnotation(Method, Class)}.
+ * A MethodParameter with HandlerMethod-specific behavior.
*/
private class HandlerMethodParameter extends MethodParameter {
-
- public HandlerMethodParameter(Method method, int parameterIndex) {
- super(method, parameterIndex);
+
+ protected HandlerMethodParameter(int index) {
+ super(HandlerMethod.this.bridgedMethod, index);
}
- /**
- * Return {@link HandlerMethod#getBeanType()} rather than the method's class, which could be
- * important for the proper discovery of generic types.
- */
@Override
public Class> getDeclaringClass() {
return HandlerMethod.this.getBeanType();
}
- /**
- * Return the method annotation via {@link HandlerMethod#getMethodAnnotation(Class)}, which will find
- * the annotation by traversing super-types and handling annotations on bridge methods correctly.
- */
@Override
public @SessionAttributes annotated handlers
* for the given number of seconds. Default is 0, preventing caching completely.
- * @SessionAttributes annotated handlers),
+ * @SessionAttributes annotated handlers),
* this setting will apply to @SessionAttributes handlers only.
* @see #setCacheSeconds
* @see org.springframework.web.bind.annotation.SessionAttributes
@@ -349,25 +348,25 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
}
/**
- * Set the ParameterNameDiscoverer to use for resolving method parameter
- * names if needed (e.g. for default attribute names). Default is a
+ * Set the ParameterNameDiscoverer to use for resolving method parameter
+ * names if needed (e.g. for default attribute names). Default is a
* {@link org.springframework.core.LocalVariableTableParameterNameDiscoverer}.
*/
public void setParameterNameDiscoverer(ParameterNameDiscoverer parameterNameDiscoverer) {
this.parameterNameDiscoverer = parameterNameDiscoverer;
}
-
+
/**
- * By default the content of the "default" model is used both during
- * rendering and redirect scenarios. Alternatively a controller method
+ * By default the content of the "default" model is used both during
+ * rendering and redirect scenarios. Alternatively a controller method
* can declare a {@link RedirectAttributes} argument and use it to provide
* attributes for a redirect.
- *
- *
- *