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-issuemaster opened this issue Dec 11, 2017 · 3 comments

Comments

Projects
None yet
2 participants
@spring-issuemaster
Copy link
Collaborator

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:

  • #20651 Incorrectly identify bridged method on interface

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

Backported to: 4.3.14

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

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-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

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-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Jan 11, 2018

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.