@RereshScope刷新的原理详解

软件发布|下载排行|最新软件

当前位置:首页IT学院IT技术

@RereshScope刷新的原理详解

gentten   2022-12-04 我要评论

在配合配置中心修改配置让应用自动刷新配置时,我们要在需要感知配置变化的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注解而导致配置自动刷新失效了。

Copyright 2022 版权所有 软件发布 访问手机版

声明:所有软件和文章来自软件开发商或者作者 如有异议 请与本站联系 联系我们