@RereshScope刷新的原理详解
作者:gentten
在配合配置中心修改配置让应用自动刷新配置时,我们要在需要感知配置变化的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
:默认noNO
:不使用代理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; }
二、配置类解析
在上文刷新时会执行BeanFacotryPostProcessor
对beanDefinition
修改和增加,其中配置类解析、类扫描的工作就是在其中执行,而对于一个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()
所返回的对象,在这里就是返回的就是proxy
。ScopedProxyFactoryBean
实现了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
**将交给对应的Socpe
去bean
,而创建bean
方式和单例bean是一样的。其他作用域像request
、session
等等都是属于这一块的扩展: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生成ScopedProxyFactoryBean
,ScopedProxyFactoryBean
生成一个代理对象使每次方法调用的目标对象都从容器中获取,而获取refresh
作用域的Bean是RefreshScope
缓存中获取。当配置中心在触发刷新时RefreshScope
会删除Socpe
缓存的Bean,然后下次获取时就会用新的Environment
创建配置修改后的Bean,这样就达到了配置的自动更新。
六、问题
为什么需要生成代理对象?
因为Bean装配是一次性的,假设没有代理的情况下,在另一个bean注入这个refreshBean
之后就无法改变了,就算refreshBean
销毁(在缓存中置为null)并后面重新生成了,但是之前引用还是老的bean
,这也是为什么没有加@RefreshScope
注解而导致配置自动刷新失效了。
到此这篇关于@RereshScope刷新的原理详解的文章就介绍到这了,更多相关@RereshScope刷新内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!