解决启用 Spring-Cloud-OpenFeign 配置可刷新项目无法启动的问题

 更新时间:2021年10月2日 20:00  点击:2117

本篇文章涉及底层设计以及原理,以及问题定位,比较深入,篇幅较长,所以拆分成上下两篇:

  • 上:问题简单描述以及 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 配置内容请搜索猪先飞以前的文章或继续浏览下面的相关文章希望大家以后多多支持猪先飞!

[!--infotagslink--]

相关文章

  • IntelliJ IDEA2021.1 配置大全(超详细教程)

    这篇文章主要介绍了IntelliJ IDEA2021.1 配置大全(超详细教程),需要的朋友可以参考下...2021-04-18
  • 基于springcloud异步线程池、高并发请求feign的解决方案

    这篇文章主要介绍了基于springcloud异步线程池、高并发请求feign的解决方案,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-02-25
  • Windows VPN服务器配置图文教程 超详细版

    VPN可以虚拟出一个专用网络,让远处的计算机和你相当于处在同一个局域网中,而中间的数据也可以实现加密传输,用处很大,特别是在一些大公司,分公司处在不同的区域。...2016-01-27
  • Tomcat配置及如何在Eclipse中启动

    这篇文章主要介绍了Tomcat配置及如何在Eclipse中启动,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-02-04
  • Laravel4安装配置的方法

    如果我们需要安培Laravel4的话最php最低要求要在php5.3.7版本并且我们需要把mcrypt与openss这两个扩展开启才可以,具体步骤我们参考下文。 前面我们介绍我了 com...2016-11-25
  • Spring AOP 对象内部方法间的嵌套调用方式

    这篇文章主要介绍了Spring AOP 对象内部方法间的嵌套调用方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-08-29
  • Spring Cloud 中@FeignClient注解中的contextId属性详解

    这篇文章主要介绍了Spring Cloud 中@FeignClient注解中的contextId属性详解,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-09-25
  • Springboot如何实现Web系统License授权认证

    这篇文章主要介绍了Springboot如何实现Web系统License授权认证,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-05-28
  • 完美解决SpringCloud-OpenFeign使用okhttp替换不生效问题

    这篇文章主要介绍了完美解决SpringCloud-OpenFeign使用okhttp替换不生效问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-02-25
  • 详解Maven profile配置管理及激活profile的几种方式

    这篇文章主要介绍了详解Maven profile配置管理及激活profile的几种方式,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-01-26
  • 如何在Spring WebFlux的任何地方获取Request对象

    这篇文章主要介绍了如何在Spring WebFlux的任何地方获取Request对象,帮助大家更好的理解和使用springboot框架,感兴趣的朋友可以了解下...2021-01-26
  • 详解SpringCloudGateway内存泄漏问题

    这篇文章主要介绍了详解SpringCloudGateway内存泄漏问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-07-16
  • IDEA如何添加配置文件到classpath中

    这篇文章主要介绍了IDEA如何添加配置文件到classpath中,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-09-19
  • 查找php配置文件php.ini所在路径的二种方法

    通常php.ini的位置在:复制代码 代码如下:/etc目录下或/usr/local/lib目录下。如果你还是找不到php.ini或者找到了php.ini修改后不生效(其实是没找对),请使用如下办法:1.新建php文件,写入如下代码复制代码 代码如下:<?phpe...2014-05-31
  • 部署PHP时的4个配置修改说明

    以下就是部署PHP时的4个配置修改说明,大家一个一个进行学习研究。1、short_open_tag 是什么呢? 决定是否允许使用代码开始标志的缩写形式(<&#63; &#63;> )。如果要和 XML 结合使用PHP,可以禁用此选项以便于嵌入使用<&#63;x...2015-10-21
  • Spring为什么不推荐使用@Autowired注解详析

    @Autowired 注解的主要功能就是完成自动注入,使用也非常简单,但这篇文章主要给大家介绍了关于Spring为什么不推荐使用@Autowired注解的相关资料,需要的朋友可以参考下...2021-11-03
  • 华为畅享20Pro配置怎么样?华为畅享20Pro参数配置分析

    华为畅享20Pro配置怎么样?对于即将上市的华为畅享20 Pro手机,很多的网友们也是相当关注的,大家都想要知道这款华为畅享20 Pro手机的配置到底怎么样,赶紧看看吧...2020-06-29
  • Vue-Router的routes配置详解

    在使用vue-router的项目中,实例化VueRouter是其配置选项routes该选项指定路由与视图的组件的关系或者路由与其他路由的关系,Router配置选项中是其中最重要的配置。本文就详细的介绍一下...2021-10-25
  • Springboot如何使用mybatis实现拦截SQL分页

    这篇文章主要介绍了Springboot使用mybatis实现拦截SQL分页,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-06-19
  • 如何解决springcloud feign 首次调用100%失败的问题

    这篇文章主要介绍了如何解决springcloud feign 首次调用100%失败的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-06-23