предисловие
Некоторое время назад я исследовал, как синхронизироваться с проектом Spring Boot при изменении конфигурации Nacos.
На этот раз давайте продолжим наблюдать за тем, как проект Spring Boot получает обновленную конфигурацию и обновляет ее в проекте.
spring-cloud-context
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-context</artifactId>
<version>2.2.8.RELEASE</version>
</dependency>
Мы часто говорим, что Spring Cloud основан на Spring Boot, почему такое утверждение?
Это потому, что Spring Cloud, по сути, внесла некоторые улучшения и новые функции в Spring Boot.
Например, то, что нам нужно знать в этом разделе, динамическое обновление конфигурации в центре конфигурации, основано наspring-cloud-context
Реализована функция, позволяющая динамически обновлять bean-компоненты во время выполнения.
от@RefreshScope
Говоря о
Из использования мы узнали, что если вы хотите, чтобы класс имел эффект динамического обновления, вам нужно использовать аннотации класса@RefreshScope
.
Итак, начнем с этой аннотации.
/**
* 将@Bean定义放入refresh scope便捷注释。
* 以这种方式注释的 Bean 可以在运行时刷新,任何使用它们的组件将在下一次方法调用时获得一个新实例,完全初始化并注 入所有依赖项
* @author Dave Syer
*/
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Scope("refresh")
@Documented
public @interface RefreshScope {
/**
* @see Scope#proxyMode()
* @return proxy mode
*/
ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
}
@RefreshScope
на самом деле правда@Scope
Оболочка, указывающая тип прокси какTARGET_CLASS
.
Этот тип представляет этот тип, создавая прокси на основе классов (с использованием CGLIB).
Заметки тоже четко написаны,Будет получен новый экземпляр при следующем вызове метода, полностью инициализированный и внедренный со всеми зависимостями..
Используемый здесь прокси-класс и логика получения новых экземпляров в прокси-классе:
CglibAopProxy # DynamicAdvisedIntercepto r# intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy)
Это означает, что если мы подключим обновленную конфигурацию, то при следующем вызове для повторной инициализации экземпляра будет загружена новая конфигурация.
оScope
, когда мы впервые использовали конфигурацию xml, мы использовали эту штуку для настройки области действия bean-компонентов. Но больше описания.
Область, Универсальная область, Обновить область
Эти три класса являются ключевыми для реализации динамического обновления конфигурации в Spring Cloud.
Давайте сначала посмотрим на абстрактные методы Scope:
public interface Scope {
// 获取真正的对象。
// 和 ObjectFactory 的机制是一样的。
Object get(String name, ObjectFactory<?> objectFactory);
// 移除对象
@Nullable
Object remove(String name);
// 注册某个 bean 销毁时的回调方法。
void registerDestructionCallback(String name, Runnable callback);
// .... 省略无关方法
}
Эти абстрактные методы вGenericScope
Есть общие реализации вRefreshScope
Это более логично для динамического обновления bean-компонентов.
Ниже мы приводим выдержку из GenericScopeget()
иdestroy()
метод, для понимания логики важнее эти два метода:
public class GenericScope implements Scope, BeanFactoryPostProcessor,BeanDefinitionRegistryPostProcessor, DisposableBean {
// 这个方法很重要
// 被 @RefreshScope 注解的类,都会被 cglib 代理。
// 代理类每次调用方法,最终都会最终先调用这个方法,获取目标类。然后再执行方法。
// this.cache.put 的效果是 如果 name 存在,就返回已存在的值;如果 name 不存在,就存入新值。
public Object get(String name, ObjectFactory<?> objectFactory) {
BeanLifecycleWrapper value = this.cache.put(name,
new BeanLifecycleWrapper(name, objectFactory));
this.locks.putIfAbsent(name, new ReentrantReadWriteLock());
try {
// 第一次执行此方法,内部会走创建 bean 的逻辑。
return value.getBean();
}
catch (RuntimeException e) {
this.errors.put(name, e);
throw e;
}
}
// 销毁 bean,就是从 cache 中移除name 。
protected boolean destroy(String name) {
BeanLifecycleWrapper wrapper = this.cache.remove(name);
if (wrapper != null) {
Lock lock = this.locks.get(wrapper.getName()).writeLock();
lock.lock();
try {
wrapper.destroy();
}
finally {
lock.unlock();
}
this.errors.remove(name);
return true;
}
return false;
}
}
Затем мы продолжаем видетьRefreshScope
, мы также выделим только интересующие методы:
public class RefreshScope extends GenericScope implements ApplicationContextAware,ApplicationListener<ContextRefreshedEvent>, Ordered {
// 刷新单个 bean
public boolean refresh(String name) {
if (!name.startsWith(SCOPED_TARGET_PREFIX)) {
name = SCOPED_TARGET_PREFIX + name;
}
if (super.destroy(name)) {
this.context.publishEvent(new RefreshScopeRefreshedEvent(name));
return true;
}
return false;
}
// 刷新所有 bean
public void refreshAll() {
super.destroy();
this.context.publishEvent(new RefreshScopeRefreshedEvent());
}
}
Здесь мы видим, что метод обновления действительно выполняется.destroy()
.
иdestroy()
Внутренняя логика , исходит изcache
удалитьname
, или очиститьcache
.
Таким образом, в следующий раз, когда мы выполним метод,cache
Если в нем нет соответствующего компонента, компонент будет повторно добавлен и инициализирован.
ContextRefresher
Выше мы узнали, как динамически обновлять бины.
Итак, как динамический компонент обновления сочетается с обновлением конфигурации? ответContextRefresher
в классе.
public class ContextRefresher {
// ...
public synchronized Set<String> refresh() {
// 刷新上下文配置环境
Set<String> keys = refreshEnvironment();
// 这里执行的就是 RefreshScope#refreshAll() 方法,即销毁缓存中的 Bean。
this.scope.refreshAll();
return keys;
}
public synchronized Set<String> refreshEnvironment() {
// 收集原本的配置项 key
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;
}
// ...
}
Мы видим, что поток здесь:
- Сначала обновите среду конфигурации контекста, а затем уничтожьте бины в кеше.
- В следующий раз, когда компонент будет получен, логика инициализации компонента будет повторена, и вновь загруженные элементы конфигурации будут внедрены в новый компонент.
На этом обновление конфигурации завершено.
Общий процесс понятен, давайте подробнееaddConfigFilesToEnvironment()
:
ConfigurableApplicationContext addConfigFilesToEnvironment() {
ConfigurableApplicationContext capture = null;
try {
// 从当前 Environment 复制一个新 Environment
StandardEnvironment environment = copyEnvironment(
this.context.getEnvironment());
// 构造 SpringApplication
SpringApplicationBuilder builder = new SpringApplicationBuilder(Empty.class)
.bannerMode(Mode.OFF).web(WebApplicationType.NONE)
.environment(environment);
builder.application()
.setListeners(Arrays.asList(new BootstrapApplicationListener(),
new ConfigFileApplicationListener()));
// 运行 SpringApplication
// 这里会走一遍 SpringApplication 启动的流程,在此这种,也就把新配置文件加载到 Environment 类中了。
capture = builder.run();
if (environment.getPropertySources().contains(REFRESH_ARGS_PROPERTY_SOURCE)) {
environment.getPropertySources().remove(REFRESH_ARGS_PROPERTY_SOURCE);
}
// 获取原上下文的属性源
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);
// update targetName to preserve ordering
targetName = name;
} else {
// targetName was null so we are at the start of the list
target.addFirst(source);
targetName = name;
}
}
}
}
}
finally {
// ....
}
return capture;
}
На данный момент с механизмом динамического обновления конфигурации Spring Boot в целом разобрались.
попробуй использоватьContextRefresher
Наконец, давайте попробуемContextRefresher
Обновим конфигурацию.
-
Создайте веб-проект Spring Boot и добавьте дополнительные зависимости
spring-cloud-context
. -
конфигурационный файл
server: port: 9004 spring: application: name: testOne damai: jj: jj xx: xx
-
интерфейс
@RestController @RequestMapping public class TestController{ @Autowired ContextRefresher contextRefresher; @Autowired private TestObj testObj; @GetMapping("/test") public String test() { return testObj.getJj(); } @GetMapping("/refresh") public void refresh() { Set<String> refresh = contextRefresher.refresh(); System.out.println(Arrays.toString(refresh.toArray())); } }
-
запуск, доступ к интерфейсу
/test
-
Измените файл конфигурации и получите доступ к интерфейсу
/refresh
damai: jj: jj123
-
интерфейс доступа
/test
Таким образом, динамическое обновление конфигурации завершено.
Заканчивать.