@RereshScope刷新的原理详解

目录
  • 一、入口
    • ScopedProxyMode
  • 二、配置类解析
    • ScopedProxyFactoryBean-生成代理对象
    • ScopedObject-从容器中获取代理目标
  • 三、作用域原理
  • 四、配置刷新
  • 五、总结
  • 六、问题

在配合配置中心修改配置让应用自动刷新配置时,我们要在需要感知配置变化的bean上面加上@RereshScope。如果我们不加上这注解,那么有可能无法完成配置自动刷新。

一、入口

可以看到@RereshScope@Scope("refresh")(bean的作用域)的派生注解并指定了作用域为refresh并在默认情况下proxyMode= ScopedProxyMode.TARGET_CLASS使用CGLIB生成代理对象

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Scope("refresh")
@Documented
public @interface RefreshScope {
    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
}

ScopedProxyMode

ScopedProxyMode表示作用域的代理模式,共有以下四个值:

  • DEFAULT:默认no
  • NO:不使用代理
  • INTERFACES:使用JDK动态代理
  • TARGET_CLASS:使用CGLIB动态代理
public enum ScopedProxyMode {
   /**
    * Default typically equals {@link #NO}, unless a different default
    * has been configured at the component-scan instruction level.
    */
   DEFAULT,
   /**
    * Do not create a scoped proxy.
    */
   NO,
   /**
    * Create a JDK dynamic proxy implementing <i>all</i> interfaces exposed by
    * the class of the target object.
    */
   INTERFACES,
   /**
    * Create a class-based proxy (uses CGLIB).
    */
   TARGET_CLASS;
}

二、配置类解析

在上文刷新时会执行BeanFacotryPostProcessorbeanDefinition修改和增加,其中配置类解析、类扫描的工作就是在其中执行,而对于一个ScopedProxyMode.NO它会解析成一个ScopedProxyFactoryBean

//ConfigurationClassBeanDefinitionReader配置类解析代码片段
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);

//如果需要生产代理则创建一个ScopedProxy的BeanDefinition
static BeanDefinitionHolder applyScopedProxyMode(
		ScopeMetadata metadata, BeanDefinitionHolder definition, BeanDefinitionRegistry registry) {

	ScopedProxyMode scopedProxyMode = metadata.getScopedProxyMode();
	if (scopedProxyMode.equals(ScopedProxyMode.NO)) {
		return definition;
	}
	boolean proxyTargetClass = scopedProxyMode.equals(ScopedProxyMode.TARGET_CLASS);
	return ScopedProxyCreator.createScopedProxy(definition, registry, proxyTargetClass);
}

//createScopedProxy 代码片段 可以看到BeanDefinition是ScopedProxyFactoryBean
RootBeanDefinition proxyDefinition = new RootBeanDefinition(ScopedProxyFactoryBean.class);

ScopedProxyFactoryBean-生成代理对象

FactoryBean在注入时返回的是getObject()所返回的对象,在这里就是返回的就是proxyScopedProxyFactoryBean实现了BeanFactoryAware那么在这个bean初始化中会调用setBeanFactory()方法,而在这个方法中,为它创建一个CGLIB代理对象作为getObject()的返回值,并使用ScopedObject来代替被代理对象。而在ScopedObject默认实现中每次都是从BeanFactory中获取(重点)。

@Override
public Object getObject() {
	if (this.proxy == null) {
		throw new FactoryBeanNotInitializedException();
	}
    //返回代理对象
	return this.proxy;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) {
   //...省略其他代码

   // Add an introduction that implements only the methods on ScopedObject.
   //增加一个拦截使用ScopedObject来被代理对象调用方法
   ScopedObject scopedObject = new DefaultScopedObject(cbf, this.scopedTargetSource.getTargetBeanName());
   //委托ScopedObject去执行
   pf.addAdvice(new DelegatingIntroductionInterceptor(scopedObject));

   // Add the AopInfrastructureBean marker to indicate that the scoped proxy
   // itself is not subject to auto-proxying! Only its target bean is. AOP时复用这个代理对象
   pf.addInterface(AopInfrastructureBean.class);
   //创建代理对象
   this.proxy = pf.getProxy(cbf.getBeanClassLoader());
}

ScopedObject-从容器中获取代理目标

作用域对象的AOP引入的接口。可以将从ScopedProxyFactoryBean创建的对象强制转换到此接口,从而可以控制访问原始目标对象并通过编程删除目标对象。在默认实现中是每次方法拦截都从容器中获取被代理的目标对象

public interface ScopedObject extends RawTargetAccess {
  //返回当前代理对象后面的目标对象
   Object getTargetObject();
   void removeFromScope();
}
public class DefaultScopedObject implements ScopedObject, Serializable {
    //...省略字段信息和构造器
	@Override
	public Object getTargetObject() {
        //从容器中获取
		return this.beanFactory.getBean(this.targetBeanName);
	}
	@Override
	public void removeFromScope() {
		this.beanFactory.destroyScopedBean(this.targetBeanName);
	}
}

三、作用域原理

BeanFactory获取bean时(doGetBean),如果**不是单例或者原型bean**将交给对应的Socpebean,而创建bean方式和单例bean是一样的。其他作用域像requestsession等等都是属于这一块的扩展:SPI+策略模式

//AbstractBeanFactory doGetBean()代码片段
String scopeName = mbd.getScope();
//获取对应的scope
final Scope scope = this.scopes.get(scopeName);
//参数检查省略。。。
try {
    //使用的对应的Socpe去获取bean 获取不到则使用后面的`ObjectFactory`
   Object scopedInstance = scope.get(beanName, () -> {
       //ObjectFactory lambda表达式 怎么创建bean
      beforePrototypeCreation(beanName);
      try {
         return createBean(beanName, mbd, args);
      }
      finally {
         afterPrototypeCreation(beanName);
      }
   });
   bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);

RefreshScope继承GenericScope每次获取bean是从自己的缓存(ConcurrentHashMap)中获取。 如果缓存中bean被销毁了则用objectFactory创建一个。

//GenericScope 中获取get实现
public Object get(String name, ObjectFactory<?> objectFactory) {
    //从缓存中获取 缓存的实现就是ConcurrentHashMap
    BeanLifecycleWrapper value = this.cache.put(name, new BeanLifecycleWrapper(name, objectFactory));
    this.locks.putIfAbsent(name, new ReentrantReadWriteLock());
    try {
        return value.getBean();
    } catch (RuntimeException var5) {
        this.errors.put(name, var5);
        throw var5;
    }
}
    private static class BeanLifecycleWrapper {
        //当前bean对象
        private Object bean;
        //销毁回调
        private Runnable callback;
        //bean名称
        private final String name;
        //bean工厂
        private final ObjectFactory<?> objectFactory;
        //获取
        public Object getBean() {
            if (this.bean == null) {
                synchronized(this.name) {
                    if (this.bean == null) {
                        this.bean = this.objectFactory.getObject();
                    }
                }
            }
            return this.bean;
        }
       //销毁
        public void destroy() {
            if (this.callback != null) {
                synchronized(this.name) {
                    Runnable callback = this.callback;
                    if (callback != null) {
                        callback.run();
                    }
                    this.callback = null;
                    //只为null
                    this.bean = null;
                }
            }
        }
}

四、配置刷新

当配置中心刷新配置之后,有两种方式可以动态刷新Bean的配置变量值,(SpringCloud-Bus还是Nacos差不多都是这么实现的):

  • 向上下文发布一个RefreshEvent事件
  • Http访问/refresh这个EndPoint

不管是什么方式,最终都会调用ContextRefresher这个类的refresh方法

public synchronized Set<String> refresh() {
     //刷新环境
     Set<String> keys = this.refreshEnvironment();
     //刷新bean 其实就是销毁refreshScope中缓存的bean
     this.scope.refreshAll();
     return keys;
}
//RefreshScope刷新
public void refreshAll() {
     super.destroy();
     this.context.publishEvent(new RefreshScopeRefreshedEvent());
}

五、总结

@RereshScope会为这个标记的bean生成ScopedProxyFactoryBeanScopedProxyFactoryBean生成一个代理对象使每次方法调用的目标对象都从容器中获取,而获取refresh作用域的Bean是RefreshScope缓存中获取。当配置中心在触发刷新时RefreshScope会删除Socpe缓存的Bean,然后下次获取时就会用新的Environment创建配置修改后的Bean,这样就达到了配置的自动更新。

六、问题

为什么需要生成代理对象?

因为Bean装配是一次性的,假设没有代理的情况下,在另一个bean注入这个refreshBean之后就无法改变了,就算refreshBean销毁(在缓存中置为null)并后面重新生成了,但是之前引用还是老的bean,这也是为什么没有加@RefreshScope注解而导致配置自动刷新失效了。

到此这篇关于@RereshScope刷新的原理详解的文章就介绍到这了,更多相关@RereshScope刷新内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • @RefreshScope 自动刷新配置文件的实例讲解

    1.在类上加@RefreshScope注解. 2.引入配置@Value. /** * @author 向振华 * @date 2018/12/17 17:20 */ @RefreshScope //配置文件自动刷新 @RestController @RequestMapping("test") public class TestController { @Value("${test.xzh}") //引入配置 private String xzh; @Request

  • Spring Cloud动态配置刷新RefreshScope使用示例详解

    目录 引言 一.了解@RefreshScope,先要了解@Scope 二.RefreshScope 的实现原理 三.使用——@RefreshScope 使用流程 引言 用过Spring Cloud的同学都知道在使用动态配置刷新的我们要配置一个 @RefreshScope,在类上才可以实现对象属性的的动态更新. @RefreshScope 能实现动态刷新全仰仗着 @Scope这个注解. 一.了解@RefreshScope,先要了解@Scope 1.RefreshScope继承于GenericSco

  • @RereshScope刷新的原理详解

    目录 一.入口 ScopedProxyMode 二.配置类解析 ScopedProxyFactoryBean-生成代理对象 ScopedObject-从容器中获取代理目标 三.作用域原理 四.配置刷新 五.总结 六.问题 在配合配置中心修改配置让应用自动刷新配置时,我们要在需要感知配置变化的bean上面加上@RereshScope.如果我们不加上这注解,那么有可能无法完成配置自动刷新. 一.入口 可以看到@RereshScope是@Scope("refresh")(bean的作用域)的

  • SpringBoot内置tomcat启动原理详解

    前言 不得不说SpringBoot的开发者是在为大众程序猿谋福利,把大家都惯成了懒汉,xml不配置了,连tomcat也懒的配置了,典型的一键启动系统,那么tomcat在springboot是怎么启动的呢? 内置tomcat 开发阶段对我们来说使用内置的tomcat是非常够用了,当然也可以使用jetty. <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-bo

  • Java MyBatis本地缓存原理详解

    目录 背景 发现问题 复现 解决问题 探究缓存的原理 Sql查询部分深入 初见缓存 告一段落 番外篇-Myabtis创建CacheKey的算法. 构造方法 结束语 背景 出现了一次生产事故,事情是这样的,我们有一个项目,Java访问数据库的框架使用的是MyBatis.然后一个业务员在系统中查询了一个订单,发现这个订单是未支付的状态,于是业务员联系客户,让客户支付,客户支付完成后,业务员又去系统查询,结果还是未支付状态,刷新了页面也是一样,不过过了一会就好了.业务员把这个延迟问题,反馈给了我们.我

  • JS作用域作用链及this使用原理详解

    目录 变量提升的原理:JavaScript的执行顺序 第一部分:变量提升部分的代码 第二部分:代码执行部分 代码执行阶段 调用栈:栈溢出的原理 如何利用调用栈 1.使用浏览器查看调用栈的信息 2.小心栈溢出 块级作用域:var.let以及const 作用域 小结 作用域链和闭包 块级作用域中的变量查找 闭包 闭包怎么回收 小练 this:从执行上下文分析this 全局执行上下文的this 函数执行上下文的this 1.通过call 2.通过对象调用 3.通过构造函数设置 this的缺陷以及应对方

  • C语言进度条的实现原理详解

    目录 \r和\n 行缓冲区 进度条 \r和\n 在程序里面,\r和\n分别代表什么意思? \r:表示“回车”,即回到当前行的起始位置 \n:表示换行,即列不变,另其一个新行 行缓冲区 什么是行缓冲区?先别慌,来看代码一执行会是什么样的状态. //代码一,mytest.c文件 # include <stdio.h> int main(void) { printf("hello linux"); sleep(1); return 0; } 现象:光标休息一秒后,打印出hello

  • MySql事务及ACID实现原理详解

    目录 逻辑架构和存储引擎 自动提交 特殊操作 ACID 特性 原子性 持久性 隔离性 脏读.不可重复读和幻读 事务隔离级别 MVCC 一致性 逻辑架构和存储引擎 自动提交 MySQL 中默认采用的是自动提交(autocommit)模式,如下所示: 在自动提交模式下,如果没有 start transaction 显式地开始一个事务,那么每个 sql 语句都会被当做一个事务执行提交操作. 通过如下方式,可以关闭 autocommit;需要注意的是,autocommit 参数是针对连接的,在一个连接中

  • mysql中的mvcc 原理详解

    目录 简介 前言 一.mysql 数据写入磁盘流程 二.redo log 1.redolog 的整体流程 2.为什么需要 redo log 三.undo log 1.undo log 特点 2.undo log 类型 3.undo log 生成过程 4.undo log 回滚过程 5.undo log的删除 四.mvcc 1.什么是MVCC 2.MVCC组成 3.快照读与当前读 快照读 当前读 五.mvcc操作演示 1.READ COMMITTED 隔离级别 2.REPEATABLE READ 

  • React为什么需要Scheduler调度器原理详解

    目录 正文 我们为什么需要Scheduler(调度器) Scheduler如何进行工作 总结 正文 最近在重学React,由于近两年没使用React突然重学发现一些很有意思的概念,首先便是React的Scheduler(调度器) 由于我对React的概念还停留在React 15之前(就是那个没有hooks的年代),所以接触Scheduler(调度器) 让我感觉很有意思: 在我印象中React的架构分为两层(React 16 之前) Reconciler(协调器)—— 负责找出变化的组件 Rend

  • 入门到精通Java SSO单点登录原理详解

    目录 1. 基础概念 2. 单点登录 3. CAS 流程 4. OAuth 流程 5. CAS和OAuth的区别 1. 基础概念 SSO单点登录(Single sign-on) 所谓单点登录就是在多个应用系统中,用户只需登录一次就可以访问所有相互信任的系统. CAS 中央认证服务(Central Authentication Service) CAS是由美国耶鲁大学发起的一个企业级开源项目,旨在为WEB应用系统提供一种可靠的单点登录解决方案(WEB SSO). OAuth2.0 开放授权(Ope

  • Spring AOP的实现原理详解及实例

    Spring AOP的实现原理详解及实例 spring 实现AOP是依赖JDK动态代理和CGLIB代理实现的. 以下是JDK动态代理和CGLIB代理简单介绍 JDK动态代理:其代理对象必须是某个接口的实现,它是通过在运行期间创建一个接口的实现类来完成对目标对象的代理. CGLIB代理:实现原理类似于JDK动态代理,只是它在运行期间生成的代理对象是针对目标类扩展的子类.CGLIB是高效的代码生成包,底层是依靠ASM(开源的Java字节码编辑类库)操作字节码实现的,性能比JDK强. 在Spring中

随机推荐