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

dubbo和openfeign一起用报错 #3990

Closed
2 tasks
xia-fei opened this issue May 8, 2019 · 12 comments
Closed
2 tasks

dubbo和openfeign一起用报错 #3990

xia-fei opened this issue May 8, 2019 · 12 comments
Assignees

Comments

@xia-fei
Copy link

xia-fei commented May 8, 2019

  • I have searched the issues of this repository and believe that this is not a duplicate.
  • I have checked the FAQ of this repository and believe that this is not a duplicate.

Environment

  • Dubbo version: 2.7.0
  • Operating System version: windows or linux
  • Java version: 1.8
    spring-cloud-starter-openfeign:2.0.2.RELEASE

Steps to reproduce this issue

  1. 项目 引入openFeign,dubbo
  2. Bean里面依赖 openFeign的客户端接口和Dubbo 消费者

例如:

@FeignClient(name = "F6HttpInterFace",configuration = HttpConfig.class, url = "${f6.api}/open-api")
public interface F6HttpInterface
@Autowired
F6HttpInterface f6HttpInterface 
@Reference
private VehicleTypeFacade vehicleTypeFacade;
  1. 启动 工程

看了下代码,应该是
openFeign 会创建 AnnotationConfigApplicationContext 并且调用refresh();
所以会发送一个openFeign spring上下文的ContextRefreshedEvent事件
然后dubbo接收,就报错了
org.apache.dubbo.config.spring.ServiceBean#onApplicationEvent

Expected Result

Spring正常启动

Actual Result

Caused by: java.lang.IllegalStateException: <dubbo:service interface="" /> interface not allow null!
	at org.apache.dubbo.config.ServiceConfig.checkAndUpdateSubConfigs(ServiceConfig.java:276)
	at org.apache.dubbo.config.ServiceConfig.export(ServiceConfig.java:328)
	at org.apache.dubbo.config.spring.ServiceBean.export(ServiceBean.java:318)
	at org.apache.dubbo.config.spring.ServiceBean.onApplicationEvent(ServiceBean.java:112)
	at org.apache.dubbo.config.spring.ServiceBean.onApplicationEvent(ServiceBean.java:58)
	at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:172)
	at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:165)
	at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:139)
	at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:400)
	at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:406)
	at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:354)
	at org.springframework.context.support.AbstractApplicationContext.finishRefresh(AbstractApplicationContext.java:886)
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:551)
	at org.springframework.cloud.context.named.NamedContextFactory.createContext(NamedContextFactory.java:117)
	at org.springframework.cloud.context.named.NamedContextFactory.getContext(NamedContextFactory.java:85)
	at org.springframework.cloud.context.named.NamedContextFactory.getInstance(NamedContextFactory.java:126)
	at org.springframework.cloud.openfeign.FeignClientFactoryBean.get(FeignClientFactoryBean.java:205)
	at org.springframework.cloud.openfeign.FeignClientFactoryBean.feign(FeignClientFactoryBean.java:84)
	at org.springframework.cloud.openfeign.FeignClientFactoryBean.getTarget(FeignClientFactoryBean.java:241)
	at org.springframework.cloud.openfeign.FeignClientFactoryBean.getObject(FeignClientFactoryBean.java:232)
	at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.doGetObjectFromFactoryBean(FactoryBeanRegistrySupport.java:171)
	... 121 common frames omitted
Just put your stack trace here!
@xia-fei
Copy link
Author

xia-fei commented May 8, 2019

我发现,当dubbo的一个提供者,依赖openFeign的bean 会出现这个问题,不依赖则没问题

@scvzerng
Copy link

我dubbo和feign混用没遇到这样子的情况额2.7.1

@shuzheng
Copy link

一个不完美的解决方法是:

@Autowired
@Lazy
F6HttpInterface f6HttpInterface;

@beiwei30
Copy link
Member

this issue does appear on 2.7.1 but disappears on 2.7.2 and later, we are still investigating.

@slankka
Copy link
Contributor

slankka commented Oct 9, 2019

2.7.3 still remains.

@mercyblitz
Copy link
Contributor

mercyblitz commented Oct 9, 2019

This error was caused by Spring Cloud Feign design, every @FeignClient will generate a Feign proxy with a new child Spring ApplicationContext that may be refreshed before its' parent ApplicationContext. If any Dubbo service bean annotated @Service was scanned by the child context, this Dubbo ServiceBean also will be initialized and it's no guarantee its dependent Spring beans are ready in that time, thus there are two ways to resolve this issue :

  1. Avoid Dubbo @Service Bean to register into the child context
  2. Via the contextId attribute of @FeignClient to specify the Spring Boot server ApplicationContext

@slankka
Copy link
Contributor

slankka commented Oct 9, 2019

@mercyblitz Thanks for take a look at this. I'm a reader of your book 《SpringBoot编程思想》.
To be honest, your solutions is not easy to achieve.

Dubbo @Service Bean is not registered by child context created by Feign. The child context sets its parent context to which ApplicationContextAware will set to. We can do nothing to prevent the ContextRefreshedEvent received by ServiceBean. Because the event is also published to parent context by AbstractApplicationContext.

To see the event and source published by feign, it's easy to implement a EventListener ApplicationListener<ContextRefreshedEvent>

@Component
public class FakeListener implements ApplicationListener<ContextRefreshedEvent>, ApplicationContextAware {
  private ApplicationContext applicationContext;

  @Override
  public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    this.applicationContext = applicationContext;
  }

  @Override
  public void onApplicationEvent(ContextRefreshedEvent event) {
    ApplicationContext eventApplicationContext = event.getApplicationContext();

    if (applicationContext == eventApplicationContext) {
      //Here is the right opportunity that Spring beans are ready.
    }
....
}

If we print the ContextRefreshedEvent's class name and its parent class name.
We will see like this:

//Published by Feign
 :  org.springframework.context.annotation.AnnotationConfigApplicationContext@2cc3b0a7  FeignContext-XXXXXXXX
 : ----- application-1  org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@2c1b9e4b
 : ---------- bootstrap  org.springframework.context.annotation.AnnotationConfigApplicationContext@27adc16e


//Published by Spring when all Beans are ready
 :  application-1  org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@2c1b9e4b
 : ----- bootstrap  org.springframework.context.annotation.AnnotationConfigApplicationContext@27adc16e

Whatever the ApplicationContext class is, the right one is setted by ApplicationContextAware .
So the right way to fix this, is to let ServiceBean listen the Event published by right context.

And there is a advantage for checking what ApplicationContext we really want, to make it invoked only once.

My advice is to change the ServiceBean code:

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        if (!isExported() && !isUnexported()) {
            if (logger.isInfoEnabled()) {
                logger.info("The service ready on spring started. service: " + getInterface());
            }
            ApplicationContext eventApplicationContext = event.getApplicationContext();
           if (applicationContext == eventApplicationContext) {
               export();//Here is the right opportunity that Spring beans are ready.
           }
          
        }
    }

To those who meet this issue and want to fix this issue urgently:
When dubbo provider depends on a FeignClient, we can use ApplicationContextAware, and get the client by

FeignClientInterface client = applicationContext.getBean(FeignClientInterface.class);

@crazyzh1984
Copy link

@slankka 方案不可行,feign触发onApplicationEvent的时候applicationContext还未注入,是null。

@slankka
Copy link
Contributor

slankka commented Oct 25, 2019

@crazyzh1984
applicationContext == eventApplicationContext, 不会报错,也不会导致export意外触发。

@crazyzh1984
Copy link

@slankka 嗯,OK了,可以。

OpenFeign会创建多个child context,每个都会触发refresh,
所以还是要比较是否event是所属context触发才比较合理。

dubbo 的 ServiceBean 是在parent context中管理的,并不在child context中,
所以也不是bean注入的问题。

问题的根源是:
待注入的ServiceBean,在还没有初始化完成前,
就因为OpenFeign创建的child context触发refresh而提前进行export了。
触发的条件是,ServiceBean的ref对象直接或者间接的依赖了OpenFeign的client。

@crazyzh1984
Copy link

OpenFeign child AnnotationConfigApplicationContext fired
parent.publishEvent(ContextRefreshedEvent)
image

crazyzh1984 pushed a commit to crazyzh1984/dubbo that referenced this issue Oct 26, 2019
slankka added a commit to slankka/dubbo that referenced this issue Oct 27, 2019
crazyzh1984 pushed a commit to crazyzh1984/dubbo that referenced this issue Oct 28, 2019
mercyblitz pushed a commit that referenced this issue Oct 29, 2019
FrankBian pushed a commit to FrankBian/dubbo that referenced this issue Nov 5, 2019
* master: (755 commits)
  add license header for LogUtils file (apache#5272)
  fix how compatibleParamSignatures is generated.
  generic codec & bootstrap api
  Refactor, revert ReferenceConfigCache package change
  init application scope configs before start referencing.
  remove sensitive data from log exception for dubbo protocol (apache#5255)
  add gpg signing plugin for protobuf compiler
  add default implementation for alibaba pacakge compatible class
  add gradle wrapper jar file
  adjust onResponse method for compatibility purpose.
  Refactor ServiceConfig package
  remove useless files
  resolve conflicts after merged master
  enhance generic invocation check
  to process a scenario when service type is not available and service invocation is not generic either
  Fix Dubbo-3990 apache#3990 (apache#5247)
  ignore compile/.gradle
  fix Applicaiton Model related issues.
  use the latest version as the default dependency.
  Postpone checkSubConfig logic in ReferenceConfig (apache#5226)
  ...
@sentenes
Copy link

2.4.1 still remains.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants