首页 > 其他分享 > SpringCloud中使用Apollo实现动态刷新

SpringCloud中使用Apollo实现动态刷新

时间:2023-04-21 17:26:27浏览次数:76  
标签:name SpringCloud void RefreshScope private 刷新 Apollo public String

Spring SpringBoot

SpringCloud中使用Apollo实现动态刷新



普通字段

在需要刷新的字段上使用@value注解即可,例如:

    @Value("${test.user.name}")
    private String name;

    @Value("${test.user.age}")
    private Integer age;

    @Value("${test.user.sex}")
    private Boolean sex;
 

bean使用@ConfigurationProperties动态刷新

bean使用@ConfigurationProperties注解目前还不支持自动刷新,得编写一定的代码实现刷新。目前官方提供2种刷新方案:

  • 基于RefreshScope实现刷新
  • 基于EnvironmentChangeEvent实现刷新

方法一:基于RefreshScope实现刷新

  1. 确保项目中已引入依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-context</artifactId>
</dependency>
 
  1. 实体类上使用@RefreshScope注解
@ConfigurationProperties("test.user")
@Data
@Component
@RefreshScope
public class TestUserProperties implements Serializable {
    private Integer age;
    private String name;
    private Boolean sex;
}
 

在namespace=config的命名空间中定义配置:

test.user.name = zhangsan
test.user.age = 10
test.user.sex = 1
 
  1. 利用RefreshScope搭配@ApolloConfigChangeListener监听实现bean的动态刷新
@Component
public class ApolloDynamicConfigPropertiesRefresh {

    @Resource
    RefreshScope refreshScope;

    @ApolloConfigChangeListener(value="config")
    private void refresh(ConfigChangeEvent changeEvent){

        refreshScope.refresh("testUserProperties");

        PrintChangeKeyUtils.printChange(changeEvent);
    }
}
 
  • @ApolloConfigChangeListener(value="config") 表示监听namespace=config的配置文件的变化
  • refreshScope.refresh("testUserProperties"); 表示如果触发监听事件,则刷新名为testUserProperties的bean;
  • PrintChangeKeyUtils.printChange(changeEvent); 表示打印发送变化的熟悉(可选),PrintChangeKeyUtils定义为:
import com.ctrip.framework.apollo.model.ConfigChange;
import com.ctrip.framework.apollo.model.ConfigChangeEvent;
import org.springframework.util.CollectionUtils;

import java.util.Set;

public class PrintChangeKeyUtils {

    public static void printChange(ConfigChangeEvent changeEvent) {
        Set<String> changeKeys = changeEvent.changedKeys();
        if (!CollectionUtils.isEmpty(changeKeys)) {
            for (String changeKey : changeKeys) {
                ConfigChange configChange = changeEvent.getChange(changeKey);
                System.out.println("key:" + changeKey + ";oldValue:" + configChange.getOldValue() + ";newValue:" + configChange.getNewValue());
            }
        }
    }
}
 

方法二:基于EnvironmentChangeEvent实现刷新

  1. 定义实体类
@ConfigurationProperties("test.user")
@Data
@Component
public class TestUserProperties implements Serializable {
    private Integer age;
    private String name;
    private Boolean sex;
}
 

与方法一的差异是不使用@RefreshScope注解

  1. 利用spring的事件驱动配合@ApolloConfigChangeListener监听实现bean的动态刷新:
@Component
public class ApolloDynamicConfigPropertiesRefresh  implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    RefreshScope refreshScope;

    @ApolloConfigChangeListener(value="config")
    private void refresh(ConfigChangeEvent changeEvent){

        applicationContext.publishEvent(new EnvironmentChangeEvent(changeEvent.changedKeys()));

        PrintChangeKeyUtils.printChange(changeEvent);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}
 

两种方式动态刷新原理浅析:

RefreshScope

首先了解Spring中几个相关的类:

  • 注解@RefreshScope(org.springframework.cloud.context.config.annotation.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;

}
 
  • 注解@Scope(org.springframework.context.annotation.Scope)
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Scope {


	@AliasFor("scopeName")
	String value() default "";

	@AliasFor("value")
	String scopeName() default "";

	ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;

}
 
  • 接口Scope(org.springframework.beans.factory.config.Scope)
public interface Scope {

	Object get(String name, ObjectFactory<?> objectFactory);

	@Nullable
	Object remove(String name);

	void registerDestructionCallback(String name, Runnable callback);

	@Nullable
	Object resolveContextualObject(String key);

	@Nullable
	String getConversationId();

}
 
  • 类RefreshScope(org.springframework.cloud.context.scope.refresh.RefreshScope)
@ManagedResource
public class RefreshScope extends GenericScope implements ApplicationContextAware,ApplicationListener<ContextRefreshedEvent>, Ordered {

	private ApplicationContext context;

	private BeanDefinitionRegistry registry;

	private boolean eager = true;

	private int order = Ordered.LOWEST_PRECEDENCE - 100;

	/**
	 * Creates a scope instance and gives it the default name: "refresh".
	 */
	public RefreshScope() {
		super.setName("refresh");
	}

	@Override
	public int getOrder() {
		return this.order;
	}

	public void setOrder(int order) {
		this.order = order;
	}

	public void setEager(boolean eager) {
		this.eager = eager;
	}

	@Override
	public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)
			throws BeansException {
		this.registry = registry;
		super.postProcessBeanDefinitionRegistry(registry);
	}

	@Override
	public void onApplicationEvent(ContextRefreshedEvent event) {
		start(event);
	}

	public void start(ContextRefreshedEvent event) {
		if (event.getApplicationContext() == this.context && this.eager
				&& this.registry != null) {
			eagerlyInitialize();
		}
	}

	private void eagerlyInitialize() {
		for (String name : this.context.getBeanDefinitionNames()) {
			BeanDefinition definition = this.registry.getBeanDefinition(name);
			if (this.getName().equals(definition.getScope())
					&& !definition.isLazyInit()) {
				Object bean = this.context.getBean(name);
				if (bean != null) {
					bean.getClass();
				}
			}
		}
	}

	@ManagedOperation(description = "Dispose of the current instance of bean name "
			+ "provided and force a refresh on next method execution.")
	public boolean refresh(String name) {
		if (!name.startsWith(SCOPED_TARGET_PREFIX)) {
			// User wants to refresh the bean with this name but that isn't the one in the
			// cache...
			name = SCOPED_TARGET_PREFIX + name;
		}
		// Ensure lifecycle is finished if bean was disposable
		if (super.destroy(name)) {
			this.context.publishEvent(new RefreshScopeRefreshedEvent(name));
			return true;
		}
		return false;
	}

	@ManagedOperation(description = "Dispose of the current instance of all beans "
			+ "in this scope and force a refresh on next method execution.")
	public void refreshAll() {
		super.destroy();
		this.context.publishEvent(new RefreshScopeRefreshedEvent());
	}

	@Override
	public void setApplicationContext(ApplicationContext context) throws BeansException {
		this.context = context;
	}
}
 

带有@RefreshScope注解后的Bean,在初始话的过程中,会通过AnnotationScopeMetadataResolver#resolveScopeMetadata提取元数据:

@Override
public ScopeMetadata resolveScopeMetadata(BeanDefinition definition) {
    ScopeMetadata metadata = new ScopeMetadata();
    if (definition instanceof AnnotatedBeanDefinition) {
        AnnotatedBeanDefinition annDef = (AnnotatedBeanDefinition) definition;
        AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(
                annDef.getMetadata(), this.scopeAnnotationType);
        if (attributes != null) {
            metadata.setScopeName(attributes.getString("value"));
            ScopedProxyMode proxyMode = attributes.getEnum("proxyMode");
            if (proxyMode == ScopedProxyMode.DEFAULT) {
                proxyMode = this.defaultProxyMode;
            }
            metadata.setScopedProxyMode(proxyMode);
        }
    }
    return metadata;
}
 

可以理解为 @RefreshScope 是scopeName="refresh"的 @Scope,其Bean的注册将通过AnnotatedBeanDefinitionReader#registerBean完成的:

private <T> void doRegisterBean(Class<T> beanClass, @Nullable String name,
			@Nullable Class<? extends Annotation>[] qualifiers, @Nullable Supplier<T> supplier,
			@Nullable BeanDefinitionCustomizer[] customizers) {

		AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass);
		if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
			return;
		}

		abd.setInstanceSupplier(supplier);
		ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
		abd.setScope(scopeMetadata.getScopeName());
		String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry));

		AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
		if (qualifiers != null) {
			for (Class<? extends Annotation> qualifier : qualifiers) {
				if (Primary.class == qualifier) {
					abd.setPrimary(true);
				}
				else if (Lazy.class == qualifier) {
					abd.setLazyInit(true);
				}
				else {
					abd.addQualifier(new AutowireCandidateQualifier(qualifier));
				}
			}
		}
		if (customizers != null) {
			for (BeanDefinitionCustomizer customizer : customizers) {
				customizer.customize(abd);
			}
		}

		BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
		definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
		BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
	}
 

测试

@RestController
@RequestMapping("/test/dynamic/config")
public class DynamicConfigTestController {

    @Value("${test.user.name}")
    private String name;

    @Value("${test.user.age}")
    private Integer age;

    @Value("${test.user.sex}")
    private Boolean sex;

    @Resource
    private TestUserProperties userProperties;

    @GetMapping("/user")
    public Map<String, Object> properties() {
        Map<String, Object> map = new HashMap<>();
        map.put("name", name);
        map.put("age", age);
        map.put("sex", sex);
        map.put("user", userProperties.toString());
        return map;
    }

}
 

参考

  1. apollo与springboot集成实现动态刷新配置
  2. @RefreshScope那些事
  0 本文链接: https://wangfeng.pro/2021/05/springcloud中使用apollo实现动态刷新.html  

标签:name,SpringCloud,void,RefreshScope,private,刷新,Apollo,public,String
From: https://www.cnblogs.com/tiancai/p/17341110.html

相关文章

  • SpringCloud集成dubbo的使用
    1.生产者(服务提供者)操作。(服务提供者方的配置)(1)添加依赖(我这里的版本是2.2.3RELEASE)<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-dubbo</artifactId></dependency>(2)提供统一业务api (建议api层和实现层分为两个模块)pub......
  • python编写CDN刷新脚本
    脚本刷新根目录[root@jenkinscdn]#catdns_flush.py#!/usr/bin/python3fromaliyunsdkcore.clientimportAcsClientfromaliyunsdkcdn.request.v20180510.RefreshObjectCachesRequestimportRefreshObjectCachesRequestimportsysiflen(sys.argv)<2:print(�......
  • 刷新汇总字段
    汇总字段由异步系统作业刷新/计算,但是我们可以通过Microsoft.Crm.Sdk强制刷新,不需要等到系统作业时间。SDK:示例代码:CalculateRollupFieldRequestcrfr_purchase_contract_contractamount=newCalculateRollupFieldRequest{ Target=newEntityReference("cssc_purchase_c......
  • 百度发布Apollo城市智驾,距离AI智能驾驶还有多远?
    推荐:将NSDT场景编辑器加入你的3D工具链。工具集:NSDT简石数字孪生随着人工智能技术的不断发展,智能驾驶已经成为了汽车行业的一个重要领域。智能驾驶可以减少人为驾驶的错误和疲劳驾驶等不安全因素,提高驾驶安全性,减少交通事故的发生,提高交通效率,降低能源消耗等。近年来,百度在智能......
  • blazor如何设置定时任务刷新页面
    提问blazor如何设置定时任务刷新页面回答1.使用Timer2.触发页面状态已经改变InvokeAsync(StateHasChanged);protectedoverrideasyncTaskOnInitializedAsync(){try{base.OnInitialized();//TODOvartim......
  • SpringCloud架构之注册中心/负载均衡组件搭建重点步骤
    我的GitHub地址:https://github.com/hiders1/cloud-demoGitHub的使用见:https://www.cnblogs.com/jinjiyese153/p/6796668.htmlVSC不见的话参考:https://blog.csdn.net/Ericjim/article/details/129215479 Nacos服务注册中心下载安装:1:Window系统远程下载GitHub地址:https://gi......
  • springcloud或springboot项目服务启动多个实例
    如果没有service,可以快捷键Alt+8,service标签没有信息,则.idea目录下的workspace.xml下替换或添加融化信息内容<componentname="RunDashboard"><optionname="configurationTypes"><set><optionvalue="SpringBootApplicationConfigurationType"/>&......
  • SpringCloud
    1.nacos除了配置中心还能干什么除了作为配置中心之外,nacos还可以实现服务注册和发现功能。服务提供者可以在启动时将自己注册到nacos中,并声明自己提供的服务名、ip地址和端口等信息。而服务消费者则可以通过nacos查询到相应的服务提供者,并直接调用其提供的服务。此外,nacos还具备......
  • SpringCloud上传大型视频文件到服务器,解决方案
    ​文件上传是最古老的互联网操作之一,20多年来几乎没有怎么变化,还是操作麻烦、缺乏交互、用户体验差。一、前端代码英国程序员RemySharp总结了这些新的接口 ,本文在他的基础之上,讨论在前端采用HTML5的API,对文件上传进行渐进式增强:    * iframe上传 * ajax上传......
  • 29-springcloud-config-5-配置信息的加解密安全处理
    前面是在Git仓库中明文存储配置信息值,很多场景下,对于某些敏感的配置内容(例如数据库账号、密码等),应该加密存储,configserver为我们考虑到了这一点,对配置内容提供了加密与解密支持;安装JCEconfigserver的加解密功能依赖JavaCryptographyExtension(JCE)Java8JCE下载地址:http://......