@RereshScope刷新的原理详解

@RereshScope刷新的原理详解

在配合配置中心修改配置让应用自动刷新配置时,我们要在需要感知配置变化的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刷新内容请搜索云海天教程以前的文章或继续浏览下面的相关文章希望大家以后多多支持云海天教程!

原文地址:https://juejin.cn/post/7172890205425238030