启用 Spring-Cloud-OpenFeign 配置可刷新,项目无法启动,我 TM 人傻了(上)

2021-12-30 16:56:31 浏览数 (1)

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

  • :问题简单描述以及 Spring Cloud RefreshScope 的原理
  • :当前 spring-cloud-openfeign spring-cloud-sleuth 带来的 bug 以及如何修复

最近在项目中想实现 OpenFeign 的配置可以动态刷新(主要是 Feign 的 Options 配置),例如:

代码语言:javascript复制
feign:
    client:
     config:
       default:
         # 链接超时
         connectTimeout: 500
         # 读取超时
         readTimeout: 8000

我们可能会观察到调用某个 FeignClient 的超时时间不合理,需要临时修改下,我们不想因为这种事情重启进程或者刷新整个 ApplicationContext,所以将这部分配置放入 spring-cloud-config 中并使用动态刷新的机制进行刷新。官方提供了这个配置方法,参考:官方文档 - Spring @RefreshScope Support

即在项目中增加配置:

代码语言:javascript复制
feign.client.refresh-enabled: true

但是在我们的项目中,增加了这个配置后,启动失败,报找不到相关 Bean 的错误:

代码语言:javascript复制
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 的获取相关的操作。

代码语言:javascript复制
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:

代码语言:javascript复制
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 也是不同的实例。编写测试:

代码语言:javascript复制
@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);
    }
}
代码语言:javascript复制
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 都是不同的对象:

代码语言:javascript复制
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:

代码语言:javascript复制
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:

代码语言:javascript复制
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:

代码语言:javascript复制
annotationConfigApplicationContext.getBeanFactory().destroyScopedBean("a");

运行代码,可以看到对应的输出:

代码语言:javascript复制
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:

代码语言:javascript复制
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。在这种实现下,就类似于单例模式的实现了。再使用下面的测试程序测试下:

代码语言:javascript复制
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 注解上面的另一个配置,即指定代理模式:

代码语言:javascript复制
@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 对象(这也同时影响了自动装载):

代码语言:javascript复制
public enum ScopedProxyMode {
    //走默认配置,没有其他外围配置则是 NO
	DEFAULT,
    //使用原始对象作为 Bean
	NO,
    //使用 JDK 的动态代理
	INTERFACES,
    //使用 CGLIB 动态代理
	TARGET_CLASS
}

我们来测试下指定 Scope Bean 的实际对象为代理的效果,我们修改下上面的测试代码,使用 CGLIB 动态代理。修改代码:

代码语言:javascript复制
@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;
}

编写测试主方法:

代码语言:javascript复制
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();
}

执行程序,输出为:

代码语言:javascript复制
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

代码语言:javascript复制
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

代码语言:javascript复制
@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

代码语言:javascript复制
@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

代码语言:javascript复制
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

代码语言:javascript复制
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:

代码语言:javascript复制
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

代码语言:javascript复制
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

代码语言:javascript复制
@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);
	}
}

0 人点赞