SpringCloud应用启动流程分析(二)

  上篇我们聊了会bootstrap context的前世今生,以及它是如何通过PropertySourceLocator来加载外部配置的。小伙伴们大概也注意到了,所有这些都发生在应用程序的启动过程中,后续如果修改了外部配置呢?应用程序能感知到吗?

1
2
3
4
5
6
7
8
9
@Component
// @RefreshScope
public class SomeComponent {
// 初始化时值就确定了
@Value("${example.key}")
private String value;

// rest are omitted...
}

退一万步讲,即使应用程序能感知到, 对于上例来说,仍然是不够的。这是因为SomeComponent是单例的,表达式${example.key}只会在其初始化时计算一次,之后无论配置项example.key如何变化也不能反映到SomeComponent#value上。聪明的你应该也想到了,如果有办法让SomeComponent在配置项example.key更新之后重新初始化,问题不就解决了吗?想想是不是这样?还是上例,如果我们打开对@RefreshScope的注释,就会发现问题解决了!!很神奇吧,那么@RefreshScope到底有什么魔力呢?

@RefreshScope

1
2
3
4
5
6
7
8
# 截取自 spring-cloud-commons reference doc, chapter 1.9
When there is a configuration change, a Spring @Bean that is marked as @RefreshScope gets special treatment.
This feature addresses the problem of stateful beans that get their configuration injected only when they
are initialized.

Refresh scope beans are lazy proxies that initialize when they are used (that is, when a method is called),
and the scope acts as a cache of initialized values. To force a bean to re-initialize on the next method
call, you must invalidate its cache entry.

  官方文档不仅描述了refresh scope解决了什么问题,同时也简要地提到了它的实现原理:

  1. Spring会为处于refresh scope的目标对象(target)创建代理(proxy
  2. refresh scope本身会缓存目标对象

因此想让某个目标对象重新初始化的话,只需要将它从缓存中移除即可。似懂非懂?没关系,咱们来分析分析(基于spring-cloud-context-2.1.2.RELEASE)。

1
2
3
4
5
6
7
8
9
10
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Scope("refresh")
public @interface RefreshScope {
/**
* 指明标注了 @RefreshScope 注解的对象需要
* 被代理,默认采用 CGLIB 动态生成子类的方式
*/
ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
}

@RefreshScope本身只是一个衍生注解,它基于元注解@Scope,而实现原理的第1点便是来自@Scope,换句话说是由容器直接支持的、开箱即用的。

@Scope/Scope

@Scope

  @Scope想必大家都很熟悉了,它通过名称来引用org.springframework.beans.factory.config.Scope的某个实现,比如@RefreshScope指定的名称是refresh,其对应的实现是org.springframework.cloud.context.scope.refresh.RefreshScope。再看#proxyMode()@RefreshScope指定了默认值ScopedProxyMode.TARGET_CLASS,表示希望以CGLIB动态生成子类的方式代理目标对象。

  再深入一点,在解析@Configuration配置类、生成BeanDefinition的过程中,如果Spring了解到属于某个Scope的目标对象需要被代理,就会额外在容器中注册一个BeanDefinition,不用说这个BeanDefinition自然就代表代理对象了,相关代码可以参见:

  1. ClassPathBeanDefinitionScanner#doScan(...)
  2. AnnotatedBeanDefinitionReader#doRegisterBean(...)
  3. ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForBeanMethod(...)
  4. ConfigurationClassBeanDefinitionReader#registerBeanDefinitionForImportedConfigurationClass(...)

这些调用最终都指向了ScopedProxyUtils#createScopedProxy(...)

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
43
44
public static BeanDefinitionHolder createScopedProxy(BeanDefinitionHolder definition,
BeanDefinitionRegistry registry,
boolean proxyTargetClass) {
// 原名
String originalBeanName = definition.getBeanName();
// 原始定义
BeanDefinition targetDefinition = definition.getBeanDefinition();
// 在原名的基础上增加 scopedTarget. 前缀
String targetBeanName = getTargetBeanName(originalBeanName);

// 创建一个生成代理的 BeanDefinition 来隐藏原始定义
// 代理对象由 ScopedProxyFactoryBean 生成
RootBeanDefinition proxyDefinition = new RootBeanDefinition(ScopedProxyFactoryBean.class);
proxyDefinition.setDecoratedDefinition(new BeanDefinitionHolder(targetDefinition, targetBeanName));
proxyDefinition.setOriginatingBeanDefinition(targetDefinition);
proxyDefinition.setSource(definition.getSource());
proxyDefinition.setRole(targetDefinition.getRole());

// 设置 targetBeanName,给 ScopedProxyFactoryBean 使用
proxyDefinition.getPropertyValues().add("targetBeanName", targetBeanName);
if (proxyTargetClass) {
targetDefinition.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
} else {
proxyDefinition.getPropertyValues().add("proxyTargetClass", Boolean.FALSE);
}

// 代理对象继承原始对象的配置
proxyDefinition.setAutowireCandidate(targetDefinition.isAutowireCandidate());
proxyDefinition.setPrimary(targetDefinition.isPrimary());
if (targetDefinition instanceof AbstractBeanDefinition) {
proxyDefinition.copyQualifiersFrom((AbstractBeanDefinition) targetDefinition);
}

// 代理对象作为自动注入的第一选择
// 如此,原始对象就被隐藏起来了
targetDefinition.setAutowireCandidate(false);
targetDefinition.setPrimary(false);

// 注册原始对象,注意这里它的 beanName 已经修改过了
registry.registerBeanDefinition(targetBeanName, targetDefinition);

// 返回代理对象的定义,它继承了原始对象的 beanName
return new BeanDefinitionHolder(proxyDefinition, originalBeanName, definition.getAliases());
}

注释还是比较详细的。最后,ScopedProxyFactoryBean创建代理对象的逻辑就完全是spring-aop的内容了,感兴趣的话可以看看这里AOP再说下去就没完没了了。

Scope

  为了内容的连贯性,咱们也说说org.springframework.beans.factory.config.Scope吧。 它最开始是为Web环境设计的(session/request scope),不过其接口本身是足够通用的,可以任意扩展,相关处理可以参见AbstractBeanFactory#doGetBean(...)

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
43
44
45
46
47
48
49
50
51
52
public interface Scope {
// 获取实例
Object get(String name, ObjectFactory<?> objectFactory);

// 移除实例
@Nullable
Object remove(String name);

// 销毁实例时额外执行的逻辑
// DisposableBean 这类接口只适用于 singleton bean
void registerDestructionCallback(String name, Runnable callback);

@Nullable
Object resolveContextualObject(String key);
@Nullable
String getConversationId();
}

protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {

// omitted...

// 通过 scopeName 找到对应的 Scope 实现
// 对我们的 refresh scope 来说,它的实现是 RefreshScope
String scopeName = mbd.getScope();
final Scope scope = this.scopes.get(scopeName);
if (scope == null) {
throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
}
try {
// 从对应的 Scope 中获取实例
// 第二个参数是一个工厂接口,负责创建新的实例
Object scopedInstance = scope.get(beanName, () -> {
// 循环依赖相关
beforePrototypeCreation(beanName);
try {
// createBean(...) 负责创建实例
// 并赋予它 Spring 语义
return createBean(beanName, mbd, args);
} finally {
afterPrototypeCreation(beanName);
}
});
// 对于 FactoryBean 来说,我们希望获取的是它创建的对象,而不是 FactoryBean 本身
bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
} catch (IllegalStateException ex) {
throw new BeanCreationException(ex);
}

// omitted...
}

RefreshScope

  实现原理的第2点——缓存,倒是由org.springframework.cloud.context.scope.refresh.RefreshScope直接提供的。缓存层也有着自己的抽象——ScopeCache,不过定义和实现都比较简单,大家就认为它是个大Map好了。看Scope接口的定义,Object get(String name, ObjectFactory<?> objectFactory)必然是和缓存交互的重点,不难发现这是在RefreshScope的基类GenericScope中实现的。

get(String name, ObjectFactory<?> objectFactory)

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
public class GenericScope implements Scope, BeanFactoryPostProcessor,
BeanDefinitionRegistryPostProcessor, DisposableBean {

// omitted...

@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
// 获取缓存中的 BeanLifecycleWrapper
// 没有的话就添加一个
BeanLifecycleWrapper value = this.cache.put(name,
new BeanLifecycleWrapper(name, objectFactory));
// 锁主要是为了避免 bean 在使用中被销毁
this.locks.putIfAbsent(name, new ReentrantReadWriteLock());
try {
// 获取目标对象(可能是新创建出来的)
// 注意,这个对象就是被代理的那一个
return value.getBean();
} catch (RuntimeException e) {
this.errors.put(name, e);
throw e;
}
}

// omitted...

private static class BeanLifecycleWrapper {
// bean name
private final String name;
// 创建 bean 的工厂
private final ObjectFactory<?> objectFactory;
// 缓存的创建好的 bean
private volatile Object bean;
// 销毁时需要被执行的额外逻辑
private Runnable callback;

// omitted...

public Object getBean() {
// double-checked locking
if (this.bean == null) {
synchronized (this.name) {
if (this.bean == null) {
// 实在没有就通过工厂创建并缓存
this.bean = this.objectFactory.getObject();
}
}
}
return this.bean;
}

public void destroy() {
if (this.callback == null) {
return;
}
synchronized (this.name) {
Runnable callback = this.callback;
if (callback != null) {
callback.run();
}
this.callback = null;
this.bean = null;
}
}

// equals(...)/hashCode() omitted...
}
}

非常常见的先查缓存,没有再创建的逻辑,留意一下这个被创建出来的对象其实就是被代理的对象。GenericScope还实现了几个其它接口,我们也来看看。

postProcessBeanFactory(…)

1
2
3
4
5
6
7
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
throws BeansException {
this.beanFactory = beanFactory;
beanFactory.registerScope(this.name, this);
setSerializationId(beanFactory);
}

实现BeanFactoryPostProcessor是为了在BeanFactory中注册自己。

postProcessBeanDefinitionRegistry(…)

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)
throws BeansException {
for (String name : registry.getBeanDefinitionNames()) {
BeanDefinition definition = registry.getBeanDefinition(name);
if (definition instanceof RootBeanDefinition) {
RootBeanDefinition root = (RootBeanDefinition) definition;
// ScopedProxyFactoryBean 是专门给位于 Scope 中的 bean 创建代理的
if (root.getDecoratedDefinition() != null && root.hasBeanClass()
&& root.getBeanClass() == ScopedProxyFactoryBean.class) {
// 再检查是不是当前 Scope
if (getName().equals(root.getDecoratedDefinition().getBeanDefinition()
.getScope())) {
// 到这里可以确定是给位于当前 Scope 中的 bean 创建代理的
root.setBeanClass(LockedScopedProxyFactoryBean.class);
// 相当于给 LockedScopedProxyFactoryBean 的构造函数传参
root.getConstructorArgumentValues().addGenericArgumentValue(this);
root.setSynthetic(true);
}
}
}
}
}

public static class LockedScopedProxyFactoryBean<S extends GenericScope>
extends ScopedProxyFactoryBean implements MethodInterceptor {

private final S scope;
public LockedScopedProxyFactoryBean(S scope) {
this.scope = scope;
}

@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();
if (AopUtils.isEqualsMethod(method) || AopUtils.isToStringMethod(method)
|| AopUtils.isHashCodeMethod(method)
|| isScopedObjectGetTargetObject(method)) {
return invocation.proceed();
}
// 获取代理对象
Object proxy = getObject();
// 获取对应的锁
ReadWriteLock readWriteLock = this.scope.getLock(this.targetBeanName);
if (readWriteLock == null) {
if (logger.isDebugEnabled()) {
logger.debug("For bean with name [" + this.targetBeanName
+ "] there is no read write lock. Will create a new one to avoid NPE");
}
readWriteLock = new ReentrantReadWriteLock();
}
// 提供 LockedScopedProxyFactoryBean 这么个类
// 主要就是为了用上这把锁,单独看这个类,可能不
// 太好理解为什么要上锁,结合 destroy() 就好理解了
// destroy()会销毁目标对象,如果调用的过程中,目标
// 对象被销毁了,这个调用就无法继续下去了
// 这里用的读锁,destroy 时自然要用写锁了
Lock lock = readWriteLock.readLock();
lock.lock();
try {
// 默认情况下,所有的代理都可以转成 Advised
if (proxy instanceof Advised) {
Advised advised = (Advised) proxy;
ReflectionUtils.makeAccessible(method);
// 将对代理对象的请求转发给目标对象
return ReflectionUtils.invokeMethod(method,
advised.getTargetSource().getTarget(),
invocation.getArguments());
}
return invocation.proceed();
} catch (UndeclaredThrowableException e) {
throw e.getUndeclaredThrowable();
} finally {
lock.unlock();
}
}
}

实现BeanDefinitionRegistryPostProcessor是为了阻止目标对象在使用的同时被销毁。

destroy()

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
@Override
public void destroy() {
List<Throwable> errors = new ArrayList<Throwable>();
// 清空缓存
Collection<BeanLifecycleWrapper> wrappers = this.cache.clear();
// 逐一销毁
for (BeanLifecycleWrapper wrapper : wrappers) {
try {
// 使用写锁,如果目标对象在使用中,这里就会被阻塞
Lock lock = this.locks.get(wrapper.getName()).writeLock();
lock.lock();
try {
// 调用之前注册的 Destruction Callback
wrapper.destroy();
} finally {
lock.unlock();
}
} catch (RuntimeException e) {
errors.add(e);
}
}
if (!errors.isEmpty()) {
throw wrapIfNecessary(errors.get(0));
}
this.errors.clear();
}

实现DisposableBean是为了在当前Scope被销毁时也销毁它所缓存的目标对象。destroy()还有一个重载版本,携带beanName作为参数,显然这个版本的作用是销毁某个特定的目标对象。既然目标对象都通过destroy()销毁了,下次访问它的时候不就可以重新初始化了吗?其关联的表达式、依赖不都可以重新计算和注入了吗?换句话说,刷新的目的已经达到了。

refreshAll/refresh(…)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public boolean refresh(String name) {
// 还记得目标对象其实被改过名吗
if (!name.startsWith(SCOPED_TARGET_PREFIX)) {
name = SCOPED_TARGET_PREFIX + name;
}
// 销毁对应的目标对象
if (super.destroy(name)) {
// 发出事件
this.context.publishEvent(new RefreshScopeRefreshedEvent(name));
return true;
}
return false;
}

public void refreshAll() {
// 销毁RefreshScope中所有的目标对象
super.destroy();
// 发出事件
this.context.publishEvent(new RefreshScopeRefreshedEvent());
}

  回到RefreshScope本身,我们想要的功能其实在它的父类GenericScope中已经实现了,RefreshScope仅仅添加了两个符合其语义的方法。至此,RefreshScope可以说再无秘密可言,不过对于应用程序如何感知外部配置的变化,我们仍未作出解答。不着急,往下看。

RefreshEventListener

  RefreshEventListener是这么一个监听器,它在应用程序启动之后,如果接收到RefreshEvent,就使用ContextRefresher来进行刷新。至于它本身的代码呢比较简单,我们就跳过它直接看ContextRefresher吧。

1
2
3
4
5
6
7
8
9
10
public class ContextRefresher {
public synchronized Set<String> refresh() {
// 刷新 Environment,如此便能感受到外部配置的变化了
Set<String> keys = refreshEnvironment();
// 销毁 RefreshScope 中的所有目标对象,如此下次使用
// 这些对象时它们就能应用上最新的配置了
refreshScope.refreshAll();
return keys;
}
}

等等!从上面的描述中大家明白如何让应用程序感知到变化吗?没错,只需要使用ApplicationContext发出一个RefreshEvent就可以了,很简单吧?回到ContextRefresher

1
2
3
4
5
6
7
8
9
10
11
public synchronized Set<String> refreshEnvironment() {
// 将当前 Environment 中的所有配置项都取出来
Map<String, Object> before = extract( this.context.getEnvironment().getPropertySources());
// 重新加载一次外部配置
addConfigFilesToEnvironment();
// 计算重新加载过外部配置后有哪些配置项发生了变化
Set<String> keys = changes(before, extract(this.context.getEnvironment().getPropertySources())).keySet();
// 发出 EnvironmentChangeEvent 事件
this.context.publishEvent(new EnvironmentChangeEvent(this.context, keys));
return keys;
}

重点自然是addConfigFilesToEnvironment()了。

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
/* For testing. */ ConfigurableApplicationContext addConfigFilesToEnvironment() {
ConfigurableApplicationContext capture = null;
try {
// 复制一份当前的 Environment (有选择的)
StandardEnvironment environment = copyEnvironment(this.context.getEnvironment());

// 复用 SpringBoot 应用的启动流程
SpringApplicationBuilder builder = new SpringApplicationBuilder(Empty.class)
.bannerMode(Mode.OFF).web(WebApplicationType.NONE)
// 将这份拷贝作为新 Context 的环境
.environment(environment);
// 设置两个必要的监听器
builder.application()
// 用来加载外部配置
.setListeners(Arrays.asList(new BootstrapApplicationListener(),
// 用来读取本地配置
new ConfigFileApplicationListener()));
capture = builder.run();
if (environment.getPropertySources().contains(REFRESH_ARGS_PROPERTY_SOURCE)) {
environment.getPropertySources().remove(REFRESH_ARGS_PROPERTY_SOURCE);
}
// 当前的
MutablePropertySources target = this.context.getEnvironment()
.getPropertySources();
String targetName = null;
// 遍历重新读取过外部配置的
for (PropertySource<?> source : environment.getPropertySources()) {
String name = source.getName();
if (target.contains(name)) {
targetName = name;
}
if (!this.standardSources.contains(name)) {
// 有则替换
if (target.contains(name)) {
target.replace(name, source);
} else {
// 无则添加
if (targetName != null) {
target.addAfter(targetName, source);
} else {
// targetName was null so we are at the start of the list
target.addFirst(source);
targetName = name;
}
}
}
} // 循环结束时,当前 Environment 也更新好了,里面的数据都是新数据了
} finally {
// 这个 ApplicationContext 只是临时用来加载外部配置的
// 用完就可以丢了
ConfigurableApplicationContext closeable = capture;
while (closeable != null) {
try {
closeable.close();
} catch (Exception e) {
// Ignore;
}
if (closeable.getParent() instanceof ConfigurableApplicationContext) {
closeable = (ConfigurableApplicationContext) closeable.getParent();
} else {
break;
}
}
}
return capture;
}

这个方法的内容我们在上篇才分析过,看过的小伙伴们应该还有点儿印象吧。至于refreshScope.refreshAll(),请各位小伙伴动动鼠标滚轮往上翻一翻。

End

  今天和大家分享了SpringCloud应用context refresh的全过程,耐心看完的小伙伴可以试着去实现个简易的配置中心。完。