From 3600644ed1776dce35c4a42d74799a90b90e359e Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Tue, 14 Dec 2021 07:09:08 +0000 Subject: [PATCH] Require type-level @Controller annotation Closes gh-22154 --- .../RequestMappingHandlerMapping.java | 3 +-- .../RequestMappingHandlerMapping.java | 3 +-- ...HandlerMethodAnnotationDetectionTests.java | 6 +++-- .../MvcUriComponentsBuilderTests.java | 14 ++++++++--- src/docs/asciidoc/web/webflux.adoc | 23 +++++++++++++++++++ src/docs/asciidoc/web/webmvc.adoc | 20 ++++++++++------ 6 files changed, 53 insertions(+), 16 deletions(-) diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerMapping.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerMapping.java index e58948a0486..ad9335ecc2f 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerMapping.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerMapping.java @@ -133,8 +133,7 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi */ @Override protected boolean isHandler(Class beanType) { - return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) || - AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class)); + return AnnotatedElementUtils.hasAnnotation(beanType, Controller.class); } /** diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java index bfb424d433f..e896bd25a1d 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java @@ -266,8 +266,7 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi */ @Override protected boolean isHandler(Class beanType) { - return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) || - AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class)); + return AnnotatedElementUtils.hasAnnotation(beanType, Controller.class); } /** diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HandlerMethodAnnotationDetectionTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HandlerMethodAnnotationDetectionTests.java index e0b9c866a78..9854477d73b 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HandlerMethodAnnotationDetectionTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HandlerMethodAnnotationDetectionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2021 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. @@ -78,7 +78,7 @@ class HandlerMethodAnnotationDetectionTests { // { ParameterizedSubclassDoesNotOverrideConcreteImplementationsFromGenericAbstractSuperclass.class, true }, // CGLIB proxy // { ParameterizedSubclassDoesNotOverrideConcreteImplementationsFromGenericAbstractSuperclass.class, false }, - { InterfaceController.class, true }, // JDK dynamic proxy + // { InterfaceController.class, true }, // JDK dynamic proxy (gh-22154: no longer supported)) { InterfaceController.class, false }, { ParameterizedInterfaceController.class, false }, // no AOP @@ -250,6 +250,7 @@ class HandlerMethodAnnotationDetectionTests { *

JDK Dynamic proxy: All annotations must be on the interface. *

Without AOP: Annotations can be on interface methods except parameter annotations. */ + @Controller static class InterfaceController implements MappingInterface { @Override @@ -443,6 +444,7 @@ class HandlerMethodAnnotationDetectionTests { *

All annotations can be on interface except parameter annotations. *

Cannot be used as JDK dynamic proxy since parameterized interface does not contain type information. */ + @Controller static class ParameterizedInterfaceController implements MappingGenericInterface { @Override diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilderTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilderTests.java index bce9d4f1a6b..dc49c0a539a 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilderTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilderTests.java @@ -77,6 +77,7 @@ import static org.springframework.web.servlet.mvc.method.annotation.MvcUriCompon * @author Rossen Stoyanchev * @author Sam Brannen */ +@SuppressWarnings("unused") public class MvcUriComponentsBuilderTests { private final MockHttpServletRequest request = new MockHttpServletRequest(); @@ -455,7 +456,8 @@ public class MvcUriComponentsBuilderTests { this.request.setServerPort(9999); this.request.setContextPath("/base"); - assertThat(fromController(PersonsAddressesController.class).buildAndExpand("123").toString()).isEqualTo("https://example.org:9999/base/api/people/123/addresses"); + assertThat(fromController(PersonsAddressesController.class).buildAndExpand("123").toString()) + .isEqualTo("https://example.org:9999/base/api/people/123/addresses"); } @Test @@ -468,8 +470,12 @@ public class MvcUriComponentsBuilderTests { this.request.setServerPort(9999); this.request.setContextPath("/base"); - assertThat(fromMethodCall(on(PersonsAddressesController.class).getAddressesForCountry("DE")) - .buildAndExpand("123").toString()).isEqualTo("https://example.org:9999/base/api/people/123/addresses/DE"); + String url = fromMethodCall(on(PersonsAddressesController.class) + .getAddressesForCountry("DE")) + .buildAndExpand("123") + .toString(); + + assertThat(url).isEqualTo("https://example.org:9999/base/api/people/123/addresses/DE"); } private void initWebApplicationContext(Class configClass) { @@ -500,6 +506,7 @@ public class MvcUriComponentsBuilderTests { } + @Controller @RequestMapping("/people/{id}/addresses") static class PersonsAddressesController { @@ -509,6 +516,7 @@ public class MvcUriComponentsBuilderTests { } } + @Controller @RequestMapping({"people"}) static class PathWithoutLeadingSlashController { diff --git a/src/docs/asciidoc/web/webflux.adoc b/src/docs/asciidoc/web/webflux.adoc index ac10e816b80..3c65f735914 100644 --- a/src/docs/asciidoc/web/webflux.adoc +++ b/src/docs/asciidoc/web/webflux.adoc @@ -1348,6 +1348,29 @@ directly to the response body versus view resolution and rendering with an HTML +[[webflux-ann-requestmapping-proxying]] +==== AOP Proxies +[.small]#<># + +In some cases, you may need to decorate a controller with an AOP proxy at runtime. +One example is if you choose to have `@Transactional` annotations directly on the +controller. When this is the case, for controllers specifically, we recommend +using class-based proxying. This is automatically the case with such annotations +directly on the controller. + +If the controller implements an interface, and needs AOP proxying, you may need to +explicitly configure class-based proxying. For example, with `@EnableTransactionManagement` +you can change to `@EnableTransactionManagement(proxyTargetClass = true)`, and with +`` you can change to ``. + +NOTE: Keep in mind that as of 6.0, with interface proxying, Spring MVC no longer detects +controllers based solely on a type-level `@RequestMapping` annotation on the interface. +Please, enable class based proxying, or otherwise the interface must also have an +`@Controller` annotation. + + + + [[webflux-ann-requestmapping]] === Request Mapping [.small]#<># diff --git a/src/docs/asciidoc/web/webmvc.adoc b/src/docs/asciidoc/web/webmvc.adoc index e0237b498d7..7dc7df941dc 100644 --- a/src/docs/asciidoc/web/webmvc.adoc +++ b/src/docs/asciidoc/web/webmvc.adoc @@ -1512,17 +1512,23 @@ directly to the response body versus view resolution and rendering with an HTML [[mvc-ann-requestmapping-proxying]] ==== AOP Proxies +[.small]#<># In some cases, you may need to decorate a controller with an AOP proxy at runtime. One example is if you choose to have `@Transactional` annotations directly on the controller. When this is the case, for controllers specifically, we recommend -using class-based proxying. This is typically the default choice with controllers. -However, if a controller must implement an interface that is not a Spring Context -callback (such as `InitializingBean`, `*Aware`, and others), you may need to explicitly -configure class-based proxying. For example, with `` you can -change to ``, and with -`@EnableTransactionManagement` you can change to -`@EnableTransactionManagement(proxyTargetClass = true)`. +using class-based proxying. This is automatically the case with such annotations +directly on the controller. + +If the controller implements an interface, and needs AOP proxying, you may need to +explicitly configure class-based proxying. For example, with `@EnableTransactionManagement` +you can change to `@EnableTransactionManagement(proxyTargetClass = true)`, and with +`` you can change to ``. + +NOTE: Keep in mind that as of 6.0, with interface proxying, Spring MVC no longer detects +controllers based solely on a type-level `@RequestMapping` annotation on the interface. +Please, enable class based proxying, or otherwise the interface must also have an +`@Controller` annotation.