1. 程式人生 > >Spring Cloud 異常—BeanCreationNotAllowedException: Error creating bean with name 'eurekaAutoServiceReg

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)

……

問題復現

問題復現很簡單。對於 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");
    }
}