Springmvc+Nacos+自定义Scope+自定义PropertyPlaceholderConfigurer,实现自动刷新配置文件

目录

背景

实现

Nacos环境搭建

Springmvc项目

pom引入nacos依赖

自定义scope

静态资源类

自定义PropertyPlaceholderConfigurer

spring-application.xml配置

config.properties


背景

springmvc项目自定义PropertyPlaceholderConfigurer,

在mergeProperties()中对接nacos配置中心,

需要自动刷新配置文件的类添加@RefreshScope注解,

监听配置中心文件更新,清理包含@RefreshScope注解的bean

实现自动刷新配置文件

实现

Nacos环境搭建

官网:什么是 Nacos

Spring集成文档:Nacos Spring 快速开始

参考之前写的博文:Springcloud+Druid+Mybatis+Seata+Nacos动态切换多数据源,分布式事务的实现

使用的nacos版本1.4.1 

Springmvc项目

pom引入nacos依赖

<dependency>
    <groupId>com.alibaba.nacos</groupId>
    <artifactId>nacos-spring-context</artifactId>
    <version>1.1.1</version>
</dependency>

自定义scope

RefreshScope注解

package com.luck.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Scope(BeanRefreshScope.SCOPE_REFRESH)
@Documented
public @interface RefreshScope {
	ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
}

BeanRefreshScope用来维护添加了@RefreshScope的bean对象 

package com.luck.annotation;

import java.util.concurrent.ConcurrentHashMap;

import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;

public class BeanRefreshScope implements Scope {

	public static final String SCOPE_REFRESH = "refresh";

	private static final BeanRefreshScope INSTANCE = new BeanRefreshScope();

	private static ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>();

	public BeanRefreshScope() {
	}

	public static BeanRefreshScope getInstance() {
		return INSTANCE;
	}

	/**
	 * 清理beanMap
	 */
	public static void clean() {
		beanMap.clear();
	}

	@Override
	public Object get(String name, ObjectFactory<?> objectFactory) {
		Object bean = beanMap.get(name);
		if (bean == null) {
			bean = objectFactory.getObject();
			beanMap.put(name, bean);
		}
		return bean;
	}

	@Override
	public Object remove(String name) {
		return beanMap.remove(name);
	}

	@Override
	public void registerDestructionCallback(String name, Runnable callback) {

	}

	@Override
	public Object resolveContextualObject(String key) {
		return null;
	}

	@Override
	public String getConversationId() {
		return null;
	}

}

使用示例

package com.luck.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import com.luck.annotation.RefreshScope;
import com.luck.utils.Constant;

@RestController
@RefreshScope
public class TestController {

	@Value("${test:123456}")
	private String test;

	@GetMapping("esbapi/test")
	public void print() {
		System.out.println(test);

		System.out.println(Constant.NACOS_SERVER_ADDRESS);

		System.out.println(Constant.getProperty("abc", ""));
	}
}

静态资源类

package com.luck.utils;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;

/**
 * 静态资源类
 */
@Component
public class Constant {

	/**
	 * nacos 服务地址
	 */
	public static String NACOS_SERVER_ADDRESS;

	/**
	 * Properties对象
	 */
	private static Properties p;

	/** spring上下文环境 */
	private static Environment environment;

	@Autowired
	public void setEnvironment(Environment environment) {
		Constant.environment = environment;
	}

	@Value("${nacos.config.server-addr:}")
	public void setNACOS_SERVER_ADDRESS(String nACOS_SERVER_ADDRESS) {
		NACOS_SERVER_ADDRESS = nACOS_SERVER_ADDRESS;
	}

	/**
	 * 从本地加载配置文件
	 * @param propertyFileName 配置文件名称
	 */
	protected static void init(String propertyFileName) {
		InputStream in = null;
		p = new Properties();
		try {
			in = Constant.class.getResourceAsStream(propertyFileName);
			p.clear();
			if (in != null) {
				p.load(in);
			}
		} catch (IOException e) {
		} finally {
			if (in != null) {
				try {
					in.close();
				} catch (IOException e) {
				}
			}
		}
	}

	/**
	 * 
	 * <p><b>方法描述:</b>获取配置参数值</p>
	 * @param key Properties对应key值
	 * @param defaultValue 默认值
	 * @return 配置参数值
	 */
	public static String getProperty(String key, String defaultValue) {
		String value = null;
		if (null != environment) {
			value = environment.getProperty(key);
		}
		if (StringUtils.isBlank(value)) {
			if (null == p) {
				init("/config.properties");
			}
			value = p.getProperty(key, defaultValue);
		}
		return value;
	}
}

自定义PropertyPlaceholderConfigurer

package com.luck.config;

import java.io.IOException;
import java.io.StringReader;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Executor;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import org.springframework.util.CollectionUtils;
import org.springframework.web.context.ContextLoader;
import org.springframework.web.context.WebApplicationContext;

import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;

import com.luck.annotation.BeanRefreshScope;
import com.luck.utils.Constant;

public class LuckPreferencesPlaceholderConfigurer extends PropertyPlaceholderConfigurer {
	protected Logger logger = LoggerFactory.getLogger(getClass());

	private static String NACOS_DATA_ID = Constant.getProperty("nacos.config.dataid", "config.properties");
	private static String NACOS_GROUP = Constant.getProperty("nacos.config.group", "DEFAULT_GROUP");
	private static String NACOS_NAMESPACE = Constant.getProperty("nacos.config.namespace", "");
	private static String NACOS_SERVERADDR = Constant.getProperty("nacos.config.server-addr", null);

	/** nacos配置中心 */
	private static ConfigService configService;

	/** 从nacos获取到的配置信息 */
	private static Properties nacosProps;

	/** 包括本地配置文件+系统配置+nacos配置的所有配置信息 */
	private static Properties allProps;

	@Override
	protected Properties mergeProperties() throws IOException {
		Properties allProperties = super.mergeProperties();
		if (null == configService) {
			configNacos(allProperties);
		}
		if (null != nacosProps) {

			logger.info("设置Spring环境配置信息");

			WebApplicationContext context = ContextLoader.getCurrentWebApplicationContext();
			if (null != context) {
				Map<String, Object> props = new LinkedHashMap<>(nacosProps.size());
				nacosProps.stringPropertyNames().stream().forEach(key -> props.put(key, nacosProps.getProperty(key)));
				Environment environment = context.getEnvironment();
				MutablePropertySources propertySources = ((ConfigurableEnvironment) environment).getPropertySources();
				String name = "first";
				PropertySource<?> propertySource = propertySources.get(name);
				if (null != propertySource) {
					propertySources.remove(name);
				}
				propertySources.addFirst(new MapPropertySource(name, props));
			}

			CollectionUtils.mergePropertiesIntoMap(nacosProps, allProperties);
		}
		allProps = allProperties;
		return allProperties;
	}

	/**
	 * 获取nacos配置
	 * @param allProperties
	 */
	private void configNacos(Properties allProperties) {
		if (StringUtils.isBlank(NACOS_SERVERADDR)) {
			return;
		}
		try {
			Properties properties = new Properties();
			properties.put(PropertyKeyConst.SERVER_ADDR, NACOS_SERVERADDR);
			properties.put(PropertyKeyConst.NAMESPACE, NACOS_NAMESPACE);
			configService = NacosFactory.createConfigService(properties);
			if (null == configService) {
				return;
			}
			String config = configService.getConfig(NACOS_DATA_ID, NACOS_GROUP, 5000);
			if (StringUtils.isBlank(config)) {
				return;
			}
			try {
				loadNacosProps(config);
			} catch (IOException e) {
				if (logger.isDebugEnabled()) {
					logger.error("加载nacos配置文件失败:{}", e.getMessage());
				}
			}
			// 添加监听
			configService.addListener(NACOS_DATA_ID, NACOS_GROUP, new Listener() {

				@Override
				public void receiveConfigInfo(String config) {

					logger.info("onReceived(String) : {}", config);
					try {
						loadNacosProps(config);
						mergeProperties();
					} catch (IOException e) {
						if (logger.isDebugEnabled()) {
							logger.error("已监听到最新nacos配置文件,但是加载失败:{}", e.getMessage());
						}
					}
					// 刷新下静态资源类
					AutowireCapableBeanFactory autowireCapableBeanFactory = ContextLoader.getCurrentWebApplicationContext().getAutowireCapableBeanFactory();
					Constant bean = autowireCapableBeanFactory.getBean(Constant.class);
					autowireCapableBeanFactory.autowireBean(bean);

					//清空BeanRefreshScope中所有bean的缓存
					BeanRefreshScope.clean();

				}

				@Override
				public Executor getExecutor() {
					return null;
				}
			});
		} catch (NacosException e) {
			e.printStackTrace();
		}
	}

	/**
	 * 加载nacos配置文件
	 * @param config
	 * @throws IOException
	 */
	private void loadNacosProps(String config) throws IOException {
		if (null == nacosProps) {
			nacosProps = new Properties();
		}
		nacosProps.load(new StringReader(config));
	}

	/**
	 * 使用allProps获取配置值
	 * @param placeholder 占位符
	 * @param props 没用了
	 * @param systemPropertiesMode 默认1,不走第一个判断,意思是不先从系统配置文件中查找配置值
	 */
	protected String resolvePlaceholder(String placeholder, Properties props, int systemPropertiesMode) {
		String propVal = null;
		if (systemPropertiesMode == SYSTEM_PROPERTIES_MODE_OVERRIDE) {
			propVal = resolveSystemProperty(placeholder);
		}
		if (propVal == null) {
			propVal = resolvePlaceholder(placeholder, allProps);
		}
		if (propVal == null && systemPropertiesMode == SYSTEM_PROPERTIES_MODE_FALLBACK) {
			propVal = resolveSystemProperty(placeholder);
		}
		return propVal;
	}
}

spring-application.xml配置

<bean id="propertyConfigurer" class="com.luck.config.LuckPreferencesPlaceholderConfigurer">
	<property name="locations">
		<list>
			<value>classpath:config.properties</value>
		</list>
	</property>
</bean>
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
    <property name="scopes">
        <map>
            <entry key="refresh" value="com.luck.annotation.BeanRefreshScope"/>
        </map>
    </property>
</bean>

config.properties

nacos.config.dataid=config.properties
nacos.config.group=DEFAULT_GROUP
nacos.config.namespace=
nacos.config.server-addr=127.0.0.1:8848

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值