SpringBoot使用Nacos
引入依赖
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>nacos-config-spring-boot-starter</artifactId>
<version>0.2.12</version>
</dependency>
增加本地配置
nacos:
config:
server-addr: 127.0.0.1:8848
bootstrap:
enable: true
log:
enable: true
data-id: cls-service
type: yaml
auto-refresh: true # 开启自动刷新
增加远程配置
cls:
cname: chars11
使用@NacosValue
@RestController
@RequestMapping("test")
public class NacosValueController {
@NacosValue(value = "${cls.cname}", autoRefreshed = true)
private String userName;
@GetMapping("t1")
public String getUserName() {
return userName;
}
}
NacosValue原理解析
NacosConfigEnvironmentProcessor
EnvironmentPostProcessorApplicationListener
实现了SmartApplicationListener
,系统启动的时候,会执行EnvironmentPostProcessorApplicationListener#onApplicationEvent
。获取容器中所有的EnvironmentPostProcessor
,执行对应的postProcessEnvironment
。
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationEnvironmentPreparedEvent) {
this.onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent)event);
}
if (event instanceof ApplicationPreparedEvent) {
this.onApplicationPreparedEvent();
}
if (event instanceof ApplicationFailedEvent) {
this.onApplicationFailedEvent();
}
}
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
ConfigurableEnvironment environment = event.getEnvironment();
SpringApplication application = event.getSpringApplication();
Iterator var4 = this.getEnvironmentPostProcessors(application.getResourceLoader(), event.getBootstrapContext()).iterator();
while(var4.hasNext()) {
EnvironmentPostProcessor postProcessor = (EnvironmentPostProcessor)var4.next();
postProcessor.postProcessEnvironment(environment, application);
}
}
NacosConfigEnvironmentProcessor#postProcessEnvironment
,如果nacos.config.bootstrap.logEnable=true
,开启预加载。系统添加了NacosConfigApplicationContextInitializer
初始化器。
public void postProcessEnvironment(ConfigurableEnvironment environment,
SpringApplication application) {
application.addInitializers(new NacosConfigApplicationContextInitializer(this));
nacosConfigProperties = NacosConfigPropertiesUtils
.buildNacosConfigProperties(environment);
if (enable()) {
System.out.println(
"[Nacos Config Boot] : The preload log configuration is enabled");
loadConfig(environment);
NacosConfigLoader nacosConfigLoader = NacosConfigLoaderFactory.getSingleton(nacosConfigProperties, environment, builder);
LogAutoFreshProcess.build(environment, nacosConfigProperties, nacosConfigLoader, builder).process();
}
}
boolean enable() {
return nacosConfigProperties != null
&& nacosConfigProperties.getBootstrap().isLogEnable();
}
NacosConfigEnvironmentProcessor#loadConfig
,调用NacosConfigLoader
进行加载。
private void loadConfig(ConfigurableEnvironment environment) {
NacosConfigLoader configLoader = new NacosConfigLoader(nacosConfigProperties,
environment, builder);
configLoader.loadConfig();
// set defer NacosPropertySource
deferPropertySources.addAll(configLoader.getNacosPropertySources());
}
NacosConfigLoader#loadConfig
,调用了NacosUtils.getContent
来获取远程配置的内容。
public void loadConfig() {
MutablePropertySources mutablePropertySources = environment.getPropertySources();
List<NacosPropertySource> sources = reqGlobalNacosConfig(globalProperties,
nacosConfigProperties.getType());
for (NacosConfigProperties.Config config : nacosConfigProperties.getExtConfig()) {
List<NacosPropertySource> elements = reqSubNacosConfig(config,
globalProperties, config.getType());
sources.addAll(elements);
}
if (nacosConfigProperties.isRemoteFirst()) {
for (ListIterator<NacosPropertySource> itr = sources.listIterator(sources.size()); itr.hasPrevious();) {
mutablePropertySources.addAfter(
StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, itr.previous());
}
} else {
for (NacosPropertySource propertySource : sources) {
mutablePropertySources.addLast(propertySource);
}
}
}
private List<NacosPropertySource> reqGlobalNacosConfig(Properties globalProperties,
ConfigType type) {
List<String> dataIds = new ArrayList<>();
// Loads all data-id information into the list in the list
if (!StringUtils.hasLength(nacosConfigProperties.getDataId())) {
final String ids = environment
.resolvePlaceholders(nacosConfigProperties.getDataIds());
dataIds.addAll(Arrays.asList(ids.split(",")));
}
else {
dataIds.add(nacosConfigProperties.getDataId());
}
final String groupName = environment
.resolvePlaceholders(nacosConfigProperties.getGroup());
final boolean isAutoRefresh = nacosConfigProperties.isAutoRefresh();
return new ArrayList<>(Arrays.asList(reqNacosConfig(globalProperties,
dataIds.toArray(new String[0]), groupName, type, isAutoRefresh)));
}
private NacosPropertySource[] reqNacosConfig(Properties configProperties,
String[] dataIds, String groupId, ConfigType type, boolean isAutoRefresh) {
final NacosPropertySource[] propertySources = new NacosPropertySource[dataIds.length];
for (int i = 0; i < dataIds.length; i++) {
if (!StringUtils.hasLength(dataIds[i])) {
continue;
}
// Remove excess Spaces
final String dataId = environment.resolvePlaceholders(dataIds[i].trim());
final String config = NacosUtils.getContent(builder.apply(configProperties),
dataId, groupId);
final NacosPropertySource nacosPropertySource = new NacosPropertySource(
dataId, groupId,
buildDefaultPropertySourceName(dataId, groupId, configProperties),
config, type.getType());
nacosPropertySource.setDataId(dataId);
nacosPropertySource.setType(type.getType());
nacosPropertySource.setGroupId(groupId);
nacosPropertySource.setAutoRefreshed(isAutoRefresh);
logger.info("load config from nacos, data-id is : {}, group is : {}",
nacosPropertySource.getDataId(), nacosPropertySource.getGroupId());
propertySources[i] = nacosPropertySource;
DeferNacosPropertySource defer = new DeferNacosPropertySource(
nacosPropertySource, configProperties, environment);
nacosPropertySources.add(defer);
}
return propertySources;
}
NacosUtils#getContent
,获取远程配置,调用核心类ConfigService
。
public static String getContent(ConfigService configService, String dataId,
String groupId) {
String content = null;
try {
content = configService.getConfig(dataId, groupId, DEFAULT_TIMEOUT);
}
catch (NacosException e) {
if (logger.isErrorEnabled()) {
logger.error("Can't get content from dataId : " + dataId + " , groupId : "
+ groupId, e);
}
}
return content;
}
NacosConfigApplicationContextInitializer
NacosConfigApplicationContextInitializer
初始化,会添加自动刷新的监听器。
public class NacosConfigApplicationContextInitializer
implements ApplicationContextInitializer<ConfigurableApplicationContext> {
// ...
@Override
public void initialize(ConfigurableApplicationContext context) {
singleton.setApplicationContext(context);
environment = context.getEnvironment();
nacosConfigProperties = NacosConfigPropertiesUtils
.buildNacosConfigProperties(environment);
final NacosConfigLoader configLoader = NacosConfigLoaderFactory.getSingleton(
nacosConfigProperties, environment, builder);
if (!enable()) {
logger.info("[Nacos Config Boot] : The preload configuration is not enabled");
}
else {
// If it opens the log level loading directly will cache
// DeferNacosPropertySource release
if (processor.enable()) {
processor.publishDeferService(context);
configLoader
.addListenerIfAutoRefreshed(processor.getDeferPropertySources());
}
else {
configLoader.loadConfig();
configLoader.addListenerIfAutoRefreshed();
}
}
final ConfigurableListableBeanFactory factory = context.getBeanFactory();
if (!factory
.containsSingleton(NacosBeanUtils.GLOBAL_NACOS_PROPERTIES_BEAN_NAME)) {
factory.registerSingleton(NacosBeanUtils.GLOBAL_NACOS_PROPERTIES_BEAN_NAME,
configLoader.getGlobalProperties());
}
}
private boolean enable() {
return processor.enable() || nacosConfigProperties.getBootstrap().isEnable();
}
}
NacosConfigLoader#addListenerIfAutoRefreshed(List<NacosConfigLoader.DeferNacosPropertySource>)
,调用NacosPropertySourcePostProcessor
添加监听器。
public void addListenerIfAutoRefreshed(
final List<DeferNacosPropertySource> deferNacosPropertySources) {
for (DeferNacosPropertySource deferNacosPropertySource : deferNacosPropertySources) {
NacosPropertySourcePostProcessor.addListenerIfAutoRefreshed(
deferNacosPropertySource.getNacosPropertySource(),
deferNacosPropertySource.getProperties(),
deferNacosPropertySource.getEnvironment());
}
}
NacosPropertySourcePostProcessor#addListenerIfAutoRefreshed
,当监听到配置变化,直接替换env的配置数据。
public static void addListenerIfAutoRefreshed(
final NacosPropertySource nacosPropertySource, final Properties properties,
final ConfigurableEnvironment environment) {
if (!nacosPropertySource.isAutoRefreshed()) { // Disable Auto-Refreshed
return;
}
final String dataId = nacosPropertySource.getDataId();
final String groupId = nacosPropertySource.getGroupId();
final String type = nacosPropertySource.getType();
final NacosServiceFactory nacosServiceFactory = getNacosServiceFactoryBean(
beanFactory);
try {
ConfigService configService = nacosServiceFactory
.createConfigService(properties);
Listener listener = new AbstractListener() {
@Override
public void receiveConfigInfo(String config) {
String name = nacosPropertySource.getName();
NacosPropertySource newNacosPropertySource = new NacosPropertySource(
dataId, groupId, name, config, type);
newNacosPropertySource.copy(nacosPropertySource);
MutablePropertySources propertySources = environment
.getPropertySources();
// replace NacosPropertySource
propertySources.replace(name, newNacosPropertySource);
}
};
if (configService instanceof EventPublishingConfigService) {
((EventPublishingConfigService) configService).addListener(dataId,
groupId, type, listener);
}
else {
configService.addListener(dataId, groupId, listener);
}
}
catch (NacosException e) {
throw new RuntimeException(
"ConfigService can't add Listener with properties : " + properties,
e);
}
}
使用DelegatingEventPublishingListener
对监听器进行包装。
public void addListener(String dataId, String group, String type, Listener listener)
throws NacosException {
Listener listenerAdapter = new DelegatingEventPublishingListener(configService,
dataId, group, type, applicationEventPublisher, executor, listener);
addListener(dataId, group, listenerAdapter);
}
DelegatingEventPublishingListener#receiveConfigInfo
,当接收到事件后,会调用内置的监听器处理,以及发布NacosConfigReceivedEvent
事件。
@Override
public void receiveConfigInfo(String content) {
onReceived(content);
publishEvent(content);
}
private void publishEvent(String content) {
NacosConfigReceivedEvent event = new NacosConfigReceivedEvent(configService,
dataId, groupId, content, configType);
applicationEventPublisher.publishEvent(event);
}
private void onReceived(String content) {
delegate.receiveConfigInfo(content);
}
NacosValueAnnotationBeanPostProcessor
NacosValueAnnotationBeanPostProcessor
实现了BeanPostProcessor
,启动的时候执行NacosValueAnnotationBeanPostProcessor#postProcessBeforeInitialization
。将带有@NacosValue
注解的属性和方法加入到一个Map中。
@Override
public Object postProcessBeforeInitialization(Object bean, final String beanName)
throws BeansException {
doWithFields(bean, beanName);
doWithMethods(bean, beanName);
return super.postProcessBeforeInitialization(bean, beanName);
}
private void doWithFields(final Object bean, final String beanName) {
ReflectionUtils.doWithFields(bean.getClass(),
new ReflectionUtils.FieldCallback() {
@Override
public void doWith(Field field) throws IllegalArgumentException {
NacosValue annotation = getAnnotation(field, NacosValue.class);
doWithAnnotation(beanName, bean, annotation, field.getModifiers(),
null, field);
}
});
}
private void doWithMethods(final Object bean, final String beanName) {
ReflectionUtils.doWithMethods(bean.getClass(),
new ReflectionUtils.MethodCallback() {
@Override
public void doWith(Method method) throws IllegalArgumentException {
NacosValue annotation = getAnnotation(method, NacosValue.class);
doWithAnnotation(beanName, bean, annotation,
method.getModifiers(), method, null);
}
});
}
private void doWithAnnotation(String beanName, Object bean, NacosValue annotation,
int modifiers, Method method, Field field) {
if (annotation != null) {
if (Modifier.isStatic(modifiers)) {
return;
}
if (annotation.autoRefreshed()) {
String placeholder = resolvePlaceholder(annotation.value());
if (placeholder == null) {
return;
}
NacosValueTarget nacosValueTarget = new NacosValueTarget(bean, beanName,
method, field, annotation.value());
put2ListMap(placeholderNacosValueTargetMap, placeholder,
nacosValueTarget);
}
}
}
private <K, V> void put2ListMap(Map<K, List<V>> map, K key, V value) {
List<V> valueList = map.get(key);
if (valueList == null) {
valueList = new ArrayList<V>();
}
valueList.add(value);
map.put(key, valueList);
}
该类同样还实现了ApplicationListener<NacosConfigReceivedEvent>
,当监听到NacosConfigReceivedEvent
,通过反射用新的值替换Bean中的属性值。
@Override
public void onApplicationEvent(NacosConfigReceivedEvent event) {
// In to this event receiver, the environment has been updated the
// latest configuration information, pull directly from the environment
// fix issue #142
for (Map.Entry<String, List<NacosValueTarget>> entry : placeholderNacosValueTargetMap
.entrySet()) {
String key = environment.resolvePlaceholders(entry.getKey());
String newValue = environment.getProperty(key);
if (newValue == null) {
continue;
}
List<NacosValueTarget> beanPropertyList = entry.getValue();
for (NacosValueTarget target : beanPropertyList) {
String md5String = MD5Utils.md5Hex(newValue, "UTF-8");
boolean isUpdate = !target.lastMD5.equals(md5String);
if (isUpdate) {
target.updateLastMD5(md5String);
Object evaluatedValue = resolveNotifyValue(target.nacosValueExpr, key, newValue);
if (target.method == null) {
setField(target, evaluatedValue);
}
else {
setMethod(target, evaluatedValue);
}
}
}
}
}
private void setMethod(NacosValueTarget nacosValueTarget, Object propertyValue) {
Method method = nacosValueTarget.method;
ReflectionUtils.makeAccessible(method);
try {
method.invoke(nacosValueTarget.bean,
convertIfNecessary(method, propertyValue));
if (logger.isDebugEnabled()) {
logger.debug("Update value with {} (method) in {} (bean) with {}",
method.getName(), nacosValueTarget.beanName, propertyValue);
}
}
catch (Throwable e) {
if (logger.isErrorEnabled()) {
logger.error("Can't update value with " + method.getName()
+ " (method) in " + nacosValueTarget.beanName + " (bean)", e);
}
}
}
private void setField(final NacosValueTarget nacosValueTarget,
final Object propertyValue) {
final Object bean = nacosValueTarget.bean;
Field field = nacosValueTarget.field;
String fieldName = field.getName();
try {
ReflectionUtils.makeAccessible(field);
field.set(bean, convertIfNecessary(field, propertyValue));
if (logger.isDebugEnabled()) {
logger.debug("Update value of the {}" + " (field) in {} (bean) with {}",
fieldName, nacosValueTarget.beanName, propertyValue);
}
}
catch (Throwable e) {
if (logger.isErrorEnabled()) {
logger.error("Can't update value of the " + fieldName + " (field) in "
+ nacosValueTarget.beanName + " (bean)", e);
}
}
}