解决启用 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 配置内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Spring Cloud OpenFeign REST服务客户端原理及用法解析

    OpenFeign是什么? OpenFeign是REST服务客户端,REST其实就是HTTP啦,所以OpenFeign其实就是HTTP客户端,那么他和HttpClient有什么不同呢 OpenFeign的使用方法更加的简单 OpenFeign配合Spring的HttpMessageConverters可以自动把结果转换成Java对象 OpenFeign配合Ribbon.Eureka和Spring Cloud LoadBalancer可以支持负载均衡 如何使用OpenFeign 第一步引入Open

  • Springcloud基于OpenFeign实现服务调用代码实例

    1.依赖 <!--引入open feign依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> </dependencies> 2.启动注解 @SpringBootApplication @Enabl

  • 详解SpringCloud-OpenFeign组件的使用

    思考: 使用RestTemplate+ribbon已经可以完成服务间的调用,为什么还要使用feign? String restTemplateForObject = restTemplate.getForObject("http://服务名/url?参数" + name, String.class); 存在问题: 1.每次调用服务都需要写这些代码,存在大量的代码冗余 2.服务地址如果修改,维护成本增高 3.使用时不够灵活 说明 https://cloud.spring.io/sprin

  • SpringCloud OpenFeign Post请求400错误解决方案

    在微服务开发中SpringCloud全家桶集成了OpenFeign用于服务调用,SpringCloud的OpenFeign使用SpringMVCContract来解析OpenFeign的接口定义. 但是SpringMVCContract的Post接口解析实现有个巨坑,就是如果使用的是@RequestParam传参的Post请求,参数是直接挂在URL上的. 问题发现与分析 最近线上服务器突然经常性出现CPU高负载的预警,经过排查发现日志出来了大量的OpenFeign跨服务调用出现400的错误(HT

  • 完美解决SpringCloud-OpenFeign使用okhttp替换不生效问题

    事发地 原默认的Feign是使用URLConnector进行通信的,当换为okhttp时,直接引入包及配置以下内容根本不生效,还是走原生的. feign: okhttp: enable: true 事件还原 创建项目并引入pom相关的依赖如下: <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xml

  • Spring Cloud 系列之服务调用 OpenFeign的实现

    1.1 简介 1.1.1 概述   Feign 旨在使编写 Java Http 客户端变得更容易.在使用 Ribbon + RestTemplate 时,利用 RestTemplate 对 http 请求的封装处理,形成了一套模版化的调用方法.但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用.所以,Feign 在 Ribbon 基础上做了进一步封装,由他来帮助我们定义和实现依赖服务接口的定义.在

  • SpringCloud 服务负载均衡和调用 Ribbon、OpenFeign的方法

    1.Ribbon Spring Cloud Ribbon是基于Netflix Ribbon实现的-套客户端―负载均衡的工具. 简单的说,Ribbon是Netlix发布的开源项目,主要功能是提供客户端的软件负载均衡算法和服务调用.Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等.简单的说,就是在配置文件中列出Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器.我们很容易使用Ribbon实现自定义的负载均

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

    本篇文章涉及底层设计以及原理,以及问题定位,比较深入,篇幅较长,所以拆分成上下两篇: 上:问题简单描述以及 Spring Cloud RefreshScope 的原理 下:当前 spring-cloud-openfeign + spring-cloud-sleuth 带来的 bug 以及如何修复 最近在项目中想实现 OpenFeign 的配置可以动态刷新(主要是 Feign 的 Options 配置),例如: feign: client: config: default: # 链接超时 conn

  • Spring Cloud动态配置刷新RefreshScope使用示例详解

    目录 引言 一.了解@RefreshScope,先要了解@Scope 二.RefreshScope 的实现原理 三.使用——@RefreshScope 使用流程 引言 用过Spring Cloud的同学都知道在使用动态配置刷新的我们要配置一个 @RefreshScope,在类上才可以实现对象属性的的动态更新. @RefreshScope 能实现动态刷新全仰仗着 @Scope这个注解. 一.了解@RefreshScope,先要了解@Scope 1.RefreshScope继承于GenericSco

  • Spring Cloud 动态刷新配置信息教程详解

    有时候在配置中心有些参数是需要修改的,这时候如何不重启而达到实时生效的效果呢? 添加依赖 <dependencies> ... <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> ... </dependencies>

  • Spring Cloud OpenFeign实例介绍使用方法

    目录 一. OpenFeign概述 二. 使用步骤 2.1 feign接口模块 2.1.1依赖配置 2.1.2编写FeignClient的接口, 并加@FeignCleint 注解 2.2 消费端使用fegin接口 2.2.1在消费者端添加feign接口依赖 2.2.2在消费者端配置文件中添加 feign.client.url 2.2.3在消费者端启动类中添加@EnableFeignClients 2.2.4在消费者端使用fegin接口 2.3 测试 一. OpenFeign概述 OpenFei

  • Spring Cloud Ribbon配置详解

    本节我们主要介绍 Ribbon 的一些常用配置和配置 Ribbon 的两种方式. 常用配置 1. 禁用 Eureka 当我们在 RestTemplate 上添加 @LoadBalanced 注解后,就可以用服务名称来调用接口了,当有多个服务的时候,还能做负载均衡. 这是因为 Eureka 中的服务信息已经被拉取到了客户端本地,如果我们不想和 Eureka 集成,可以通过下面的配置方法将其禁用. # 禁用 Eureka ribbon.eureka.enabled=false 当我们禁用了 Eure

  • Spring Cloud OpenFeign 的五个优化技巧

    目录 一.超时优化 1.设置Ribbon超时时间 2.设置OpenFeign超时时间 二.请求连接优化 1.引入Apache HttpClient依赖 2.开启Apache HttpClient使用 三.数据压缩 四.负载均衡优化 五.日志级别优化 总结 前言: OpenFeign 是 Spring 官方推出的一种声明式服务调用和负载均衡组件.它的出现就是为了替代已经进入停更维护状态的 Feign(Netflix Feign),同时它也是 Spring 官方的顶级开源项目.我们在日常的开发中使用

  • Spring Cloud OpenFeign模版化客户端

    OpenFeign是什么? OpenFeign是一个显示声明式的WebService客户端.使用OpenFeign能让编写Web Service客户端更加简单.使用时只需定义服务接口,然后在上面添加注解.OpenFeign也支持可拔插式的编码和解码器.spring cloud对feign进行了封装,使其支持MVC注解和HttpMessageConverts.和eureka(服务注册中心)和ribbon组合可以实现负载均衡.在Spring Cloud中使用OpenFeign,可以做到使用HTTP请

  • Spring Cloud之配置中心的搭建

    Spring Cloud是现在流行的分布式服务框架,它提供了很多有用的组件.比如:配置中心.Eureka服务发现.消息总线.熔断机制等. 配置中心在Spring Cloud的众多组件中是比较基础的,它提供了配置文件的统一管理,可以很轻松的切换不通的环境. 它的具体结构如下: 存储配置文件的文件系统(通常使用git) 配置中心服务端(从文件系统获取最新的配置文件,为客户端提供配置信息) 配置客户端(从配置中心获取配置信息) Spring Cloud是建立在Spring Boot基础上的,Sprin

  • spring cloud config 配置中心快速实现过程解析

    spring-cloud-config 配置中心实现 Spring Cloud Config 用于为分布式系统中的基础设施和微服务应用提供集中化的外部配置支持,分为server端和client端. server端为分布式配置中心,是一个独立的微服务应用:client端为分布式系统中的基础设置或微服务应用,通过指定配置中心来管理相关的配置. Spring Cloud Config 构建的配置中心,除了适用于 Spring 构建的应用外,也可以在任何其他语言构建的应用中使用. Spring Clou

随机推荐