spring HandlerMethodAnnotationDetectionTests 源码

  • 2022-08-08
  • 浏览 (316)

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 源码目录

相关文章

spring AbstractRequestAttributesArgumentResolverTests 源码

spring AbstractServletHandlerMethodTests 源码

spring CrossOriginTests 源码

spring DeferredResultReturnValueHandlerTests 源码

spring ExceptionHandlerExceptionResolverTests 源码

spring ExtendedServletRequestDataBinderTests 源码

spring HttpEntityMethodProcessorMockTests 源码

spring HttpEntityMethodProcessorTests 源码

spring MatrixVariablesMapMethodArgumentResolverTests 源码

spring MatrixVariablesMethodArgumentResolverTests 源码

0  赞