SpringCloud @RefreshScope注解源碼層面深入分析
寫在前面
最近在研究Spring Cloud和Spring Cloud Alibaba源碼,在看到Nacos的配置中心的時候,有注意到自動刷新配置的玩法,底層實現(xiàn)依靠@RefreshScope注解。那么為什么要寫這篇文章呢?筆者認為@RefreshScope注解源碼實現(xiàn)跨度特別大,從Spring Cloud Alibaba 到Spring Cloud 到 Spring Boot 再到Spring,筆者認為能夠理解它的源碼實現(xiàn)的話對Spring全家桶的理解又能上升一個檔次~
正文
版本如果下:
Spring:5.3.23
Spring Boot:2.6.3
Spring Cloud:3.1.4
Spring Cloud Alibaba:2021.0.4.0
Nacos:2.0.4
先會用,再深入源碼,所以我們需要從案例出發(fā)。
@RestController
@RefreshScope
public class ConsumerController {
@Value("${consumer.value:moren}")
private String value;
@RequestMapping("/consumer")
public String consumer(){
return value;
}
}

能夠正常使用Nacos服務端的配置數(shù)據(jù),所以,我們發(fā)現(xiàn)在類上存在@RefreshScope注解,所以我們的重心看往@RefreshScope注解。
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Scope("refresh")
@Documented
public @interface RefreshScope {
/**
* @see Scope#proxyMode()
* @return proxy mode
*/
ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
}此時,很清楚的看到@RefreshScope注解聚合了@Scope注解,并且賦予了"refresh". 此時,從Spring慣用玩法,我們需要找到Spring何時解析的@Scope注解,何時解析的@RefreshScope注解。
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
for (String basePackage : basePackages) {
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
// 解析當前類上是否存在@scope注解
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
…………
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
// 生成Scope代理類
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
@Override
public ScopeMetadata resolveScopeMetadata(BeanDefinition definition) {
if (definition instanceof AnnotatedBeanDefinition) {
AnnotatedBeanDefinition annDef = (AnnotatedBeanDefinition) definition;
AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(
annDef.getMetadata(), this.scopeAnnotationType);
}
return metadata;
}
protected Class<? extends Annotation> scopeAnnotationType = Scope.class;上面代碼,是解析@ComponentScan時,掃描指定路徑包的@Component注解類,而我們的@RestController也是一個@Component,并且我們的@RestController類上還有@RefreshScope注解,而@RefreshScope注解又存在@Scope注解。所以,接下來我們分析如何做Scope的代理。
public static BeanDefinitionHolder createScopedProxy(BeanDefinitionHolder definition,
BeanDefinitionRegistry registry, boolean proxyTargetClass) {
// 拿到原類的名字,和對應的BeanDefinition
String originalBeanName = definition.getBeanName();
BeanDefinition targetDefinition = definition.getBeanDefinition();
String targetBeanName = getTargetBeanName(originalBeanName);
// 手動創(chuàng)建Scope代理類的BeanDefinition
// 設置BeanDefinition的BeanClass為ScopedProxyFactoryBean
RootBeanDefinition proxyDefinition = new RootBeanDefinition(ScopedProxyFactoryBean.class);
proxyDefinition.setDecoratedDefinition(new BeanDefinitionHolder(targetDefinition, targetBeanName));
proxyDefinition.setOriginatingBeanDefinition(targetDefinition);
proxyDefinition.setSource(definition.getSource());
proxyDefinition.setRole(targetDefinition.getRole());
…………
// 將代理類設置為自動注入,并且優(yōu)先級最高。
proxyDefinition.setAutowireCandidate(targetDefinition.isAutowireCandidate());
proxyDefinition.setPrimary(targetDefinition.isPrimary());
// 將原類設置為不能自動注入
targetDefinition.setAutowireCandidate(false);
targetDefinition.setPrimary(false);
// 將原類的BeanDefinition注冊到工廠中
registry.registerBeanDefinition(targetBeanName, targetDefinition);
return new BeanDefinitionHolder(proxyDefinition, originalBeanName, definition.getAliases());
}
public static String getTargetBeanName(String originalBeanName) {
return TARGET_NAME_PREFIX + originalBeanName;
}
private static final String TARGET_NAME_PREFIX = "scopedTarget.";這里就是非常關鍵的部分, 有沒有感覺是在"偷天換日"。手動創(chuàng)建BeanDefintion,把ScopedProxyFactoryBean作為BeanClass,并且把原類的name作為手動創(chuàng)建BeanDefintion的name。把原類的BeanDefinition的name加上前綴scopedTarget. 相信Spring底子好的讀者很容易看明白。下面是改變的流程圖。

那么,說了這么多的意義在哪里呢?我們的主題不是動態(tài)刷新么,怎么連注冊中心的影子都沒見到?年輕人,不要著急,文章開頭就說了本篇文章跨度很大~
此時,我們需要從Spring Cloud規(guī)范包入手,相信各位讀者知道,Spring-Cloud-Commons和Spring-Cloud-Context 這兩個Spring Cloud規(guī)范包。從他們的spring.factories自動裝配包可以得知以下信息。

@Bean
@ConditionalOnMissingBean(RefreshScope.class)
public static RefreshScope refreshScope() {
return new RefreshScope();
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnBootstrapEnabled
public LegacyContextRefresher legacyContextRefresher(ConfigurableApplicationContext context, RefreshScope scope,
RefreshProperties properties) {
return new LegacyContextRefresher(context, scope, properties);
}
@Bean
public RefreshEventListener refreshEventListener(ContextRefresher contextRefresher) {
return new RefreshEventListener(contextRefresher);
}從spring.factroies自動裝配規(guī)范文件中我們能看到RefreshAutoConfiguration類,從類名也能獲取到很多信息。再從其中的@Bean中可以看到RefreshScope 、LegacyContextRefresher 、RefreshEventListener三個類。那么下面從筆者的解釋和源碼深入理解這三個類~!
- RefreshScope:擴展@Scope注解,并且CRUD名字為refresh的@Scope注解類,之前介紹的@RefreshScope注解中存在value為refresh的@Scope注解。
- LegacyContextRefresher:用來刷新Environment。
- RefreshEventListener:用來監(jiān)聽ApplicationReadyEvent和RefreshEvent事件。
public class RefreshScope extends GenericScope
implements ApplicationContextAware, ApplicationListener<ContextRefreshedEvent>, Ordered
// RefreshScope的父類
// 實現(xiàn)了BeanFactoryPostProcessor擴展接口
public class GenericScope
implements Scope, BeanFactoryPostProcessor, BeanDefinitionRegistryPostProcessor, DisposableBean
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
// 把當前Scope注冊到BeanFacotry中。
beanFactory.registerScope(this.name, this);
setSerializationId(beanFactory);
}
@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;
if (root.getDecoratedDefinition() != null && root.hasBeanClass()
&& root.getBeanClass() == ScopedProxyFactoryBean.class) {
if(getName().equals(root.getDecoratedDefinition().getBeanDefinition().getScope())) {
// 再一次的偷天換日
root.setBeanClass(LockedScopedProxyFactoryBean.class);
root.getConstructorArgumentValues().addGenericArgumentValue(this);
root.setSynthetic(true);
}
}
}
}
}這里注意到RefreshScope自動注入的類,這里需要區(qū)分@RefreshScope注解和RefreshScope類。它的父類GenericScope實現(xiàn)了BeanFactroyPostProcessor。在postProcessBeanDefinitionRegistry回掉中可以清楚的看到再一次上演了"偷天換日",把ScopedProxyFactoryBean換成了LockedScopedProxyFactoryBean。并且在postProcessBeanFactory回掉中把RefreshScope作為Scope注冊到BeanFacotry工廠中。
// SmartApplicationListener子類監(jiān)聽器
public class RefreshEventListener implements SmartApplicationListener
// 事件回掉。
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationReadyEvent) {
handle((ApplicationReadyEvent) event);
}
// 一定要注意到RefreshEvent事件,非常非常非常重要!!!
// 后續(xù)我們只需要找到哪里發(fā)出的這個事件即可。
else if (event instanceof RefreshEvent) {
handle((RefreshEvent) event);
}
}
public void handle(RefreshEvent event) {
if (this.ready.get()) {
Set<String> keys = this.refresh.refresh();
}
}
public synchronized Set<String> refresh() {
// 刷新Environment上下文。而我們知道配置數(shù)據(jù)是放在Environment中的。
Set<String> keys = refreshEnvironment();
// 調(diào)用scope的refreshAll,從方法就可以知道,要刷新所有的數(shù)據(jù)
this.scope.refreshAll();
return keys;
}- RefreshEventListener作為SmartApplicationListener的子類實現(xiàn)onApplicationEvent方法
- 監(jiān)聽ApplicationReadyEvent和RefreshEvent事件
- 調(diào)用ContextRefresh類(LegacyContextRefresher)的refresh方法
- refreshEnvironment方法刷新Environment,返回發(fā)生改變的配置數(shù)據(jù)
- 調(diào)用RefreshScope類的refreshAll方法,把整個RefreshScope中存放的Bean給destroy。
public synchronized Set<String> refreshEnvironment() {
// 拿到更新前的Environment
Map<String, Object> before = extract(this.context.getEnvironment().getPropertySources());
// 更新Environment。
updateEnvironment();
// 對比更新后和更新前,返回發(fā)生變化的數(shù)據(jù)。
Set<String> keys = changes(before, extract(this.context.getEnvironment().getPropertySources())).keySet();
this.context.publishEvent(new EnvironmentChangeEvent(this.context, keys));
return keys;
}
@Override
protected void updateEnvironment() {
addConfigFilesToEnvironment();
}
ConfigurableApplicationContext addConfigFilesToEnvironment() {
ConfigurableApplicationContext capture = null;
try {
StandardEnvironment environment = copyEnvironment(getContext().getEnvironment());
…………
// 創(chuàng)建一個Spring Boot的啟動器,目的:為了創(chuàng)建出Spring的上下文(這樣整個上下文刷新就可以得到最新的environment)。
// 這里要注意,刷新Spring上下文的environment是手動放入的,
// 也即重新刷新Spring上下文的環(huán)境變量會加載到手動創(chuàng)建的environment中
SpringApplicationBuilder builder = new SpringApplicationBuilder(Empty.class).bannerMode(Banner.Mode.OFF)
.web(WebApplicationType.NONE).environment(environment);
builder.application().setListeners(
Arrays.asList(new BootstrapApplicationListener(), new BootstrapConfigFileApplicationListener()));
capture = builder.run();
MutablePropertySources target = getContext().getEnvironment().getPropertySources();
String targetName = null;
// 把再次刷新Spring上下文environment得到的數(shù)據(jù)賦值到原有的environment環(huán)境變量中(這不就完成了配置數(shù)據(jù)的刷新么)
for (PropertySource<?> source : environment.getPropertySources()) {
if (!this.standardSources.contains(name)) {
if (target.contains(name)) {
target.replace(name, source);
}
else {
if (targetName != null) {
target.addAfter(targetName, source);
// update targetName to preserve ordering
targetName = name;
}
else {
// targetName was null so we are at the start of the list
target.addFirst(source);
targetName = name;
}
}
}
}
}
// finally中把臨時創(chuàng)建出的Application上下文給關閉。
finally {
ConfigurableApplicationContext closeable = capture;
while (closeable != null) {
closeable.close();
if (closeable.getParent() instanceof ConfigurableApplicationContext) {
closeable = (ConfigurableApplicationContext) closeable.getParent();
}
else {
break;
}
}
}
return capture;
}這里就是重點所在。一言以蔽之:手動創(chuàng)建Environment對象,然后重新走一遍上下文刷新,這樣可以得到最新的配置文件,然后把手動創(chuàng)建Environment對象賦值給當前舊的上下文,這樣就完成了動態(tài)刷新配置。詳細流程如下:
- 創(chuàng)建出Environment對象。
- 創(chuàng)建SpringApplicationBuilder對象,也即Spring Boot的啟動器(手動傳入創(chuàng)建出的Environment對象,這樣刷新上下文的時候使用的是這里創(chuàng)建的Environment對象)
- 調(diào)用SpringApplication的run方法,刷新Spring Boot和Spring上下文(刷新完成后返回Spring上下文)。
- 把刷新Spring Boot和Spring上下文得到的Environment對象的屬性拷貝到當前Spring上下文中
- close掉刷新完成后返回Spring上下文的。
public void refreshAll() {
super.destroy();
this.context.publishEvent(new RefreshScopeRefreshedEvent());
}
@Override
public void destroy() {
List<Throwable> errors = new ArrayList<Throwable>();
// 清空緩存
Collection<BeanLifecycleWrapper> wrappers = this.cache.clear();
// 調(diào)用摧毀的回掉函數(shù)
for (BeanLifecycleWrapper wrapper : wrappers) {
try {
Lock lock = this.locks.get(wrapper.getName()).writeLock();
lock.lock();
try {
wrapper.destroy();
}
finally {
lock.unlock();
}
}
catch (RuntimeException e) {
errors.add(e);
}
}
}這里就是把RefreshScope中所有的緩存的給clear,并且回掉Bean的destroy方法。這里我必須再次強調(diào)RefreshScope的作用,就是CRUD 類上標有value為"refresh"的@Scope注解(@RefreshScope不就是么,所以我們的ConsumerController這個Bean就是交給RefreshScope管理,可能到這里筆者有點懵逼,為什么ConsumerController交給RefreshScope管理?他不是Spring的Bean么,不是要進入三級緩存中么???那么下面就是為了解答這個問題。)
我們看到getBean的doGetBean方法創(chuàng)建Bean的流程中。
protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
throws BeansException {
// 創(chuàng)建singleton的bean
if (mbd.isSingleton()) {
…………
}
// 創(chuàng)建Prototype的bean
else if (mbd.isPrototype()) {
…………
}
// 創(chuàng)建其他Scope作用域的bean
else {
String scopeName = mbd.getScope();
if (!StringUtils.hasLength(scopeName)) {
throw new IllegalStateException("No scope name defined for bean '" + beanName + "'");
}
// 根據(jù)name拿到對應的Scope。
Scope scope = this.scopes.get(scopeName);
if (scope == null) {
throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
}
try {
// 調(diào)用scope的get方法,所以對應的scope可以緩存。
Object scopedInstance = scope.get(beanName, () -> {
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);
}
}
}在else代碼塊中會去處理不同Scope作用域的Bean,而在RefreshScope的父類GenericScope中的postProcessBeanFactory回掉方法中(前面有介紹)會把RefreshScope作為一個Scope注冊到BeanFactory中(所以上文的refreshAll方法把RefreshScope的緩存全部clear掉了,下次就會去createBean,就會重新走一遍Spring創(chuàng)建Bean的過程,而環(huán)境變量已經(jīng)更改了,@Value注解的注入就會注入到新的環(huán)境變量中的配置數(shù)據(jù))。
這里把RefreshScope的緩存全部clear掉了,那么總要有一個地方每次都來拿一遍數(shù)據(jù)(這樣緩存在就拿緩存的,緩存不在(緩存不在就代表被clear了,而clear掉了代表有地方發(fā)生了RefreshEvent事件,執(zhí)行了refreshAll方法和refreshEnvironment,緩存被清除了,環(huán)境變量被更改了)就重新createBean,拿到最新的環(huán)境變量)
此時,我們是不是忘了,我們的ConsumerController被代理了呢?上文介紹RefreshScope的父類GenericScope中postProcessBeanDefinitionRegistry方法注冊了LockedScopedProxyFactoryBean。
public static class LockedScopedProxyFactoryBean<S extends GenericScope> extends ScopedProxyFactoryBean
implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
// 反射的Method
Method method = invocation.getMethod();
…………
try {
if (proxy instanceof Advised) {
Advised advised = (Advised) proxy;
ReflectionUtils.makeAccessible(method);
// advised.getTargetSource().getTarget()方法會去getBean
return ReflectionUtils.invokeMethod(method, advised.getTargetSource().getTarget(),
invocation.getArguments());
}
return invocation.proceed();
}
}
}
public class SimpleBeanTargetSource extends AbstractBeanFactoryBasedTargetSource {
@Override
public Object getTarget() throws Exception {
// 看到熟悉的getBean方法了。
return getBeanFactory().getBean(getTargetBeanName());
}
}LockedScopedProxyFactoryBean實現(xiàn)了MethodInterceptor接口,所以只要調(diào)用ConsumerController類中的方法就會走到LockedScopedProxyFactoryBean的invoke方法。恰好在invoke方法中會去調(diào)用BeanFactory的getBean方法。而getBean再到doGetBean,再到上面介紹的else代碼塊中,就走到RefreshScope類中的get方法了。
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
// 拿緩存,如果緩存中沒有就創(chuàng)建
BeanLifecycleWrapper value = this.cache.put(name, new BeanLifecycleWrapper(name, objectFactory));
this.locks.putIfAbsent(name, new ReentrantReadWriteLock());
try {
// 拿緩存,如果緩存中沒有就回掉createBean方法。
return value.getBean();
}
catch (RuntimeException e) {
this.errors.put(name, e);
throw e;
}
}看到get方法其實就恍然大悟了,因為在LockedScopedProxyFactoryBean的invoke方法中會調(diào)用getBean,getBean調(diào)用doGetBean中會調(diào)用scope.get方法,而在get方法中會去拿緩存,如果沒有緩存就會創(chuàng)建一個新的,而新的就會去執(zhí)行createBean方法創(chuàng)建一個新的Bean出來。恰好在這之前緩存已經(jīng)被清除了,環(huán)境變量更新了。最終createBean方法創(chuàng)建的時候@Value注入的就是最新的環(huán)境變量中的配置數(shù)據(jù)。
所以,RefreshScope類+@RefreshScope注解控制了Bean的創(chuàng)建,RefreshEvent事件控制了緩存的clear和環(huán)境變量的更新。但是我們似乎還沒有閉環(huán)RefreshEvent事件在哪里發(fā)出的。
public class NacosContextRefresher
implements ApplicationListener<ApplicationReadyEvent>, ApplicationContextAware {
private void registerNacosListener(final String groupKey, final String dataKey) {
// 一個group、dataid對應一組事件。
String key = NacosPropertySourceRepository.getMapKey(dataKey, groupKey);
// 創(chuàng)建一個Nacos事件監(jiān)聽器
Listener listener = listenerMap.computeIfAbsent(key,
lst -> new AbstractSharedListener() {
@Override
public void innerReceive(String dataId, String group,
String configInfo) {
// 這里發(fā)送了RefreshEvent事件。
applicationContext.publishEvent(
new RefreshEvent(this, null, "Refresh Nacos config"));
}
});
try {
// 添加Nacos的內(nèi)部事件
configService.addListener(dataKey, groupKey, listener);
}
}
}對于Nacos的配置中心代碼不過細講,我們能夠知道RefreshEvent事件是從Nacos內(nèi)部發(fā)出的即可
到此這篇關于SpringCloud @RefreshScope注解源碼層面深入分析的文章就介紹到這了,更多相關SpringCloud @RefreshScope內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
springboot @Configuration和@Componment的區(qū)別及說明
這篇文章主要介紹了springboot @Configuration和@Componment的區(qū)別及說明,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-06-06
記一次Maven項目改造成SpringBoot項目的過程實踐
本文主要介紹了Maven項目改造成SpringBoot項目的過程實踐,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-03-03

