added order property to AnnotationMethodHandlerAdapter (SPR-6516)

This commit is contained in:
Juergen Hoeller
2009-12-07 09:42:44 +00:00
parent abf6a7ee18
commit f72769a621
2 changed files with 225 additions and 162 deletions

View File

@@ -52,6 +52,7 @@ import org.springframework.beans.factory.config.BeanExpressionContext;
import org.springframework.beans.factory.config.BeanExpressionResolver;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.Ordered;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.http.HttpInputMessage;
@@ -127,22 +128,22 @@ import org.springframework.web.util.WebUtils;
* @see #setSessionAttributeStore
* @since 2.5
*/
public class AnnotationMethodHandlerAdapter extends WebContentGenerator implements HandlerAdapter, BeanFactoryAware {
public class AnnotationMethodHandlerAdapter extends WebContentGenerator
implements HandlerAdapter, Ordered, BeanFactoryAware {
/**
* Log category to use when no mapped handler is found for a request.
*
* @see #pageNotFoundLogger
*/
public static final String PAGE_NOT_FOUND_LOG_CATEGORY = "org.springframework.web.servlet.PageNotFound";
/**
* Additional logger to use when no mapped handler is found for a request.
*
* @see #PAGE_NOT_FOUND_LOG_CATEGORY
*/
protected static final Log pageNotFoundLogger = LogFactory.getLog(PAGE_NOT_FOUND_LOG_CATEGORY);
private UrlPathHelper urlPathHelper = new UrlPathHelper();
private PathMatcher pathMatcher = new AntPathMatcher();
@@ -167,6 +168,8 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
new HttpMessageConverter[]{new ByteArrayHttpMessageConverter(), new StringHttpMessageConverter(),
new FormHttpMessageConverter(), new SourceHttpMessageConverter()};
private int order = Ordered.LOWEST_PRECEDENCE;
private ConfigurableBeanFactory beanFactory;
private BeanExpressionContext expressionContext;
@@ -174,16 +177,18 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
private final Map<Class<?>, ServletHandlerMethodResolver> methodResolverCache =
new ConcurrentHashMap<Class<?>, ServletHandlerMethodResolver>();
public AnnotationMethodHandlerAdapter() {
// no restriction of HTTP methods by default
super(false);
}
/**
* Set if URL lookup should always use the full path within the current servlet context. Else, the path within the
* current servlet mapping is used if applicable (that is, in the case of a ".../*" servlet mapping in web.xml).
* Set if URL lookup should always use the full path within the current servlet
* context. Else, the path within the current servlet mapping is used if applicable
* (that is, in the case of a ".../*" servlet mapping in web.xml).
* <p>Default is "false".
*
* @see org.springframework.web.util.UrlPathHelper#setAlwaysUseFullPath
*/
public void setAlwaysUseFullPath(boolean alwaysUseFullPath) {
@@ -191,10 +196,10 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
}
/**
* Set if context path and request URI should be URL-decoded. Both are returned <i>undecoded</i> by the Servlet API, in
* contrast to the servlet path. <p>Uses either the request encoding or the default encoding according to the Servlet
* spec (ISO-8859-1).
*
* Set if context path and request URI should be URL-decoded. Both are returned
* <i>undecoded</i> by the Servlet API, in contrast to the servlet path.
* <p>Uses either the request encoding or the default encoding according
* to the Servlet spec (ISO-8859-1).
* @see org.springframework.web.util.UrlPathHelper#setUrlDecode
*/
public void setUrlDecode(boolean urlDecode) {
@@ -202,8 +207,9 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
}
/**
* Set the UrlPathHelper to use for resolution of lookup paths. <p>Use this to override the default UrlPathHelper with
* a custom subclass, or to share common UrlPathHelper settings across multiple HandlerMappings and HandlerAdapters.
* Set the UrlPathHelper to use for resolution of lookup paths.
* <p>Use this to override the default UrlPathHelper with a custom subclass,
* or to share common UrlPathHelper settings across multiple HandlerMappings and HandlerAdapters.
*/
public void setUrlPathHelper(UrlPathHelper urlPathHelper) {
Assert.notNull(urlPathHelper, "UrlPathHelper must not be null");
@@ -211,10 +217,8 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
}
/**
* Set the PathMatcher implementation to use for matching URL paths against registered URL patterns. Default is
* AntPathMatcher.
*
* @see org.springframework.util.AntPathMatcher
* Set the PathMatcher implementation to use for matching URL paths against registered URL patterns.
* <p>Default is {@link org.springframework.util.AntPathMatcher}.
*/
public void setPathMatcher(PathMatcher pathMatcher) {
Assert.notNull(pathMatcher, "PathMatcher must not be null");
@@ -222,8 +226,9 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
}
/**
* Set the MethodNameResolver to use for resolving default handler methods (carrying an empty
* <code>@RequestMapping</code> annotation). <p>Will only kick in when the handler method cannot be resolved uniquely
* Set the MethodNameResolver to use for resolving default handler methods
* (carrying an empty <code>@RequestMapping</code> annotation).
* <p>Will only kick in when the handler method cannot be resolved uniquely
* through the annotation metadata already.
*/
public void setMethodNameResolver(MethodNameResolver methodNameResolver) {
@@ -231,15 +236,16 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
}
/**
* Specify a WebBindingInitializer which will apply pre-configured configuration to every DataBinder that this
* controller uses.
* Specify a WebBindingInitializer which will apply pre-configured
* configuration to every DataBinder that this controller uses.
*/
public void setWebBindingInitializer(WebBindingInitializer webBindingInitializer) {
this.webBindingInitializer = webBindingInitializer;
}
/**
* Specify the strategy to store session attributes with. <p>Default is {@link org.springframework.web.bind.support.DefaultSessionAttributeStore},
* Specify the strategy to store session attributes with.
* <p>Default is {@link org.springframework.web.bind.support.DefaultSessionAttributeStore},
* storing session attributes in the HttpSession, using the same attribute name as in the model.
*/
public void setSessionAttributeStore(SessionAttributeStore sessionAttributeStore) {
@@ -248,11 +254,11 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
}
/**
* Cache content produced by <code>@SessionAttributes</code> annotated handlers for the given number of seconds.
* Default is 0, preventing caching completely. <p>In contrast to the "cacheSeconds" property which will apply to all
* general handlers (but not to <code>@SessionAttributes</code> annotated handlers), this setting will apply to
* <code>@SessionAttributes</code> annotated handlers only.
*
* Cache content produced by <code>@SessionAttributes</code> annotated handlers
* for the given number of seconds. Default is 0, preventing caching completely.
* <p>In contrast to the "cacheSeconds" property which will apply to all general handlers
* (but not to <code>@SessionAttributes</code> annotated handlers), this setting will
* apply to <code>@SessionAttributes</code> annotated handlers only.
* @see #setCacheSeconds
* @see org.springframework.web.bind.annotation.SessionAttributes
*/
@@ -261,15 +267,20 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
}
/**
* Set if controller execution should be synchronized on the session, to serialize parallel invocations from the same
* client. <p>More specifically, the execution of each handler method will get synchronized if this flag is "true". The
* best available session mutex will be used for the synchronization; ideally, this will be a mutex exposed by
* HttpSessionMutexListener. <p>The session mutex is guaranteed to be the same object during the entire lifetime of the
* session, available under the key defined by the <code>SESSION_MUTEX_ATTRIBUTE</code> constant. It serves as a safe
* reference to synchronize on for locking on the current session. <p>In many cases, the HttpSession reference itself a
* safe mutex as well, since it will always be the same object reference for the same active logical session. However,
* this is not guaranteed across different servlet containers; the only 100% safe way is a session mutex.
*
* Set if controller execution should be synchronized on the session,
* to serialize parallel invocations from the same client.
* <p>More specifically, the execution of the <code>handleRequestInternal</code>
* method will get synchronized if this flag is "true". The best available
* session mutex will be used for the synchronization; ideally, this will
* be a mutex exposed by HttpSessionMutexListener.
* <p>The session mutex is guaranteed to be the same object during
* the entire lifetime of the session, available under the key defined
* by the <code>SESSION_MUTEX_ATTRIBUTE</code> constant. It serves as a
* safe reference to synchronize on for locking on the current session.
* <p>In many cases, the HttpSession reference itself is a safe mutex
* as well, since it will always be the same object reference for the
* same active logical session. However, this is not guaranteed across
* different servlet containers; the only 100% safe way is a session mutex.
* @see org.springframework.web.util.HttpSessionMutexListener
* @see org.springframework.web.util.WebUtils#getSessionMutex(javax.servlet.http.HttpSession)
*/
@@ -278,61 +289,76 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
}
/**
* Set the ParameterNameDiscoverer to use for resolving method parameter names if needed (e.g. for default attribute
* names). <p>Default is a {@link org.springframework.core.LocalVariableTableParameterNameDiscoverer}.
* Set the ParameterNameDiscoverer to use for resolving method parameter names if needed
* (e.g. for default attribute names).
* <p>Default is a {@link org.springframework.core.LocalVariableTableParameterNameDiscoverer}.
*/
public void setParameterNameDiscoverer(ParameterNameDiscoverer parameterNameDiscoverer) {
this.parameterNameDiscoverer = parameterNameDiscoverer;
}
/**
* Set a custom WebArgumentResolvers to use for special method parameter types. Such a custom WebArgumentResolver will
* kick in first, having a chance to resolve an argument value before the standard argument handling kicks in.
* Set a custom WebArgumentResolvers to use for special method parameter types.
* <p>Such a custom WebArgumentResolver will kick in first, having a chance to resolve
* an argument value before the standard argument handling kicks in.
*/
public void setCustomArgumentResolver(WebArgumentResolver argumentResolver) {
this.customArgumentResolvers = new WebArgumentResolver[]{argumentResolver};
this.customArgumentResolvers = new WebArgumentResolver[] {argumentResolver};
}
/**
* Set one or more custom WebArgumentResolvers to use for special method parameter types. Any such custom
* WebArgumentResolver will kick in first, having a chance to resolve an argument value before the standard argument
* handling kicks in.
* Set one or more custom WebArgumentResolvers to use for special method parameter types.
* <p>Any such custom WebArgumentResolver will kick in first, having a chance to resolve
* an argument value before the standard argument handling kicks in.
*/
public void setCustomArgumentResolvers(WebArgumentResolver[] argumentResolvers) {
this.customArgumentResolvers = argumentResolvers;
}
/**
* Set a custom ModelAndViewResolvers to use for special method return types. Such a custom ModelAndViewResolver will
* kick in first, having a chance to resolve an return value before the standard ModelAndView handling kicks in.
* Set a custom ModelAndViewResolvers to use for special method return types.
* <p>Such a custom ModelAndViewResolver will kick in first, having a chance to resolve
* a return value before the standard ModelAndView handling kicks in.
*/
public void setCustomModelAndViewResolver(ModelAndViewResolver customModelAndViewResolver) {
this.customModelAndViewResolvers = new ModelAndViewResolver[]{customModelAndViewResolver};
this.customModelAndViewResolvers = new ModelAndViewResolver[] {customModelAndViewResolver};
}
/**
* Set one or more custom ModelAndViewResolvers to use for special method return types. Any such custom
* ModelAndViewResolver will kick in first, having a chance to resolve an return value before the standard ModelAndView
* handling kicks in.
* Set one or more custom ModelAndViewResolvers to use for special method return types.
* <p>Any such custom ModelAndViewResolver will kick in first, having a chance to resolve
* a return value before the standard ModelAndView handling kicks in.
*/
public void setCustomModelAndViewResolvers(ModelAndViewResolver[] customModelAndViewResolvers) {
this.customModelAndViewResolvers = customModelAndViewResolvers;
}
/**
* Returns the message body converters to use. These converters are used to convert from and to HTTP requests and
* responses.
* Set the message body converters to use.
* <p>These converters are used to convert from and to HTTP requests and responses.
*/
public void setMessageConverters(HttpMessageConverter<?>[] messageConverters) {
this.messageConverters = messageConverters;
}
/**
* Return the message body converters that this adapter has been configured with.
*/
public HttpMessageConverter<?>[] getMessageConverters() {
return messageConverters;
}
/**
* Set the message body converters to use. These converters are used to convert from and to HTTP requests and
* responses.
* Specify the order value for this HandlerAdapter bean.
* <p>Default value is <code>Integer.MAX_VALUE</code>, meaning that it's non-ordered.
* @see org.springframework.core.Ordered#getOrder()
*/
public void setMessageConverters(HttpMessageConverter<?>[] messageConverters) {
this.messageConverters = messageConverters;
public void setOrder(int order) {
this.order = order;
}
public int getOrder() {
return this.order;
}
public void setBeanFactory(BeanFactory beanFactory) {
@@ -342,6 +368,7 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
}
}
public boolean supports(Object handler) {
return getMethodResolver(handler).hasHandlerMethods();
}
@@ -393,7 +420,9 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
return -1;
}
/** Build a HandlerMethodResolver for the given handler type. */
/**
* Build a HandlerMethodResolver for the given handler type.
*/
private ServletHandlerMethodResolver getMethodResolver(Object handler) {
Class handlerClass = ClassUtils.getUserClass(handler);
ServletHandlerMethodResolver resolver = this.methodResolverCache.get(handlerClass);
@@ -404,7 +433,10 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
return resolver;
}
/** Servlet-specific subclass of {@link HandlerMethodResolver}. */
/**
* Servlet-specific subclass of {@link HandlerMethodResolver}.
*/
private class ServletHandlerMethodResolver extends HandlerMethodResolver {
private ServletHandlerMethodResolver(Class<?> handlerType) {
@@ -606,14 +638,17 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
}
}
/** Servlet-specific subclass of {@link HandlerMethodInvoker}. */
/**
* Servlet-specific subclass of {@link HandlerMethodInvoker}.
*/
private class ServletHandlerMethodInvoker extends HandlerMethodInvoker {
private boolean responseArgumentUsed = false;
private ServletHandlerMethodInvoker(HandlerMethodResolver resolver) {
super(resolver, webBindingInitializer, sessionAttributeStore, parameterNameDiscoverer,
customArgumentResolvers, getMessageConverters());
customArgumentResolvers, messageConverters);
}
@Override
@@ -679,7 +714,6 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
@Override
protected Object resolveStandardArgument(Class parameterType, NativeWebRequest webRequest) throws Exception {
HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
HttpServletResponse response = (HttpServletResponse) webRequest.getNativeResponse();
@@ -717,11 +751,8 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
}
@SuppressWarnings("unchecked")
public ModelAndView getModelAndView(Method handlerMethod,
Class handlerType,
Object returnValue,
ExtendedModelMap implicitModel,
ServletWebRequest webRequest) throws Exception {
public ModelAndView getModelAndView(Method handlerMethod, Class handlerType, Object returnValue,
ExtendedModelMap implicitModel, ServletWebRequest webRequest) throws Exception {
ResponseStatus responseStatusAnn = AnnotationUtils.findAnnotation(handlerMethod, ResponseStatus.class);
if (responseStatusAnn != null) {
@@ -792,6 +823,7 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
@SuppressWarnings("unchecked")
private void handleResponseBody(Object returnValue, ServletWebRequest webRequest)
throws ServletException, IOException {
HttpInputMessage inputMessage = new ServletServerHttpRequest(webRequest.getRequest());
List<MediaType> acceptedMediaTypes = inputMessage.getHeaders().getAccept();
if (acceptedMediaTypes.isEmpty()) {
@@ -800,8 +832,8 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
HttpOutputMessage outputMessage = new ServletServerHttpResponse(webRequest.getResponse());
Class<?> returnValueType = returnValue.getClass();
List<MediaType> allSupportedMediaTypes = new ArrayList<MediaType>();
if (getMessageConverters() != null) {
for (HttpMessageConverter messageConverter : getMessageConverters()) {
if (messageConverters != null) {
for (HttpMessageConverter messageConverter : messageConverters) {
allSupportedMediaTypes.addAll(messageConverter.getSupportedMediaTypes());
for (MediaType acceptedMediaType : acceptedMediaTypes) {
if (messageConverter.canWrite(returnValueType, acceptedMediaType)) {
@@ -816,6 +848,10 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
}
}
/**
* Holder for request mapping metadata. Allows for finding a best matching candidate.
*/
static class RequestMappingInfo {
String[] paths = new String[0];
@@ -852,6 +888,7 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
}
}
/**
* Comparator capable of sorting {@link RequestMappingInfo}s (RHIs) so that sorting a list with this comparator will
* result in: <ul> <li>RHIs with {@linkplain RequestMappingInfo#matchedPaths better matched paths} take prescedence