解決啟用 Spring-Cloud-OpenFeign 配置可刷新項目無法啟動的問題

本篇文章涉及底層設計以及原理,以及問題定位,比較深入,篇幅較長,所以拆分成上下兩篇:

  • 上:問題簡單描述以及 Spring Cloud RefreshScope 的原理
  • 下:當前 spring-cloud-openfeign + spring-cloud-sleuth 帶來的 bug 以及如何修復

最近在項目中想實現 OpenFeign 的配置可以動態刷新(主要是 Feign 的 Options 配置),例如:

feign:
    client:
     config:
       default:
         # 鏈接超時
         connectTimeout: 500
         # 讀取超時
         readTimeout: 8000

我們可能會觀察到調用某個 FeignClient 的超時時間不合理,需要臨時修改下,我們不想因為這種事情重啟進程或者刷新整個 ApplicationContext,所以將這部分配置放入 spring-cloud-config 中並使用動態刷新的機制進行刷新。官方提供瞭這個配置方法,參考:官方文檔 – Spring @RefreshScope Support

即在項目中增加配置:

feign.client.refresh-enabled: true

但是在我們的項目中,增加瞭這個配置後,啟動失敗,報找不到相關 Bean 的錯誤:

Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named ‘feign.Request.Options-testService1Client’ available
 at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:863)
 at org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1344)
 at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:309)
 at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:213)
 at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1160)
 at org.springframework.cloud.openfeign.FeignContext.getInstance(FeignContext.java:57)
 at org.springframework.cloud.openfeign.FeignClientFactoryBean.getOptionsByName(FeignClientFactoryBean.java:363)
 at org.springframework.cloud.openfeign.FeignClientFactoryBean.configureUsingConfiguration(FeignClientFactoryBean.java:195)
 at org.springframework.cloud.openfeign.FeignClientFactoryBean.configureFeign(FeignClientFactoryBean.java:158)
 at org.springframework.cloud.openfeign.FeignClientFactoryBean.feign(FeignClientFactoryBean.java:132)
 at org.springframework.cloud.openfeign.FeignClientFactoryBean.getTarget(FeignClientFactoryBean.java:382)
 at org.springframework.cloud.openfeign.FeignClientFactoryBean.getObject(FeignClientFactoryBean.java:371)
 at org.springframework.cloud.openfeign.FeignClientsRegistrar.lambda$registerFeignClient$0(FeignClientsRegistrar.java:235)
 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.obtainFromSupplier(AbstractAutowireCapableBeanFactory.java:1231)
 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1173)
 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:564)
 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:524)
 … 74 more

問題分析

通過這個 Bean 名稱,其實可以看出來這個 Bean 是我們開始提到要動態刷新的 Feign.Options,裡面有連接超時、讀取超時等配置。名字後面的部分是我們創建的 FeignClient 上面 @FeignClient 註解裡面的 contextId。

在創建 FeignClient 的時候,需要加載這個 Feign.Options Bean,每個 FeignClient 都有自己的 ApplicationContext,這個 Feign.Options Bean 就是屬於每個 FeignClient 單獨的 ApplicationContext 的。這個是通過 Spring Cloud 的 NamedContextFactory 實現的。對於 NamedContextFactory 的深入分析,可以參考我的這篇文章:

對於 OpenFeign 的配置開啟動態刷新,其實就是對於 FeignClient 就是要刷新每個 FeignClient 的 Feign.Options 這個 Bean。那麼如何實現呢?我們先來看 spring-cloud 的動態刷新 Bean 的實現方式。首先我們要搞清楚,什麼是 Scope。

Bean 的 Scope

從字面意思上面理解,Scope 即 Bean 的作用域。從實現上面理解,Scope 即我們在獲取 Bean 的時候,這個 Bean 是如何獲取的。

Spring 框架中自帶兩個耳熟能詳的 Scope,即 singleton 和 prototype。singleton 即每次從 BeanFactory 獲取一個 Bean 的時候(getBean),對於同一個 Bean 每次返回的都是同一個對象,即單例模式。prototype 即每次從 BeanFactory 獲取一個 Bean 的時候,對於同一個 Bean 每次都新創建一個對象返回,即工廠模式。

同時,我們還可以根據自己需要去擴展 Scope,定義獲取 Bean 的方式。舉一個簡單的例子,我們自定義一個 TestScope。自定義的 Scope 需要先定義一個實現 org.springframework.beans.factory.config.Scope 接口的類,來定義在這個 Scope 下的 Bean 的獲取相關的操作。

public interface Scope {
    //獲取這個 bean,在 BeanFactory.getBean 的時候會被調用
    Object get(String name, ObjectFactory<?> objectFactory);
    //在調用 BeanFactory.destroyScopedBean 的時候,會調用這個方法
    @Nullable
	Object remove(String name);
	//註冊 destroy 的 callback
	//這個是可選實現,提供給外部註冊銷毀 bean 的回調。可以在 remove 的時候,執行這裡傳入的 callback。
	void registerDestructionCallback(String name, Runnable callback);
	//如果一個 bean 不在 BeanFactory 中,而是根據上下文創建的,例如每個 http 請求創建一個獨立的 bean,這樣從 BeanFactory 中就拿不到瞭,就會從這裡拿
	//這個也是可選實現
	Object resolveContextualObject(String key);
	//可選實現,類似於 session id 用戶區分不同上下文的
	String getConversationId();
}

我們來實現一個簡單的 Scope:

public static class TestScope implements Scope {
    @Override
    public Object get(String name, ObjectFactory<?> objectFactory) {
        return objectFactory.getObject();
    }
    @Override
    public Object remove(String name) {
        return null;
    }
    @Override
    public void registerDestructionCallback(String name, Runnable callback) {
    }
    @Override
    public Object resolveContextualObject(String key) {
        return null;
    }
    @Override
    public String getConversationId() {
        return null;
    }
}

這個 Scope 隻是實現瞭 get 方法。直接通過傳入的 objectFactory 創建一個新的 bean。這種 Scope 下每次調用 BeanFactory.getFactory 都會返回一個新的 Bean,自動裝載到不同 Bean 的這種 Scope 下的 Bean 也是不同的實例。編寫測試:

@Configuration
public static class Config {
    @Bean
    //自定義 Scope 的名字是 testScope
    @org.springframework.context.annotation.Scope(value = "testScope")
    public A a() {
        return new A();
    }
    //自動裝載進來
    @Autowired
    private A a;
}

public static class A {
    public void test() {
        System.out.println(this);
    }
}
public static void main(String[] args) {
    //創建一個 ApplicationContext
    AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext();
    //註冊我們自定義的 Scope
    annotationConfigApplicationContext.getBeanFactory().registerScope("testScope", new TestScope());
    //註冊我們需要的配置 Bean
    annotationConfigApplicationContext.register(Config.class);
    //調用 refresh 初始化 ApplicationContext
    annotationConfigApplicationContext.refresh();
    //獲取 Config 這個 Bean
    Config config = annotationConfigApplicationContext.getBean(Config.class);
    //調用自動裝載的 Bean
    config.a.test();
    //從 BeanFactory 調用 getBean 獲取 A
    annotationConfigApplicationContext.getBean(A.class).test();
    annotationConfigApplicationContext.getBean(A.class).test();
}

執行代碼,叢輸出上可以看出,這三個 A 都是不同的對象:

com.hopegaming.spring.cloud.parent.ScopeTest$A@5241cf67
com.hopegaming.spring.cloud.parent.ScopeTest$A@716a7124
com.hopegaming.spring.cloud.parent.ScopeTest$A@77192705

我們再來修改我們的 Bean,讓它成為一個 Disposable Bean:

public static class A implements DisposableBean {
    public void test() {
        System.out.println(this);
    }

    @Override
    public void destroy() throws Exception {
        System.out.println(this + " is destroyed");
    }
}

再修改下我們的自定義 Scope:

public static class TestScope implements Scope {
    private Runnable callback;
    @Override
    public Object get(String name, ObjectFactory<?> objectFactory) {
        return objectFactory.getObject();
    }

    @Override
    public Object remove(String name) {
        System.out.println(name + " is removed");
        this.callback.run();
        System.out.println("callback finished");
        return null;
    }

    @Override
    public void registerDestructionCallback(String name, Runnable callback) {
        System.out.println("registerDestructionCallback is called");
        this.callback = callback;
    }

    @Override
    public Object resolveContextualObject(String key) {
        System.out.println("resolveContextualObject is called");
        return null;
    }

    @Override
    public String getConversationId() {
        System.out.println("getConversationId is called");
        return null;
    }
}

在測試代碼中,增加調用 destroyScopedBean 銷毀 bean:

annotationConfigApplicationContext.getBeanFactory().destroyScopedBean("a");

運行代碼,可以看到對應的輸出:

registerDestructionCallback is called
a is removed
com.hopegaming.spring.cloud.parent.ScopeTest$A@716a7124 is destroyed
callback finished

對於 DisposableBean 或者其他有相關生命周期類型的 Bean,BeanFactory 會通過 registerDestructionCallback 將生命周期需要的操作回調傳進來。使用 BeanFactory.destroyScopedBean 銷毀 Bean 的時候,會調用 Scope 的 remove 方法,我們可以在操作完成時,調用 callback 回調完成 Bean 生命周期。

接下來我們嘗試實現一種單例的 Scope,方式非常簡單,主要基於 ConcurrentHashMap:

public static class TestScope implements Scope {

    private final ConcurrentHashMap<String, Object> map = new ConcurrentHashMap<>();
    private final ConcurrentHashMap<String, Runnable> callback = new ConcurrentHashMap<>();

    @Override
    public Object get(String name, ObjectFactory<?> objectFactory) {
        System.out.println("get is called");
        return map.compute(name, (k, v) -> {
            if (v == null) {
                v = objectFactory.getObject();
            }
            return v;
        });
    }

    @Override
    public Object remove(String name) {
        this.map.remove(name);
        System.out.println(name + " is removed");
        this.callback.get(name).run();
        System.out.println("callback finished");
        return null;
    }

    @Override
    public void registerDestructionCallback(String name, Runnable callback) {
        System.out.println("registerDestructionCallback is called");
        this.callback.put(name, callback);
    }

    @Override
    public Object resolveContextualObject(String key) {
        return null;
    }

    @Override
    public String getConversationId() {
        return null;
    }
}

我們使用兩個 ConcurrentHashMap 緩存這個 Scope 下的 Bean,以及對應的 Destroy Callback。在這種實現下,就類似於單例模式的實現瞭。再使用下面的測試程序測試下:

public static void main(String[] args) {
    AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext();
    annotationConfigApplicationContext.getBeanFactory().registerScope("testScope", new TestScope());
    annotationConfigApplicationContext.register(Config.class);
    annotationConfigApplicationContext.refresh();
    Config config = annotationConfigApplicationContext.getBean(Config.class);
    config.a.test();
    annotationConfigApplicationContext.getBean(A.class).test();
    //Config 類中註冊 Bean 的方法名稱為 a,所以 Bean 名稱也為 a
    annotationConfigApplicationContext.getBeanFactory().destroyScopedBean("a");
    config.a.test();
    annotationConfigApplicationContext.getBean(A.class).test();
}

我們在銷毀 Bean 之前,使用自動裝載和 BeanFactory.getBean 分別去請求獲取 A 這個 Bean 並調用 test 方法。然後銷毀這個 Bean。在這之後,再去使用自動裝載的和 BeanFactory.getBean 分別去請求獲取 A 這個 Bean 並調用 test 方法。可以從輸出中看出, BeanFactory.getBean 請求的是新的 Bean 瞭,但是自動裝載的裡面還是已銷毀的那個 bean。那麼如何實現讓自動裝載的也是新的 Bean,也就是重新註入呢?

這就涉及到瞭 Scope 註解上面的另一個配置,即指定代理模式:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Scope {
	@AliasFor("scopeName")
	String value() default "";
	@AliasFor("value")
	String scopeName() default "";
	ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;
}

其中第三個配置,ScopedProxyMode 是配置獲取這個 Bean 的時候,獲取的是原始 Bean 對象還是代理的 Bean 對象(這也同時影響瞭自動裝載):

public enum ScopedProxyMode {
    //走默認配置,沒有其他外圍配置則是 NO
	DEFAULT,
    //使用原始對象作為 Bean
	NO,
    //使用 JDK 的動態代理
	INTERFACES,
    //使用 CGLIB 動態代理
	TARGET_CLASS
}

我們來測試下指定 Scope Bean 的實際對象為代理的效果,我們修改下上面的測試代碼,使用 CGLIB 動態代理。修改代碼:

@Configuration
public static class Config {
    @Bean
    @org.springframework.context.annotation.Scope(value = "testScope"
            //指定代理模式為基於 CGLIB
            , proxyMode = ScopedProxyMode.TARGET_CLASS
    )
    public A a() {
        return new A();
    }
    @Autowired
    private A a;
}

編寫測試主方法:

public static void main(String[] args) {
    AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext();
    annotationConfigApplicationContext.getBeanFactory().registerScope("testScope", new TestScope());
    annotationConfigApplicationContext.register(Config.class);
    annotationConfigApplicationContext.refresh();
    Config config = annotationConfigApplicationContext.getBean(Config.class);
    config.a.test();
    annotationConfigApplicationContext.getBean(A.class).test();
    //查看 Bean 實例的類型
    System.out.println(config.a.getClass());
    System.out.println(annotationConfigApplicationContext.getBean(A.class).getClass());
    //這時候我們需要註意,代理 Bean 的名稱有所變化,需要通過 ScopedProxyUtils 獲取
    annotationConfigApplicationContext.getBeanFactory().destroyScopedBean(ScopedProxyUtils.getTargetBeanName("a"));
    config.a.test();
    annotationConfigApplicationContext.getBean(A.class).test();
}

執行程序,輸出為:

get is called
registerDestructionCallback is called
com.hopegaming.spring.cloud.parent.ScopeTest$A@3dd69f5a
get is called
com.hopegaming.spring.cloud.parent.ScopeTest$A@3dd69f5a
class com.hopegaming.spring.cloud.parent.ScopeTest$A$$EnhancerBySpringCGLIB$$2fa625ee
class com.hopegaming.spring.cloud.parent.ScopeTest$A$$EnhancerBySpringCGLIB$$2fa625ee
scopedTarget.a is removed
com.hopegaming.spring.cloud.parent.ScopeTest$A@3dd69f5a is destroyed
callback finished
get is called
registerDestructionCallback is called
com.hopegaming.spring.cloud.parent.ScopeTest$A@3aa3193a
get is called
com.hopegaming.spring.cloud.parent.ScopeTest$A@3aa3193a

從輸出中可以看出:

  • 每次對於自動裝載的 Bean 的調用,都會調用自定義 Scope 的 get 方法重新獲取 Bean
  • 每次通過 BeanFactory 獲取 Bean,也會調用自定義 Scope 的 get 方法重新獲取 Bean
  • 獲取的 Bean 實例,是一個 CGLIB 代理對象
  • 在 Bean 被銷毀後,無論是通過 BeanFactory 獲取 Bean 還是自動裝載的 Bean,都是新的 Bean

那麼 Scope 是如何實現這些的呢?我們接下來簡單分析下源碼

Scope 基本原理

如果一個 Bean 沒有聲明任何 Scope,那麼他的 Scope 就會被賦值成 singleton,也就是默認的 Bean 都是單例的。這個對應 BeanFactory 註冊 Bean 之前需要生成 Bean 定義,在 Bean 定義的時候會賦上這個默認值,對應源碼:

AbstractBeanFactory

protected RootBeanDefinition getMergedBeanDefinition(
	String beanName, BeanDefinition bd, @Nullable BeanDefinition containingBd)
	throws BeanDefinitionStoreException {
    //省略我們不關心的源碼
	if (!StringUtils.hasLength(mbd.getScope())) {
		mbd.setScope(SCOPE_SINGLETON);
	}
	//省略我們不關心的源碼
}

在聲明一個 Bean 具有特殊 Scope 之前,我們需要定義這個自定義 Scope 並把它註冊到 BeanFactory 中。這個 Scope 名稱必須全局唯一,因為之後區分不同 Scope 就是通過這個名字進行區分的。註冊 Scope 對應源碼:

AbstractBeanFactory

@Override
public void registerScope(String scopeName, Scope scope) {
	Assert.notNull(scopeName, "Scope identifier must not be null");
	Assert.notNull(scope, "Scope must not be null");
	//不能為 singleton 和 prototype 這兩個預設的 scope
	if (SCOPE_SINGLETON.equals(scopeName) || SCOPE_PROTOTYPE.equals(scopeName)) {
		throw new IllegalArgumentException("Cannot replace existing scopes 'singleton' and 'prototype'");
	}
	//放入 scopes 這個 map 中,key 為名稱,value 為自定義 Scope
	Scope previous = this.scopes.put(scopeName, scope);
	//可以看出,後面放入的會替換前面的,這個我們要盡量避免出現。
	if (previous != null && previous != scope) {
		if (logger.isDebugEnabled()) {
			logger.debug("Replacing scope '" + scopeName + "' from [" + previous + "] to [" + scope + "]");
		}
	}
	else {
		if (logger.isTraceEnabled()) {
			logger.trace("Registering scope '" + scopeName + "' with implementation [" + scope + "]");
		}
	}
}

當聲明一個 Bean 具有特殊的 Scope 之後,獲取這個 Bean 的時候,就會有特殊的邏輯,參考通過 BeanFactory 獲取 Bean 的核心源碼代碼:

AbstractBeanFactory

@SuppressWarnings("unchecked")
protected <T> T doGetBean(
	String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
	throws BeansException {
	//省略我們不關心的源碼
	// 創建 Bean 實例
    if (mbd.isSingleton()) {
    	//創建或者返回單例實例
    } else if (mbd.isPrototype()) {
    	//每次創建一個新實例
    } else {
    	//走到這裡代表這個 Bean 屬於自定義 Scope
    	String scopeName = mbd.getScope();
    	//必須有 Scope 名稱
    	if (!StringUtils.hasLength(scopeName)) {
    		throw new IllegalStateException("No scope name defined for bean ´" + beanName + "'");
    	}
    	//通過 Scope 名稱獲取對應的 Scope,自定義 Scope 需要手動註冊進來
    	Scope scope = this.scopes.get(scopeName);
    	if (scope == null) {
    		throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
    	}
    	try {
    		//調用自定義 Scope 的 get 方法獲取 Bean
    		Object scopedInstance = scope.get(beanName, () -> {
    			//同時將創建 Bean 需要的生命周期的回調傳入,用於創建 Bean
    			beforePrototypeCreation(beanName);
    			try {
    				return createBean(beanName, mbd, args);
    			}
    			finally {
    				afterPrototypeCreation(beanName);
    			}
    		});
    		beanInstance = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
    	}
    	catch (IllegalStateException ex) {
    		throw new ScopeNotActiveException(beanName, scopeName, ex);
    	}
    }
    //省略我們不關心的源碼
}

同時,如果我們定義 Scope Bean 的代理方式為 CGLIB,那麼在獲取 Bean 定義的時候,就會根據原始 Bean 定義創建 Scope 代理的 Bean 定義,對應源碼:

ScopedProxyUtils

public static BeanDefinitionHolder createScopedProxy(BeanDefinitionHolder definition,
			BeanDefinitionRegistry registry, boolean proxyTargetClass) {

    //原始目標 Bean 名稱
	String originalBeanName = definition.getBeanName();
	//獲取原始目標 Bean 定義
	BeanDefinition targetDefinition = definition.getBeanDefinition();
	//獲取代理 Bean 名稱
	String targetBeanName = getTargetBeanName(originalBeanName);

    //創建類型為 ScopedProxyFactoryBean 的 Bean
	RootBeanDefinition proxyDefinition = new RootBeanDefinition(ScopedProxyFactoryBean.class);
    //根據原始目標 Bean 定義的屬性,配置代理 Bean 定義的相關屬性,省略這部分源碼
	
    //根據原始目標 Bean 的自動裝載屬性,復制到代理 Bean 定義
	proxyDefinition.setAutowireCandidate(targetDefinition.isAutowireCandidate());
	proxyDefinition.setPrimary(targetDefinition.isPrimary());
	if (targetDefinition instanceof AbstractBeanDefinition) {
		proxyDefinition.copyQualifiersFrom((AbstractBeanDefinition) targetDefinition);
	}

	//設置原始 Bean 定義為不自動裝載並且不為 Primary
	//這樣通過 BeanFactory 獲取 Bean 以及自動裝載的都是代理 Bean 而不是原始目標 Bean
	targetDefinition.setAutowireCandidate(false);
	targetDefinition.setPrimary(false);

	//使用新名稱註冊 Bean
	registry.registerBeanDefinition(targetBeanName, targetDefinition);

	return new BeanDefinitionHolder(proxyDefinition, originalBeanName, definition.getAliases());
}

private static final String TARGET_NAME_PREFIX = "scopedTarget.";
//這個就是獲取代理 Bean 名稱的工具方法,我們上面 Destroy Bean 的時候也有用到
public static String getTargetBeanName(String originalBeanName) {
	return TARGET_NAME_PREFIX + originalBeanName;
}

這個代理 Bean 有啥作用呢?其實主要用處就是每次調用 Bean 的任何方法的時候,都會通過 BeanFactory 獲取這個 Bean 進行調用。參考源碼:

代理類 ScopedProxyFactoryBean

public class ScopedProxyFactoryBean extends ProxyConfig
		implements FactoryBean<Object>, BeanFactoryAware, AopInfrastructureBean {
    private final SimpleBeanTargetSource scopedTargetSource = new SimpleBeanTargetSource();
    //這個就是通過 SimpleBeanTargetSource 生成的實際代理,對於 Bean 的方法調用都會通過這個 proxy 進行調用
    private Object proxy;
}

SimpleBeanTargetSource 就是實際的代理源,他的實現非常簡單,核心方法就是使用 Bean 名稱通過 BeanFactory 獲取這個 Bean:

public class SimpleBeanTargetSource extends AbstractBeanFactoryBasedTargetSource {
	@Override
	public Object getTarget() throws Exception {
		return getBeanFactory().getBean(getTargetBeanName());
	}
}

通過 BeanFactory 獲取這個 Bean,通過上面源碼分析可以知道,對於自定義 Scope 的 Bean 就會調用自定義 Scope 的 get 方法。

然後是 Bean 的銷毀,在 BeanFactory 創建這個 Bean 對象的時候,就會調用自定義 Scope 的 registerDestructionCallback 將 Bean 銷毀的回調傳入:

AbstractBeanFactory

protected void registerDisposableBeanIfNecessary(String beanName, Object bean, RootBeanDefinition mbd) {
	AccessControlContext acc = (System.getSecurityManager() != null ? getAccessControlContext() : null);
	if (!mbd.isPrototype() && requiresDestruction(bean, mbd)) {
		if (mbd.isSingleton()) {
			//對於 singleton
			registerDisposableBean(beanName, new DisposableBeanAdapter(
					bean, beanName, mbd, getBeanPostProcessorCache().destructionAware, acc));
		}
		else {
			//對於自定義 Scope
			Scope scope = this.scopes.get(mbd.getScope());
			if (scope == null) {
				throw new IllegalStateException("No Scope registered for scope name '" + mbd.getScope() + "'");
			}
			//調用 registerDestructionCallback
			scope.registerDestructionCallback(beanName, new DisposableBeanAdapter(
					bean, beanName, mbd, getBeanPostProcessorCache().destructionAware, acc));
		}
	}
}

在我們想銷毀 Scope Bean 的時候,需要調用的是 BeanFactory 的 destroyScopedBean 方法,這個方法會調用自定義 Scope 的 remove:

AbstractBeanFactory

@Override
public void destroyScopedBean(String beanName) {
	RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
	//僅針對自定義 Scope Bean 使用
	if (mbd.isSingleton() || mbd.isPrototype()) {
		throw new IllegalArgumentException(
				"Bean name '" + beanName + "' does not correspond to an object in a mutable scope");
	}
	String scopeName = mbd.getScope();
	Scope scope = this.scopes.get(scopeName);
	if (scope == null) {
		throw new IllegalStateException("No Scope SPI registered for scope name '" + scopeName + "'");
	}
	//調用自定義 Scope 的 remove 方法
	Object bean = scope.remove(beanName);
	if (bean != null) {
		destroyBean(beanName, bean, mbd);
	}
}

到此這篇關於解決啟用 Spring-Cloud-OpenFeign 配置可刷新項目無法啟動的問題的文章就介紹到這瞭,更多相關Spring-Cloud-OpenFeign 配置內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: