温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

@RereshScope刷新的原理是什么

发布时间:2022-12-05 09:30:19 来源:亿速云 阅读:128 作者:iii 栏目:开发技术

本文小编为大家详细介绍“@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刷新的原理是什么”文章已经介绍完毕,想要掌握这篇文章的知识点还需要大家自己动手实践使用过才能领会,如果想了解更多相关内容的文章,欢迎关注亿速云行业资讯频道。

    向AI问一下细节

    免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

    AI