目录
scope原理
从容器中获取scope bean
负责创建scope bean的ScopedProxyFactoryBean
RefreshScope
RefreshScope缓存清理
配置重新加载
scope原理
从容器中获取scope bean
scope是spring framework中的概念,对于singleton和prototype对象的获取在beanFactory中直接实现,其他scope对象的获取则委托给scope自己。当然,bean的实例化、依赖注入还得依靠beanFactory,scope只是控制bean的生命周期,决定什么时候重新创建bean。
具体的逻辑在AbstractBeanFactory的doGetBean方法:
protected <T> T doGetBean(...){
//...
final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
if (mbd.isSingleton()) {
//...
} else if (mbd.isPrototype())
//...
} else {
String scopeName = mbd.getScope();
final Scope scope = this.scopes.get(scopeName);
Object scopedInstance = scope.get(beanName, () -> {
return createBean(beanName, mbd, args);
});
}
//...
}
负责创建scope bean的ScopedProxyFactoryBean
一个正常bean A注入到令一个bean B后,B保持A的引用不会再改变。对于非单例的bean,每次调用它的时候都有可能要获取一个新的bean实例。所以不能将scope对象之间注入到其他bean中,而是要注入一个代理,每当代理被调用时再获取真正的bean实例执行相应的方法,这时有可能使用之前创建好的实例也有可能重新创建一个新实例。
如果一个bean被@scope标注,则需要对它的BeanDefinition做设置scope名字、配置对应的FacoryBean等特殊处理,相关逻辑见ClassPathBeanDefinitionScanner的doScan方法
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
for (String basePackage : basePackages) {
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
//...
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
// 创建了另外一个新的ScopedProxyFactoryBean的BeanDefinition
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
BeanDefinition经过AnnotationConfigUtils.applyScopedProxyMode的处理会为负责创建代理对象的ScopedProxyFactoryBean创建一个新的BeanDefinition,并使用scope bean的名字注册到BeanFactory中。ScopedProxyFactoryBean就是负责创建scope bean的FactoryBean。
scope bean本来的BeanDefinition也注册到BeanFactory中,使用的名字是在原名字前加上scopedTarget.前缀。
BeanDefinition相关处理逻辑见ScopedProxyUtils:
public static BeanDefinitionHolder createScopedProxy(BeanDefinitionHolder definition,
BeanDefinitionRegistry registry, boolean proxyTargetClass) {
String originalBeanName = definition.getBeanName();
BeanDefinition targetDefinition = definition.getBeanDefinition();
String targetBeanName = getTargetBeanName(originalBeanName);
RootBeanDefinition proxyDefinition = new RootBeanDefinition(ScopedProxyFactoryBean.class);
proxyDefinition.setDecoratedDefinition(new BeanDefinitionHolder(targetDefinition, targetBeanName));
proxyDefinition.setOriginatingBeanDefinition(targetDefinition);
//...
// 原来的BeanDefinition在这里注册,名字前面加上了scopedTarget.
registry.registerBeanDefinition(targetBeanName, targetDefinition);
return new BeanDefinitionHolder(proxyDefinition, originalBeanName, definition.getAliases());
}
FactoryBean本身作为单例bean,在容器refresh单例创建的时候,发现一个bean是FactoryBean则会在beanName前加上&前缀调用getBean完成FactoryBean的创建。
而ScopedProxyFactoryBean实现了BeanFactoryAware接口,它的实例创建之后执行initializeBean,其中在setBeanFactory的时候创建了代理对象并保存在了ScopedProxyFactoryBean。
public void setBeanFactory(BeanFactory beanFactory) {
//...
ConfigurableBeanFactory cbf = (ConfigurableBeanFactory) beanFactory;
this.scopedTargetSource.setBeanFactory(beanFactory);
ProxyFactory pf = new ProxyFactory();
//...
this.proxy = pf.getProxy(cbf.getBeanClassLoader());
}
其他bean依赖注入scope bean的时候会先查出FactoryBean,然后从FactoryBean获取到代理。
public Object getObject() {
if (this.proxy == null) {
throw new FactoryBeanNotInitializedException();
}
return this.proxy;
}
代理执行相关方法的时候会调用getBean(scopedTarget.beanName)获取真正scope bean的实例,这个时候的beanName有一个scopedTarget.前缀。有前缀的beanName获取到的是真正的scope bean的实例,不带前缀获取到的是代理对象实例,带取址符&前缀则获取到FactoryBean。
RefreshScope
以上说的都是spring中原有的功能,RefreshScope则是springcloud中scope的一种实现,它的名字就是refresh,所以它负责加了@RefreshScope注解的bean的创建。
RefreshScope在RefreshAutoConfiguration中被声明,作为BeanDefinitionRegistryPostProcessor被提前初始化,并作为scope把自己注册到beanFactory中。注册的逻辑在父类GenericScope中:
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
throws BeansException {
this.beanFactory = beanFactory;
beanFactory.registerScope(this.name, this);
setSerializationId(beanFactory);
}
RefreshScope是GenericScope的子类,主要的逻辑都在GenericScope中实现。
public class GenericScope implements Scope, BeanFactoryPostProcessor,
BeanDefinitionRegistryPostProcessor, DisposableBean {
// ...
private BeanLifecycleWrapperCache cache = new BeanLifecycleWrapperCache(
new StandardScopeCache());
// ...
}
GenericScope的内部维护一个cache,每当BeanFactory来获取对象的时候都会先从缓存中获取。所以,每次配置更新时只要清除缓存,下次获取对象的时候就会重新创建新对象,为新对象注入最新属性,这样就实现了RefreshScope的语义。
public Object get(String name, ObjectFactory<?> objectFactory) {
BeanLifecycleWrapper value = this.cache.put(name,
new BeanLifecycleWrapper(name, objectFactory));
this.locks.putIfAbsent(name, new ReentrantReadWriteLock());
try {
return value.getBean();
}
catch (RuntimeException e) {
this.errors.put(name, e);
throw e;
}
}
上面代码中虽然看似每次都新创建一个对象放入缓存中,实际上是创建了一个objectFactory的封装对象,并没有真正创建对象。而cache的put逻辑最终实现为map的putIfAbsent,即缓存中已存在key则返回原来的value。见StandardScopeCache:
public class StandardScopeCache implements ScopeCache {
private final ConcurrentMap<String, Object> cache = new ConcurrentHashMap<String, Object>();
// ...
public Object put(String name, Object value) {
Object result = this.cache.putIfAbsent(name, value);
if (result != null) {
return result;
}
return value;
}
}
RefreshScope缓存清理
上面说到配置更新后需要清除RefreshScope中的缓存,ContextRefresher负责完成这一任务。它由RefreshAutoConfiguration引入,创建的时候会自动注入RefreshScope和context。
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RefreshScope.class)
@ConditionalOnProperty(name = RefreshAutoConfiguration.REFRESH_SCOPE_ENABLED,
matchIfMissing = true)
@AutoConfigureBefore(HibernateJpaAutoConfiguration.class)
public class RefreshAutoConfiguration {
@Bean
@ConditionalOnMissingBean(RefreshScope.class)
public static RefreshScope refreshScope() {
return new RefreshScope();
}
@Bean
@ConditionalOnMissingBean
public ContextRefresher contextRefresher(ConfigurableApplicationContext context,
RefreshScope scope) {
return new ContextRefresher(context, scope);
}
// ...
}
ContextRefresher的refresh方法就是清理RefreshScope缓存的入口
public synchronized Set<String> refresh() {
Set<String> keys = refreshEnvironment();
this.scope.refreshAll();
return keys;
}
ContextRefresher里的scope就是RefreshScope,它的refreshAll最终会落实到到GenericScope的destroy方法,其中清理了所有的缓存。
public void destroy() {
//...
Collection<BeanLifecycleWrapper> wrappers = this.cache.clear();
//...
}
配置重新加载
要想动态刷新配置,光清除RefreshScope的缓存还不够,还要具备重新加载配置到context中的能力,这一任务也是ContextRefresher完成的。实际上就是在refresh方法中清理RefreshScope缓存之前,即refreshEnvironment方法中完成了配置的重新加载。
public synchronized Set<String> refreshEnvironment() {
Map<String, Object> before = extract(
this.context.getEnvironment().getPropertySources());
addConfigFilesToEnvironment();
Set<String> keys = changes(before,
extract(this.context.getEnvironment().getPropertySources())).keySet();
this.context.publishEvent(new EnvironmentChangeEvent(this.context, keys));
return keys;
}
跟springcloud的启动非常相似,也是用SpringApplication新创建了一个context,新的context加载了最新的配置,然后再合并到原来的context中。因为构建SpringApplication时添加了BootstrapApplicationListener,所以PropertySourceLocator会重新执行。如果PropertySourceLocator里面实现了从数据库或其他网址加载配置,则现在已经加载了最新的配置到context中。
结合上面的RefreshScope缓存的清理,当RefreshScope bean下次被调用的时候就已经是最新的配置了。
ConfigurableApplicationContext addConfigFilesToEnvironment() {
ConfigurableApplicationContext capture = null;
try {
StandardEnvironment environment = copyEnvironment(
this.context.getEnvironment());
SpringApplicationBuilder builder = new SpringApplicationBuilder(Empty.class).environment(environment)...;
builder.application()
.setListeners(Arrays.asList(new BootstrapApplicationListener(),
new ConfigFileApplicationListener()));
capture = builder.run();
//...
MutablePropertySources target = this.context.getEnvironment()
.getPropertySources();
String targetName = null;
for (PropertySource<?> source : environment.getPropertySources()) {
String name = source.getName();
if (target.contains(name)) {
targetName = name;
}
if (!this.standardSources.contains(name)) {
if (target.contains(name)) {
target.replace(name, source);
}
else {
if (targetName != null) {
target.addAfter(targetName, source);
}
else {
// targetName was null so we are at the start of the list
target.addFirst(source);
targetName = name;
}
}
}
}
}
return capture;
}
结合上面的RefreshScope缓存的清理和配置重新加载,当RefreshScope bean下次被调用的时候就已经是最新的配置了。