предисловие
Текст был включен в мой репозиторий GitHub, добро пожаловать, звезда:GitHub.com/bin39232820…
Лучшее время посадить дерево было десять лет назад, затем сейчас
болтовня
В прошлой статье был проанализирован принцип работы центра конфигурации Nacos, принцип работы клиента и принцип работы сервера, далее нам необходимо сотрудничать с аннотацией @RefreshScope для завершения нашей автоматической настройки
- Принцип работы майнинг-пита SpringBoot2.2.0+Nacos как распределенного центра настройки (1)
- Принцип работы Nacos как распределенного конфигурационного центра (2)
BeanScope
В SpringIOC известный BeanScope имеет синглтон (singleton) и прототип (prototype). Scope бина влияет на управление бином. Например, когда создается бин с Scope=singleton, IOC сохранит экземпляр в карте, чтобы гарантировать, что этот компонент A имеет один и только один экземпляр в контексте IOC. В SpringCloud добавлена область области обновления, которая также изменяет управление bean-компонентами уникальным образом, чтобы его можно было обновить с помощью внешней конфигурации (. Значение внешней конфигурации.
Так как же этот прицел обеспечивает горячую загрузку? RefreshScope в основном выполняет следующие действия:
Управляйте жизненным циклом компонента отдельно При создании bean-компонента, если это RefreshScope, он кэшируется в специально управляемой ScopeMap, чтобы можно было управлять жизненным циклом bean-компонента, Scope которого Refresh. воссоздать бин После обновления внешней конфигурации будет запущено действие, которое очистит bean-компоненты в ScopeMap выше, таким образом, эти bean-компоненты будут воссозданы контейнером IOC и внедрены в класс с последним значением внешней конфигурации. для достижения горячей загрузки Эффект нового значения Давайте углубимся в исходный код, чтобы проверить наше утверждение выше.
Управление жизненным циклом RefreshBean
Прежде всего, если вам нужен bean-компонент, который может автоматически загружать значения конфигурации в горячем режиме, bean-компонент должен быть аннотирован с помощью @RefreshScope, а затем посмотрите, что делает эта аннотация:
@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 имеет атрибут proxyMode=ScopedProxyMode.TARGET_CLASS, который используется для динамического прокси AOP и будет упомянут позже.
Видно, что это составная аннотация, помеченная @Scope("refresh"), которая изменяет Scope bean-компонента на тип обновления и аннотирует класс BootStrap в SpringBoot с помощью @SpringBootApplication (внутри находится @ComponentScan) , просто Он будет сканировать управляемые аннотациями компоненты в пакете. Когда он сканирует компонент, аннотированный с помощью RefreshScope, он изменит область действия своего BeanDefinition на обновление. В чем польза?
При создании Bean он переходит к методу doGetBean BeanFactory для создания Bean.Различные области имеют разные методы создания:
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
//....
// Create bean instance.
// 单例Bean的创建
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
//...
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
// 原型Bean的创建
else if (mbd.isPrototype()) {
// It's a prototype -> create a new instance.
// ...
try {
prototypeInstance = createBean(beanName, mbd, args);
}
//...
bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}
else {
// 由上面的RefreshScope注解可以知道,这里scopeName=refresh
String scopeName = mbd.getScope();
// 获取Refresh的Scope对象
final Scope scope = this.scopes.get(scopeName);
if (scope == null) {
throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
}
try {
// 让Scope对象去管理Bean
Object scopedInstance = scope.get(beanName, () -> {
beforePrototypeCreation(beanName);
try {
return createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
});
bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
}
//...
}
}
//...
}
//...
}
Здесь можно увидеть несколько вещей:
- Bean-компоненты синглтона и прототипа жестко запрограммированы для отдельной обработки.
- За исключением синглетонов и bean-компонентов-прототипов, другие Scope обрабатываются объектами Scope.
- Конкретный процесс создания bean-компонента выполняется IOC, но bean-компонент получается через объект Scope.
Здесь объектом Scope, полученным с помощью scope.get, является RefreshScope.Видно, что создание bean-компонентов по-прежнему выполняется IOC (метод createBean), но для получения bean-компонентов используется метод get объекта RefreshScope. метод реализован в родительском классе GenericScope. :
public Object get(String name, ObjectFactory<?> objectFactory) {
// 将Bean缓存下来
BeanLifecycleWrapper value = this.cache.put(name,
new BeanLifecycleWrapper(name, objectFactory));
this.locks.putIfAbsent(name, new ReentrantReadWriteLock());
try {
// 创建Bean,只会创建一次,后面直接返回创建好的Bean
return value.getBean();
}
catch (RuntimeException e) {
this.errors.put(name, e);
throw e;
}
}
Во-первых, Bean запаковывается и кэшируется здесь.
Здесь объектом Scope, полученным с помощью scope.get, является RefreshScope.Видно, что создание bean-компонентов по-прежнему выполняется IOC (метод createBean), но для получения bean-компонентов используется метод get объекта RefreshScope. метод реализован в родительском классе GenericScope. :
public Object get(String name, ObjectFactory<?> objectFactory) {
// 将Bean缓存下来
BeanLifecycleWrapper value = this.cache.put(name,
new BeanLifecycleWrapper(name, objectFactory));
this.locks.putIfAbsent(name, new ReentrantReadWriteLock());
try {
// 创建Bean,只会创建一次,后面直接返回创建好的Bean
return value.getBean();
}
catch (RuntimeException e) {
this.errors.put(name, e);
throw e;
}
}
private final ScopeCache cache;
// 这里进入上面的 BeanLifecycleWrapper value = this.cache.put(name, new BeanLifecycleWrapper(name, objectFactory));
public BeanLifecycleWrapper put(String name, BeanLifecycleWrapper value) {
return (BeanLifecycleWrapper) this.cache.put(name, value);
}
Объект ScopeCache здесь на самом деле является HashMap:
public class StandardScopeCache implements ScopeCache {
private final ConcurrentMap<String, Object> cache = new ConcurrentHashMap<String, Object>();
//...
public Object get(String name) {
return this.cache.get(name);
}
// 如果不存在,才会put进去
public Object put(String name, Object value) {
// result若不等于null,表示缓存存在了,不会进行put操作
Object result = this.cache.putIfAbsent(name, value);
if (result != null) {
// 直接返回旧对象
return result;
}
// put成功,返回新对象
return value;
}
}
Вот чтобы упаковать боб в объект. На карте, в следующий раз, если вы получите BeaBean, или старый BeanWrapper. Вернуться к применению метода Получить, далее - звонить методом GetBean BeanWRapper:
// 实际Bean对象,缓存下来了
private Object bean;
public Object getBean() {
if (this.bean == null) {
synchronized (this.name) {
if (this.bean == null) {
this.bean = this.objectFactory.getObject();
}
}
}
return this.bean;
}
Видно, что переменная компонента бобов в BeanWAPPER - это фактический фасоль. Если первое получение определенно пустое, метод Createbean Cannfactory будет призван создать боб, и он будет сохранен после того, как он будет сохранен.
Видно, что RefreshScope управляет жизненным циклом бинов с Scope=Refresh.
Воссоздать RefreshBean
Когда центр конфигурации обновляет конфигурацию, есть два способа динамического обновления значения переменной конфигурации бина (это почти то же самое для SpringCloud-Bus или Nacos):
- Опубликовать RefreshEvent в контексте
- Http-доступ/обновление этой конечной точки
Независимо от того, какой метод обновления режима, в конечном счете, вызывает ContextRefresher этого класса, поэтому мы должны проанализировать вход в принципы расположения тепловой нагрузки:
// 这就是我们上面一直分析的Scope对象(实际上可以看作一个保存refreshBean的Map)
private RefreshScope scope;
public synchronized Set<String> refresh() {
// 更新上下文中Environment外部化配置值
Set<String> keys = refreshEnvironment();
// 调用scope对象的refreshAll方法
this.scope.refreshAll();
return keys;
}
Обычно мы используем @value, @configurationproperties для получения значения переменной конфигурации, а нижний уровень находится в IOC для получения значения свойства через контекстный объект Environment, а затем полагаемся на внедрение в отражающий набор для объекта bean.
Итак, если мы обновим значение свойства в среде, затем заново создадим RefreshBean и снова выполним вышеуказанную инъекцию зависимостей, сможем ли мы завершить горячую загрузку конфигурации? Значение переменной @Value может быть загружено как последнее.
Здесь обновление объекта Environment и повторное внедрение зависимостей делают то же, что и два вышеуказанных метода:
- Set keys = refreshEnvironment();
- this.scope.refreshAll();
Обновить объект среды
Ниже приведено краткое описание того, как обновить значение свойства в среде.
public synchronized Set<String> refreshEnvironment() {
// 获取刷新配置前的配置信息,对比用
Map<String, Object> before = extract(
this.context.getEnvironment().getPropertySources());
// 刷新Environment
addConfigFilesToEnvironment();
// 这里上下文的Environment已经是新的值了
// 进行新旧对比,结果返回有变化的值
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对象,copy一份
StandardEnvironment environment = copyEnvironment(
this.context.getEnvironment());
// SpringBoot启动类builder,准备新做一个Spring上下文启动
SpringApplicationBuilder builder = new SpringApplicationBuilder(Empty.class)
// banner和web都关闭,因为只是想单纯利用新的Spring上下文构造一个新的Environment
.bannerMode(Mode.OFF).web(WebApplicationType.NONE)
// 传入我们刚刚copy的Environment实例
.environment(environment);
// 启动上下文
capture = builder.run();
// 这个时候,通过上下文SpringIOC的启动,刚刚Environment对象就变成带有最新配置值的Environment了
// 获取旧的外部化配置列表
MutablePropertySources target = this.context.getEnvironment()
.getPropertySources();
String targetName = null;
// 遍历这个最新的Environment外部化配置列表
for (PropertySource<?> source : environment.getPropertySources()) {
String name = source.getName();
if (target.contains(name)) {
targetName = name;
}
// 某些配置源不做替换,读者自行查看源码
// 一般的配置源都会进入if语句
if (!this.standardSources.contains(name)) {
if (target.contains(name)) {
// 用新的配置替换旧的配置
target.replace(name, source);
}
else {
//....
}
}
}
}
//....
}
Видно, что в конечном счете это метод контекста запуска SpringBoot.Создается новый контекст Spring, потому что Spring будет инициализировать Environment в контексте после запуска и получит последнюю конфигурацию.Поэтому запуск SpringBoot здесь используется для получения последней конфигурации Назначение объекта Environment. Затем замените значение конфигурации в объекте Environment в старом контексте.
Воссоздать RefreshBean
После вышеуказанных действий по обновлению объекта Environment значения конфигурации в контексте уже актуальны. Идея восходит к методу обновления ContextRefresher, а затем будет вызываться метод refreshAll объекта Scope:
public void refreshAll() {
// 销毁Bean
super.destroy();
this.context.publishEvent(new RefreshScopeRefreshedEvent());
}
public void destroy() {
List<Throwable> errors = new ArrayList<Throwable>();
// 缓存清空
Collection<BeanLifecycleWrapper> wrappers = this.cache.clear();
// ...
}
Помните обсуждение кэширования в разделе об управлении жизненным циклом RefreshBean выше, переменная cache — это карта, содержащая экземпляр RefreshBean, и карта очищается непосредственно здесь.
Идея восходит к процессу doGetBean BeanFactory, а получение RefreshBean из IOC-контейнера осуществляется методом get RefreshScope:
public Object get(String name, ObjectFactory<?> objectFactory) {
// 由于刚刚清空了缓存Map,这里就会put一个新的BeanLifecycleWrapper实例
BeanLifecycleWrapper value = this.cache.put(name,
new BeanLifecycleWrapper(name, objectFactory));
this.locks.putIfAbsent(name, new ReentrantReadWriteLock());
try {
// 在这里是新的BeanLifecycleWrapper实例调用getBean方法
return value.getBean();
}
catch (RuntimeException e) {
this.errors.put(name, e);
throw e;
}
}
public Object getBean() {
// 由于是新的BeanLifecycleWrapper实例,这里一定为null
if (this.bean == null) {
synchronized (this.name) {
if (this.bean == null) {
// 调用IOC容器的createBean,再创建一个Bean出来
this.bean = this.objectFactory.getObject();
}
}
}
return this.bean;
}
Видно, что RefreshBean воссоздается контейнером IOC в это время.После функции внедрения зависимостей IOC, @Value является новым значением конфигурации. На этом реализация функции горячей загрузки в основном закончена.
Согласно вышеуказанному анализу, мы видим, что до тех пор, пока мы получаем GetBean из контейнера IOC каждый раз, полученный ремня обновления должна быть бобов с последним значением конфигурации.
конец
До сих пор мы поняли и завершили.Сяо Люлю фактически усвоил общую идею.Многие детали не так ясны.Подготовьтесь к продолжению изучения в будущем.
ежедневные комплименты
Хорошо всем, вышеизложенное является полным содержанием этой статьи. Люди, которые могут видеть это здесь, всенастоящий порошок.
Творить нелегко. Ваша поддержка и признание — самая большая мотивация для моего творчества. Увидимся в следующей статье.
Six Meridians Excalibur | Text [Original] Если в этом блоге есть какие-то ошибки, прошу покритиковать и посоветовать, буду очень признателен!