??上篇我們聊了會(huì)bootstrap context的前世今生,以及它是如何通過(guò)PropertySourceLocator來(lái)加載外部配置的。小伙伴們大概也注意到了,所有這些都發(fā)生在應(yīng)用程序的啟動(dòng)過(guò)程中,后續(xù)如果修改了外部配置呢?應(yīng)用程序能感知到嗎?
@Component
// @RefreshScope
public class SomeComponent {
// 初始化時(shí)值就確定了
@Value("${example.key}")
private String value;
// rest are omitted...
}
退一萬(wàn)步講,即使應(yīng)用程序能感知到, 對(duì)于上例來(lái)說(shuō),仍然是不夠的。這是因?yàn)?code>SomeComponent是單例的,表達(dá)式${example.key}只會(huì)在其初始化時(shí)計(jì)算一次,之后無(wú)論配置項(xiàng)example.key如何變化也不能反映到SomeComponent#value上。聰明的你應(yīng)該也想到了,如果有辦法讓SomeComponent在配置項(xiàng)example.key更新之后重新初始化,問(wèn)題不就解決了嗎?想想是不是這樣?還是上例,如果我們打開(kāi)對(duì)@RefreshScope的注釋?zhuān)蜁?huì)發(fā)現(xiàn)問(wèn)題解決了??!很神奇吧,那么@RefreshScope到底有什么魔力呢?
@RefreshScope
# 截取自 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解決了什么問(wèn)題,同時(shí)也簡(jiǎn)要地提到了它的實(shí)現(xiàn)原理:
-
Spring會(huì)為處于refresh scope的目標(biāo)對(duì)象(target)創(chuàng)建代理(proxy) -
refresh scope本身會(huì)緩存目標(biāo)對(duì)象
因此想讓某個(gè)目標(biāo)對(duì)象重新初始化的話(huà),只需要將它從緩存中移除即可。似懂非懂?沒(méi)關(guān)系,咱們來(lái)分析分析(基于spring-cloud-context-2.1.2.RELEASE)。
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Scope("refresh")
public @interface RefreshScope {
/**
* 指明標(biāo)注了 @RefreshScope 注解的對(duì)象需要
* 被代理,默認(rèn)采用 CGLIB 動(dòng)態(tài)生成子類(lèi)的方式
*/
ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
}
@RefreshScope本身只是一個(gè)衍生注解,它基于元注解@Scope,而實(shí)現(xiàn)原理的第1點(diǎn)便是來(lái)自@Scope,換句話(huà)說(shuō)是由容器直接支持的、開(kāi)箱即用的。
@Scope/Scope
@Scope
??@Scope想必大家都很熟悉了,它通過(guò)名稱(chēng)來(lái)引用org.springframework.beans.factory.config.Scope的某個(gè)實(shí)現(xiàn),比如@RefreshScope指定的名稱(chēng)是refresh,其對(duì)應(yīng)的實(shí)現(xiàn)是org.springframework.cloud.context.scope.refresh.RefreshScope。再看#proxyMode(),@RefreshScope指定了默認(rèn)值ScopedProxyMode.TARGET_CLASS,表示希望以CGLIB動(dòng)態(tài)生成子類(lèi)的方式代理目標(biāo)對(duì)象。
??再深入一點(diǎn),在解析@Configuration配置類(lèi)、生成BeanDefinition的過(guò)程中,如果Spring了解到屬于某個(gè)Scope的目標(biāo)對(duì)象需要被代理,就會(huì)額外在容器中注冊(cè)一個(gè)BeanDefinition,不用說(shuō)這個(gè)BeanDefinition自然就代表代理對(duì)象了,相關(guān)代碼可以參見(jiàn):
ClassPathBeanDefinitionScanner#doScan(...)AnnotatedBeanDefinitionReader#doRegisterBean(...)ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForBeanMethod(...)ConfigurationClassBeanDefinitionReader#registerBeanDefinitionForImportedConfigurationClass(...)
這些調(diào)用最終都指向了ScopedProxyUtils#createScopedProxy(...):
public static BeanDefinitionHolder createScopedProxy(BeanDefinitionHolder definition,
BeanDefinitionRegistry registry,
boolean proxyTargetClass) {
// 原名
String originalBeanName = definition.getBeanName();
// 原始定義
BeanDefinition targetDefinition = definition.getBeanDefinition();
// 在原名的基礎(chǔ)上增加 scopedTarget. 前綴
String targetBeanName = getTargetBeanName(originalBeanName);
// 創(chuàng)建一個(gè)生成代理的 BeanDefinition 來(lái)隱藏原始定義
// 代理對(duì)象由 ScopedProxyFactoryBean 生成
RootBeanDefinition proxyDefinition = new RootBeanDefinition(ScopedProxyFactoryBean.class);
proxyDefinition.setDecoratedDefinition(new BeanDefinitionHolder(targetDefinition, targetBeanName));
proxyDefinition.setOriginatingBeanDefinition(targetDefinition);
proxyDefinition.setSource(definition.getSource());
proxyDefinition.setRole(targetDefinition.getRole());
// 設(shè)置 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);
}
// 代理對(duì)象繼承原始對(duì)象的配置
proxyDefinition.setAutowireCandidate(targetDefinition.isAutowireCandidate());
proxyDefinition.setPrimary(targetDefinition.isPrimary());
if (targetDefinition instanceof AbstractBeanDefinition) {
proxyDefinition.copyQualifiersFrom((AbstractBeanDefinition) targetDefinition);
}
// 代理對(duì)象作為自動(dòng)注入的第一選擇
// 如此,原始對(duì)象就被隱藏起來(lái)了
targetDefinition.setAutowireCandidate(false);
targetDefinition.setPrimary(false);
// 注冊(cè)原始對(duì)象,注意這里它的 beanName 已經(jīng)修改過(guò)了
registry.registerBeanDefinition(targetBeanName, targetDefinition);
// 返回代理對(duì)象的定義,它繼承了原始對(duì)象的 beanName
return new BeanDefinitionHolder(proxyDefinition, originalBeanName, definition.getAliases());
}
注釋還是比較詳細(xì)的。最后,ScopedProxyFactoryBean創(chuàng)建代理對(duì)象的邏輯就完全是spring-aop的內(nèi)容了,感興趣的話(huà)可以看看這里,AOP再說(shuō)下去就沒(méi)完沒(méi)了了。
Scope
??為了內(nèi)容的連貫性,咱們也說(shuō)說(shuō)org.springframework.beans.factory.config.Scope吧。 它最開(kāi)始是為Web環(huán)境設(shè)計(jì)的(session/request scope),不過(guò)其接口本身是足夠通用的,可以任意擴(kuò)展,相關(guān)處理可以參見(jiàn)AbstractBeanFactory#doGetBean(...):
public interface Scope {
// 獲取實(shí)例
Object get(String name, ObjectFactory<?> objectFactory);
// 移除實(shí)例
@Nullable
Object remove(String name);
// 銷(xiāo)毀實(shí)例時(shí)額外執(zhí)行的邏輯
// DisposableBean 這類(lèi)接口只適用于 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...
// 通過(guò) scopeName 找到對(duì)應(yīng)的 Scope 實(shí)現(xiàn)
// 對(duì)我們的 refresh scope 來(lái)說(shuō),它的實(shí)現(xiàn)是 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 {
// 從對(duì)應(yīng)的 Scope 中獲取實(shí)例
// 第二個(gè)參數(shù)是一個(gè)工廠(chǎng)接口,負(fù)責(zé)創(chuàng)建新的實(shí)例
Object scopedInstance = scope.get(beanName, () -> {
// 循環(huán)依賴(lài)相關(guān)
beforePrototypeCreation(beanName);
try {
// createBean(...) 負(fù)責(zé)創(chuàng)建實(shí)例
// 并賦予它 Spring 語(yǔ)義
return createBean(beanName, mbd, args);
} finally {
afterPrototypeCreation(beanName);
}
});
// 對(duì)于 FactoryBean 來(lái)說(shuō),我們希望獲取的是它創(chuàng)建的對(duì)象,而不是 FactoryBean 本身
bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
} catch (IllegalStateException ex) {
throw new BeanCreationException(ex);
}
// omitted...
}
RefreshScope
??實(shí)現(xiàn)原理的第2點(diǎn)——緩存,倒是由org.springframework.cloud.context.scope.refresh.RefreshScope直接提供的。緩存層也有著自己的抽象——ScopeCache,不過(guò)定義和實(shí)現(xiàn)都比較簡(jiǎn)單,大家就認(rèn)為它是個(gè)大Map好了。看Scope接口的定義,Object get(String name, ObjectFactory<?> objectFactory)必然是和緩存交互的重點(diǎn),不難發(fā)現(xiàn)這是在RefreshScope的基類(lèi)GenericScope中實(shí)現(xiàn)的。
get(String name, ObjectFactory<?> objectFactory)
public class GenericScope implements Scope, BeanFactoryPostProcessor,
BeanDefinitionRegistryPostProcessor, DisposableBean {
// omitted...
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
// 獲取緩存中的 BeanLifecycleWrapper
// 沒(méi)有的話(huà)就添加一個(gè)
BeanLifecycleWrapper value = this.cache.put(name,
new BeanLifecycleWrapper(name, objectFactory));
// 鎖主要是為了避免 bean 在使用中被銷(xiāo)毀
this.locks.putIfAbsent(name, new ReentrantReadWriteLock());
try {
// 獲取目標(biāo)對(duì)象(可能是新創(chuàng)建出來(lái)的)
// 注意,這個(gè)對(duì)象就是被代理的那一個(gè)
return value.getBean();
} catch (RuntimeException e) {
this.errors.put(name, e);
throw e;
}
}
// omitted...
private static class BeanLifecycleWrapper {
// bean name
private final String name;
// 創(chuàng)建 bean 的工廠(chǎng)
private final ObjectFactory<?> objectFactory;
// 緩存的創(chuàng)建好的 bean
private volatile Object bean;
// 銷(xiāo)毀時(shí)需要被執(zhí)行的額外邏輯
private Runnable callback;
// omitted...
public Object getBean() {
// double-checked locking
if (this.bean == null) {
synchronized (this.name) {
if (this.bean == null) {
// 實(shí)在沒(méi)有就通過(guò)工廠(chǎng)創(chuàng)建并緩存
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...
}
}
非常常見(jiàn)的先查緩存,沒(méi)有再創(chuàng)建的邏輯,留意一下這個(gè)被創(chuàng)建出來(lái)的對(duì)象其實(shí)就是被代理的對(duì)象。GenericScope還實(shí)現(xiàn)了幾個(gè)其它接口,我們也來(lái)看看。
postProcessBeanFactory(...)
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
throws BeansException {
this.beanFactory = beanFactory;
beanFactory.registerScope(this.name, this);
setSerializationId(beanFactory);
}
實(shí)現(xiàn)BeanFactoryPostProcessor是為了在BeanFactory中注冊(cè)自己。
postProcessBeanDefinitionRegistry(...)
@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 是專(zhuān)門(mén)給位于 Scope 中的 bean 創(chuàng)建代理的
if (root.getDecoratedDefinition() != null && root.hasBeanClass()
&& root.getBeanClass() == ScopedProxyFactoryBean.class) {
// 再檢查是不是當(dāng)前 Scope
if (getName().equals(root.getDecoratedDefinition().getBeanDefinition()
.getScope())) {
// 到這里可以確定是給位于當(dāng)前 Scope 中的 bean 創(chuàng)建代理的
root.setBeanClass(LockedScopedProxyFactoryBean.class);
// 相當(dāng)于給 LockedScopedProxyFactoryBean 的構(gòu)造函數(shù)傳參
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();
}
// 獲取代理對(duì)象
Object proxy = getObject();
// 獲取對(duì)應(yīng)的鎖
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 這么個(gè)類(lèi)
// 主要就是為了用上這把鎖,單獨(dú)看這個(gè)類(lèi),可能不
// 太好理解為什么要上鎖,結(jié)合 destroy() 就好理解了
// destroy()會(huì)銷(xiāo)毀目標(biāo)對(duì)象,如果調(diào)用的過(guò)程中,目標(biāo)
// 對(duì)象被銷(xiāo)毀了,這個(gè)調(diào)用就無(wú)法繼續(xù)下去了
// 這里用的讀鎖,destroy 時(shí)自然要用寫(xiě)鎖了
Lock lock = readWriteLock.readLock();
lock.lock();
try {
// 默認(rèn)情況下,所有的代理都可以轉(zhuǎn)成 Advised
if (proxy instanceof Advised) {
Advised advised = (Advised) proxy;
ReflectionUtils.makeAccessible(method);
// 將對(duì)代理對(duì)象的請(qǐng)求轉(zhuǎn)發(fā)給目標(biāo)對(duì)象
return ReflectionUtils.invokeMethod(method,
advised.getTargetSource().getTarget(),
invocation.getArguments());
}
return invocation.proceed();
} catch (UndeclaredThrowableException e) {
throw e.getUndeclaredThrowable();
} finally {
lock.unlock();
}
}
}
實(shí)現(xiàn)BeanDefinitionRegistryPostProcessor是為了阻止目標(biāo)對(duì)象在使用的同時(shí)被銷(xiāo)毀。
destroy()
@Override
public void destroy() {
List<Throwable> errors = new ArrayList<Throwable>();
// 清空緩存
Collection<BeanLifecycleWrapper> wrappers = this.cache.clear();
// 逐一銷(xiāo)毀
for (BeanLifecycleWrapper wrapper : wrappers) {
try {
// 使用寫(xiě)鎖,如果目標(biāo)對(duì)象在使用中,這里就會(huì)被阻塞
Lock lock = this.locks.get(wrapper.getName()).writeLock();
lock.lock();
try {
// 調(diào)用之前注冊(cè)的 Destruction Callback
wrapper.destroy();
} finally {
lock.unlock();
}
} catch (RuntimeException e) {
errors.add(e);
}
}
if (!errors.isEmpty()) {
throw wrapIfNecessary(errors.get(0));
}
this.errors.clear();
}
實(shí)現(xiàn)DisposableBean是為了在當(dāng)前Scope被銷(xiāo)毀時(shí)也銷(xiāo)毀它所緩存的目標(biāo)對(duì)象。destroy()還有一個(gè)重載版本,攜帶beanName作為參數(shù),顯然這個(gè)版本的作用是銷(xiāo)毀某個(gè)特定的目標(biāo)對(duì)象。既然目標(biāo)對(duì)象都通過(guò)destroy()銷(xiāo)毀了,下次訪(fǎng)問(wèn)它的時(shí)候不就可以重新初始化了嗎?其關(guān)聯(lián)的表達(dá)式、依賴(lài)不都可以重新計(jì)算和注入了嗎?換句話(huà)說(shuō),刷新的目的已經(jīng)達(dá)到了。
refreshAll/refresh(...)
public boolean refresh(String name) {
// 還記得目標(biāo)對(duì)象其實(shí)被改過(guò)名嗎
if (!name.startsWith(SCOPED_TARGET_PREFIX)) {
name = SCOPED_TARGET_PREFIX + name;
}
// 銷(xiāo)毀對(duì)應(yīng)的目標(biāo)對(duì)象
if (super.destroy(name)) {
// 發(fā)出事件
this.context.publishEvent(new RefreshScopeRefreshedEvent(name));
return true;
}
return false;
}
public void refreshAll() {
// 銷(xiāo)毀RefreshScope中所有的目標(biāo)對(duì)象
super.destroy();
// 發(fā)出事件
this.context.publishEvent(new RefreshScopeRefreshedEvent());
}
??回到RefreshScope本身,我們想要的功能其實(shí)在它的父類(lèi)GenericScope中已經(jīng)實(shí)現(xiàn)了,RefreshScope僅僅添加了兩個(gè)符合其語(yǔ)義的方法。至此,RefreshScope可以說(shuō)再無(wú)秘密可言,不過(guò)對(duì)于應(yīng)用程序如何感知外部配置的變化,我們?nèi)晕醋鞒鼋獯?。不著急,往下看?/p>
RefreshEventListener
??RefreshEventListener是這么一個(gè)監(jiān)聽(tīng)器,它在應(yīng)用程序啟動(dòng)之后,如果接收到RefreshEvent,就使用ContextRefresher來(lái)進(jìn)行刷新。至于它本身的代碼呢比較簡(jiǎn)單,我們就跳過(guò)它直接看ContextRefresher吧。
public class ContextRefresher {
public synchronized Set<String> refresh() {
// 刷新 Environment,如此便能感受到外部配置的變化了
Set<String> keys = refreshEnvironment();
// 銷(xiāo)毀 RefreshScope 中的所有目標(biāo)對(duì)象,如此下次使用
// 這些對(duì)象時(shí)它們就能應(yīng)用上最新的配置了
refreshScope.refreshAll();
return keys;
}
}
等等!從上面的描述中大家明白如何讓?xiě)?yīng)用程序感知到變化嗎?沒(méi)錯(cuò),只需要使用ApplicationContext發(fā)出一個(gè)RefreshEvent就可以了,很簡(jiǎn)單吧?回到ContextRefresher。
public synchronized Set<String> refreshEnvironment() {
// 將當(dāng)前 Environment 中的所有配置項(xiàng)都取出來(lái)
Map<String, Object> before = extract( this.context.getEnvironment().getPropertySources());
// 重新加載一次外部配置
addConfigFilesToEnvironment();
// 計(jì)算重新加載過(guò)外部配置后有哪些配置項(xiàng)發(fā)生了變化
Set<String> keys = changes(before, extract(this.context.getEnvironment().getPropertySources())).keySet();
// 發(fā)出 EnvironmentChangeEvent 事件
this.context.publishEvent(new EnvironmentChangeEvent(this.context, keys));
return keys;
}
重點(diǎn)自然是addConfigFilesToEnvironment()了。
/* For testing. */ ConfigurableApplicationContext addConfigFilesToEnvironment() {
ConfigurableApplicationContext capture = null;
try {
// 復(fù)制一份當(dāng)前的 Environment (有選擇的)
StandardEnvironment environment = copyEnvironment(this.context.getEnvironment());
// 復(fù)用 SpringBoot 應(yīng)用的啟動(dòng)流程
SpringApplicationBuilder builder = new SpringApplicationBuilder(Empty.class)
.bannerMode(Mode.OFF).web(WebApplicationType.NONE)
// 將這份拷貝作為新 Context 的環(huán)境
.environment(environment);
// 設(shè)置兩個(gè)必要的監(jiān)聽(tīng)器
builder.application()
// 用來(lái)加載外部配置
.setListeners(Arrays.asList(new BootstrapApplicationListener(),
// 用來(lái)讀取本地配置
new ConfigFileApplicationListener()));
capture = builder.run();
if (environment.getPropertySources().contains(REFRESH_ARGS_PROPERTY_SOURCE)) {
environment.getPropertySources().remove(REFRESH_ARGS_PROPERTY_SOURCE);
}
// 當(dāng)前的
MutablePropertySources target = this.context.getEnvironment()
.getPropertySources();
String targetName = null;
// 遍歷重新讀取過(guò)外部配置的
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 {
// 無(wú)則添加
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;
}
}
}
} // 循環(huán)結(jié)束時(shí),當(dāng)前 Environment 也更新好了,里面的數(shù)據(jù)都是新數(shù)據(jù)了
} finally {
// 這個(gè) ApplicationContext 只是臨時(shí)用來(lái)加載外部配置的
// 用完就可以丟了
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;
}
這個(gè)方法的內(nèi)容我們?cè)?a target="_blank">上篇才分析過(guò),看過(guò)的小伙伴們應(yīng)該還有點(diǎn)兒印象吧。至于refreshScope.refreshAll(),請(qǐng)各位小伙伴動(dòng)動(dòng)鼠標(biāo)滾輪往上翻一翻。
End
??今天和大家分享了SpringCloud應(yīng)用context refresh的全過(guò)程,耐心看完的小伙伴可以試著去實(shí)現(xiàn)個(gè)簡(jiǎn)易的配置中心。完。