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:
parent
28f7b26294
commit
790d515f8c
|
|
@ -18,6 +18,9 @@ package org.springframework.web.method;
|
|||
|
||||
import java.lang.annotation.Annotation;
|
||||
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.LogFactory;
|
||||
|
|
@ -353,6 +356,9 @@ public class HandlerMethod {
|
|||
*/
|
||||
protected class HandlerMethodParameter extends SynthesizingMethodParameter {
|
||||
|
||||
@Nullable
|
||||
private volatile Annotation[] combinedAnnotations;
|
||||
|
||||
public HandlerMethodParameter(int index) {
|
||||
super(HandlerMethod.this.bridgedMethod, index);
|
||||
}
|
||||
|
|
@ -376,6 +382,42 @@ public class HandlerMethod {
|
|||
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
|
||||
public HandlerMethodParameter clone() {
|
||||
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");
|
||||
* 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"));
|
||||
}
|
||||
|
||||
@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
|
||||
public void handleRequestBody() throws Exception {
|
||||
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")
|
||||
@SessionAttributes(types = TestBean.class)
|
||||
private static class Handler {
|
||||
private static class Handler implements HandlerIfc {
|
||||
|
||||
@InitBinder("dateParam")
|
||||
public void initBinder(WebDataBinder dataBinder, @RequestParam("datePattern") String datePattern) {
|
||||
|
|
@ -388,6 +496,45 @@ public class RequestMappingHandlerAdapterIntegrationTests {
|
|||
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)
|
||||
@ResponseBody
|
||||
public String handleRequestBody(@RequestBody byte[] bytes) throws Exception {
|
||||
|
|
|
|||
Loading…
Reference in New Issue