Description
Robert Thornton opened SPR-16288 and commented
I've upgraded my project from Spring Boot 1.5.8.RELEASE (using Spring Framework 4.3.12) to 1.5.9.RELEASE (using Spring Framework 4.3.13), and I am now getting an ambiguous mapping error on startup.
Caused by: java.lang.IllegalStateException: Ambiguous mapping. Cannot map 'articleController' method
public org.springframework.data.domain.Page demo.ArticleController.find(org.springframework.data.domain.Pageable,demo.EntityPredicate) throws java.io.IOException
to {[/v1/articles],methods=[GET],params=[page]}: There is already 'articleController' bean method
public org.springframework.data.domain.Page<demo.Article> demo.ArticleController.find(org.springframework.data.domain.Pageable,demo.ArticlePredicate) mapped.
at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$MappingRegistry.assertUniqueMethodMapping(AbstractHandlerMethodMapping.java:576) ~[spring-webmvc-4.3.13.RELEASE.jar:4.3.13.RELEASE]
at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$MappingRegistry.register(AbstractHandlerMethodMapping.java:540) ~[spring-webmvc-4.3.13.RELEASE.jar:4.3.13.RELEASE]
at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.registerHandlerMethod(AbstractHandlerMethodMapping.java:264) ~[spring-webmvc-4.3.13.RELEASE.jar:4.3.13.RELEASE]
at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.detectHandlerMethods(AbstractHandlerMethodMapping.java:250) ~[spring-webmvc-4.3.13.RELEASE.jar:4.3.13.RELEASE]
at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.initHandlerMethods(AbstractHandlerMethodMapping.java:214) ~[spring-webmvc-4.3.13.RELEASE.jar:4.3.13.RELEASE]
at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.afterPropertiesSet(AbstractHandlerMethodMapping.java:184) ~[spring-webmvc-4.3.13.RELEASE.jar:4.3.13.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping.afterPropertiesSet(RequestMappingHandlerMapping.java:127) ~[spring-webmvc-4.3.13.RELEASE.jar:4.3.13.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1687) ~[spring-beans-4.3.13.RELEASE.jar:4.3.13.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1624) ~[spring-beans-4.3.13.RELEASE.jar:4.3.13.RELEASE]
I've isolated the cause and simplified it to the following sample code that reproduces the issue:
package demo;
import java.io.IOException;
import java.time.Instant;
import java.util.List;
import java.util.UUID;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import static demo.ApiConstants.ARTICLES_PATH;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
@RestController
@RequestMapping(path = ARTICLES_PATH)
class ArticleController implements ApiConstants, ResourceEndpoint<Article, ArticlePredicate> {
@GetMapping(params = "page")
public Page<Article> find(Pageable pageable, ArticlePredicate predicate) {
throw new UnsupportedOperationException("not implemented");
}
@GetMapping
public List<Article> find(Sort sort, ArticlePredicate predicate) {
throw new UnsupportedOperationException("not implemented");
}
}
interface ApiConstants {
String API_V1 = "/v1";
String ARTICLES_PATH = API_V1 + "/articles";
}
interface ResourceEndpoint<E extends Entity, P extends EntityPredicate> {
Page<E> find(Pageable pageable, P predicate) throws IOException;
List<E> find(Sort sort, P predicate) throws IOException;
}
abstract class Entity {
public UUID id;
public String createdBy;
public Instant createdDate;
}
class Article extends Entity {
public String slug;
public String title;
public String content;
}
abstract class EntityPredicate<E extends Entity> {
public String createdBy;
public Instant createdBefore;
public Instant createdAfter;
public boolean accept(E entity) {
return (createdBy == null || createdBy.equals(entity.createdBy)) &&
(createdBefore == null || createdBefore.compareTo(entity.createdDate) >= 0) &&
(createdAfter == null || createdAfter.compareTo(entity.createdDate) >= 0);
}
}
class ArticlePredicate extends EntityPredicate<Article> {
public String query;
@Override
public boolean accept(Article entity) {
return super.accept(entity) && (query == null || (entity.title.contains(query) || entity.content.contains(query)));
}
}
The ArticleController
class implements two interfaces: ApiConstants
, and ResourceEndpoint
. The ResourceEndpoint
interface is a generic interface that accepts type parameters for the resource entity and a predicate. If I remove the ApiConstants
interface, the error goes away, suggesting that Spring MVC may be confused when the controller implements more than one interface. I've verified that the above code still works on Spring Boot 1.5.8 (using Spring Framework 4.3.12)
The above sample code doesn't demonstrate why I'm using the two interfaces. Its intent is simply to demonstrate the regression in functionality.
Affects: 4.3.13
Issue Links:
- Incorrectly identify bridged method on interface [SPR-16103] #20651 Incorrectly identify bridged method on interface
Referenced from: pull request #1632, and commits 347c2da, 69c882c, 121f9e3
Backported to: 4.3.14