啟用 Spring-Cloud-OpenFeign 配置可重新整理,專案無法啟動,我 TM 人傻了(上)

乾貨滿滿張雜湊發表於2021-10-01

image

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

  • :問題簡單描述以及 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);
	}
}

微信搜尋“我的程式設計喵”關注公眾號,每日一刷,輕鬆提升技術,斬獲各種offer

相關文章