来源
在spring-cloud-context包下(2.1.3 release)
为什么要与Spring深度结合?
我们开发的项目基本都是基于spring,一个配置可能在多个地方使用,所以为了更好的更新依赖的Bean的配置,与spring结合修改起来方便
如何与Spring深度结合?
一. 初始化
1. 首先RefreshAutoConfiguration中的RefreshScopeBeanDefinitionEnhancer会实现BeanDefinitionRegistryPostProcessor;可以在BeanDefinitionRegistry期间对被refreshscope修饰的类变成代理类(ScopedProxyFactoryBean);如果判断为refreshScope,会执行GenericScope下的postProcessBeanDefinitionRegistry,会将代理类变更为(LockedScopedProxyFactoryBean),此类专用作安全的刷新实际bean(读写锁)
2. RefreshScope监听Spring中的ContextRefreshedEvent事件,如果代理BeanDefinitionRegistry设置为非懒加载,就会提前初始化bean
二. 实际获取bean(默认懒加载)
1. 再调用代理对象,想获取其实际bean的方法时,会执行LockedScopedProxyFactoryBean的invoke方法执行实际代理的方法;而实际代理又有一个DelegatingIntroductionInterceptor拦截器来执行实际对象bean的初始化(如果初始化过直接拿到对象),实际调用的是GenericScope中的get方法;初始化完成后返回调用的方法。
如何做到配置更新(改变@Value、@ConfigurationProperties修饰的对象的变量)?
- 发布RefreshEvent事件
- RefreshEventListener监听RefreshEvent事件;后续发布EnvironmentChangeEvent(修改@ConfigurationProperties的属性)、销毁代理对象里边的实际bean对象和RefreshScopeRefreshedEvent事件并且,销毁genericScope缓存的实际bean对象
如何做到更新过程中其他线程读取安全,不会出现NPL?
在发布刷新事件后,会销毁代理实际对象;在下一次读取时,会有个读写锁,在写入的时候,阻塞读,等待写入成功后,放开阻塞;所以读写锁保证了更新过程中的线程安全问题
如何做到懒加载?
在spring初始化期间,只会创建一个代理对象;在真正使用的时候,获取bean的属性,会有拦截(1. 先执行GenericScope中的LockedScopedProxyFactoryBean中的invoke方法 2. 再执行DelegatingIntroductionInterceptor中的invoke方法,如果bean没有初始化会先初始化再调用get方法获取属性)
为什么要做成代理?
- 懒加载
- 线程安全
公司自建组件disconf为什么不用发布RefreshEvent事件?
因为我们大部分系统未引入spring cloud 的context包,所以没有用
为什么@ConfigurationProperties和普通bean的更新方式不一样?
@ConfigurationProperties采用的是监听EnvironmentChangeEvent事件来重新绑定rebind属性
而@Value采用的是销毁代理中的原始bean
因为ConfigurationProperties只注重属性的获取与赋值
而普通的bean有很多额外的操作,像DataSource,改变了url需要先销毁原先的,再重建
为什么@ConfigurationProperties的rebind不使用替换,而是先销毁再初始化
- 为了保证整个操作的原子性,如果是替换,部分成功部分失败,将会造成不可预知的后果
- 可以针对final修饰的属性进行重建
- 完美支持@PostStruct、@PreDestory