目录
自定义PropertyPlaceholderConfigurer
背景
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