spring HandlerMethodAnnotationDetectionTests 源码
spring HandlerMethodAnnotationDetectionTests 代码
文件路径:/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HandlerMethodAnnotationDetectionTests.java
/*
* Copyright 2002-2022 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.mvc.method.annotation;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.stream.Stream;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.aop.interceptor.SimpleTraceInterceptor;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.StaticMethodMatcherPointcut;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.support.GenericWebApplicationContext;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
import org.springframework.web.testfixture.servlet.MockHttpServletResponse;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Named.named;
import static org.junit.jupiter.params.provider.Arguments.arguments;
/**
* Test various scenarios for detecting method-level and method parameter annotations depending
* on where they are located -- on interfaces, parent classes, in parameterized methods, or in
* combination with proxies.
*
* @author Rossen Stoyanchev
* @author Sam Brannen
*/
class HandlerMethodAnnotationDetectionTests {
static Stream<Arguments> handlerTypes() {
return Stream.of(
args(SimpleController.class, true), // CGLIB proxy
args(SimpleController.class, false),
args(AbstractClassController.class, true), // CGLIB proxy
args(AbstractClassController.class, false),
args(ParameterizedAbstractClassController.class, true), // CGLIB proxy
args(ParameterizedAbstractClassController.class, false),
args(ParameterizedSubclassOverridesDefaultMappings.class, true), // CGLIB proxy
args(ParameterizedSubclassOverridesDefaultMappings.class, false),
// TODO [SPR-9517] Enable ParameterizedSubclassDoesNotOverrideConcreteImplementationsFromGenericAbstractSuperclass test cases
// args(ParameterizedSubclassDoesNotOverrideConcreteImplementationsFromGenericAbstractSuperclass.class, true), // CGLIB proxy
// args(ParameterizedSubclassDoesNotOverrideConcreteImplementationsFromGenericAbstractSuperclass.class, false),
// args(InterfaceController.class, true), // JDK dynamic proxy (gh-22154: no longer supported))
args(InterfaceController.class, false),
args(ParameterizedInterfaceController.class, false), // no AOP
args(SupportClassController.class, true), // CGLIB proxy
args(SupportClassController.class, false)
);
}
private static Arguments args(Class<?> controllerType, boolean useAutoProxy) {
return arguments(named(controllerType.getSimpleName(), controllerType), useAutoProxy);
}
private RequestMappingHandlerMapping handlerMapping;
private RequestMappingHandlerAdapter handlerAdapter;
private ExceptionHandlerExceptionResolver exceptionResolver;
private void setUp(Class<?> controllerType, boolean useAutoProxy) {
GenericWebApplicationContext context = new GenericWebApplicationContext();
context.registerBeanDefinition("controller", new RootBeanDefinition(controllerType));
context.registerBeanDefinition("handlerMapping", new RootBeanDefinition(RequestMappingHandlerMapping.class));
context.registerBeanDefinition("handlerAdapter", new RootBeanDefinition(RequestMappingHandlerAdapter.class));
context.registerBeanDefinition("exceptionResolver", new RootBeanDefinition(ExceptionHandlerExceptionResolver.class));
if (useAutoProxy) {
DefaultAdvisorAutoProxyCreator autoProxyCreator = new DefaultAdvisorAutoProxyCreator();
autoProxyCreator.setBeanFactory(context.getBeanFactory());
context.getBeanFactory().addBeanPostProcessor(autoProxyCreator);
context.registerBeanDefinition("controllerAdvice", new RootBeanDefinition(ControllerAdvisor.class));
}
context.refresh();
this.handlerMapping = context.getBean(RequestMappingHandlerMapping.class);
this.handlerAdapter = context.getBean(RequestMappingHandlerAdapter.class);
this.exceptionResolver = context.getBean(ExceptionHandlerExceptionResolver.class);
context.close();
}
@ParameterizedTest(name = "[{index}] controller = {0}, auto-proxy = {1}")
@MethodSource("handlerTypes")
void requestMappingMethod(Class<?> controllerType, boolean useAutoProxy) throws Exception {
setUp(controllerType, useAutoProxy);
String datePattern = "MM:dd:yyyy";
SimpleDateFormat dateFormat = new SimpleDateFormat(datePattern);
String dateA = "11:01:2011";
String dateB = "11:02:2011";
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/path1/path2");
request.setParameter("datePattern", datePattern);
request.addHeader("header1", dateA);
request.addHeader("header2", dateB);
HandlerExecutionChain chain = handlerMapping.getHandler(request);
assertThat(chain).isNotNull();
ModelAndView mav = handlerAdapter.handle(request, new MockHttpServletResponse(), chain.getHandler());
assertThat(mav.getModel().get("attr1")).as("model attr1:").isEqualTo(dateFormat.parse(dateA));
assertThat(mav.getModel().get("attr2")).as("model attr2:").isEqualTo(dateFormat.parse(dateB));
MockHttpServletResponse response = new MockHttpServletResponse();
exceptionResolver.resolveException(request, response, chain.getHandler(), new Exception("failure"));
assertThat(response.getHeader("Content-Type")).isEqualTo("text/plain;charset=ISO-8859-1");
assertThat(response.getContentAsString()).isEqualTo("failure");
}
/**
* SIMPLE CASE
*/
@Controller
static class SimpleController {
@InitBinder
public void initBinder(WebDataBinder dataBinder, @RequestParam("datePattern") String pattern) {
CustomDateEditor dateEditor = new CustomDateEditor(new SimpleDateFormat(pattern), false);
dataBinder.registerCustomEditor(Date.class, dateEditor);
}
@ModelAttribute
public void initModel(@RequestHeader("header1") Date date, Model model) {
model.addAttribute("attr1", date);
}
@RequestMapping(value="/path1/path2", method=RequestMethod.POST)
@ModelAttribute("attr2")
public Date handle(@RequestHeader("header2") Date date) throws Exception {
return date;
}
@ExceptionHandler(Exception.class)
@ResponseBody
public String handleException(Exception exception) {
return exception.getMessage();
}
}
@Controller
static abstract class MappingAbstractClass {
@InitBinder
public abstract void initBinder(WebDataBinder dataBinder, String pattern);
@ModelAttribute
public abstract void initModel(Date date, Model model);
@RequestMapping(value="/path1/path2", method=RequestMethod.POST)
@ModelAttribute("attr2")
public abstract Date handle(Date date, Model model) throws Exception;
@ExceptionHandler(Exception.class)
@ResponseBody
public abstract String handleException(Exception exception);
}
/**
* CONTROLLER WITH ABSTRACT CLASS
* <p>All annotations can be on methods in the abstract class except parameter annotations.
*/
static class AbstractClassController extends MappingAbstractClass {
@Override
public void initBinder(WebDataBinder dataBinder, @RequestParam("datePattern") String pattern) {
CustomDateEditor dateEditor = new CustomDateEditor(new SimpleDateFormat(pattern), false);
dataBinder.registerCustomEditor(Date.class, dateEditor);
}
@Override
public void initModel(@RequestHeader("header1") Date date, Model model) {
model.addAttribute("attr1", date);
}
@Override
public Date handle(@RequestHeader("header2") Date date, Model model) throws Exception {
return date;
}
@Override
public String handleException(Exception exception) {
return exception.getMessage();
}
}
// SPR-9374
@RequestMapping
interface MappingInterface {
@InitBinder
void initBinder(WebDataBinder dataBinder, @RequestParam("datePattern") String thePattern);
@ModelAttribute
void initModel(@RequestHeader("header1") Date date, Model model);
@RequestMapping(value="/path1/path2", method=RequestMethod.POST)
@ModelAttribute("attr2")
Date handle(@RequestHeader("header2") Date date, Model model) throws Exception;
@ExceptionHandler(Exception.class)
@ResponseBody
String handleException(Exception exception);
}
/**
* CONTROLLER WITH INTERFACE
* <p>JDK Dynamic proxy: All annotations must be on the interface.
* <p>Without AOP: Annotations can be on interface methods except parameter annotations.
*/
@Controller
static class InterfaceController implements MappingInterface {
@Override
public void initBinder(WebDataBinder dataBinder, @RequestParam("datePattern") String thePattern) {
CustomDateEditor dateEditor = new CustomDateEditor(new SimpleDateFormat(thePattern), false);
dataBinder.registerCustomEditor(Date.class, dateEditor);
}
@Override
public void initModel(@RequestHeader("header1") Date date, Model model) {
model.addAttribute("attr1", date);
}
@Override
public Date handle(@RequestHeader("header2") Date date, Model model) throws Exception {
return date;
}
@Override
public String handleException(Exception exception) {
return exception.getMessage();
}
}
@Controller
static abstract class MappingGenericAbstractClass<A, B, C> {
@InitBinder
public abstract void initBinder(WebDataBinder dataBinder, A thePattern);
@ModelAttribute
public abstract void initModel(B date, Model model);
@RequestMapping(value="/path1/path2", method=RequestMethod.POST)
@ModelAttribute("attr2")
public abstract Date handle(C date, Model model) throws Exception;
@ExceptionHandler(Exception.class)
@ResponseBody
public abstract String handleException(Exception exception);
}
/**
* CONTROLLER WITH PARAMETERIZED BASE CLASS
* <p>All annotations can be on methods in the abstract class except parameter annotations.
*/
static class ParameterizedAbstractClassController extends MappingGenericAbstractClass<String, Date, Date> {
@Override
public void initBinder(WebDataBinder dataBinder, @RequestParam("datePattern") String thePattern) {
CustomDateEditor dateEditor = new CustomDateEditor(new SimpleDateFormat(thePattern), false);
dataBinder.registerCustomEditor(Date.class, dateEditor);
}
@Override
public void initModel(@RequestHeader("header1") Date date, Model model) {
model.addAttribute("attr1", date);
}
@Override
public Date handle(@RequestHeader("header2") Date date, Model model) throws Exception {
return date;
}
@Override
public String handleException(Exception exception) {
return exception.getMessage();
}
}
@Controller
static abstract class MappedGenericAbstractClassWithConcreteImplementations<A, B, C> {
@InitBinder
public abstract void initBinder(WebDataBinder dataBinder, A thePattern);
@ModelAttribute
public abstract void initModel(B date, Model model);
@RequestMapping(value = "/path1/path2", method = RequestMethod.POST)
@ModelAttribute("attr2")
public Date handle(C date, Model model) throws Exception {
return (Date) date;
}
@ExceptionHandler(Exception.class)
@ResponseBody
public abstract String handleException(Exception exception);
}
static class ParameterizedSubclassDoesNotOverrideConcreteImplementationsFromGenericAbstractSuperclass extends
MappedGenericAbstractClassWithConcreteImplementations<String, Date, Date> {
@Override
public void initBinder(WebDataBinder dataBinder, @RequestParam("datePattern") String thePattern) {
CustomDateEditor dateEditor = new CustomDateEditor(new SimpleDateFormat(thePattern), false);
dataBinder.registerCustomEditor(Date.class, dateEditor);
}
@Override
public void initModel(@RequestHeader("header1") Date date, Model model) {
model.addAttribute("attr1", date);
}
// does not override handle()
@Override
public String handleException(Exception exception) {
return exception.getMessage();
}
}
@Controller
static abstract class GenericAbstractClassDeclaresDefaultMappings<A, B, C> {
@InitBinder
public abstract void initBinder(WebDataBinder dataBinder, A thePattern);
@ModelAttribute
public abstract void initModel(B date, Model model);
// /foo/bar should be overridden in concrete subclass
@RequestMapping(value = "/foo/bar", method = RequestMethod.POST)
// attrFoo should be overridden in concrete subclass
@ModelAttribute("attrFoo")
public abstract Date handle(C date, Model model) throws Exception;
@ExceptionHandler(Exception.class)
@ResponseBody
public abstract String handleException(Exception exception);
}
static class ParameterizedSubclassOverridesDefaultMappings
extends GenericAbstractClassDeclaresDefaultMappings<String, Date, Date> {
@Override
public void initBinder(WebDataBinder dataBinder, @RequestParam("datePattern") String thePattern) {
CustomDateEditor dateEditor = new CustomDateEditor(new SimpleDateFormat(thePattern), false);
dataBinder.registerCustomEditor(Date.class, dateEditor);
}
@Override
public void initModel(@RequestHeader("header1") Date date, Model model) {
model.addAttribute("attr1", date);
}
@Override
@RequestMapping(value = "/path1/path2", method = RequestMethod.POST)
// NOTE: @ModelAttribute will NOT be found on the abstract superclass if
// @RequestMapping is declared locally. Thus, we have to redeclare
// @ModelAttribute locally as well.
@ModelAttribute("attr2")
public Date handle(@RequestHeader("header2") Date date, Model model) throws Exception {
return date;
}
@Override
public String handleException(Exception exception) {
return exception.getMessage();
}
}
@RequestMapping
interface MappingGenericInterface<A, B, C> {
@InitBinder
void initBinder(WebDataBinder dataBinder, A thePattern);
@ModelAttribute
void initModel(B date, Model model);
@RequestMapping(value="/path1/path2", method=RequestMethod.POST)
@ModelAttribute("attr2")
Date handle(C date, Model model) throws Exception;
@ExceptionHandler(Exception.class)
@ResponseBody
String handleException(Exception exception);
}
/**
* CONTROLLER WITH PARAMETERIZED INTERFACE
* <p>All annotations can be on interface except parameter annotations.
* <p>Cannot be used as JDK dynamic proxy since parameterized interface does not contain type information.
*/
@Controller
static class ParameterizedInterfaceController implements MappingGenericInterface<String, Date, Date> {
@Override
@InitBinder
public void initBinder(WebDataBinder dataBinder, @RequestParam("datePattern") String thePattern) {
CustomDateEditor dateEditor = new CustomDateEditor(new SimpleDateFormat(thePattern), false);
dataBinder.registerCustomEditor(Date.class, dateEditor);
}
@Override
@ModelAttribute
public void initModel(@RequestHeader("header1") Date date, Model model) {
model.addAttribute("attr1", date);
}
@Override
@RequestMapping(value="/path1/path2", method=RequestMethod.POST)
@ModelAttribute("attr2")
public Date handle(@RequestHeader("header2") Date date, Model model) throws Exception {
return date;
}
@Override
@ExceptionHandler(Exception.class)
@ResponseBody
public String handleException(Exception exception) {
return exception.getMessage();
}
}
/**
* SPR-8248
* <p>Support class contains all annotations. Subclass has type-level @{@link RequestMapping}.
*/
@Controller
static class MappingSupportClass {
@InitBinder
public void initBinder(WebDataBinder dataBinder, @RequestParam("datePattern") String thePattern) {
CustomDateEditor dateEditor = new CustomDateEditor(new SimpleDateFormat(thePattern), false);
dataBinder.registerCustomEditor(Date.class, dateEditor);
}
@ModelAttribute
public void initModel(@RequestHeader("header1") Date date, Model model) {
model.addAttribute("attr1", date);
}
@RequestMapping(value="/path2", method=RequestMethod.POST)
@ModelAttribute("attr2")
public Date handle(@RequestHeader("header2") Date date, Model model) throws Exception {
return date;
}
@ExceptionHandler(Exception.class)
@ResponseBody
public String handleException(Exception exception) {
return exception.getMessage();
}
}
@Controller
@RequestMapping("/path1")
static class SupportClassController extends MappingSupportClass {
}
@SuppressWarnings("serial")
static class ControllerAdvisor extends DefaultPointcutAdvisor {
public ControllerAdvisor() {
super(getControllerPointcut(), new SimpleTraceInterceptor());
}
private static StaticMethodMatcherPointcut getControllerPointcut() {
return new StaticMethodMatcherPointcut() {
@Override
public boolean matches(Method method, @Nullable Class<?> targetClass) {
return ((AnnotationUtils.findAnnotation(targetClass, Controller.class) != null) ||
(AnnotationUtils.findAnnotation(targetClass, RequestMapping.class) != null));
}
};
}
}
}
相关信息
相关文章
spring AbstractRequestAttributesArgumentResolverTests 源码
spring AbstractServletHandlerMethodTests 源码
spring DeferredResultReturnValueHandlerTests 源码
spring ExceptionHandlerExceptionResolverTests 源码
spring ExtendedServletRequestDataBinderTests 源码
spring HttpEntityMethodProcessorMockTests 源码
spring HttpEntityMethodProcessorTests 源码
0
赞
热门推荐
-
2、 - 优质文章
-
3、 gate.io
-
8、 golang
-
9、 openharmony
-
10、 Vue中input框自动聚焦