Skip to content

Commit a6de3b1

Browse files
committed
Prevent duplicate controller registrations through class-level @RequestMapping.
When we detected @BasePathAwareController and @RepositoryRestController instances, we now reject types that use @RequestMapping on the class level as doing so causes an inevitable registration of the controller with Spring MVC. Fixes #1342, #1628, #1686, #1946.
1 parent 2e1223c commit a6de3b1

File tree

4 files changed

+59
-12
lines changed

4 files changed

+59
-12
lines changed

spring-data-rest-tests/spring-data-rest-tests-jpa/src/test/java/org/springframework/data/rest/webmvc/jpa/JpaRepositoryConfig.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,9 @@ void someMethod(@PathVariable String id) {}
5959
}
6060

6161
@RepositoryRestController
62-
@RequestMapping("orders")
6362
static class OrdersJsonController {
6463

65-
@RequestMapping(value = "/search/sort", method = RequestMethod.POST, produces = "application/hal+json")
64+
@RequestMapping(value = "/orders/search/sort", method = RequestMethod.POST, produces = "application/hal+json")
6665
void someMethodWithArgs(Sort sort, Pageable pageable, DefaultedPageable defaultedPageable) {}
6766
}
6867
}

spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/BasePathAwareHandlerMapping.java

+28-2
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,14 @@
2828
import javax.servlet.http.HttpServletRequest;
2929
import javax.servlet.http.HttpServletRequestWrapper;
3030

31+
import org.springframework.core.annotation.AnnotatedElementUtils;
3132
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
3233
import org.springframework.data.util.ProxyUtils;
3334
import org.springframework.http.HttpHeaders;
3435
import org.springframework.http.MediaType;
3536
import org.springframework.util.Assert;
3637
import org.springframework.util.StringUtils;
38+
import org.springframework.web.bind.annotation.RequestMapping;
3739
import org.springframework.web.method.HandlerMethod;
3840
import org.springframework.web.servlet.mvc.condition.ProducesRequestCondition;
3941
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
@@ -46,6 +48,7 @@
4648
*/
4749
public class BasePathAwareHandlerMapping extends RequestMappingHandlerMapping {
4850

51+
private static final String AT_REQUEST_MAPPING_ON_TYPE = "Spring Data REST controller %s must not use @RequestMapping on class level as this would cause double registration with Spring MVC!";
4952
private final RepositoryRestConfiguration configuration;
5053

5154
/**
@@ -140,15 +143,38 @@ protected ProducesRequestCondition customize(ProducesRequestCondition condition)
140143
return condition;
141144
}
142145

143-
/*
144-
* (non-Javadoc)
146+
/**
147+
* Returns whether the given type is considered a handler. Performs additional configuration checks. If you only want
148+
* to customize the handler selection criteria, override {@link #isHandlerInternal(Class)}. Will be made final in 4.0.
149+
*
145150
* @see org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#isHandler(java.lang.Class)
151+
* @deprecated for overriding in 3.6. Will be made final in 4.0.
146152
*/
147153
@Override
154+
@Deprecated
148155
protected boolean isHandler(Class<?> beanType) {
149156

150157
Class<?> type = ProxyUtils.getUserClass(beanType);
158+
boolean isSpringDataRestHandler = isHandlerInternal(type);
159+
160+
if (!isSpringDataRestHandler) {
161+
return false;
162+
}
163+
164+
if (AnnotatedElementUtils.hasAnnotation(type, RequestMapping.class)) {
165+
throw new IllegalStateException(String.format(AT_REQUEST_MAPPING_ON_TYPE, beanType.getName()));
166+
}
167+
168+
return isSpringDataRestHandler;
169+
}
151170

171+
/**
172+
* Returns whether the given controller type is considered a handler.
173+
*
174+
* @param type will never be {@literal null}.
175+
* @return
176+
*/
177+
protected boolean isHandlerInternal(Class<?> type) {
152178
return type.isAnnotationPresent(BasePathAwareController.class);
153179
}
154180

spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestHandlerMapping.java

+3-8
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,12 @@
2525
import javax.servlet.http.HttpServletRequest;
2626

2727
import org.springframework.core.annotation.AnnotatedElementUtils;
28-
import org.springframework.core.annotation.AnnotationUtils;
2928
import org.springframework.data.repository.support.Repositories;
3029
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
3130
import org.springframework.data.rest.core.mapping.HttpMethods;
3231
import org.springframework.data.rest.core.mapping.ResourceMappings;
3332
import org.springframework.data.rest.core.mapping.ResourceMetadata;
3433
import org.springframework.data.rest.webmvc.support.JpaHelper;
35-
import org.springframework.data.util.ProxyUtils;
3634
import org.springframework.data.util.Streamable;
3735
import org.springframework.hateoas.MediaTypes;
3836
import org.springframework.http.HttpMethod;
@@ -182,14 +180,11 @@ protected HandlerMethod handleNoMatch(Set<RequestMappingInfo> requestMappingInfo
182180

183181
/*
184182
* (non-Javadoc)
185-
* @see org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#isHandler(java.lang.Class)
183+
* @see org.springframework.data.rest.webmvc.BasePathAwareHandlerMapping#isHandlerInternal(java.lang.Class)
186184
*/
187185
@Override
188-
protected boolean isHandler(Class<?> beanType) {
189-
190-
Class<?> type = ProxyUtils.getUserClass(beanType);
191-
192-
return AnnotationUtils.findAnnotation(type, RepositoryRestController.class) != null;
186+
protected boolean isHandlerInternal(Class<?> type) {
187+
return AnnotatedElementUtils.hasAnnotation(type, RepositoryRestController.class);
193188
}
194189

195190
/*

spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/BasePathAwareHandlerMappingUnitTests.java

+27
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
import org.springframework.aop.framework.ProxyFactory;
2626
import org.springframework.aop.support.AopUtils;
2727
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
28+
import org.springframework.stereotype.Controller;
29+
import org.springframework.web.bind.annotation.RequestMapping;
2830

2931
/**
3032
* Unit tests for {@link BasePathAwareHandlerMapping}.
@@ -60,6 +62,23 @@ public void doesNotConsiderMetaAnnotation() {
6062
assertThat(mapping.isHandler(type)).isFalse();
6163
}
6264

65+
@Test // #1342, #1628, #1686, #1946
66+
public void rejectsAtRequestMappingOnCustomController() {
67+
68+
assertThatIllegalStateException()
69+
.isThrownBy(() -> {
70+
mapping.isHandler(InvalidController.class);
71+
})
72+
.withMessageContaining(InvalidController.class.getName());
73+
}
74+
75+
@Test // #1342, #1628, #1686, #1946
76+
public void doesNotRejectAtRequestMappingOnStandardMvcController() {
77+
78+
assertThatNoException()
79+
.isThrownBy(() -> mapping.isHandler(ValidController.class));
80+
}
81+
6382
private static Class<?> createProxy(Object source) {
6483

6584
ProxyFactory factory = new ProxyFactory(source);
@@ -87,4 +106,12 @@ public boolean isHandler(Class<?> beanType) {
87106
return super.isHandler(beanType);
88107
}
89108
}
109+
110+
@BasePathAwareController
111+
@RequestMapping("/sample")
112+
static class InvalidController {}
113+
114+
@Controller
115+
@RequestMapping("/sample")
116+
static class ValidController {}
90117
}

0 commit comments

Comments
 (0)