一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - 解決啟用 Spring-Cloud-OpenFeign 配置可刷新項目無法啟動的問題

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

2022-01-25 13:30干貨滿滿張哈希 Java教程

這篇文章主要介紹了解決啟用 Spring-Cloud-OpenFeign 配置可刷新項目無法啟動的問題,本文重點給大家介紹Spring-Cloud-OpenFeign的原理及問題解決方法,需要的朋友可以參考下

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

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

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

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

?
1
2
3
4
5
6
7
8
feign:
    client:
     config:
       default:
         # 鏈接超時
         connectTimeout: 500
         # 讀取超時
         readTimeout: 8000

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

即在項目中增加配置:

?
1
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 的獲取相關的操作。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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 也是不同的實例。編寫測試:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@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);
    }
}
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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 都是不同的對象:

?
1
2
3
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:

?
1
2
3
4
5
6
7
8
9
10
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:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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:

?
1
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:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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。在這種實現下,就類似于單例模式的實現了。再使用下面的測試程序測試下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
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 注解上面的另一個配置,即指定代理模式:

?
1
2
3
4
5
6
7
8
9
10
@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 對象(這也同時影響了自動裝載):

?
1
2
3
4
5
6
7
8
9
10
public enum ScopedProxyMode {
    //走默認配置,沒有其他外圍配置則是 NO
    DEFAULT,
    //使用原始對象作為 Bean
    NO,
    //使用 JDK 的動態代理
    INTERFACES,
    //使用 CGLIB 動態代理
    TARGET_CLASS
}

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
@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;
}

編寫測試主方法:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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

?
1
2
3
4
5
6
7
8
9
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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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

?
1
2
3
4
5
6
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:

?
1
2
3
4
5
6
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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@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 配置內容請搜索服務器之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持服務器之家!

原文鏈接:https://www.cnblogs.com/zhxdick/p/15359268.html

延伸 · 閱讀

精彩推薦
  • Java教程Java使用SAX解析xml的示例

    Java使用SAX解析xml的示例

    這篇文章主要介紹了Java使用SAX解析xml的示例,幫助大家更好的理解和學習使用Java,感興趣的朋友可以了解下...

    大行者10067412021-08-30
  • Java教程Java實現搶紅包功能

    Java實現搶紅包功能

    這篇文章主要為大家詳細介紹了Java實現搶紅包功能,采用多線程模擬多人同時搶紅包,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙...

    littleschemer13532021-05-16
  • Java教程升級IDEA后Lombok不能使用的解決方法

    升級IDEA后Lombok不能使用的解決方法

    最近看到提示IDEA提示升級,尋思已經有好久沒有升過級了。升級完畢重啟之后,突然發現好多錯誤,本文就來介紹一下如何解決,感興趣的可以了解一下...

    程序猿DD9332021-10-08
  • Java教程Java8中Stream使用的一個注意事項

    Java8中Stream使用的一個注意事項

    最近在工作中發現了對于集合操作轉換的神器,java8新特性 stream,但在使用中遇到了一個非常重要的注意點,所以這篇文章主要給大家介紹了關于Java8中S...

    阿杜7482021-02-04
  • Java教程小米推送Java代碼

    小米推送Java代碼

    今天小編就為大家分享一篇關于小米推送Java代碼,小編覺得內容挺不錯的,現在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧...

    富貴穩中求8032021-07-12
  • Java教程20個非常實用的Java程序代碼片段

    20個非常實用的Java程序代碼片段

    這篇文章主要為大家分享了20個非常實用的Java程序片段,對java開發項目有所幫助,感興趣的小伙伴們可以參考一下 ...

    lijiao5352020-04-06
  • Java教程Java BufferWriter寫文件寫不進去或缺失數據的解決

    Java BufferWriter寫文件寫不進去或缺失數據的解決

    這篇文章主要介紹了Java BufferWriter寫文件寫不進去或缺失數據的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望...

    spcoder14552021-10-18
  • Java教程xml與Java對象的轉換詳解

    xml與Java對象的轉換詳解

    這篇文章主要介紹了xml與Java對象的轉換詳解的相關資料,需要的朋友可以參考下...

    Java教程網2942020-09-17
主站蜘蛛池模板: 亚洲色图图 | 亚洲人影院 | 国产精彩视频 | 成人欧美视频在线看免费 | 91tv破解版不限次数 | 思思久久精品在热线热 | 成人私人影院在线观看网址 | 国产成人h综合亚洲欧美在线 | 日本小视频网站 | 国产资源站 | 女人与d0gxxx | 欧美视频一区二区专区 | 午夜视频在线网站 | 二区三区在线观看 | 欧美性白人顶级hd | 亚洲品质自拍网站 | 91嫩草国产在线观看免费 | 大胆暴露亚洲美女xxxx | 天天色踪合 | 无人区在线观看免费完整版免费 | 男人猛戳女人下部30分钟 | 四虎永久在线精品波多野结衣 | 女子校生下媚药在线观看 | 欧美日韩国产亚洲人成 | 6080窝窝理论 | 欧美成人免费一区在线播放 | 亚洲福利一区 | 校花的第一次好紧好爽 | 国内体内she精视频免费 | 久久青草费线频观看国产 | 日韩免费高清专区 | 成人区精品一区二区毛片不卡 | 母性本能 | 男人狂躁女人下半身 | 女人又色又爽又黄 | 国产成人lu在线视频 | 污污免费 | 呜呜别塞了啊抽插 | 白丝女仆被啪到深夜漫画 | 国产99精品视频 | 四虎在线永久视频观看 |