Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ambiguous mapping error when using generic interface [SPR-16288] #20835

Closed
spring-projects-issues opened this issue Dec 11, 2017 · 3 comments
Closed
Assignees
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) status: backported An issue that has been backported to maintenance branches type: regression A bug that is also a regression
Milestone

Comments

@spring-projects-issues
Copy link
Collaborator

spring-projects-issues commented Dec 11, 2017

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:

Referenced from: pull request #1632, and commits 347c2da, 69c882c, 121f9e3

Backported to: 4.3.14

@spring-projects-issues
Copy link
Collaborator Author

spring-projects-issues commented Dec 12, 2017

Brian Clozel commented

Hi Juergen Hoeller

I looked at this issue and it seems that there's a behaviour change between Spring Framework 4.3.12 and 4.3.13 on this. More specifically, MVC detects an additional Controller method in 4.3.13.

In AbstractHandlerMethodMapping::detectHandlerMethods, the MethodIntrospector.selectMethods call finds:

"public org.springframework.data.domain.Page com.example.demo.ArticleController.find(org.springframework.data.domain.Pageable,com.example.demo.ArticlePredicate)" -> "{[/v1/articles],methods=[GET],params=[page]}"
"public org.springframework.data.domain.Page com.example.demo.ArticleController.find(org.springframework.data.domain.Pageable,com.example.demo.EntityPredicate) throws java.io.IOException" -> "{[/v1/articles],methods=[GET],params=[page]}"

At the compilation level, the compiler assumes one implements the other and doesn't complain.

Is this somehow linked to #20651 ?

@spring-projects-issues
Copy link
Collaborator Author

spring-projects-issues commented Dec 12, 2017

Juergen Hoeller commented

This could indeed be a side effect of #20651. I'll sort this out for 4.3.14.

@spring-projects-issues
Copy link
Collaborator Author

Juergen Hoeller commented

This turned out to be a rather stupid bug in BridgeMethodResolver where our search loop effectively short-circuited the iteration over more than one interface. Fixed for 5.0.3 now, and to be backported to 4.3.14 ASAP.

@spring-projects-issues spring-projects-issues added type: regression A bug that is also a regression status: backported An issue that has been backported to maintenance branches in: web Issues in web modules (web, webmvc, webflux, websocket) labels Jan 11, 2019
@spring-projects-issues spring-projects-issues added this to the 5.0.3 milestone Jan 11, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) status: backported An issue that has been backported to maintenance branches type: regression A bug that is also a regression
Projects
None yet
Development

No branches or pull requests

2 participants