Spring-cloud-netflix: Feign + interface inheritance + fallback => Ambiguous mapping

Created on 24 Jan 2018  ·  14Comments  ·  Source: spring-cloud/spring-cloud-netflix

Trying to combine to elements from the documentation :

I'm facing IllegalStateException: Ambiguous mapping. Cannot map at springBoot start

According to my understanding it is both good pratices

Build on gradle with
SpringBoot version : 1.5.9.RELEASE
SpringCloud version : Edgware.SR1

Case :

  • One Api interface in a dedicated gradle module
@RequestMapping("/api/student")
public interface StudentAPI {

    @GetMapping("/options/names")
    List<String> optionNames();
}
  • One Backend module exposing a rest controller in SpringBoot
@RestController
public class StudentApiImpl implements StudentAPI { ...}
  • One edge SpringBoot application consuming Backend using Feign + Fallback
@Controller
@RequestMapping("/web/student")
public class StudentController {
    private static final Logger LOG = LoggerFactory.getLogger(StudentController.class);

    @Autowired
    private StudentApiClient apiClient;

    @GetMapping(path = "/notes/{option}")
    public String displayStudentNotesForOption(@PathVariable String option, Model model) {
}

@FeignClient(name = "jacademie-student-server", fallback = StudentApiClientFallback.class)
public interface StudentApiClient extends StudentAPI { }

@Component
public class StudentApiClientFallback implements StudentApiClient {
...
}


feign.hystrix.enabled: true

Error is :

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'requestMappingHandlerMapping' defined in class path resource [org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]: Invocation of init method failed; nested exception is java.lang.IllegalStateException: Ambiguous mapping. Cannot map 'fr.tse.jacademie.springBoot.student.client.StudentApiClient' method 
public abstract java.util.List<java.lang.String> fr.tse.jacademie.springBoot.student.StudentAPI.optionNames()
to {[/api//student/options/names],methods=[GET]}: There is already 'studentApiClientFallback' bean method
public java.util.List<java.lang.String> fr.tse.jacademie.springBoot.student.client.StudentApiClientFallback.optionNames() mapped.
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1628) ~[spring-beans-4.3.13.RELEASE.jar:4.3.13.RELEASE]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:555) ~[spring-beans-4.3.13.RELEASE.jar:4.3.13.RELEASE]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483) ~[spring-beans-4.3.13.RELEASE.jar:4.3.13.RELEASE]
        at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306) ~[spring-beans-4.3.13.RELEASE.jar:4.3.13.RELEASE]
        at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230) ~[spring-beans-4.3.13.RELEASE.jar:4.3.13.RELEASE]
        at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302) ~[spring-beans-4.3.13.RELEASE.jar:4.3.13.RELEASE]
        at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197) ~[spring-beans-4.3.13.RELEASE.jar:4.3.13.RELEASE]
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:761) ~[spring-beans-4.3.13.RELEASE.jar:4.3.13.RELEASE]
        at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:867) ~[spring-context-4.3.13.RELEASE.jar:4.3.13.RELEASE]
        at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:543) ~[spring-context-4.3.13.RELEASE.jar:4.3.13.RELEASE]
        at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:122) ~[spring-boot-1.5.9.RELEASE.jar:1.5.9.RELEASE]
        at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:693) [spring-boot-1.5.9.RELEASE.jar:1.5.9.RELEASE]
        at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:360) [spring-boot-1.5.9.RELEASE.jar:1.5.9.RELEASE]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:303) [spring-boot-1.5.9.RELEASE.jar:1.5.9.RELEASE]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1118) [spring-boot-1.5.9.RELEASE.jar:1.5.9.RELEASE]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1107) [spring-boot-1.5.9.RELEASE.jar:1.5.9.RELEASE]
        at fr.tse.jacademie.springBoot.student.client.StudentClientApplication.main(StudentClientApplication.java:17) [main/:na]
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_151]
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_151]
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_151]
        at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_151]
        at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49) [spring-boot-devtools-1.5.9.RELEASE.jar:1.5.9.RELEASE]
Caused by: java.lang.IllegalStateException: Ambiguous mapping. Cannot map 'fr.tse.jacademie.springBoot.student.client.StudentApiClient' method 
public abstract java.util.List<java.lang.String> fr.tse.jacademie.springBoot.student.StudentAPI.optionNames()
to {[/api//student/options/names],methods=[GET]}: There is already 'studentApiClientFallback' bean method
public java.util.List<java.lang.String> fr.tse.jacademie.springBoot.student.client.StudentApiClientFallback.optionNames() 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]
        ... 21 common frames omitted

Can't find any documentation pointing me the missing pieces ... or what I'm doing wrong.
So I think it is a bug

Most helpful comment

I met the same problem today. But my feign client declaration code is like this:

@FeignClient(value = "aaa", fallback = ClientFallBack.class)
//@RequestMapping("/aaa/bbb") // <-- THE EVIL CODE
public interface Client {
    @RequestMapping(value = "/ccc", method = RequestMethod.GET)
    @ResponseBody
    public Response function(@RequestParam("ddd") int a);
}

@Component
class ClientFallBack implements Client {
    @Override
    public Response function(int a) {
        return null;
    }
}

the "EVIL CODE" line is the key. If we have that line uncommented, the described exception is thrown. If it's not, everything is OK.

Not sure it's the same thing @smougenot is facing. But it's worth mentioning.

All 14 comments

Would it be possible to provide a complete project that reproduces the problem?

ASAP I'll copy paste those sample code into a code archive with build config

@smougenot Hi, maybe you can use @FeignClient(fallback = XxxxFactory.class) instead.

here is my code for example. Sorry, it's scala code, but I am sure it's easy to you.

import com.github.yingzhuo.service.api.{EchoClient => ApiEchoClient}
import org.springframework.cloud.netflix.feign.FeignClient

@FeignClient(name = "node-service-provider", fallbackFactory = classOf[EchoClientFallbackFactory])
trait EchoClient extends ApiEchoClient
import feign.hystrix.FallbackFactory
import org.springframework.stereotype.Component

@Component
class EchoClientFallbackFactory extends FallbackFactory[EchoClient] {

    override def create(cause: Throwable): EchoClient = _ => "<fallback>"

}

It worked for me.

Closing due to lack of requested feedback. If you would like us to look at this issue, please provide the requested information and we will re-open the issue.

I met the same problem today. But my feign client declaration code is like this:

@FeignClient(value = "aaa", fallback = ClientFallBack.class)
//@RequestMapping("/aaa/bbb") // <-- THE EVIL CODE
public interface Client {
    @RequestMapping(value = "/ccc", method = RequestMethod.GET)
    @ResponseBody
    public Response function(@RequestParam("ddd") int a);
}

@Component
class ClientFallBack implements Client {
    @Override
    public Response function(int a) {
        return null;
    }
}

the "EVIL CODE" line is the key. If we have that line uncommented, the described exception is thrown. If it's not, everything is OK.

Not sure it's the same thing @smougenot is facing. But it's worth mentioning.

Can you provide the complete project that reproduces the problem?

The issue occurs when doing additional request mappings on class/interface level.
I quickly created a minimal working demonstration of this issue.

See: https://github.com/MoritzKrischke/hystrixmappingproblem
I inserted a todo comment on ApiClient where the error occurs.

@MoritzKrischke I cant try to reproduce this because i dont have the "cardealer" service. However I have never seen anyone add a @RequestMapping annotation to a feign client. If you separate the two, I assume you dont see the error right?

Hi @ryanjbaxter, you won´t need the service since the bean wiring fails during startup.

If we remove @RequestMapping on class/interface level the error is gone. As current solution we just merged the @RequestMapping from class/interface level into the declarations made on method level.

requestmapping

The reason why we want add a @RequestMapping is that we make the provider API as a module. So, for the provider, SampleControler implements SampleAPI;

For the consumer, just create an interface SampleClient extend SampleAPI, add an a @FeignClient on it. The consumer do not need write a FeignClient again, just share the same definition from the provider.

For why this error produced, The FeingClient has a @RequestMapping and the fallback has the same @RequestMapping because of the inherent. The key is RequestMappingHandlerMapping bean initialize error.

see
org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.MappingRegistry#register

public void register(T mapping, Object handler, Method method) {
            this.readWriteLock.writeLock().lock();
    try {
        HandlerMethod handlerMethod = createHandlerMethod(handler, method);
        assertUniqueMethodMapping(handlerMethod, mapping);

        if (logger.isInfoEnabled()) {
            logger.info("Mapped \"" + mapping + "\" onto " + handlerMethod);
        }
        this.mappingLookup.put(mapping, handlerMethod);

        List<String> directUrls = getDirectUrls(mapping);
        for (String url : directUrls) {
            this.urlLookup.add(url, mapping);
        }

        String name = null;
        if (getNamingStrategy() != null) {
            name = getNamingStrategy().getName(handlerMethod, mapping);
            addMappingName(name, handlerMethod);
        }

        CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
        if (corsConfig != null) {
            this.corsLookup.put(handlerMethod, corsConfig);
        }

        this.registry.put(mapping, new MappingRegistration<T>(mapping, handlerMethod, directUrls, name));
    }
    finally {
        this.readWriteLock.writeLock().unlock();
    }
}
private void assertUniqueMethodMapping(HandlerMethod newHandlerMethod, T mapping) {
    HandlerMethod handlerMethod = this.mappingLookup.get(mapping);
    if (handlerMethod != null && !handlerMethod.equals(newHandlerMethod)) {
        throw new IllegalStateException(
                "Ambiguous mapping. Cannot map '" + newHandlerMethod.getBean() + "' method \n" +
                newHandlerMethod + "\nto " + mapping + ": There is already '" +
                handlerMethod.getBean() + "' bean method\n" + handlerMethod + " mapped.");
    }
}

I think RequestMappingHandlerMapping scan our FeignClient as an Endpoint.

There is a complete sample:

shared api module:

@RequestMapping("/api/v1/users")
public interface UserApi {

    @GetMapping("/")
    List<UserVo> list();

    @GetMapping("/fallback")
    String fallback();
}

provider-demo

@RestController
public class UserController implements UserApi {

    private List<User> users = Lists.newArrayList(
        new User(1, "谭浩强", 100, LocalDate.now()),
        new User(2, "严蔚敏", 120, LocalDate.now()),
        new User(3, "谭浩强", 100, LocalDate.now()),
        new User(4, "James Gosling", 150, LocalDate.now()),
        new User(6, "Doug Lea", 150, LocalDate.now())
    );

    @Override
    public List<UserVo> list() {
        return users.stream()
            .map(u -> new UserVo(u.getId(), u.getName(), u.getAge(), u.getBirth()))
            .collect(Collectors.toList());
    }

    @Override
    public String fallback() {
        Random random = new Random();
        boolean b = random.nextBoolean();
        if (b){
            return "success.";
        }

        throw new RuntimeException("test fallback.");
    }
}

consumer-demo

@FeignClient( value = "PROVIDER-DEMO", fallback = UserClientFallback.class)
public interface UserClient extends UserApi {
}
@Component
public class UserClientFallback implements UserClient {

    @Override
    public List<UserVo> list() {
        UserVo userVo = new UserVo();
        userVo.setAge(1);
        userVo.setBirth(LocalDate.now());
        userVo.setId(1);
        userVo.setName("fallback");
        return Lists.newArrayList(userVo);
    }

    @Override
    public String fallback() {
        return "this is fallback.";
    }
}

So we can just add a different @RequestMapping on the Fallback-Class

@RequestMapping("/will-never-access-this-endpoint")
public class UserClientFallback implements UserClient {

It is a well known issue that feign clients can not have request mappings on the interface.

I have the same problem.

@cjbi

It is a well known issue that feign clients can not have request mappings on the interface.

I had the same issue and i solved it enabling the Hystrix support in Feign, just adding this property to 'application.properties':

feign.hystrix.enabled = true

(I'm using a FallbackFactory)

I thought with Hystrix on the classpath, this would be automatically enabled, but I was wrong :P
I hope this can help.

Was this page helpful?
0 / 5 - 0 ratings