文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

@RereshScope刷新的原理详解

2022-12-08 20:53

关注

在配合配置中心修改配置让应用自动刷新配置时,我们要在需要感知配置变化的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表示作用域的代理模式,共有以下四个值:

public enum ScopedProxyMode {
   
   DEFAULT,
   
   NO,
   
   INTERFACES,
   
   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差不多都是这么实现的):

不管是什么方式,最终都会调用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刷新内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯