HandlerMethod exposes interface parameter annotations as well
The HandlerMethodParameter arrangement uses an approach similar to ModelAttributeMethodProcessor's FieldAwareConstructorParameter, merging the local parameter annotations with interface-declared annotations. Issue: SPR-11055
This commit is contained in:
@@ -18,6 +18,9 @@ package org.springframework.web.method;
|
|||||||
|
|
||||||
import java.lang.annotation.Annotation;
|
import java.lang.annotation.Annotation;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
@@ -353,6 +356,9 @@ public class HandlerMethod {
|
|||||||
*/
|
*/
|
||||||
protected class HandlerMethodParameter extends SynthesizingMethodParameter {
|
protected class HandlerMethodParameter extends SynthesizingMethodParameter {
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private volatile Annotation[] combinedAnnotations;
|
||||||
|
|
||||||
public HandlerMethodParameter(int index) {
|
public HandlerMethodParameter(int index) {
|
||||||
super(HandlerMethod.this.bridgedMethod, index);
|
super(HandlerMethod.this.bridgedMethod, index);
|
||||||
}
|
}
|
||||||
@@ -376,6 +382,42 @@ public class HandlerMethod {
|
|||||||
return HandlerMethod.this.hasMethodAnnotation(annotationType);
|
return HandlerMethod.this.hasMethodAnnotation(annotationType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Annotation[] getParameterAnnotations() {
|
||||||
|
Annotation[] anns = this.combinedAnnotations;
|
||||||
|
if (anns == null) {
|
||||||
|
anns = super.getParameterAnnotations();
|
||||||
|
Class<?>[] ifcs = getDeclaringClass().getInterfaces();
|
||||||
|
for (Class<?> ifc : ifcs) {
|
||||||
|
try {
|
||||||
|
Method method = ifc.getMethod(getExecutable().getName(), getExecutable().getParameterTypes());
|
||||||
|
Annotation[] paramAnns = method.getParameterAnnotations()[getParameterIndex()];
|
||||||
|
if (paramAnns.length > 0) {
|
||||||
|
List<Annotation> merged = new ArrayList<>(anns.length + paramAnns.length);
|
||||||
|
merged.addAll(Arrays.asList(anns));
|
||||||
|
for (Annotation fieldAnn : paramAnns) {
|
||||||
|
boolean existingType = false;
|
||||||
|
for (Annotation ann : anns) {
|
||||||
|
if (ann.annotationType() == fieldAnn.annotationType()) {
|
||||||
|
existingType = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!existingType) {
|
||||||
|
merged.add(fieldAnn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
anns = merged.toArray(new Annotation[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (NoSuchMethodException ex) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.combinedAnnotations = anns;
|
||||||
|
}
|
||||||
|
return anns;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HandlerMethodParameter clone() {
|
public HandlerMethodParameter clone() {
|
||||||
return new HandlerMethodParameter(this);
|
return new HandlerMethodParameter(this);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2002-2016 the original author or authors.
|
* Copyright 2002-2018 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -221,6 +221,87 @@ public class RequestMappingHandlerAdapterIntegrationTests {
|
|||||||
assertEquals(new URI("http://localhost/contextPath/main/path"), model.get("url"));
|
assertEquals(new URI("http://localhost/contextPath/main/path"), model.get("url"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void handleInInterface() throws Exception {
|
||||||
|
Class<?>[] parameterTypes = new Class<?>[] {int.class, String.class, String.class, String.class, Map.class,
|
||||||
|
Date.class, Map.class, String.class, String.class, TestBean.class, Errors.class, TestBean.class,
|
||||||
|
Color.class, HttpServletRequest.class, HttpServletResponse.class, TestBean.class, TestBean.class,
|
||||||
|
User.class, OtherUser.class, Model.class, UriComponentsBuilder.class};
|
||||||
|
|
||||||
|
String datePattern = "yyyy.MM.dd";
|
||||||
|
String formattedDate = "2011.03.16";
|
||||||
|
Date date = new GregorianCalendar(2011, Calendar.MARCH, 16).getTime();
|
||||||
|
TestBean sessionAttribute = new TestBean();
|
||||||
|
TestBean requestAttribute = new TestBean();
|
||||||
|
|
||||||
|
request.addHeader("Content-Type", "text/plain; charset=utf-8");
|
||||||
|
request.addHeader("header", "headerValue");
|
||||||
|
request.addHeader("anotherHeader", "anotherHeaderValue");
|
||||||
|
request.addParameter("datePattern", datePattern);
|
||||||
|
request.addParameter("dateParam", formattedDate);
|
||||||
|
request.addParameter("paramByConvention", "paramByConventionValue");
|
||||||
|
request.addParameter("age", "25");
|
||||||
|
request.setCookies(new Cookie("cookie", "99"));
|
||||||
|
request.setContent("Hello World".getBytes("UTF-8"));
|
||||||
|
request.setUserPrincipal(new User());
|
||||||
|
request.setContextPath("/contextPath");
|
||||||
|
request.setServletPath("/main");
|
||||||
|
System.setProperty("systemHeader", "systemHeaderValue");
|
||||||
|
Map<String, String> uriTemplateVars = new HashMap<>();
|
||||||
|
uriTemplateVars.put("pathvar", "pathvarValue");
|
||||||
|
request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVars);
|
||||||
|
request.getSession().setAttribute("sessionAttribute", sessionAttribute);
|
||||||
|
request.setAttribute("requestAttribute", requestAttribute);
|
||||||
|
|
||||||
|
HandlerMethod handlerMethod = handlerMethod("handleInInterface", parameterTypes);
|
||||||
|
ModelAndView mav = handlerAdapter.handle(request, response, handlerMethod);
|
||||||
|
ModelMap model = mav.getModelMap();
|
||||||
|
|
||||||
|
assertEquals("viewName", mav.getViewName());
|
||||||
|
assertEquals(99, model.get("cookie"));
|
||||||
|
assertEquals("pathvarValue", model.get("pathvar"));
|
||||||
|
assertEquals("headerValue", model.get("header"));
|
||||||
|
assertEquals(date, model.get("dateParam"));
|
||||||
|
|
||||||
|
Map<?, ?> map = (Map<?, ?>) model.get("headerMap");
|
||||||
|
assertEquals("headerValue", map.get("header"));
|
||||||
|
assertEquals("anotherHeaderValue", map.get("anotherHeader"));
|
||||||
|
assertEquals("systemHeaderValue", model.get("systemHeader"));
|
||||||
|
|
||||||
|
map = (Map<?, ?>) model.get("paramMap");
|
||||||
|
assertEquals(formattedDate, map.get("dateParam"));
|
||||||
|
assertEquals("paramByConventionValue", map.get("paramByConvention"));
|
||||||
|
|
||||||
|
assertEquals("/contextPath", model.get("value"));
|
||||||
|
|
||||||
|
TestBean modelAttr = (TestBean) model.get("modelAttr");
|
||||||
|
assertEquals(25, modelAttr.getAge());
|
||||||
|
assertEquals("Set by model method [modelAttr]", modelAttr.getName());
|
||||||
|
assertSame(modelAttr, request.getSession().getAttribute("modelAttr"));
|
||||||
|
|
||||||
|
BindingResult bindingResult = (BindingResult) model.get(BindingResult.MODEL_KEY_PREFIX + "modelAttr");
|
||||||
|
assertSame(modelAttr, bindingResult.getTarget());
|
||||||
|
assertEquals(1, bindingResult.getErrorCount());
|
||||||
|
|
||||||
|
String conventionAttrName = "testBean";
|
||||||
|
TestBean modelAttrByConvention = (TestBean) model.get(conventionAttrName);
|
||||||
|
assertEquals(25, modelAttrByConvention.getAge());
|
||||||
|
assertEquals("Set by model method [modelAttrByConvention]", modelAttrByConvention.getName());
|
||||||
|
assertSame(modelAttrByConvention, request.getSession().getAttribute(conventionAttrName));
|
||||||
|
|
||||||
|
bindingResult = (BindingResult) model.get(BindingResult.MODEL_KEY_PREFIX + conventionAttrName);
|
||||||
|
assertSame(modelAttrByConvention, bindingResult.getTarget());
|
||||||
|
|
||||||
|
assertTrue(model.get("customArg") instanceof Color);
|
||||||
|
assertEquals(User.class, model.get("user").getClass());
|
||||||
|
assertEquals(OtherUser.class, model.get("otherUser").getClass());
|
||||||
|
|
||||||
|
assertSame(sessionAttribute, model.get("sessionAttribute"));
|
||||||
|
assertSame(requestAttribute, model.get("requestAttribute"));
|
||||||
|
|
||||||
|
assertEquals(new URI("http://localhost/contextPath/main/path"), model.get("url"));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void handleRequestBody() throws Exception {
|
public void handleRequestBody() throws Exception {
|
||||||
Class<?>[] parameterTypes = new Class<?>[] {byte[].class};
|
Class<?>[] parameterTypes = new Class<?>[] {byte[].class};
|
||||||
@@ -327,9 +408,36 @@ public class RequestMappingHandlerAdapterIntegrationTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private interface HandlerIfc {
|
||||||
|
|
||||||
|
String handleInInterface(
|
||||||
|
@CookieValue("cookie") int cookie,
|
||||||
|
@PathVariable("pathvar") String pathvar,
|
||||||
|
@RequestHeader("header") String header,
|
||||||
|
@RequestHeader(defaultValue = "#{systemProperties.systemHeader}") String systemHeader,
|
||||||
|
@RequestHeader Map<String, Object> headerMap,
|
||||||
|
@RequestParam("dateParam") Date dateParam,
|
||||||
|
@RequestParam Map<String, Object> paramMap,
|
||||||
|
String paramByConvention,
|
||||||
|
@Value("#{request.contextPath}") String value,
|
||||||
|
@ModelAttribute("modelAttr") @Valid TestBean modelAttr,
|
||||||
|
Errors errors,
|
||||||
|
TestBean modelAttrByConvention,
|
||||||
|
Color customArg,
|
||||||
|
HttpServletRequest request,
|
||||||
|
HttpServletResponse response,
|
||||||
|
@SessionAttribute TestBean sessionAttribute,
|
||||||
|
@RequestAttribute TestBean requestAttribute,
|
||||||
|
User user,
|
||||||
|
@ModelAttribute OtherUser otherUser,
|
||||||
|
Model model,
|
||||||
|
UriComponentsBuilder builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
@SessionAttributes(types = TestBean.class)
|
@SessionAttributes(types = TestBean.class)
|
||||||
private static class Handler {
|
private static class Handler implements HandlerIfc {
|
||||||
|
|
||||||
@InitBinder("dateParam")
|
@InitBinder("dateParam")
|
||||||
public void initBinder(WebDataBinder dataBinder, @RequestParam("datePattern") String datePattern) {
|
public void initBinder(WebDataBinder dataBinder, @RequestParam("datePattern") String datePattern) {
|
||||||
@@ -388,6 +496,45 @@ public class RequestMappingHandlerAdapterIntegrationTests {
|
|||||||
return "viewName";
|
return "viewName";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String handleInInterface(
|
||||||
|
int cookie,
|
||||||
|
String pathvar,
|
||||||
|
String header,
|
||||||
|
String systemHeader,
|
||||||
|
Map<String, Object> headerMap,
|
||||||
|
Date dateParam,
|
||||||
|
Map<String, Object> paramMap,
|
||||||
|
String paramByConvention,
|
||||||
|
String value,
|
||||||
|
TestBean modelAttr,
|
||||||
|
Errors errors,
|
||||||
|
TestBean modelAttrByConvention,
|
||||||
|
Color customArg,
|
||||||
|
HttpServletRequest request,
|
||||||
|
HttpServletResponse response,
|
||||||
|
TestBean sessionAttribute,
|
||||||
|
TestBean requestAttribute,
|
||||||
|
User user,
|
||||||
|
OtherUser otherUser,
|
||||||
|
Model model,
|
||||||
|
UriComponentsBuilder builder) {
|
||||||
|
|
||||||
|
model.addAttribute("cookie", cookie).addAttribute("pathvar", pathvar).addAttribute("header", header)
|
||||||
|
.addAttribute("systemHeader", systemHeader).addAttribute("headerMap", headerMap)
|
||||||
|
.addAttribute("dateParam", dateParam).addAttribute("paramMap", paramMap)
|
||||||
|
.addAttribute("paramByConvention", paramByConvention).addAttribute("value", value)
|
||||||
|
.addAttribute("customArg", customArg).addAttribute(user)
|
||||||
|
.addAttribute("sessionAttribute", sessionAttribute)
|
||||||
|
.addAttribute("requestAttribute", requestAttribute)
|
||||||
|
.addAttribute("url", builder.path("/path").build().toUri());
|
||||||
|
|
||||||
|
assertNotNull(request);
|
||||||
|
assertNotNull(response);
|
||||||
|
|
||||||
|
return "viewName";
|
||||||
|
}
|
||||||
|
|
||||||
@ResponseStatus(HttpStatus.ACCEPTED)
|
@ResponseStatus(HttpStatus.ACCEPTED)
|
||||||
@ResponseBody
|
@ResponseBody
|
||||||
public String handleRequestBody(@RequestBody byte[] bytes) throws Exception {
|
public String handleRequestBody(@RequestBody byte[] bytes) throws Exception {
|
||||||
|
|||||||
Reference in New Issue
Block a user