本以为这是一个很简单的事情,因为之前一直使用他当注册中心,界面看起来也比较友好,翻看官方也有spring cloud文档,万万没想到真用起来就坑了,从下午一直debug加改源码到现在
正常我们使用
@FeignClient(value = "paasUserFacade", path = "/im/user")
public interface IPaasUserFacade extends IBaseController<PaasUserRequestModel> {
}
这些就可以调用了,但是nacos不行,因为注册到nacos上面的之后lemur-paas这个项目的id,并没有passUserFacade的这个所以所以一直报服务找不到,然后就看官方文档
@SpringBootApplication
@EnableDiscoveryClient
public class NacosConsumerApplication {
@LoadBalanced
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(NacosConsumerApplication.class, args);
}
@RestController
public class TestController {
private final RestTemplate restTemplate;
@Autowired
public TestController(RestTemplate restTemplate) {this.restTemplate = restTemplate;}
@RequestMapping(value = "/echo/{str}", method = RequestMethod.GET)
public String echo(@PathVariable String str) {
return restTemplate.getForObject("http://service-provider/echo/" + str, String.class);
}
}
}
这种方案如何行,这不是反人类,然后就进入慢慢的源码寻找之旅
1.以为ribbonRequest 没有设置serviceKey导致的问题,自己set了下依然不行
FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
this.delegate, request, uriWithoutHost);
IClientConfig requestConfig = getClientConfig(options, clientName);
return lbClient(clientName)
.executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();
2.加入contextId 启动不起来因为contextId会作为类的Alise,spring会禁止多个类叫一个名字
@FeignClient(value = "paasUserFacade", contextId = "lemur-paas", path = "/im/user")
public interface IPaasUserFacade extends IBaseController<PaasUserRequestModel> {
}
按照提示加上了
spring:
main:
allow-bean-definition-overriding: true
启动测试,发现依然拿到是 paasUserFacade,此路依然不通
3.查看注册的时候如何获取的类FeignClientsRegistrar
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
this.validate(attributes);
definition.addPropertyValue("url", this.getUrl(attributes));
definition.addPropertyValue("path", this.getPath(attributes));
String name = this.getName(attributes);
definition.addPropertyValue("name", name);
String contextId = this.getContextId(attributes);
definition.addPropertyValue("contextId", contextId);
definition.addPropertyValue("type", className);
definition.addPropertyValue("decode404", attributes.get("decode404"));
definition.addPropertyValue("fallback", attributes.get("fallback"));
definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
definition.setAutowireMode(2);
String alias = contextId + "FeignClient";
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
boolean primary = (Boolean)attributes.get("primary");
beanDefinition.setPrimary(primary);
String qualifier = this.getQualifier(attributes);
if (StringUtils.hasText(qualifier)) {
alias = qualifier;
}
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[]{alias});
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
private void validate(Map<String, Object> attributes) {
AnnotationAttributes annotation = AnnotationAttributes.fromMap(attributes);
validateFallback(annotation.getClass("fallback"));
validateFallbackFactory(annotation.getClass("fallbackFactory"));
}
static void validateFallback(final Class clazz) {
Assert.isTrue(!clazz.isInterface(), "Fallback class must implement the interface annotated by @FeignClient");
}
static void validateFallbackFactory(final Class clazz) {
Assert.isTrue(!clazz.isInterface(), "Fallback factory must produce instances of fallback classes that implement the interface annotated by @FeignClient");
}
String getName(Map<String, Object> attributes) {
String name = (String)attributes.get("serviceId");
if (!StringUtils.hasText(name)) {
name = (String)attributes.get("name");
}
if (!StringUtils.hasText(name)) {
name = (String)attributes.get("value");
}
name = this.resolve(name);
return getName(name);
}
private String getContextId(Map<String, Object> attributes) {
String contextId = (String)attributes.get("contextId");
if (!StringUtils.hasText(contextId)) {
return this.getName(attributes);
} else {
contextId = this.resolve(contextId);
return getName(contextId);
}
}
static String getName(String name) {
if (!StringUtils.hasText(name)) {
return "";
} else {
String host = null;
try {
String url;
if (!name.startsWith("http://") && !name.startsWith("https://")) {
url = "http://" + name;
} else {
url = name;
}
host = (new URI(url)).getHost();
} catch (URISyntaxException var3) {
}
Assert.state(host != null, "Service id not legal hostname (" + name + ")");
return name;
}
}
发现url是用的context但是怎么都是不生效的,然后继续debug,查看到底哪里获取的url
发现在FeignClientFactoryBean 有如下代码
<T> T getTarget() {
FeignContext context = this.applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context);
if (!StringUtils.hasText(this.url)) {
iif (!this.name.startsWith("http")) {
this.url = "http://" + this.name;
} else {
this.url = this.name;
}
this.url += cleanPath();
return (T) loadBalance(builder, context,
new HardCodedTarget<>(this.type, this.name, this.url));
}
if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
this.url = "http://" + this.url;
}
String url = this.url + cleanPath();
Client client = getOptional(context, Client.class);
if (client != null) {
if (client instanceof LoadBalancerFeignClient) {
// not load balancing because we have a url,
// but ribbon is on the classpath, so unwrap
client = ((LoadBalancerFeignClient) client).getDelegate();
}
builder.client(client);
}
Targeter targeter = get(context, Targeter.class);
return (T) targeter.target(this, builder, context,
new HardCodedTarget<>(this.type, this.name, url));
}
这个就决定了我们访问地址的方法 我在if (!StringUtils.hasText(this.url)) { 之后加入了
if (StringUtils.hasText(this.contextId) && !this.name.startsWith("http")){
this.url = "http://" + this.contextId;
}else if (StringUtils.hasText(this.contextId)){
this.url = this.contextId;
}
优先判断contextId,来获取负载的地址,这样就解决了问题,虽然自己改动了源码 ,但依然不nacos官网和各种demo里面的方式好多了,而且等spring哪天兼容了nacos也就把自己改的类删除了就完成了.接口类还是使用原来的就可以了
顺便说一句:url为啥会使用绝对路径,就是因为url非空状态下回返回tartge的直接代理