一文带你从零到一深入透析 @RefreshScope 结合 Nacos 动态刷新源码(中)

简介: 一文带你从零到一深入透析 @RefreshScope 结合 Nacos 动态刷新源码(中)

原始 singleton Bean 加载过程

从 ScopedProxyUtils#createScopedProxy 方法中可以得知原始 Bean scope 属性是 “” 值,而目标 Bean scope 属性继承了原始 Bean 未改变之前的值,也就是 refresh

完成 Bean 单例 Bean 加载的过程是在 DefaultListableBeanFactory#preInstantiateSingletons 方法中完成的,该方法中部分源码如下:

// 当前方法的判别表明了原始 Bean 就是单例的,而目标 Bean 是非单例的
public boolean isSingleton() {
  return "singleton".equals(this.scope) || "".equals(this.scope);
}
// 非抽象的、单例的、非懒加载的,只有满足这三个条件才会继续往下面去 getBean->doGetBean->createBean->doCreateBean
} while(bd.isAbstract());
} while(!bd.isSingleton());
} while(bd.isLazyInit());
// 此处原始 Bean 就是一个 FactoryBean 了,所以先 getBean 创建 FactoryBean 接口的实例
if (this.isFactoryBean(beanName)) {
  bean = this.getBean("&" + beanName);
  break;
}
// 这里就会去触发调用 FactoryBean#getObject 方法获取里面的实例
this.getBean(beanName);

在以上中为什么说原始 Bean 已经是一个 FactoryBean 对象了呢?

因为在前面执行 GenericScope#postProcessBeanDefinitionRegistry 时,为原始 Bean 重新赋值了 beanClass 属性,对应的值就是 LockedScopedProxyFactoryBean

查看 LockedScopedProxyFactoryBean 类图即可明白,如下所示:

从继承关系来看,当前类就实现了 FactoryBean 接口,同时它也实现了 MethodInterceptor 接口(这里埋一个引子,后续生成代理对象以后就会调用 LockedScopedProxyFactoryBean#invoke 方法)

说到了 FactoryBean,那必然就需要看它的 getObject、isSingleton 方法,但是在调用 getObject 方法在实际应用时是通过手动去调用的,在这边创建先要创建 FactoryBean 实例,它必然会经过填充属性(populateBean)->初始化 Bean(initializeBean) 这个过程的

再观察类图,它实现了 BeanFactoryAware 方法, 同时在初始化 Bean(initializeBean) 会调用 invokeAwareMethods 方法,它会处理三个 Aware 接口的方法:BeanNameAware#setBeanName、BeanClassLoaderAware#setBeanClassLoader、BeanFactoryAware#setBeanFactory,在这里只有一个 Aware 方法满足,所以接下来观察它的 setBeanFactory 作了什么处理

// LockedScopedProxyFactoryBean.java
public void setBeanFactory(BeanFactory beanFactory) {
  // 调用父类的 setBeanFactory 方法,主要的处理也是在它的父方法中
  super.setBeanFactory(beanFactory);
  // 获取父类创建好的代理对象
  Object proxy = getObject();
  if (proxy instanceof Advised) {
    // 将父类创建好的代理对象被优先加载调用
    Advised advised = (Advised) proxy;
    advised.addAdvice(0, this);
  }
}
// LockedScopedProxyFactoryBean 父类 ScopedProxyFactoryBean
private final SimpleBeanTargetSource scopedTargetSource = new SimpleBeanTargetSource();
public ScopedProxyFactoryBean() {
  // proxyTargetClass=true
  this.setProxyTargetClass(true);
}
public void setTargetBeanName(String targetBeanName) {
  this.targetBeanName = targetBeanName;
  // 在填充属性阶段同时为 scopedTargetSource 设置了目标名称
  this.scopedTargetSource.setTargetBeanName(targetBeanName);
}
public void setBeanFactory(BeanFactory beanFactory) {
  if (!(beanFactory instanceof ConfigurableBeanFactory)) {
    throw new IllegalStateException("Not running in a ConfigurableBeanFactory: " + beanFactory);
  } else {
    ConfigurableBeanFactory cbf = (ConfigurableBeanFactory)beanFactory;
    this.scopedTargetSource.setBeanFactory(beanFactory);
    // 创建动态代理的核心类
    ProxyFactory pf = new ProxyFactory();
    pf.copyFrom(this);
    // 设置目前源对象类:SimpleBeanTargetSource
    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 {
      // proxyTargetClass=true,无参构造方法设置过了
      if (!this.isProxyTargetClass() || beanType.isInterface() || Modifier.isPrivate(beanType.getModifiers())) {
        pf.setInterfaces(ClassUtils.getAllInterfacesForClass(beanType, cbf.getBeanClassLoader()));
      }
      // 准备好,后续拦截器会调用 SimpleBeanTargetSource#getTargetObject 方法
      ScopedObject scopedObject = new DefaultScopedObject(cbf, this.scopedTargetSource.getTargetBeanName());
      pf.addAdvice(new DelegatingIntroductionInterceptor(scopedObject));
      pf.addInterface(AopInfrastructureBean.class);
      this.proxy = pf.getProxy(cbf.getBeanClassLoader());
    }
  }
}
// 子类手动调用
public Object getObject() {
  if (this.proxy == null) {
    throw new FactoryBeanNotInitializedException();
  } else {
    return this.proxy;
  }
}

这里先记录一下,后面阶段会调用此处:ProxyFactory 代理工厂类设置了目标源对象 SimpleBeanTargetSource,该目标源对象在填充属性阶段设置了 targetBeanName 值

但是你没想到吧,到这里,还是它动态刷新的准备工作,这里只是把原始 Bean 加载完成,目标 Bean 还没有进行处理呢!!!

目标 refresh Bean 加载过程

刚刚说到了,目标 Bean 是非单例的,它的 scope 属性值为 refresh,所以说在执行 DefaultListableBeanFactory#preInstantiateSingletons 是不会对它作任何处理工作,除非我们绕开这个方法,直接调用 getBean 方法就可以获取实例对象了,那么下面就看 spring-cloud 是如何应用 spring-boot 组件来完成这个骚操作的呢?

重要的点还是在 RefreshScope 类里面的方法中,这里再把它的类图贴出来:

发现了它实现了 ApplicationListener 接口,它肯定是监听了某个事件,事件名:ContextRefreshedEvent,并且它肯定实现了 onApplicationEvent 方法

// RefreshScope.java
public void onApplicationEvent(ContextRefreshedEvent event) {
  start(event);
}
public void start(ContextRefreshedEvent event) {
  // eager 默认值就是 true
  if (event.getApplicationContext() == this.context && this.eager
      && this.registry != null) {
    eagerlyInitialize();
  }
}
// 提前初始化
private void eagerlyInitialize() {
  for (String name : this.context.getBeanDefinitionNames()) {
    BeanDefinition definition = this.registry.getBeanDefinition(name);
    // scope 属性值=当前 name 也就是 refresh、lazyInit 属性为设置的话默认就是 false
    if (this.getName().equals(definition.getScope())
        && !definition.isLazyInit()) {
      // 此处就是直接绕过了,调用 getBean 方法
      Object bean = this.context.getBean(name);
      if (bean != null) {
        bean.getClass();
      }
    }
  }
}

先整理一下 spring-boot 是在什么发送 ContextRefreshedEvent 这个事件的呢?

其实就是在执行完 DefaultListableBeanFactory#preInstantiateSingletons 方法以后,马上就调用 finishRefresh 方法随即就发送事件啦!!

getBean->doGetBean 方法,该方法内有这么个调用逻辑,贴上部分 doGetBean 方法的源码:

// isSingleton & isPrototype 都不满足,所以就只需要关注 else 分支的逻辑了
if (mbd.isSingleton()) {
  .....
} else if (mbd.isPrototype() {
  .....
} else {
  String scopeName = mbd.getScope();
  if (!StringUtils.hasLength(scopeName)) {
    throw new IllegalStateException("No scope name defined for bean ´" + beanName + "'");
  }
  // scopeName=refresh,scopes 集合中在之前 GenericScope#postProcessBeanFactory 方法已经添加进去了
  Scope scope = this.scopes.get(scopeName);
  if (scope == null) {
    throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
  }
  try {
    // 核心方法就是这个了
    Object scopedInstance = scope.get(beanName, () -> {
      beforePrototypeCreation(beanName);
      try {
        return createBean(beanName, mbd, args);
      }
      finally {
        afterPrototypeCreation(beanName);
      }
    });
    bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
  }
  catch (IllegalStateException ex) {
    throw new BeanCreationException(beanName,
                                    "Scope '" + scopeName + "' is not active for the current thread; consider " +
                                    "defining a scoped proxy for this bean if you intend to refer to it from a singleton",
                                    ex);
  }
}

Scope#get 方法,Scope 是一个顶级接口,当然是找它的子类 GenericScope、RefreshScope 了,而 RefreshScope 并没有实现这个方法,所以直接定位到 GenericScope#get 方法,源码如下:

public Object get(String name, ObjectFactory<?> objectFactory) {
  // 存入 ScopeCache#cache 集合中
  BeanLifecycleWrapper value = this.cache.put(name, new BeanLifecycleWrapper(name, objectFactory));
  this.locks.putIfAbsent(name, new ReentrantReadWriteLock());
  try {
    // 这里调用的是上面 lambda 表达式中的 createBean 方法
    return value.getBean();
  }
  catch (RuntimeException e) {
    this.errors.put(name, e);
    throw e;
  }
}

BeanLifecycleWrapper 是用于存储缓存的,cache 是一个 ConcurrentMap 结构,创建的这个对象实例就是一个普通的对象,不会做任何的代理增强处理

Refresh 动态刷新监听器

结合前言中提到:@RefreshScope 所在的 package 包名,在该模块下会自动装配下两个核心类:RefreshScope、RefreshEventListener,前面的内容都是准备工作,而且只介绍了核心类 RefreshScope 作用以及提前准备好的 scope=refresh 实例对象;RefreshEventListener 未介绍,它是一个监听器,那么肯定有地方会发布这个事件,所以这边又要介绍另外一个自动装配进来的配置类了.

介绍 spring-cloud-alibaba-config 模块内会自动装配进来 NacosContextRefresher 核心类,它实现了 ApplicationListener<ApplicationReadyEvent> 接口,而它是在执行完之前所有的准备工作以后,发起了应用准备就绪事件,随即 NacosContextRefresher#onApplicationEvent 方法就会接受到事件进行调用,到这里,就弄清楚它的来龙去脉,继续往下走,看该方法处理做了什么样的操作!

private AtomicBoolean ready = new AtomicBoolean(false);
public void onApplicationEvent(ApplicationReadyEvent event) {
  // CAS 操作来确保只能加载一次
  if (this.ready.compareAndSet(false, true)) {
    this.registerNacosListenersForApplications();
  }
}

目录
相关文章
|
Nacos
Nacos源码构建报错程序包不存在com.alibaba.nacos.consistency.entity
Nacos源码构建报错程序包不存在com.alibaba.nacos.consistency.entity
452 0
Nacos源码构建报错程序包不存在com.alibaba.nacos.consistency.entity
|
3月前
|
关系型数据库 MySQL Java
“惊呆了!无需改动Nacos源码,轻松实现SGJDBC连接MySQL?这操作太秀了,速来围观,错过等哭!”
【8月更文挑战第7天】在使用Nacos进行服务治理时,常需连接MySQL存储数据。使用特定的SGJDBC驱动连接MySQL时,一般无需修改Nacos源码。需确保SGJDBC已添加至类路径,并在Nacos配置文件中指定使用SGJDBC的JDBC URL。示例中展示如何配置Nacos使用MySQL及SGJDBC,并在应用中通过Nacos API获取配置信息建立数据库连接,实现灵活集成不同JDBC驱动的目标。
110 0
|
Java Nacos 数据库
nacos源码打包及相关配置
nacos源码打包及相关配置
314 4
|
Cloud Native Java Go
解决Nacos配置刷新问题: 如何启用配置刷新功能以及与`@RefreshScope`注解的关联问题
解决Nacos配置刷新问题: 如何启用配置刷新功能以及与`@RefreshScope`注解的关联问题
1172 0
|
6月前
|
关系型数据库 MySQL 数据库连接
我想问一下用nacos连接mysql数据库但要用sgjdbc连接,需要改nacos源码吗?
我想问一下用nacos连接mysql数据库但要用sgjdbc连接,需要改nacos源码吗?
147 0
|
Java 测试技术 Nacos
Sentinel源码改造,实现Nacos双向通信!
Sentinel源码改造,实现Nacos双向通信!
221 0
Sentinel源码改造,实现Nacos双向通信!
|
Java API Nacos
Nacos服务健康检查与服务变动事件发布源码解析
Nacos服务健康检查与服务变动事件发布源码解析
94 0
|
11天前
|
负载均衡 应用服务中间件 Nacos
Nacos配置中心
Nacos配置中心
40 1
Nacos配置中心
|
7天前
|
监控 Java 测试技术
Nacos 配置中心变更利器:自定义标签灰度
本文是对 MSE Nacos 应用自定义标签灰度的功能介绍,欢迎大家升级版本进行试用。
|
10天前
|
网络安全 Nacos 开发者
Nacos作为流行的微服务注册与配置中心,“节点提示暂时不可用”是常见的问题之一
Nacos作为流行的微服务注册与配置中心,其稳定性和易用性备受青睐。然而,“节点提示暂时不可用”是常见的问题之一。本文将探讨该问题的原因及解决方案,帮助开发者快速定位并解决问题,确保服务的正常运行。通过检查服务实例状态、网络连接、Nacos配置、调整健康检查策略等步骤,可以有效解决这一问题。
22 4