
现在服务端中配置中心起着至关重要的作用,在Spring Cloud中也提供了Config Service来扮演者配置中心的角色,开源项目中还有Nacos等常用的配置中心,这次我们就讨论下Spring Cloud下配置中心的加载以及更新过程等。
Bootstrap Application
在Spring Cloud中经常使用bootstrap.properties(yaml)配置文件,首先来说下bootstrap.properties与application.properties的区别。在Spring Cloud应用中会创建一个Bootstrap Context作为主应用的父Context。Bootstrap Context读取bootstrap.properties负责从外部的配置中心中加载、解析配置信息,这两个Context共享一个Enviroment。Bootstrap里面的属性会优先加载,它们默认不能被本地配置覆盖。
通过在spring.factories文件中配置org.springframework.cloud.bootstrap.BootstrapConfiguration可以在引导过程中设置一些自定义信息。这些操作将在ApplicatioinContext初始化之前处理,可以通过@Order指定初始化顺序。注意在使用的时候要使用特殊的包,以保证不要被@ComponentScan注解扫描到而导致重复加载。引导过程添加额外配置的默认属性源是从Spring Cloud Config Server中加载的,可以通过添加类型为PropertySourceLocator的Bean到BootstrapContext中来实现从其他源加载配置。
Refresh Scope
Spring Cloud中增加了@RefreshScope注解来支持对类属性的变化刷新,配置信息的变化主要通过对RefreshEvent事件的监听来实现。下面我们主要分下Spring Cloud下@RefreshScope+@Value实现配置动态更新的过程。
整体处理调用过程如下表所示:
RefreshEvent监听 | ||||
RefreshEventListener | ||||
onApplicationEvent() | ||||
ContextRefresher#refresh() | ||||
refreshEnvironment() | ||||
addConfigFilesToEnvironment() | ||||
ConfigurableApplicationContext#publishEvent() | ||||
RefreshScope#refreshAll() | ||||
destroy() | ||||
ApplicationContext#publishEvent() | ||||
@RefreshScope注解对象的特殊处理 | ||||
RefreshScope | ||||
GenericScope | ||||
postProcessBeanDefinitionRegistry() | ||||
LockedScopedProxyFactoryBean | ||||
setBeanFactory() | ||||
super.setBeanFactory() | ||||
@RefreshScope属性访问 | ||||
LockedScopedProxyFactoryBean#invoke() | ||||
SimpleBeanTargetSource#getTarget() | ||||
AbstractApplicationContext#getBean() | ||||
GenericScope#get() | ||||
BeanLifecycleWrapper#getBean() | ||||
ReflectionUtils#invokeMethod() |
RefreshEvent监听
RefreshEventListener类,实现了SmartApplicationListener,可以监听ApplicationReadyEvent以及RefreshEvent。
在onApplicationEvent()方法中,当收到RefreshEvent时候,将会调用ContextRefresher#refresh()方法。
ContextRefresher#refresh()
public synchronized Set<String> refresh() {
Set<String> keys = refreshEnvironment();
this.scope.refreshAll();
return keys;
}
这里做了两件事情
- 将现有的Environment中的属性进行Copy,再通过refreshEnvironment()更新为最新的Environment信息,然后进行对比发现变化。
- 执行Scope的全量刷新。
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;
}
主要是执行了Enviroment的刷新,是通过重新创建了一个BootstrapContxt来实现的,通过对比前后Enviroment的变化发布EnvironmentChangeEvent事件。
ConfigurableApplicationContext addConfigFilesToEnvironment() {
ConfigurableApplicationContext capture = null;
try {
StandardEnvironment environment = copyEnvironment(
this.context.getEnvironment());
SpringApplicationBuilder builder = new SpringApplicationBuilder(Empty.class)
.bannerMode(Mode.OFF).web(WebApplicationType.NONE)
.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);
targetName = name;
}
else {
target.addFirst(source);
targetName = name;
}
}
}
}
} finally {
ConfigurableApplicationContext closeable = capture;
while (closeable != null) {
closeable.close();
if (closeable.getParent() instanceof ConfigurableApplicationContext) {
closeable = (ConfigurableApplicationContext) closeable.getParent();
}
else {
break;
}
}
}
return capture;
}
addConfigFilesToEnvironment() --> 通过SpringApplicationBuilder,以及copy的environment创建了一个新的最简ApplicationContext,这样就会触发重新加载引导过程,也就会从配置中心中读取到最新的配置,加载到Enviroment中(这也是为什么Spring Boot使用Bootstrap Context来引导启动的原因,这样可以保证主Context正常运行,只加载引导部分)。然后将最新加载的Enviroment的属性替换应用现有的ApplicationContext中的Enviroment的属性,来达到Enviroment更新的目的,最后将新创建的Application Context销毁掉。
ConfigurableApplicationContext#publishEvent() --> 发布Enviroment中变化的key的EnvironmentChangeEvent事件。
RefreshScope#refreshAll()
主要执行了缓存的清除,以及RefreshScopeRefreshedEvent事件的发布。
destroy() -> 执行了缓存的清除,其实就是HashMap的清空,同时执行每个缓存对象BeanLifecycleWrapper的destroy方法,清空缓存对象,this.bean = null。
ApplicationContext#publishEvent() --> 发布RefreshScopeRefreshedEvent事件。
@RefreshScope注解对象的特殊处理
RefreshScope类继承自GenericScope,而GenericScope实现了BeanDefinitionRegistryPostProcessor。所以查看postProcessBeanDefinitionRegistry()方法。
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)
throws BeansException {
for (String name : registry.getBeanDefinitionNames()) {
BeanDefinition definition = registry.getBeanDefinition(name);
if (definition instanceof RootBeanDefinition) {
RootBeanDefinition root = (RootBeanDefinition) definition;
if (root.getDecoratedDefinition() != null && root.hasBeanClass()
&& root.getBeanClass() == ScopedProxyFactoryBean.class) {
if (getName().equals(root.getDecoratedDefinition().getBeanDefinition()
.getScope())) {
root.setBeanClass(LockedScopedProxyFactoryBean.class);
root.getConstructorArgumentValues().addGenericArgumentValue(this);
// surprising that a scoped proxy bean definition is not already
// marked as synthetic?
root.setSynthetic(true);
}
}
}
}
}
方法中,判断BeanDefinition的Scope与当前对象的Scope是否一致(当前的为refresh),如果一致则将BeanDefinition的BeanClass更新为LockedScopedProxyFactoryBean,看类名也知道是这个操作是将原先直接获取类对象,更改为从ProxyFactoryBean中获取。
LockedScopedProxyFactoryBean
LockedScopedProxyFactoryBean类继承自ScopedProxyFactoryBean,实现了FactoryBean,MethodInterceptor接口。
public void setBeanFactory(BeanFactory beanFactory) {
super.setBeanFactory(beanFactory);
Object proxy = getObject();
if (proxy instanceof Advised) {
Advised advised = (Advised) proxy;
advised.addAdvice(0, this);
}
}
首先查看setBeanFactory()方法,这里首先调用了父类的setBeanFactory()方法,然后将自己作为增强加入到增强中。
public void setBeanFactory(BeanFactory beanFactory) {
if (!(beanFactory instanceof ConfigurableBeanFactory)) {
throw new IllegalStateException("Not running in a ConfigurableBeanFactory: " + beanFactory);
} else {
ConfigurableBeanFactory cbf = (ConfigurableBeanFactory)beanFactory;
this.scopedTargetSource.setBeanFactory(beanFactory);
ProxyFactory pf = new ProxyFactory();
pf.copyFrom(this);
pf.setTargetSource(this.scopedTargetSource);
Assert.notNull(this.targetBeanName, "Property 'targetBeanName' is required");
Class<?> beanType = beanFactory.getType(this.targetBeanName);
if (beanType == null) {
throw new IllegalStateException("Cannot create scoped proxy for bean '" + this.targetBeanName + "': Target type could not be determined at the time of proxy creation.");
} else {
if (!this.isProxyTargetClass() || beanType.isInterface() || Modifier.isPrivate(beanType.getModifiers())) {
pf.setInterfaces(ClassUtils.getAllInterfacesForClass(beanType, cbf.getBeanClassLoader()));
}
ScopedObject scopedObject = new DefaultScopedObject(cbf, this.scopedTargetSource.getTargetBeanName());
pf.addAdvice(new DelegatingIntroductionInterceptor(scopedObject));
pf.addInterface(AopInfrastructureBean.class);
this.proxy = pf.getProxy(cbf.getBeanClassLoader());
}
}
}
super.setBeanFactory() --> 首先构建了ProxyFactory,设置targetSource为SimpleBeanTargetSource,这个操作很重要,因为SimpleBeanTargetSource#getTarget()方法每次执行时候会从BeanFactory中获取最新的Bean,对实现动态更新属性很重要。之后通过执行this.proxy = pf.getProxy(cbf.getBeanClassLoader());构建了proxy对象。
advised.addAdvice(0, this) --> 由于LockedScopedProxyFactoryBean同时实现了MethodInterceptor接口,所以可以作为增强加入到对象的动态代理处理中。
@RefreshScope属性访问
当发生@RefreshScope注解对象方法调用时候,由于每个对象都被包装成了LockedScopedProxyFactoryBean,需要看相关的增加类,也就是前面将自身加入到增强中的LockedScopedProxyFactoryBean的invoke()方法。
public Object invoke(MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();
if (AopUtils.isEqualsMethod(method) || AopUtils.isToStringMethod(method)
|| AopUtils.isHashCodeMethod(method)
|| isScopedObjectGetTargetObject(method)) {
return invocation.proceed();
}
Object proxy = getObject();
ReadWriteLock readWriteLock = this.scope.getLock(this.targetBeanName);
if (readWriteLock == null) {
readWriteLock = new ReentrantReadWriteLock();
}
Lock lock = readWriteLock.readLock();
lock.lock();
try {
if (proxy instanceof Advised) {
Advised advised = (Advised) proxy;
ReflectionUtils.makeAccessible(method);
return ReflectionUtils.invokeMethod(method,
advised.getTargetSource().getTarget(),
invocation.getArguments());
}
return invocation.proceed();
}
finally {
lock.unlock();
}
}
这里首先会根据ReadWriteLock判断当前的操作是否处于锁定。之后主要是看这一个调用ReflectionUtils.invokeMethod(method, advised.getTargetSource().getTarget(), invocation.getArguments());
advised.getTargetSource().getTarget()
这个核心理解advised.getTargetSource().getTarget(),advised.getTargetSource()就是上面设置的SimpleBeanTargetSource对象,而SimpleBeanTargetSource#getTarget()方法,其实就是调用了BeanFactory#getBean()方法。
AbstractBeanFactory#getBean() --> AbstractBeanFactory#doGetBean(),这里会做如下判断
if (mbd.isSingleton()) {
......
}
else if (mbd.isPrototype()) {
......
}
else {
String scopeName = mbd.getScope();
Scope scope = this.scopes.get(scopeName);
try {
Object scopedInstance = scope.get(beanName, () -> {
beforePrototypeCreation(beanName);
try {
return createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
});
bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
}
}
很显然,分支会走到else中。这里获取到的Scope也就是之前提到过的RefreshScope对象,然后执行RefreshScope#get()方法。
RefreshScope#get() --> 首先从缓存中获取BeanLifecycleWrapper对象,然后调用其getBean()方法。
BeanLifecycleWrapper#getBean() --> 在这个方法中,可以看到如果bean不为空直接返回,如果持有的bean对象如果为空,则会objectFactory中获取。而objectFactory的getObject()就是上面代码中的匿名函数,也就是会调用AbstractApplicationContext#createBean(beanName, mbd, args)创建一个新的对象。
在RefreshScope#refreshAll()的时候会将BeanLifecycleWrapper的对象置为null,到这里的时候就会创建一个新的对象,由于新的对象创建会使用当前最新的配置,也就实现了配置的刷新。
ReflectionUtils#invokeMethod()
在获取到了最新的可执行对象后,通过反射调用,自然也可以获取到最新的执行结果。
可以看到SpringCloud的实现机制还是比较复杂的,使用动态代理增加了理解的复杂性。为了达到属性更新能及时获取到,使用动态代理来保证每次方法调用时候,都从BeanFactory中获取最新的对象,调用过程复杂,也使用了不少锁。在性能上不会是最优的。
网友评论