SpringCloud @RefreshScope注解源碼層面深入分析
寫在前面
最近在研究Spring Cloud和Spring Cloud Alibaba源碼,在看到Nacos的配置中心的時(shí)候,有注意到自動(dòng)刷新配置的玩法,底層實(shí)現(xiàn)依靠@RefreshScope注解。那么為什么要寫這篇文章呢?筆者認(rèn)為@RefreshScope注解源碼實(shí)現(xiàn)跨度特別大,從Spring Cloud Alibaba 到Spring Cloud 到 Spring Boot 再到Spring,筆者認(rèn)為能夠理解它的源碼實(shí)現(xiàn)的話對(duì)Spring全家桶的理解又能上升一個(gè)檔次~
正文
版本如果下:
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
先會(huì)用,再深入源碼,所以我們需要從案例出發(fā)。
@RestController @RefreshScope public class ConsumerController { @Value("${consumer.value:moren}") private String value; @RequestMapping("/consumer") public String consumer(){ return value; } }
能夠正常使用Nacos服務(wù)端的配置數(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; }
此時(shí),很清楚的看到@RefreshScope注解聚合了@Scope注解,并且賦予了"refresh". 此時(shí),從Spring慣用玩法,我們需要找到Spring何時(shí)解析的@Scope注解,何時(shí)解析的@RefreshScope注解。
protected Set<BeanDefinitionHolder> doScan(String... basePackages) { for (String basePackage : basePackages) { Set<BeanDefinition> candidates = findCandidateComponents(basePackage); for (BeanDefinition candidate : candidates) { // 解析當(dāng)前類上是否存在@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時(shí),掃描指定路徑包的@Component注解類,而我們的@RestController也是一個(gè)@Component,并且我們的@RestController類上還有@RefreshScope注解,而@RefreshScope注解又存在@Scope注解。所以,接下來(lái)我們分析如何做Scope的代理。
public static BeanDefinitionHolder createScopedProxy(BeanDefinitionHolder definition, BeanDefinitionRegistry registry, boolean proxyTargetClass) { // 拿到原類的名字,和對(duì)應(yīng)的BeanDefinition String originalBeanName = definition.getBeanName(); BeanDefinition targetDefinition = definition.getBeanDefinition(); String targetBeanName = getTargetBeanName(originalBeanName); // 手動(dòng)創(chuàng)建Scope代理類的BeanDefinition // 設(shè)置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()); ………… // 將代理類設(shè)置為自動(dòng)注入,并且優(yōu)先級(jí)最高。 proxyDefinition.setAutowireCandidate(targetDefinition.isAutowireCandidate()); proxyDefinition.setPrimary(targetDefinition.isPrimary()); // 將原類設(shè)置為不能自動(dòng)注入 targetDefinition.setAutowireCandidate(false); targetDefinition.setPrimary(false); // 將原類的BeanDefinition注冊(cè)到工廠中 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.";
這里就是非常關(guān)鍵的部分, 有沒(méi)有感覺(jué)是在"偷天換日"。手動(dòng)創(chuàng)建BeanDefintion,把ScopedProxyFactoryBean作為BeanClass,并且把原類的name作為手動(dòng)創(chuàng)建BeanDefintion的name。把原類的BeanDefinition的name加上前綴scopedTarget. 相信Spring底子好的讀者很容易看明白。下面是改變的流程圖。
那么,說(shuō)了這么多的意義在哪里呢?我們的主題不是動(dòng)態(tài)刷新么,怎么連注冊(cè)中心的影子都沒(méi)見(jiàn)到?年輕人,不要著急,文章開(kāi)頭就說(shuō)了本篇文章跨度很大~
此時(shí),我們需要從Spring Cloud規(guī)范包入手,相信各位讀者知道,Spring-Cloud-Commons和Spring-Cloud-Context 這兩個(gè)Spring Cloud規(guī)范包。從他們的spring.factories自動(dòng)裝配包可以得知以下信息。
@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自動(dòng)裝配規(guī)范文件中我們能看到RefreshAutoConfiguration類,從類名也能獲取到很多信息。再?gòu)钠渲械腀Bean中可以看到RefreshScope 、LegacyContextRefresher 、RefreshEventListener三個(gè)類。那么下面從筆者的解釋和源碼深入理解這三個(gè)類~!
- RefreshScope:擴(kuò)展@Scope注解,并且CRUD名字為refresh的@Scope注解類,之前介紹的@RefreshScope注解中存在value為refresh的@Scope注解。
- LegacyContextRefresher:用來(lái)刷新Environment。
- RefreshEventListener:用來(lái)監(jiān)聽(tīng)ApplicationReadyEvent和RefreshEvent事件。
public class RefreshScope extends GenericScope implements ApplicationContextAware, ApplicationListener<ContextRefreshedEvent>, Ordered // RefreshScope的父類 // 實(shí)現(xiàn)了BeanFactoryPostProcessor擴(kuò)展接口 public class GenericScope implements Scope, BeanFactoryPostProcessor, BeanDefinitionRegistryPostProcessor, DisposableBean @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { this.beanFactory = beanFactory; // 把當(dāng)前Scope注冊(cè)到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自動(dòng)注入的類,這里需要區(qū)分@RefreshScope注解和RefreshScope類。它的父類GenericScope實(shí)現(xiàn)了BeanFactroyPostProcessor。在postProcessBeanDefinitionRegistry回掉中可以清楚的看到再一次上演了"偷天換日",把ScopedProxyFactoryBean換成了LockedScopedProxyFactoryBean。并且在postProcessBeanFactory回掉中把RefreshScope作為Scope注冊(cè)到BeanFacotry工廠中。
// SmartApplicationListener子類監(jiān)聽(tīng)器 public class RefreshEventListener implements SmartApplicationListener // 事件回掉。 @Override public void onApplicationEvent(ApplicationEvent event) { if (event instanceof ApplicationReadyEvent) { handle((ApplicationReadyEvent) event); } // 一定要注意到RefreshEvent事件,非常非常非常重要?。?! // 后續(xù)我們只需要找到哪里發(fā)出的這個(gè)事件即可。 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的子類實(shí)現(xiàn)onApplicationEvent方法
- 監(jiān)聽(tīng)ApplicationReadyEvent和RefreshEvent事件
- 調(diào)用ContextRefresh類(LegacyContextRefresher)的refresh方法
- refreshEnvironment方法刷新Environment,返回發(fā)生改變的配置數(shù)據(jù)
- 調(diào)用RefreshScope類的refreshAll方法,把整個(gè)RefreshScope中存放的Bean給destroy。
public synchronized Set<String> refreshEnvironment() { // 拿到更新前的Environment Map<String, Object> before = extract(this.context.getEnvironment().getPropertySources()); // 更新Environment。 updateEnvironment(); // 對(duì)比更新后和更新前,返回發(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)建一個(gè)Spring Boot的啟動(dòng)器,目的:為了創(chuàng)建出Spring的上下文(這樣整個(gè)上下文刷新就可以得到最新的environment)。 // 這里要注意,刷新Spring上下文的environment是手動(dòng)放入的, // 也即重新刷新Spring上下文的環(huán)境變量會(huì)加載到手動(dòng)創(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中把臨時(shí)創(chuàng)建出的Application上下文給關(guān)閉。 finally { ConfigurableApplicationContext closeable = capture; while (closeable != null) { closeable.close(); if (closeable.getParent() instanceof ConfigurableApplicationContext) { closeable = (ConfigurableApplicationContext) closeable.getParent(); } else { break; } } } return capture; }
這里就是重點(diǎn)所在。一言以蔽之:手動(dòng)創(chuàng)建Environment對(duì)象,然后重新走一遍上下文刷新,這樣可以得到最新的配置文件,然后把手動(dòng)創(chuàng)建Environment對(duì)象賦值給當(dāng)前舊的上下文,這樣就完成了動(dòng)態(tài)刷新配置。詳細(xì)流程如下:
- 創(chuàng)建出Environment對(duì)象。
- 創(chuàng)建SpringApplicationBuilder對(duì)象,也即Spring Boot的啟動(dòng)器(手動(dòng)傳入創(chuàng)建出的Environment對(duì)象,這樣刷新上下文的時(shí)候使用的是這里創(chuàng)建的Environment對(duì)象)
- 調(diào)用SpringApplication的run方法,刷新Spring Boot和Spring上下文(刷新完成后返回Spring上下文)。
- 把刷新Spring Boot和Spring上下文得到的Environment對(duì)象的屬性拷貝到當(dāng)前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方法。這里我必須再次強(qiáng)調(diào)RefreshScope的作用,就是CRUD 類上標(biāo)有value為"refresh"的@Scope注解(@RefreshScope不就是么,所以我們的ConsumerController這個(gè)Bean就是交給RefreshScope管理,可能到這里筆者有點(diǎn)懵逼,為什么ConsumerController交給RefreshScope管理?他不是Spring的Bean么,不是要進(jìn)入三級(jí)緩存中么???那么下面就是為了解答這個(gè)問(wèn)題。)
我們看到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拿到對(duì)應(yīng)的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方法,所以對(duì)應(yīng)的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代碼塊中會(huì)去處理不同Scope作用域的Bean,而在RefreshScope的父類GenericScope中的postProcessBeanFactory回掉方法中(前面有介紹)會(huì)把RefreshScope作為一個(gè)Scope注冊(cè)到BeanFactory中(所以上文的refreshAll方法把RefreshScope的緩存全部clear掉了,下次就會(huì)去createBean,就會(huì)重新走一遍Spring創(chuàng)建Bean的過(guò)程,而環(huán)境變量已經(jīng)更改了,@Value注解的注入就會(huì)注入到新的環(huán)境變量中的配置數(shù)據(jù))。
這里把RefreshScope的緩存全部clear掉了,那么總要有一個(gè)地方每次都來(lái)拿一遍數(shù)據(jù)(這樣緩存在就拿緩存的,緩存不在(緩存不在就代表被clear了,而clear掉了代表有地方發(fā)生了RefreshEvent事件,執(zhí)行了refreshAll方法和refreshEnvironment,緩存被清除了,環(huán)境變量被更改了)就重新createBean,拿到最新的環(huán)境變量)
此時(shí),我們是不是忘了,我們的ConsumerController被代理了呢?上文介紹RefreshScope的父類GenericScope中postProcessBeanDefinitionRegistry方法注冊(cè)了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()方法會(huì)去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實(shí)現(xiàn)了MethodInterceptor接口,所以只要調(diào)用ConsumerController類中的方法就會(huì)走到LockedScopedProxyFactoryBean的invoke方法。恰好在invoke方法中會(huì)去調(diào)用BeanFactory的getBean方法。而getBean再到doGetBean,再到上面介紹的else代碼塊中,就走到RefreshScope類中的get方法了。
@Override public Object get(String name, ObjectFactory<?> objectFactory) { // 拿緩存,如果緩存中沒(méi)有就創(chuàng)建 BeanLifecycleWrapper value = this.cache.put(name, new BeanLifecycleWrapper(name, objectFactory)); this.locks.putIfAbsent(name, new ReentrantReadWriteLock()); try { // 拿緩存,如果緩存中沒(méi)有就回掉createBean方法。 return value.getBean(); } catch (RuntimeException e) { this.errors.put(name, e); throw e; } }
看到get方法其實(shí)就恍然大悟了,因?yàn)樵贚ockedScopedProxyFactoryBean的invoke方法中會(huì)調(diào)用getBean,getBean調(diào)用doGetBean中會(huì)調(diào)用scope.get方法,而在get方法中會(huì)去拿緩存,如果沒(méi)有緩存就會(huì)創(chuàng)建一個(gè)新的,而新的就會(huì)去執(zhí)行createBean方法創(chuàng)建一個(gè)新的Bean出來(lái)。恰好在這之前緩存已經(jīng)被清除了,環(huán)境變量更新了。最終createBean方法創(chuàng)建的時(shí)候@Value注入的就是最新的環(huán)境變量中的配置數(shù)據(jù)。
所以,RefreshScope類+@RefreshScope注解控制了Bean的創(chuàng)建,RefreshEvent事件控制了緩存的clear和環(huán)境變量的更新。但是我們似乎還沒(méi)有閉環(huán)RefreshEvent事件在哪里發(fā)出的。
public class NacosContextRefresher implements ApplicationListener<ApplicationReadyEvent>, ApplicationContextAware { private void registerNacosListener(final String groupKey, final String dataKey) { // 一個(gè)group、dataid對(duì)應(yīng)一組事件。 String key = NacosPropertySourceRepository.getMapKey(dataKey, groupKey); // 創(chuàng)建一個(gè)Nacos事件監(jiān)聽(tīng)器 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); } } }
對(duì)于Nacos的配置中心代碼不過(guò)細(xì)講,我們能夠知道RefreshEvent事件是從Nacos內(nèi)部發(fā)出的即可
到此這篇關(guān)于SpringCloud @RefreshScope注解源碼層面深入分析的文章就介紹到這了,更多相關(guān)SpringCloud @RefreshScope內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springboot @Configuration和@Componment的區(qū)別及說(shuō)明
這篇文章主要介紹了springboot @Configuration和@Componment的區(qū)別及說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-06-06記一次Maven項(xiàng)目改造成SpringBoot項(xiàng)目的過(guò)程實(shí)踐
本文主要介紹了Maven項(xiàng)目改造成SpringBoot項(xiàng)目的過(guò)程實(shí)踐,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03SpringBoot使用Log4j的知識(shí)點(diǎn)整理
在本篇文章里小編給大家整理的是關(guān)于SpringBoot使用Log4j的知識(shí)點(diǎn),需要的朋友們可以參考學(xué)習(xí)下。2020-02-02