@RereshScope刷新的原理详解
gentten 人气:0在配合配置中心修改配置让应用自动刷新配置时,我们要在需要感知配置变化的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
注解而导致配置自动刷新失效了。
加载全部内容