提交 790d515f 编写于 作者: J Juergen Hoeller

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
上级 28f7b262
...@@ -18,6 +18,9 @@ package org.springframework.web.method; ...@@ -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 { ...@@ -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 { ...@@ -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);
......
/* /*
* 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 { ...@@ -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 { ...@@ -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 { ...@@ -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 {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册