實現機制:#
bean 屬性的自動刷新原理:#
在 spring2 的時候,新增了自定義作用域,也就是除了單例和原型,新增了 scope 註解和接口,以便提高 bean 的儲存生命週期,與它相關的接口和類為
ConfigurableBeanFactory.registerScope,
CustomScopeConfigurer,
org.springframework.aop.scope.ScopedProxyFactoryBean, org.springframework.web.context.request.RequestScope,
org.springframework.web.context.request.SessionScope
實現的基本原理為
- 包掃描的時候,識別到 @scope 接口後將 beandefinition 修改為 ScopedProxyFactoryBean,具體在
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
for (String basePackage : basePackages) {
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
if (candidate instanceof AbstractBeanDefinition abstractBeanDefinition) {
postProcessBeanDefinition(abstractBeanDefinition, beanName);
}
if (candidate instanceof AnnotatedBeanDefinition annotatedBeanDefinition) {
AnnotationConfigUtils.processCommonDefinitionAnnotations(annotatedBeanDefinition);
}
// 也就是在這裡,找到所有scope註解的類,將定義更換為ScopedProxyFactoryBean
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
跟進去代碼發現
public static BeanDefinitionHolder createScopedProxy(BeanDefinitionHolder definition, BeanDefinitionRegistry registry, boolean proxyTargetClass) {
String originalBeanName = definition.getBeanName();
BeanDefinition targetDefinition = definition.getBeanDefinition();
String targetBeanName = getTargetBeanName(originalBeanName);
// 可以發現,在這裡創建了ScopedProxyFactoryBean的bean定義
RootBeanDefinition proxyDefinition = new RootBeanDefinition(ScopedProxyFactoryBean.class);
// 將原始類設置進去,也就是被scope註解的類
proxyDefinition.setDecoratedDefinition(new BeanDefinitionHolder(targetDefinition, targetBeanName));
proxyDefinition.setOriginatingBeanDefinition(targetDefinition);
proxyDefinition.setSource(definition.getSource());
proxyDefinition.setRole(targetDefinition.getRole());
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 abd) {
proxyDefinition.copyQualifiersFrom(abd);
}
targetDefinition.setAutowireCandidate(false);
targetDefinition.setPrimary(false);
registry.registerBeanDefinition(targetBeanName, targetDefinition);
return new BeanDefinitionHolder(proxyDefinition, originalBeanName, definition.getAliases());
}
那麼,ScopedProxyFactoryBean 做了什麼呢?為什麼要修改為這個類,我們看看這個類的結構
// 可以看到,實現了FactoryBean(肯定的,不然怎麼獲取bean,只是將原始bean作了進一步包裝),還有BeanFactoryAware,這個是為了獲取到BeanFactory從而獲取到實例bean,AopInfrastructureBean這個接口表明這個類可以實現aop的邏輯,標記不會被aop自己包裝,實現這個接口的還非常重要的InfrastructureAdvisorAutoProxyCreator和AspectJAwareAdvisorAutoProxyCreator,一個是spring自帶的aop,一個是啟用AspectJ的aop,這部分內容也很有趣,有時間再更新一篇關於springaop的文章
public class ScopedProxyFactoryBean extends ProxyConfig implements FactoryBean<Object>, BeanFactoryAware, AopInfrastructureBean
既然實現了 FactoryBean,我們看看這裡獲取的 bean 做了什麼處理,很簡單,首先,在設置 BeanFactory 的時候生成代理
public void setBeanFactory(BeanFactory beanFactory) {
if (beanFactory instanceof ConfigurableBeanFactory cbf) {
this.scopedTargetSource.setBeanFactory(beanFactory);
ProxyFactory pf = new ProxyFactory();
pf.copyFrom(this);
pf.setTargetSource(this.scopedTargetSource);
Assert.notNull(this.targetBeanName, "Property 'targetBeanName' is required");
Class beanType = beanFactory.getType(this.targetBeanName);
if (beanType == null) {
throw new IllegalStateException("Cannot create scoped proxy for bean '" + this.targetBeanName + "': Target type could not be determined at the time of proxy creation.");
} else {
if (!this.isProxyTargetClass() || beanType.isInterface() || Modifier.isPrivate(beanType.getModifiers())) {
pf.setInterfaces(ClassUtils.getAllInterfacesForClass(beanType, cbf.getBeanClassLoader()));
}
ScopedObject scopedObject = new DefaultScopedObject(cbf, this.scopedTargetSource.getTargetBeanName());
pf.addAdvice(new DelegatingIntroductionInterceptor(scopedObject));
pf.addInterface(AopInfrastructureBean.class);
this.proxy = pf.getProxy(cbf.getBeanClassLoader());
}
} else {
throw new IllegalStateException("Not running in a ConfigurableBeanFactory: " + beanFactory);
}
}
然後獲取 bean 的時候返回代理
public Object getObject() {
if (this.proxy == null) {
throw new FactoryBeanNotInitializedException();
} else {
return this.proxy;
}
}
生成的代理有什麼用呢?進一步看看代理類的邏輯,其實就是在每次調用方法的時候,會先進入 aop 回調方法,去獲取原始對象
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object oldProxy = null;
boolean setProxyContext = false;
Object target = null;
TargetSource targetSource = this.advised.getTargetSource();
try {
if (this.advised.exposeProxy) {
// Make invocation available if necessary.
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
//也就是這裡,獲取beanfactory獲取原始對象
target = targetSource.getTarget();
Class<?> targetClass = (target != null ? target.getClass() : null);
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
Object retVal;
if (chain.isEmpty()) {
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
}
else {
// We need to create a method invocation...
retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
}
return processReturnType(proxy, target, method, args, retVal);
}
finally {
if (target != null && !targetSource.isStatic()) {
targetSource.releaseTarget(target);
}
if (setProxyContext) {
// Restore old proxy.
AopContext.setCurrentProxy(oldProxy);
}
}
}
那麼問題回到開始,哪裡起到了動態刷新呢?其實如果在配置中心的配置改變時,將 bean 銷毀掉,那麼下次調用的時候去獲取不是就可以獲取最新的 bean 嗎,確實,springcloud 就是這麼做的,springcloud 使用 refreshscope 註解配合 RefreshScope 類,refreshscope 註解將包裝為 ScopedProxyFactoryBean,RefreshScope 類負責處理 bean 的生命週期,也就是說,獲取的 bean 不再是去原始的 beanfactory 中獲取,而是到 RefreshScope 中獲取,源碼如下
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// Explicitly remove instance from singleton cache: It might have been put there
// eagerly by the creation process, to allow for circular reference resolution.
// Also remove any beans that received a temporary reference to the bean.
destroySingleton(beanName);
throw ex;
}
});
beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
else if (mbd.isPrototype()) {
// It's a prototype -> create a new instance.
Object prototypeInstance = null;
try {
beforePrototypeCreation(beanName);
prototypeInstance = createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
beanInstance = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}
else {
String scopeName = mbd.getScope();
if (!StringUtils.hasLength(scopeName)) {
throw new IllegalStateException("No scope name defined for bean '" + beanName + "'");
}
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 {
return createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
});
beanInstance = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
}
catch (IllegalStateException ex) {
throw new ScopeNotActiveException(beanName, scopeName, ex);
}
}
scope 是在 RefreshScope 中被註冊的,因為實現了 BeanFactoryPostProcessor, BeanDefinitionRegistryPostProcessor
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
// 將RefreshScope註冊進去,以便上面方便獲取和調用Scope scope = this.scopes.get(scopeName);
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 root) {
if (root.getDecoratedDefinition() != null && root.hasBeanClass()
&& root.getBeanClass() == ScopedProxyFactoryBean.class) {
if (getName().equals(root.getDecoratedDefinition().getBeanDefinition().getScope())) {
// 這裡其實是把包掃描進去的ScopedProxyFactoryBean進一步更換為LockedScopedProxyFactoryBean,當然的是refresh的情況下,為了加下鎖,這裡邏輯不重要
root.setBeanClass(LockedScopedProxyFactoryBean.class);
root.getConstructorArgumentValues().addGenericArgumentValue(this);
// surprising that a scoped proxy bean definition is not already
// marked as synthetic?
root.setSynthetic(true);
}
}
}
}
}
okok,終於到這裡了,休息一下,我們回顧一下上面做了什麼?
- 將標註了 @refreshscope 的 bean 包裝為 LockedScopedProxyFactoryBean,產生每次調用方法都去 RefreshScope 中獲取 bean 對象
- RefreshScope 是 springcloud 註冊進來的,在 springcoud-context 中的自動配置類中,註冊到 beanfactory 中
那麼問題來了,為什麼每次獲取的時候都是最新對象呢,我們自然而然想到的是,每次刷新配置的時候,將 RefreshScope 保存的 bean 銷毀,然後下次調用方法的時候就會獲取到最新的 bean 了,這裡也是這麼做的,每次刷新的時候都會通知 RefreshScope 進行銷毀,源碼如下,
public void refreshAll() {
super.destroy();
this.context.publishEvent(new RefreshScopeRefreshedEvent());
}
@Override
public void destroy() {
List<Throwable> errors = new ArrayList<>();
// 調用refreshAll後會清理所有快取,所以下次獲取的時候是最新的
Collection<BeanLifecycleWrapper> wrappers = this.cache.clear();
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);
}
}
if (!errors.isEmpty()) {
throw wrapIfNecessary(errors.get(0));
}
this.errors.clear();
}
那麼什麼時候調用的呢?在 ConfigDataContextRefresher 中可以看到調用
public synchronized Set<String> refresh() {
Set<String> keys = refreshEnvironment();
this.scope.refreshAll();
return keys;
}
// springcloud將RefreshScope註冊進來
@Bean
@ConditionalOnMissingBean
@ConditionalOnBootstrapDisabled
public ConfigDataContextRefresher configDataContextRefresher(ConfigurableApplicationContext context,
RefreshScope scope, RefreshProperties properties) {
return new ConfigDataContextRefresher(context, scope, properties);
}
ConfigDataContextRefresher 又是誰調用的呢?在 RefreshEventListener 中可以看到
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationReadyEvent) {
handle((ApplicationReadyEvent) event);
}
// 接收到RefreshEvent後刷新
else if (event instanceof RefreshEvent) {
handle((RefreshEvent) event);
}
}
public void handle(ApplicationReadyEvent event) {
this.ready.compareAndSet(false, true);
}
public void handle(RefreshEvent event) {
if (this.ready.get()) { // don't handle events before app is ready
log.debug("Event received " + event.getEventDesc());
Set<String> keys = this.refresh.refresh();
log.info("Refresh keys changed: " + keys);
}
}
//springcloud 將上面的ConfigDataContextRefresher注入進來
@Bean
public RefreshEventListener refreshEventListener(ContextRefresher contextRefresher) {
return new RefreshEventListener(contextRefresher);
}
最後 RefreshEvent 事件是誰發布的呢?在我們引入 nacos-config 依賴之後,會注入一個 bean,看起來有關,我們進去看看
@Bean
public NacosContextRefresher nacosContextRefresher(
NacosConfigManager nacosConfigManager,
NacosRefreshHistory nacosRefreshHistory) {
// Consider that it is not necessary to be compatible with the previous
// configuration
// and use the new configuration if necessary.
return new NacosContextRefresher(nacosConfigManager, nacosRefreshHistory);
}
private void registerNacosListener(final String groupKey, final String dataKey) {
String key = NacosPropertySourceRepository.getMapKey(dataKey, groupKey);
Listener listener = listenerMap.computeIfAbsent(key,
lst -> new AbstractSharedListener() {
@Override
public void innerReceive(String dataId, String group,
String configInfo) {
refreshCountIncrement();
nacosRefreshHistory.addRefreshRecord(dataId, group, configInfo);
NacosSnapshotConfigManager.putConfigSnapshot(dataId, group,
configInfo);
// 可以看到,每次配置更新的時候會發布RefreshEvent事件
applicationContext.publishEvent(
new RefreshEvent(this, null, "Refresh Nacos config"));
if (log.isDebugEnabled()) {
log.debug(String.format(
"Refresh Nacos config group=%s,dataId=%s,configInfo=%s",
group, dataId, configInfo));
}
}
});
try {
configService.addListener(dataKey, groupKey, listener);
log.info("[Nacos Config] Listening config: dataId={}, group={}", dataKey,
groupKey);
}
catch (NacosException e) {
log.warn(String.format(
"register fail for nacos listener ,dataId=[%s],group=[%s]", dataKey,
groupKey), e);
}
}
ook,終於到頭了,也就是每次 nacos 更新配置的時候,會發布 RefreshEvent 事件,然後 RefreshEventListener 接收事件調用 ConfigDataContextRefresher 中的 refresh,進一步調用 RefreshScope 中的 refresh,然後就將快取清空了,下次獲取就是最新的了
還有一件事,刷新過程我們看看#
public synchronized Set<String> refresh() {
Set<String> keys = refreshEnvironment();
this.scope.refreshAll();
return keys;
}
// 這裡刷新環境的時候還會發出EnvironmentChangeEvent事件,這是nacos中ConfigurationPropertiesRebinder的事件源,會從新設置所有ConfigurationPropertiesBeans
public synchronized Set<String> refreshEnvironment() {
Map<String, Object> before = extract(this.context.getEnvironment().getPropertySources());
updateEnvironment();
Set<String> keys = changes(before, extract(this.context.getEnvironment().getPropertySources())).keySet();
this.context.publishEvent(new EnvironmentChangeEvent(this.context, keys));
return keys;
}
//也就是這裡
@Bean
@ConditionalOnMissingBean(search = SearchStrategy.CURRENT)
@ConditionalOnNonDefaultBehavior
public ConfigurationPropertiesRebinder smartConfigurationPropertiesRebinder(
ConfigurationPropertiesBeans beans) {
// If using default behavior, not use SmartConfigurationPropertiesRebinder.
// Minimize te possibility of making mistakes.
return new SmartConfigurationPropertiesRebinder(beans);
}
內部為:它會收集所有的ConfigurationPropertiesBeans
private boolean rebind(String name, ApplicationContext appContext) {
try {
Object bean = appContext.getBean(name);
if (AopUtils.isAopProxy(bean)) {
bean = ProxyUtils.getTargetObject(bean);
}
if (bean != null) {
// TODO: determine a more general approach to fix this.
// see
// https://github.com/spring-cloud/spring-cloud-commons/issues/571
if (getNeverRefreshable().contains(bean.getClass().getName())) {
return false; // ignore
}
appContext.getAutowireCapableBeanFactory().destroyBean(bean);
appContext.getAutowireCapableBeanFactory().initializeBean(bean, name);
return true;
}
}
catch (RuntimeException e) {
this.errors.put(name, e);
throw e;
}
catch (Exception e) {
this.errors.put(name, e);
throw new IllegalStateException("Cannot rebind to " + name, e);
}
return false;
}
結尾#
上面就是 refreshscope 自動刷新的流程了,其實還有一點,nacos 是如何監聽配置刷新和發布事件的呢,這裡面就涉及到 netty 了,具體來說,nacos 會有一個定時任務去查看是否由配置的更改
@Override
public void startInternal() {
executor.schedule(() -> {
while (!executor.isShutdown() && !executor.isTerminated()) {
try {
listenExecutebell.poll(5L, TimeUnit.SECONDS);
if (executor.isShutdown() || executor.isTerminated()) {
continue;
}
//這裡
executeConfigListen();
} catch (Throwable e) {
LOGGER.error("[rpc listen execute] [rpc listen] exception", e);
try {
Thread.sleep(50L);
} catch (InterruptedException interruptedException) {
//ignore
}
notifyListenConfig();
}
}
}, 0L, TimeUnit.MILLISECONDS);
}
netty 還沒學,下次再進一步看看,當然還有 bootstrap 中如何將遠程配置拉去,以及 EnvironmentPostProcessorApplicationListener 中獲取配置也要寫寫,和配置拉去也有關,因為 springboot2.4?之後將 bootstrap 取消,提出了 EnvironmentPostProcessorApplicationListener,更方便的配置導入
此文由 Mix Space 同步更新至 xLog
原始鏈接為 https://me.liuyaowen.club/posts/default/20240816and1