解決啟用 Spring-Cloud-OpenFeign 配置可刷新項目無法啟動的問題
本篇文章涉及底層設(shè)計以及原理,以及問題定位,比較深入,篇幅較長,所以拆分成上下兩篇:
- 上:問題簡單描述以及 Spring Cloud RefreshScope 的原理
- 下:當(dāng)前 spring-cloud-openfeign + spring-cloud-sleuth 帶來的 bug 以及如何修復(fù)
最近在項目中想實現(xiàn) OpenFeign 的配置可以動態(tài)刷新(主要是 Feign 的 Options 配置),例如:
feign: client: config: default: # 鏈接超時 connectTimeout: 500 # 讀取超時 readTimeout: 8000
我們可能會觀察到調(diào)用某個 FeignClient 的超時時間不合理,需要臨時修改下,我們不想因為這種事情重啟進程或者刷新整個 ApplicationContext,所以將這部分配置放入 spring-cloud-config 中并使用動態(tài)刷新的機制進行刷新。官方提供了這個配置方法,參考:官方文檔 - Spring @RefreshScope Support
即在項目中增加配置:
feign.client.refresh-enabled: true
但是在我們的項目中,增加了這個配置后,啟動失敗,報找不到相關(guān) 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 是我們開始提到要動態(tài)刷新的 Feign.Options,里面有連接超時、讀取超時等配置。名字后面的部分是我們創(chuàng)建的 FeignClient 上面 @FeignClient
注解里面的 contextId。
在創(chuàng)建 FeignClient 的時候,需要加載這個 Feign.Options Bean,每個 FeignClient 都有自己的 ApplicationContext,這個 Feign.Options Bean 就是屬于每個 FeignClient 單獨的 ApplicationContext 的。這個是通過 Spring Cloud 的 NamedContextFactory 實現(xiàn)的。對于 NamedContextFactory 的深入分析,可以參考我的這篇文章:
對于 OpenFeign 的配置開啟動態(tài)刷新,其實就是對于 FeignClient 就是要刷新每個 FeignClient 的 Feign.Options 這個 Bean。那么如何實現(xiàn)呢?我們先來看 spring-cloud 的動態(tài)刷新 Bean 的實現(xiàn)方式。首先我們要搞清楚,什么是 Scope。
Bean 的 Scope
從字面意思上面理解,Scope 即 Bean 的作用域。從實現(xiàn)上面理解,Scope 即我們在獲取 Bean 的時候,這個 Bean 是如何獲取的。
Spring 框架中自帶兩個耳熟能詳?shù)?Scope,即 singleton 和 prototype。singleton 即每次從 BeanFactory 獲取一個 Bean 的時候(getBean
),對于同一個 Bean 每次返回的都是同一個對象,即單例模式。prototype 即每次從 BeanFactory 獲取一個 Bean 的時候,對于同一個 Bean 每次都新創(chuàng)建一個對象返回,即工廠模式。
同時,我們還可以根據(jù)自己需要去擴展 Scope,定義獲取 Bean 的方式。舉一個簡單的例子,我們自定義一個 TestScope。自定義的 Scope 需要先定義一個實現(xiàn) org.springframework.beans.factory.config.Scope
接口的類,來定義在這個 Scope 下的 Bean 的獲取相關(guān)的操作。
public interface Scope { //獲取這個 bean,在 BeanFactory.getBean 的時候會被調(diào)用 Object get(String name, ObjectFactory<?> objectFactory); //在調(diào)用 BeanFactory.destroyScopedBean 的時候,會調(diào)用這個方法 @Nullable Object remove(String name); //注冊 destroy 的 callback //這個是可選實現(xiàn),提供給外部注冊銷毀 bean 的回調(diào)??梢栽?remove 的時候,執(zhí)行這里傳入的 callback。 void registerDestructionCallback(String name, Runnable callback); //如果一個 bean 不在 BeanFactory 中,而是根據(jù)上下文創(chuàng)建的,例如每個 http 請求創(chuàng)建一個獨立的 bean,這樣從 BeanFactory 中就拿不到了,就會從這里拿 //這個也是可選實現(xiàn) Object resolveContextualObject(String key); //可選實現(xiàn),類似于 session id 用戶區(qū)分不同上下文的 String getConversationId(); }
我們來實現(xiàn)一個簡單的 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 只是實現(xiàn)了 get 方法。直接通過傳入的 objectFactory 創(chuàng)建一個新的 bean。這種 Scope 下每次調(diào)用 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) { //創(chuàng)建一個 ApplicationContext AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(); //注冊我們自定義的 Scope annotationConfigApplicationContext.getBeanFactory().registerScope("testScope", new TestScope()); //注冊我們需要的配置 Bean annotationConfigApplicationContext.register(Config.class); //調(diào)用 refresh 初始化 ApplicationContext annotationConfigApplicationContext.refresh(); //獲取 Config 這個 Bean Config config = annotationConfigApplicationContext.getBean(Config.class); //調(diào)用自動裝載的 Bean config.a.test(); //從 BeanFactory 調(diào)用 getBean 獲取 A annotationConfigApplicationContext.getBean(A.class).test(); annotationConfigApplicationContext.getBean(A.class).test(); }
執(zhí)行代碼,叢輸出上可以看出,這三個 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; } }
在測試代碼中,增加調(diào)用 destroyScopedBean 銷毀 bean:
annotationConfigApplicationContext.getBeanFactory().destroyScopedBean("a");
運行代碼,可以看到對應(yīng)的輸出:
registerDestructionCallback is called
a is removed
com.hopegaming.spring.cloud.parent.ScopeTest$A@716a7124 is destroyed
callback finished
對于 DisposableBean 或者其他有相關(guān)生命周期類型的 Bean,BeanFactory 會通過 registerDestructionCallback 將生命周期需要的操作回調(diào)傳進來。使用 BeanFactory.destroyScopedBean
銷毀 Bean 的時候,會調(diào)用 Scope 的 remove 方法,我們可以在操作完成時,調(diào)用 callback 回調(diào)完成 Bean 生命周期。
接下來我們嘗試實現(xiàn)一種單例的 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,以及對應(yīng)的 Destroy Callback。在這種實現(xiàn)下,就類似于單例模式的實現(xiàn)了。再使用下面的測試程序測試下:
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 并調(diào)用 test 方法。然后銷毀這個 Bean。在這之后,再去使用自動裝載的和 BeanFactory.getBean 分別去請求獲取 A 這個 Bean 并調(diào)用 test 方法。可以從輸出中看出, BeanFactory.getBean 請求的是新的 Bean 了,但是自動裝載的里面還是已銷毀的那個 bean。那么如何實現(xiàn)讓自動裝載的也是新的 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 { //走默認(rèn)配置,沒有其他外圍配置則是 NO DEFAULT, //使用原始對象作為 Bean NO, //使用 JDK 的動態(tài)代理 INTERFACES, //使用 CGLIB 動態(tài)代理 TARGET_CLASS }
我們來測試下指定 Scope Bean 的實際對象為代理的效果,我們修改下上面的測試代碼,使用 CGLIB 動態(tài)代理。修改代碼:
@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(); }
執(zhí)行程序,輸出為:
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 的調(diào)用,都會調(diào)用自定義 Scope 的 get 方法重新獲取 Bean
- 每次通過 BeanFactory 獲取 Bean,也會調(diào)用自定義 Scope 的 get 方法重新獲取 Bean
- 獲取的 Bean 實例,是一個 CGLIB 代理對象
- 在 Bean 被銷毀后,無論是通過 BeanFactory 獲取 Bean 還是自動裝載的 Bean,都是新的 Bean
那么 Scope 是如何實現(xiàn)這些的呢?我們接下來簡單分析下源碼
Scope 基本原理
如果一個 Bean 沒有聲明任何 Scope,那么他的 Scope 就會被賦值成 singleton,也就是默認(rèn)的 Bean 都是單例的。這個對應(yīng) BeanFactory 注冊 Bean 之前需要生成 Bean 定義,在 Bean 定義的時候會賦上這個默認(rèn)值,對應(yīng)源碼:
protected RootBeanDefinition getMergedBeanDefinition( String beanName, BeanDefinition bd, @Nullable BeanDefinition containingBd) throws BeanDefinitionStoreException { //省略我們不關(guān)心的源碼 if (!StringUtils.hasLength(mbd.getScope())) { mbd.setScope(SCOPE_SINGLETON); } //省略我們不關(guān)心的源碼 }
在聲明一個 Bean 具有特殊 Scope 之前,我們需要定義這個自定義 Scope 并把它注冊到 BeanFactory 中。這個 Scope 名稱必須全局唯一,因為之后區(qū)分不同 Scope 就是通過這個名字進行區(qū)分的。注冊 Scope 對應(yīng)源碼:
@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 這兩個預(yù)設(shè)的 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); //可以看出,后面放入的會替換前面的,這個我們要盡量避免出現(xiàn)。 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 + "]"); } } }
當(dāng)聲明一個 Bean 具有特殊的 Scope 之后,獲取這個 Bean 的時候,就會有特殊的邏輯,參考通過 BeanFactory 獲取 Bean 的核心源碼代碼:
@SuppressWarnings("unchecked") protected <T> T doGetBean( String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException { //省略我們不關(guān)心的源碼 // 創(chuàng)建 Bean 實例 if (mbd.isSingleton()) { //創(chuàng)建或者返回單例實例 } else if (mbd.isPrototype()) { //每次創(chuàng)建一個新實例 } else { //走到這里代表這個 Bean 屬于自定義 Scope String scopeName = mbd.getScope(); //必須有 Scope 名稱 if (!StringUtils.hasLength(scopeName)) { throw new IllegalStateException("No scope name defined for bean ´" + beanName + "'"); } //通過 Scope 名稱獲取對應(yīng)的 Scope,自定義 Scope 需要手動注冊進來 Scope scope = this.scopes.get(scopeName); if (scope == null) { throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'"); } try { //調(diào)用自定義 Scope 的 get 方法獲取 Bean Object scopedInstance = scope.get(beanName, () -> { //同時將創(chuàng)建 Bean 需要的生命周期的回調(diào)傳入,用于創(chuàng)建 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); } } //省略我們不關(guān)心的源碼 }
同時,如果我們定義 Scope Bean 的代理方式為 CGLIB,那么在獲取 Bean 定義的時候,就會根據(jù)原始 Bean 定義創(chuàng)建 Scope 代理的 Bean 定義,對應(yīng)源碼:
public static BeanDefinitionHolder createScopedProxy(BeanDefinitionHolder definition, BeanDefinitionRegistry registry, boolean proxyTargetClass) { //原始目標(biāo) Bean 名稱 String originalBeanName = definition.getBeanName(); //獲取原始目標(biāo) Bean 定義 BeanDefinition targetDefinition = definition.getBeanDefinition(); //獲取代理 Bean 名稱 String targetBeanName = getTargetBeanName(originalBeanName); //創(chuàng)建類型為 ScopedProxyFactoryBean 的 Bean RootBeanDefinition proxyDefinition = new RootBeanDefinition(ScopedProxyFactoryBean.class); //根據(jù)原始目標(biāo) Bean 定義的屬性,配置代理 Bean 定義的相關(guān)屬性,省略這部分源碼 //根據(jù)原始目標(biāo) Bean 的自動裝載屬性,復(fù)制到代理 Bean 定義 proxyDefinition.setAutowireCandidate(targetDefinition.isAutowireCandidate()); proxyDefinition.setPrimary(targetDefinition.isPrimary()); if (targetDefinition instanceof AbstractBeanDefinition) { proxyDefinition.copyQualifiersFrom((AbstractBeanDefinition) targetDefinition); } //設(shè)置原始 Bean 定義為不自動裝載并且不為 Primary //這樣通過 BeanFactory 獲取 Bean 以及自動裝載的都是代理 Bean 而不是原始目標(biāo) 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 有啥作用呢?其實主要用處就是每次調(diào)用 Bean 的任何方法的時候,都會通過 BeanFactory 獲取這個 Bean 進行調(diào)用。參考源碼:
public class ScopedProxyFactoryBean extends ProxyConfig implements FactoryBean<Object>, BeanFactoryAware, AopInfrastructureBean { private final SimpleBeanTargetSource scopedTargetSource = new SimpleBeanTargetSource(); //這個就是通過 SimpleBeanTargetSource 生成的實際代理,對于 Bean 的方法調(diào)用都會通過這個 proxy 進行調(diào)用 private Object proxy; }
SimpleBeanTargetSource
就是實際的代理源,他的實現(xiàn)非常簡單,核心方法就是使用 Bean 名稱通過 BeanFactory 獲取這個 Bean:
public class SimpleBeanTargetSource extends AbstractBeanFactoryBasedTargetSource { @Override public Object getTarget() throws Exception { return getBeanFactory().getBean(getTargetBeanName()); } }
通過 BeanFactory 獲取這個 Bean,通過上面源碼分析可以知道,對于自定義 Scope 的 Bean 就會調(diào)用自定義 Scope 的 get 方法。
然后是 Bean 的銷毀,在 BeanFactory 創(chuàng)建這個 Bean 對象的時候,就會調(diào)用自定義 Scope 的 registerDestructionCallback 將 Bean 銷毀的回調(diào)傳入:
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() + "'"); } //調(diào)用 registerDestructionCallback scope.registerDestructionCallback(beanName, new DisposableBeanAdapter( bean, beanName, mbd, getBeanPostProcessorCache().destructionAware, acc)); } } }
在我們想銷毀 Scope Bean 的時候,需要調(diào)用的是 BeanFactory 的 destroyScopedBean 方法,這個方法會調(diào)用自定義 Scope 的 remove:
@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 + "'"); } //調(diào)用自定義 Scope 的 remove 方法 Object bean = scope.remove(beanName); if (bean != null) { destroyBean(beanName, bean, mbd); } }
到此這篇關(guān)于解決啟用 Spring-Cloud-OpenFeign 配置可刷新項目無法啟動的問題的文章就介紹到這了,更多相關(guān)Spring-Cloud-OpenFeign 配置內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringCloud OpenFeign Post請求400錯誤解決方案
- 完美解決SpringCloud-OpenFeign使用okhttp替換不生效問題
- SpringCloud升級2020.0.x版之OpenFeign簡介與使用實現(xiàn)思路
- springcloud引入spring-cloud-starter-openfeign失敗的解決
- 解決引入spring-cloud-starter-openfeign后部分類找不到的問題
- SpringCloud?OpenFeign?服務(wù)調(diào)用傳遞?token的場景分析
- Spring?Cloud?OpenFeign?遠(yuǎn)程調(diào)用
相關(guān)文章
Mybatis-Plus設(shè)置全局或者局部ID自增的實現(xiàn)
在使用Mybatis-Plus新增的時候,我們往往想要id隨著數(shù)據(jù)庫自增,本文主要介紹了Mybatis-Plus設(shè)置全局或者局部ID自增的實現(xiàn),具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-01-01Flyway詳解及Springboot集成Flyway的詳細(xì)教程
Flayway是一款數(shù)據(jù)庫版本控制管理工具,,支持?jǐn)?shù)據(jù)庫版本自動升級,Migrations可以寫成sql腳本,也可以寫在java代碼里。這篇文章主要介紹了Flyway詳解及Springboot集成Flyway的詳細(xì)教程的相關(guān)資料,需要的朋友可以參考下2020-07-07SpringBoot整合mybatis-plus進階詳細(xì)教程
本文主要對mybatis-plus的條件構(gòu)造器、AR模式、插件、逆向工程、自定義全局操作、公共字段自動填充等知識點進行講解,需要的朋友參考下吧2021-09-09Maven默認(rèn)中央倉庫(settings.xml 配置詳解)
這篇文章主要介紹了Maven默認(rèn)中央倉庫(settings.xml 配置詳解),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-12-12Java網(wǎng)絡(luò)通信中URL與HTTP編程技術(shù)詳解
要想實現(xiàn)網(wǎng)絡(luò)編程,除了可以使用Socket之外,我們還可以利用URL編程或HTTP編程技術(shù),所以今天這篇文章,就給大家介紹一下URL編程和HTTP編程技術(shù),看看這兩種技術(shù)有什么特點,文中有詳細(xì)的代碼講解,需要的朋友可以參考下2023-11-11Spring AOP攔截-三種方式實現(xiàn)自動代理詳解
這篇文章主要介紹了Spring AOP攔截-三種方式實現(xiàn)自動代理詳解,還是比較不錯的,這里分享給大家,供需要的朋友參考。2017-11-11