解决@FeignClient的地址被映射成Mapping地址

 

前端时间打算将FeignClient进行服务调用的接口类抽取成独立的模块

发生报错后看了一遍SpringMVC的初始化源码后解决问题

过程比较清晰觉得有必要记录一下

项目情况:

项目API模块

A项目 Controller实现API模块的接口

B项目 FeignClient继承API模块的接口

这样子A项目的Controller与B项目的FeignClient方法就通过 API模块的接口达成了一致

如图

需求

我这时候有一个C服务也需要调用A服务的Controller

那就需要把B服务的FeignClient接口复制一份到C服务中使用

问题

当我需要用A服务的接口时, 我就要去其他服务找找有没有继承好的接口复制过来用.

感觉复用性不高,重复性动作无意义.还不如把FeignClient也放到API模块中大家一起用,省的到处复制粘贴

报错

当我把FeignClient丢到API模块后出现报错

Caused by: java.lang.IllegalStateException: Ambiguous mapping. Cannot map 'com.xxx.remote.market.MarketEntryClient' method 
public abstract com.xxx.XHResult<x> com.xxx.MarketEntryService.findAll()
to {[/market/entry/findAll],methods=[GET]}: There is already 'marketEntryController' bean method

大意就是Maping路径对应的Bean方法已经存在, 意思就是URL路径重复注册了.

验证

从API模块中删除FeignClient接口就不会报错, 问题点就在于FeignClient接口

寻找报错位置:

通过到springMVC源码中搜索报错的关键字 There is already

位置org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.MappingRegistry#assertUniqueMethodMapping

可以看到是register方法中调用的这个验证,

alt+f7找到向上找register的调用源头

 

调用来源org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#detectHandlerMethods

中调用的registerHandlerMethod再调用的register方法

而register方法中的Mapping参数在上层叫做handler,由更上层提供

那就继续向上找

源头就在这,这里名字叫做beanName

if (beanType != null && isHandler(beanType)) {
   detectHandlerMethods(beanName);
}

我在这里打下断点重新启动项目

这里FeignClient接口竟然被当做Handler类调用注册了

Controler的方法在这之前已经注册过,这里FeignClient的方法再次注册肯定出问题

看到在调用detectHandlerMethods方法前有一个isHandler(beanType)的判断,跟进

@Override
protected boolean isHandler(Class<?> beanType) {
   return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
         AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}

从源码上可以看到只要带有@Controller或@RequestMapping注解的方法都会返回true

正好我们的FeignClient接口是继承带有@RequestMapping注解的接口所以也会返回true

这里从网上查了资料可以通过覆写isHandler方法来排除@FeignClient注解的方法

在springBoot 2.x中有两种方式(都是通过继承RequestMappingHandlerMapping覆写isHandler方法), 

完整代码

第一种方式

@Configuration
@ConditionalOnClass({Feign.class})
public class FeignConfig extends WebMvcConfigurationSupport {

    @Override
    @Nullable
    public RequestMappingHandlerMapping createRequestMappingHandlerMapping() {
        return new FeignRequestMappingHandlerMapping();
    }

    private static class FeignRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
        @Override
        protected boolean isHandler(Class<?> beanType) {
            return super.isHandler(beanType) &&
                    !AnnotatedElementUtils.hasAnnotation(beanType, FeignClient.class);
        }
    }
}

这种写法会让WebMvcAutoConfiguration失效(SpringBoot自动装配MVC配置的类)

当项目中有WebMvcConfigurationSupport的类就不会初始化

第二种方式

@Configuration
@ConditionalOnClass({Feign.class})
public class FeignConfig implements WebMvcRegistrations {

    private RequestMappingHandlerMapping requestMappingHandlerMapping = new FeignRequestMappingHandlerMapping();

    @Override
    @Nullable
    public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
        return requestMappingHandlerMapping;
    }

    private static class FeignRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
        @Override
        protected boolean isHandler(Class<?> beanType) {
            return super.isHandler(beanType) &&
                    !AnnotatedElementUtils.hasAnnotation(beanType, FeignClient.class);
        }
    }
}

这种方法WebMvcAutoConfiguration会生效

会装载WebMvcAutoConfiguration里的

@Configuration
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration {

在构造方法中就会将实现WebMvcRegistrations接口的类传入

在调用createRequestMappingHandlerMapping的时候就可以把我们自定义的RequestMappingHandlerMapping载入

这两种方法在我们项目中实测中都没什么问题, 可以完美的排除带有FeignClient的接口方法

 

一些细节

第一种方式是通过继承WebMvcConfigurationSupport覆写createRequestMappingHandlerMapping方法

实现的自定义RequestMappingHandlerMapping, 在源码上也有说明,允许用户自定义

第二种方法是依赖于SpringBoot默认自动自动配置的方式插入的

其实EnableWebMvcConfiguration继承的DelegatingWebMvcConfiguration上游也是继承的WebMvcConfigurationSupport.

如果你项目用了@EnableWebMvc注解

配置类也是DelegatingWebMvcConfiguration

WebMvcConfigurationSupport类上注释原话

This is the main class providing the configuration behind the MVC Java config.

表示这个类是SpringMVC配置的核心

 

补充:

使用第一种方式之后项目引入静态资源放在rescoure\static目录,会出现无法映射的情况(错误404)

原因是实现了WebMvcConfigurationSupport会让SpringBoot默认的静态资源配置不生效

解决

实现addResourceHandlers方法即可.

如何实现:

照抄SpringBoot默认实现代码

位置WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter#addResourceHandlers

registry.addResourceHandler(staticPathPattern)
        .addResourceLocations(getResourceLocations(
                this.resourceProperties.getStaticLocations()))
        .setCachePeriod(getSeconds(cachePeriod))
        .setCacheControl(cacheControl);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章