Spring Cloud 异常—BeanCreationNotAllowedException: Error creating bean with name 'eurekaAutoServiceReg

Spring Clound 销毁时报异常

BeanCreationNotAllowedException: Error creating bean with name 'eurekaAutoServiceReg
rg.springframework.beans.factory.BeanCreationNotAllowedException: Error creating bean with name 'eurekaAutoServiceRegistration': Singleton bean creation not allowed while singletons of this factory are in destruction (Do not request a bean from a BeanFactory in a destroy method implementation!)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:216)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
    at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1081)

……

原因分析及解决方案参考:https://github.com/spring-cloud/spring-cloud-netflix/issues/1952

问题复现
问题复现很简单。对于 spring-cloud 的 Dalston 版本,引入如下依赖:

<dependencies>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-starter-eureka</artifactId>
	</dependency>		
	<dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-starter-feign</artifactId>
	</dependency>
</dependencies>

编写一个 Application,运行,在shutdown时出现错误。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.feign.EnableFeignClients;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.stereotype.Service;
 
 
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class DestoryExApp {
 
	@FeignClient("test")
	public static interface TestClient {
	}
 
	@Service
	public static class TestSerivce {
		@Autowired
		protected TestClient testClient;
	}
 
	public static void main(String[] args) throws Exception {
		ConfigurableApplicationContext context = SpringApplication.run(DestoryExApp.class, args);
		Thread.sleep(2000);
		context.close();
	}
}

原因分析
销毁顺序问题。

根本原因是关闭ApplicationContext时,Spring将销毁所有单例 bean,首先销毁eurekaAutoServiceRegistion,然后销毁feignContext。当销毁 feignContext 时,同时销毁所有与之关联的 FeignClient。由于 eurekaAutoServiceRegistration 监听ContextClosedEvent,这些事件将被发送到该bean。不幸的是,因为它已经被销毁了,所以出现上面的异常(尝试在销毁中创建bean)。

解决方案
作为权宜之计,调整一下销毁顺序。通过 BeanFactoryPostProcessor 改变一下依赖关系,进而影响销毁顺序。

import java.util.Arrays;
 
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.stereotype.Component;
 
 
@Component
public class FeignBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
		if (containsBeanDefinition(beanFactory, "feignContext", "eurekaAutoServiceRegistration")) {
			/* 调整依赖顺序,这样会先销毁 feignContext, 再销毁 eurekaAutoServiceRegistration */
			BeanDefinition bd = beanFactory.getBeanDefinition("feignContext");
			bd.setDependsOn("eurekaAutoServiceRegistration");
		}
	}
 
	private boolean containsBeanDefinition(ConfigurableListableBeanFactory beanFactory, String... beans) {
		return Arrays.stream(beans).allMatch(b -> beanFactory.containsBeanDefinition(b));
	}
 
}

详细原因分析
当关闭ApplicationContext 时, Spring 会销毁所有的 disposable beans (以及依赖于它们的bean)。In this case:

FeignContext 实现了 DisposableBean 接口;
InetUtils 实现了 AutoCloseable 接口;
EurekaServiceRegistry 拥有 public 属性的 close 方法;
所以它们都被看作 disposable beans。

由于 EurekaAutoServiceRegistration 依赖于 InetUtils 和 EurekaServiceRegistry beans,所以他们中的的, 任何一个被销毁,EurekaAutoServiceRegistration 也将被销毁。
销毁遵循先入后出的循序(FILO)。通常应用程序的代码不会依赖 InetUtils or EurekaServiceRegistry, 而是依赖于 FeignClient 接口.。这意味着 FeignContext 通常会先于 InetUtils 和 EurekaServiceRegistry 被创建, 而后于它们被销毁。销毁顺序如下:

InetUtils 或 EurekaServiceRegistry 被销毁;首先销毁 EurekaAutoServiceRegistration ;
接着销毁 InetUtils and EurekaServiceRegistry.
接着,销毁 FeignContext ,将会关闭上下文中所有关联于 FeignClients 的bean。
EurekaAutoServiceRegistration 监听了 ContextClosedEvent 事件,但是它已经被销毁, ApplicationContext 会尝试再次创建它,从而获得异常。

权宜之计:确保 InetUtils 和 EurekaServiceRegistry 先于 FeignContext 被创建。销毁顺序变更为:

销毁 FeignContext 以及关联的所有 FeignClients实例;
EurekaAutoServiceRegistration 监听到 ContextClosedEvent 并处理这些时间.
准备销毁 InetUtils or EurekaServiceRegistry ,首先销毁 EurekaAutoServiceRegistration .
接着销毁 InetUtils 和 EurekaServiceRegistry。

@Component
public class FeignBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
 
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        BeanDefinition bd = beanFactory.getBeanDefinition("feignContext");
        bd.setDependsOn("eurekaServiceRegistry", "inetUtils");
    }
}

猜你喜欢

转载自blog.csdn.net/zhuralll112/article/details/88813516
今日推荐