配置管理
概述
配置管理模块负责系统的核心配置,包括应用属性配置、线程池配置、异步任务配置、校验器配置等。该模块采用 Spring Boot 的自动配置机制,为系统提供开箱即用的基础配置。
模块结构
text
config/
├── AsyncConfig.java # 异步任务配置
├── SpringFeaturesConfig.java # Spring全局特性配置
├── ThreadPoolConfig.java # 线程池配置
├── ValidatorConfig.java # 校验器配置
├── properties/ # 配置属性类
│ ├── AppProperties.java # 应用属性配置
│ └── ThreadPoolProperties.java # 线程池属性配置
├── validation/ # 校验相关配置
│ └── I18nMessageInterceptor.java # 国际化消息拦截器
└── factory/ # 工厂类
└── YmlPropertySourceFactory.java # YAML配置源工厂应用属性配置
AppProperties
应用级别的基础属性配置,定义了系统的核心参数。
java
@Data
@ConfigurationProperties(prefix = "app")
public class AppProperties {
/**
* 应用id
*/
private String id;
/**
* 应用名称/标题
*/
private String title;
/**
* 应用版本
*/
private String version;
/**
* 应用版权年份
*/
private String copyrightYear;
/**
* 应用本地文件上传路径
*/
private String uploadPath;
/**
* 应用基础路径(供支付等模块构建回调地址使用)
*/
private String baseApi;
}配置示例
yaml
app:
id: ryplus_uni
title: ryplus-uni后台管理
version: ${revision}
copyright-year: 2025
upload-path: /ruoyi/server/uploadPath
base-api: https://ruoyi.plus使用方式
java
@Component
public class AppService {
@Autowired
private AppProperties appProperties;
public void buildCallbackUrl(String path) {
String url = appProperties.getBaseApi() + path;
// 构建回调地址逻辑
}
}线程池配置
ThreadPoolConfig
提供系统通用的线程池配置,支持传统线程和虚拟线程自动切换。
核心特性
- 自动模式切换: 根据 JVM 支持情况自动选择虚拟线程或传统线程
- 优雅关闭: 应用停止时安全关闭线程池
- 异常处理: 统一的任务执行异常处理
- 动态配置: 支持通过配置文件调整线程池参数
配置类
java
@Slf4j
@AutoConfiguration
@EnableConfigurationProperties(ThreadPoolProperties.class)
public class ThreadPoolConfig {
/**
* 核心线程数 = CPU 核心数 + 1
*/
private final int core = Runtime.getRuntime().availableProcessors() + 1;
/**
* 普通任务线程池
*/
@Bean(name = "threadPoolTaskExecutor")
@ConditionalOnProperty(prefix = "thread-pool", name = "enabled", havingValue = "true")
public ThreadPoolTaskExecutor threadPoolTaskExecutor(ThreadPoolProperties properties) {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(core);
executor.setMaxPoolSize(core * 2);
executor.setQueueCapacity(properties.getQueueCapacity());
executor.setKeepAliveSeconds(properties.getKeepAliveSeconds());
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return executor;
}
/**
* 定时任务线程池
*/
@Bean(name = "scheduledExecutorService")
protected ScheduledExecutorService scheduledExecutorService() {
// 根据系统支持情况选择虚拟线程或传统线程
BasicThreadFactory.Builder builder = new BasicThreadFactory.Builder().daemon(true);
if (SpringUtils.isVirtual()) {
// 使用虚拟线程
builder.namingPattern("virtual-schedule-pool-%d")
.wrappedFactory(new VirtualThreadTaskExecutor().getVirtualThreadFactory());
} else {
// 使用传统线程
builder.namingPattern("schedule-pool-%d");
}
return new ScheduledThreadPoolExecutor(core, builder.build(),
new ThreadPoolExecutor.CallerRunsPolicy()) {
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
ThreadUtils.logException(r, t);
}
};
}
}配置属性
java
@Data
@ConfigurationProperties(prefix = "thread-pool")
public class ThreadPoolProperties {
/**
* 是否开启线程池
*/
private boolean enabled;
/**
* 队列最大长度
*/
private int queueCapacity;
/**
* 线程池维护线程所允许的空闲时间(秒)
*/
private int keepAliveSeconds;
}配置示例
yaml
thread-pool:
enabled: true
queue-capacity: 1000
keep-alive-seconds: 300使用方式
java
@Service
public class TaskService {
@Autowired
@Qualifier("threadPoolTaskExecutor")
private ThreadPoolTaskExecutor taskExecutor;
public void executeAsyncTask() {
taskExecutor.execute(() -> {
// 异步任务逻辑
System.out.println("执行异步任务");
});
}
}异步配置
AsyncConfig
配置 Spring 的异步执行机制,支持虚拟线程和传统线程池的自动切换。
java
@AutoConfiguration
public class AsyncConfig implements AsyncConfigurer {
/**
* 自定义 @Async 注解的线程执行器
*/
@Override
public Executor getAsyncExecutor() {
// 检查是否支持虚拟线程
if (SpringUtils.isVirtual()) {
return new VirtualThreadTaskExecutor("async-");
}
return SpringUtils.getBean("scheduledExecutorService");
}
/**
* 异步执行异常处理器
*/
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return (throwable, method, objects) -> {
throwable.printStackTrace();
StringBuilder sb = new StringBuilder();
sb.append("异步任务异常 - ").append(throwable.getMessage())
.append(", 方法名称 - ").append(method.getName());
if (ArrayUtil.isNotEmpty(objects)) {
sb.append(", 方法参数 - ").append(Arrays.toString(objects));
}
throw ServiceException.of(sb.toString());
};
}
}使用方式
java
@Service
public class EmailService {
@Async
public void sendEmailAsync(String to, String subject, String content) {
// 异步发送邮件
try {
Thread.sleep(2000); // 模拟邮件发送
log.info("邮件发送成功: {}", to);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new ServiceException("邮件发送被中断");
}
}
}校验器配置
ValidatorConfig
自定义 Bean Validation 配置,集成国际化消息拦截器。
java
@AutoConfiguration(before = ValidationAutoConfiguration.class)
public class ValidatorConfig {
/**
* 配置校验框架
*/
@Bean
public Validator validator(MessageSource messageSource) {
try (LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean()) {
// 设置自定义的国际化消息拦截器
factoryBean.setMessageInterpolator(new I18nMessageInterceptor(messageSource));
// 设置使用 HibernateValidator 校验器
factoryBean.setProviderClass(HibernateValidator.class);
// 配置快速失败模式
Properties properties = new Properties();
properties.setProperty("hibernate.validator.fail_fast", "true");
factoryBean.setValidationProperties(properties);
factoryBean.afterPropertiesSet();
return factoryBean.getValidator();
}
}
}国际化消息拦截器
java
@Component
public class I18nMessageInterceptor implements MessageInterpolator {
private final MessageSource messageSource;
private final MessageInterpolator defaultInterpolator;
public I18nMessageInterceptor(MessageSource messageSource) {
this.messageSource = messageSource;
this.defaultInterpolator = new ResourceBundleMessageInterpolator();
}
@Override
public String interpolate(String messageTemplate, Context context, Locale locale) {
if (StringUtils.isBlank(messageTemplate)) {
return messageTemplate;
}
String trimmedTemplate = messageTemplate.trim();
// 判断是否为简化的国际化键格式
if (RegexValidator.isValidI18nKey(trimmedTemplate)) {
try {
// 获取国际化消息
String i18nMessage = messageSource.getMessage(trimmedTemplate, null, locale);
// 交给默认插值器处理约束属性
return defaultInterpolator.interpolate(i18nMessage, context, locale);
} catch (Exception e) {
return trimmedTemplate;
}
}
// 其他情况委托给默认插值器
return defaultInterpolator.interpolate(messageTemplate, context, locale);
}
}使用示例
java
public class UserBo {
// 直接使用国际化键,无需花括号
@NotBlank(message = "user.username.required")
private String username;
@Email(message = "user.email.format.invalid")
private String email;
}Spring 全局特性配置
SpringFeaturesConfig
启用 Spring 的全局特性,如 AOP 和异步处理。
java
@AutoConfiguration
@EnableAspectJAutoProxy // 启用 AOP
@EnableAsync(proxyTargetClass = true) // 启用异步处理
public class SpringFeaturesConfig {
// 此类仅用于配置注解,无需添加方法
}功能说明:
- @EnableAspectJAutoProxy: 启用基于注解的 AOP 支持
- @EnableAsync: 启用异步方法执行支持,使用 CGLIB 代理
YAML 配置源工厂
YmlPropertySourceFactory
支持 Spring 加载 YAML 格式的配置文件。
java
public class YmlPropertySourceFactory extends DefaultPropertySourceFactory {
@Override
public PropertySource<?> createPropertySource(String name, EncodedResource resource)
throws IOException {
String sourceName = resource.getResource().getFilename();
// 判断是否为 YAML 格式文件
if (StringUtils.isNotBlank(sourceName) &&
StringUtils.endsWithAny(sourceName, ".yml", ".yaml")) {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(resource.getResource());
factory.afterPropertiesSet();
return new PropertiesPropertySource(sourceName, factory.getObject());
}
return super.createPropertySource(name, resource);
}
}使用方式
java
@Component
@PropertySource(value = "classpath:custom-config.yml",
factory = YmlPropertySourceFactory.class)
public class CustomConfig {
// 配置类实现
}自动配置
系统通过 spring.factories 文件实现自动配置:
properties
# META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
plus.ruoyi.common.core.config.properties.AppProperties
plus.ruoyi.common.core.config.AsyncConfig
plus.ruoyi.common.core.config.SpringFeaturesConfig
plus.ruoyi.common.core.config.ThreadPoolConfig
plus.ruoyi.common.core.config.ValidatorConfig
plus.ruoyi.common.core.utils.SpringUtils配置最佳实践
1. 环境配置分离
yaml
# application.yml (通用配置)
app:
title: ryplus-uni后台管理
copyright-year: 2025
thread-pool:
enabled: true
---
# application-dev.yml (开发环境)
app:
upload-path: D:/ruoyi/uploads
base-api: http://localhost:5500
thread-pool:
queue-capacity: 100
keep-alive-seconds: 60
---
# application-prod.yml (生产环境)
app:
upload-path: /opt/ruoyi/uploads
base-api: https://api.ruoyi.com
thread-pool:
queue-capacity: 1000
keep-alive-seconds: 3002. 配置属性校验
java
@Data
@ConfigurationProperties(prefix = "app")
@Validated
public class AppProperties {
@NotBlank(message = "应用ID不能为空")
private String id;
@NotBlank(message = "应用标题不能为空")
private String title;
@Pattern(regexp = "\\d+\\.\\d+\\.\\d+", message = "版本号格式不正确")
private String version;
@Min(value = 1990, message = "版权年份不能小于1990")
@Max(value = 2030, message = "版权年份不能大于2030")
private Integer copyrightYear;
}3. 条件化配置
java
@Configuration
public class ConditionalConfig {
@Bean
@ConditionalOnProperty(name = "app.cache.enabled", havingValue = "true")
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager();
}
@Bean
@ConditionalOnMissingBean(RedisTemplate.class)
public RedisTemplate<String, Object> redisTemplate() {
// Redis配置
return new RedisTemplate<>();
}
}4. 配置加密
yaml
# 敏感配置加密
spring:
datasource:
password: ENC(encrypted_password)
app:
secret-key: ENC(encrypted_secret)5. 配置监控
java
@Component
@ConfigurationPropertiesBinding
public class ConfigurationChangeListener {
@EventListener
public void handleConfigChange(EnvironmentChangeEvent event) {
log.info("配置发生变化: {}", event.getKeys());
// 处理配置变更逻辑
}
}注意事项
1. 线程池配置
- 核心线程数: 建议设置为 CPU核心数 + 1
- 最大线程数: 通常设置为核心线程数的 2 倍
- 队列容量: 根据业务负载调整,避免 OOM
- 拒绝策略: 生产环境建议使用 CallerRunsPolicy
2. 虚拟线程使用
- JDK版本: 需要 JDK 17 或更高版本
- 适用场景: IO 密集型任务,不适合 CPU 密集型
- 注意事项: 避免使用 ThreadLocal,可能影响性能
3. 异步配置
- 异常处理: 必须配置异常处理器,避免异常丢失
- 线程池: 避免使用默认线程池,自定义线程池配置
- 事务处理: 异步方法中事务会失效,需要特殊处理
4. 配置安全
- 敏感信息: 使用配置加密,不要明文存储密码
- 权限控制: 限制配置文件的访问权限
- 环境隔离: 不同环境使用不同的配置文件
5. 性能优化
- 懒加载: 使用 @Lazy 注解延迟初始化
- 条件注解: 使用 @ConditionalOn* 避免不必要的 Bean 创建
- 配置缓存: 避免重复读取配置文件
扩展配置
自定义配置属性
java
@Data
@Component
@ConfigurationProperties(prefix = "custom")
public class CustomProperties {
private Redis redis = new Redis();
private Security security = new Security();
@Data
public static class Redis {
private String host = "localhost";
private int port = 6379;
private String password;
private int database = 0;
}
@Data
public static class Security {
private boolean enabled = true;
private String[] excludePaths = {};
}
}自定义自动配置
java
@AutoConfiguration
@ConditionalOnClass(CustomService.class)
@EnableConfigurationProperties(CustomProperties.class)
public class CustomAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public CustomService customService(CustomProperties properties) {
return new CustomService(properties);
}
}通过合理的配置管理,可以确保系统的灵活性、可维护性和性能优化。配置模块作为系统的基础设施,需要重点关注其稳定性和扩展性。
配置文件加载顺序
Spring Boot 配置优先级
Spring Boot 按照以下优先级加载配置(从高到低):
text
1. 命令行参数 (--server.port=8080)
2. Java 系统属性 (System.getProperties())
3. 操作系统环境变量
4. jar 包外部的 application-{profile}.yml
5. jar 包内部的 application-{profile}.yml
6. jar 包外部的 application.yml
7. jar 包内部的 application.yml
8. @PropertySource 注解加载的配置
9. 默认属性 (SpringApplication.setDefaultProperties)配置文件位置优先级
text
1. file:./config/ # 项目根目录下的 config 文件夹
2. file:./config/*/ # 项目根目录下 config 的子文件夹
3. file:./ # 项目根目录
4. classpath:/config/ # 类路径下的 config 文件夹
5. classpath:/ # 类路径根目录多配置文件加载示例
java
/**
* 配置文件加载顺序演示
*/
@Configuration
public class ConfigLoadingDemo {
@Value("${app.name:默认应用名}")
private String appName;
@PostConstruct
public void showConfigSources() {
// 获取环境对象
ConfigurableEnvironment env = (ConfigurableEnvironment)
SpringUtils.getBean(Environment.class);
// 打印所有配置源
MutablePropertySources sources = env.getPropertySources();
sources.forEach(source -> {
log.info("配置源: {} - {}", source.getName(), source.getClass().getSimpleName());
});
}
}配置中心集成
Nacos 配置中心
java
/**
* Nacos 配置中心集成
*/
@Configuration
@EnableNacosConfig(globalProperties = @NacosProperties(serverAddr = "${nacos.server-addr}"))
public class NacosConfigConfiguration {
/**
* 监听配置变更
*/
@NacosConfigListener(dataId = "app-config.yaml", groupId = "DEFAULT_GROUP")
public void onConfigChange(String newConfig) {
log.info("Nacos 配置变更: {}", newConfig);
// 刷新本地配置缓存
refreshLocalConfig(newConfig);
}
/**
* 手动获取 Nacos 配置
*/
@Autowired
private ConfigService configService;
public String getRemoteConfig(String dataId, String group) {
try {
return configService.getConfig(dataId, group, 5000);
} catch (NacosException e) {
log.error("获取 Nacos 配置失败: dataId={}, group={}", dataId, group, e);
throw ServiceException.of("配置获取失败");
}
}
}Nacos 配置示例
yaml
# bootstrap.yml
spring:
application:
name: ruoyi-system
cloud:
nacos:
config:
server-addr: ${NACOS_SERVER:127.0.0.1:8848}
namespace: ${NACOS_NAMESPACE:dev}
group: DEFAULT_GROUP
file-extension: yaml
shared-configs:
- data-id: common-config.yaml
group: SHARED_GROUP
refresh: true
extension-configs:
- data-id: redis-config.yaml
group: EXTENSION_GROUP
refresh: trueApollo 配置中心
java
/**
* Apollo 配置中心集成
*/
@Configuration
@EnableApolloConfig
public class ApolloConfigConfiguration {
@ApolloConfig
private Config config;
@ApolloConfigChangeListener
public void onChange(ConfigChangeEvent changeEvent) {
changeEvent.changedKeys().forEach(key -> {
ConfigChange change = changeEvent.getChange(key);
log.info("Apollo 配置变更: key={}, oldValue={}, newValue={}, changeType={}",
change.getPropertyName(),
change.getOldValue(),
change.getNewValue(),
change.getChangeType());
});
}
/**
* 获取配置值
*/
public String getProperty(String key, String defaultValue) {
return config.getProperty(key, defaultValue);
}
/**
* 获取整数配置
*/
public int getIntProperty(String key, int defaultValue) {
return config.getIntProperty(key, defaultValue);
}
}动态配置刷新
RefreshScope 实现
java
/**
* 支持动态刷新的配置类
*/
@Component
@RefreshScope
@ConfigurationProperties(prefix = "dynamic")
@Data
public class DynamicConfig {
/**
* 功能开关
*/
private boolean featureEnabled;
/**
* 限流阈值
*/
private int rateLimitThreshold;
/**
* 超时时间(毫秒)
*/
private long timeoutMs;
/**
* 白名单列表
*/
private List<String> whitelist = new ArrayList<>();
}
/**
* 配置刷新事件监听
*/
@Component
@Slf4j
public class ConfigRefreshListener {
@Autowired
private DynamicConfig dynamicConfig;
@EventListener(RefreshScopeRefreshedEvent.class)
public void onRefresh(RefreshScopeRefreshedEvent event) {
log.info("配置已刷新,当前值: featureEnabled={}, rateLimitThreshold={}",
dynamicConfig.isFeatureEnabled(),
dynamicConfig.getRateLimitThreshold());
}
}手动刷新配置
java
/**
* 配置刷新服务
*/
@Service
@Slf4j
public class ConfigRefreshService {
@Autowired
private RefreshScope refreshScope;
@Autowired
private ContextRefresher contextRefresher;
/**
* 刷新指定 Bean 的配置
*/
public void refreshBean(String beanName) {
refreshScope.refresh(beanName);
log.info("Bean 配置已刷新: {}", beanName);
}
/**
* 刷新所有 RefreshScope Bean
*/
public void refreshAll() {
refreshScope.refreshAll();
log.info("所有 RefreshScope Bean 配置已刷新");
}
/**
* 刷新环境配置
*/
public Set<String> refreshEnvironment() {
Set<String> changedKeys = contextRefresher.refresh();
log.info("环境配置已刷新,变更的键: {}", changedKeys);
return changedKeys;
}
}
/**
* 配置刷新 REST 接口
*/
@RestController
@RequestMapping("/config")
public class ConfigRefreshController {
@Autowired
private ConfigRefreshService refreshService;
@PostMapping("/refresh")
@SaCheckRole("admin")
public R<Set<String>> refresh() {
Set<String> changedKeys = refreshService.refreshEnvironment();
return R.ok(changedKeys);
}
@PostMapping("/refresh/{beanName}")
@SaCheckRole("admin")
public R<Void> refreshBean(@PathVariable String beanName) {
refreshService.refreshBean(beanName);
return R.ok();
}
}配置监控与审计
配置变更审计
java
/**
* 配置变更审计服务
*/
@Service
@Slf4j
public class ConfigAuditService {
@Autowired
private ConfigAuditLogMapper auditLogMapper;
/**
* 记录配置变更
*/
public void recordChange(String key, String oldValue, String newValue,
String operator, String source) {
ConfigAuditLog log = new ConfigAuditLog();
log.setConfigKey(key);
log.setOldValue(maskSensitive(oldValue));
log.setNewValue(maskSensitive(newValue));
log.setOperator(operator);
log.setSource(source);
log.setChangeTime(LocalDateTime.now());
log.setClientIp(ServletUtils.getClientIP());
auditLogMapper.insert(log);
}
/**
* 脱敏处理
*/
private String maskSensitive(String value) {
if (value == null) {
return null;
}
// 对密码、密钥等敏感配置进行脱敏
if (value.length() > 4) {
return value.substring(0, 2) + "****" + value.substring(value.length() - 2);
}
return "****";
}
/**
* 查询配置变更历史
*/
public List<ConfigAuditLog> getChangeHistory(String configKey, int limit) {
return auditLogMapper.selectByKey(configKey, limit);
}
}
/**
* 配置审计日志实体
*/
@Data
@TableName("sys_config_audit_log")
public class ConfigAuditLog {
@TableId(type = IdType.AUTO)
private Long id;
/** 配置键 */
private String configKey;
/** 旧值 */
private String oldValue;
/** 新值 */
private String newValue;
/** 操作人 */
private String operator;
/** 来源(手动/自动同步/配置中心) */
private String source;
/** 变更时间 */
private LocalDateTime changeTime;
/** 客户端IP */
private String clientIp;
}配置健康检查
java
/**
* 配置健康指示器
*/
@Component
public class ConfigHealthIndicator implements HealthIndicator {
@Autowired
private AppProperties appProperties;
@Autowired(required = false)
private ConfigService nacosConfigService;
@Override
public Health health() {
Health.Builder builder = new Health.Builder();
// 检查必要配置
if (StringUtils.isBlank(appProperties.getId())) {
return builder.down()
.withDetail("error", "应用ID未配置")
.build();
}
// 检查配置中心连接
if (nacosConfigService != null) {
try {
String status = nacosConfigService.getServerStatus();
if (!"UP".equals(status)) {
return builder.down()
.withDetail("error", "Nacos 配置中心不可用")
.withDetail("status", status)
.build();
}
} catch (Exception e) {
return builder.down()
.withDetail("error", "Nacos 连接失败: " + e.getMessage())
.build();
}
}
return builder.up()
.withDetail("appId", appProperties.getId())
.withDetail("version", appProperties.getVersion())
.build();
}
}配置指标收集
java
/**
* 配置指标收集器
*/
@Component
@Slf4j
public class ConfigMetricsCollector {
private final MeterRegistry meterRegistry;
// 配置刷新计数器
private final Counter refreshCounter;
// 配置变更计数器
private final Counter changeCounter;
// 配置加载耗时
private final Timer loadTimer;
public ConfigMetricsCollector(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
this.refreshCounter = Counter.builder("config.refresh.count")
.description("配置刷新次数")
.register(meterRegistry);
this.changeCounter = Counter.builder("config.change.count")
.description("配置变更次数")
.register(meterRegistry);
this.loadTimer = Timer.builder("config.load.time")
.description("配置加载耗时")
.register(meterRegistry);
}
/**
* 记录配置刷新
*/
public void recordRefresh() {
refreshCounter.increment();
}
/**
* 记录配置变更
*/
public void recordChange(String key) {
changeCounter.increment();
Counter.builder("config.change.key")
.tag("key", key)
.register(meterRegistry)
.increment();
}
/**
* 记录配置加载耗时
*/
public <T> T recordLoad(Supplier<T> loader) {
return loadTimer.record(loader);
}
}配置属性绑定高级用法
嵌套属性绑定
java
/**
* 复杂嵌套配置属性
*/
@Data
@ConfigurationProperties(prefix = "system")
public class SystemProperties {
/** 基础配置 */
private Basic basic = new Basic();
/** 缓存配置 */
private Cache cache = new Cache();
/** 安全配置 */
private Security security = new Security();
/** 集群配置 */
private Map<String, ClusterNode> clusters = new HashMap<>();
@Data
public static class Basic {
private String name;
private String description;
private String timezone = "Asia/Shanghai";
private Locale locale = Locale.SIMPLIFIED_CHINESE;
}
@Data
public static class Cache {
private boolean enabled = true;
private Duration ttl = Duration.ofMinutes(30);
private int maxSize = 1000;
private CacheType type = CacheType.REDIS;
public enum CacheType {
LOCAL, REDIS, CAFFEINE
}
}
@Data
public static class Security {
private boolean enabled = true;
private String[] allowedOrigins = {"*"};
private Duration tokenTtl = Duration.ofHours(2);
private List<String> excludePaths = new ArrayList<>();
}
@Data
public static class ClusterNode {
private String host;
private int port;
private int weight = 1;
private boolean master = false;
}
}YAML 配置示例
yaml
system:
basic:
name: RuoYi-Plus
description: 企业级后台管理系统
timezone: Asia/Shanghai
locale: zh_CN
cache:
enabled: true
ttl: 30m
max-size: 1000
type: redis
security:
enabled: true
allowed-origins:
- https://ruoyi.plus
- https://admin.ruoyi.plus
token-ttl: 2h
exclude-paths:
- /api/public/**
- /health/**
clusters:
node1:
host: 192.168.1.10
port: 8080
weight: 2
master: true
node2:
host: 192.168.1.11
port: 8080
weight: 1
master: false构造器绑定
java
/**
* 使用构造器绑定的不可变配置
*/
@ConfigurationProperties(prefix = "immutable")
public class ImmutableProperties {
private final String apiKey;
private final String secretKey;
private final Duration timeout;
private final List<String> endpoints;
@ConstructorBinding
public ImmutableProperties(
String apiKey,
String secretKey,
@DefaultValue("30s") Duration timeout,
@DefaultValue("") List<String> endpoints) {
this.apiKey = apiKey;
this.secretKey = secretKey;
this.timeout = timeout;
this.endpoints = endpoints;
}
// 只有 getter,没有 setter
public String getApiKey() {
return apiKey;
}
public String getSecretKey() {
return secretKey;
}
public Duration getTimeout() {
return timeout;
}
public List<String> getEndpoints() {
return Collections.unmodifiableList(endpoints);
}
}配置属性转换器
java
/**
* 自定义配置转换器
*/
@Component
@ConfigurationPropertiesBinding
public class StringToPatternConverter implements Converter<String, Pattern> {
@Override
public Pattern convert(String source) {
if (StringUtils.isBlank(source)) {
return null;
}
return Pattern.compile(source);
}
}
/**
* Duration 转换器(支持中文)
*/
@Component
@ConfigurationPropertiesBinding
public class ChineseDurationConverter implements Converter<String, Duration> {
private static final Map<String, Function<Long, Duration>> UNITS = Map.of(
"秒", Duration::ofSeconds,
"分", Duration::ofMinutes,
"分钟", Duration::ofMinutes,
"时", Duration::ofHours,
"小时", Duration::ofHours,
"天", Duration::ofDays,
"日", Duration::ofDays
);
@Override
public Duration convert(String source) {
if (StringUtils.isBlank(source)) {
return null;
}
// 尝试标准格式
try {
return Duration.parse(source);
} catch (Exception ignored) {
}
// 尝试中文格式
for (Map.Entry<String, Function<Long, Duration>> entry : UNITS.entrySet()) {
if (source.endsWith(entry.getKey())) {
String numberPart = source.substring(0, source.length() - entry.getKey().length());
long value = Long.parseLong(numberPart.trim());
return entry.getValue().apply(value);
}
}
throw new IllegalArgumentException("无法解析时间配置: " + source);
}
}多环境配置策略
Profile 配置
java
/**
* 环境感知配置
*/
@Configuration
public class EnvironmentAwareConfig {
@Autowired
private Environment environment;
/**
* 判断当前环境
*/
public boolean isProduction() {
return Arrays.asList(environment.getActiveProfiles()).contains("prod");
}
public boolean isDevelopment() {
return Arrays.asList(environment.getActiveProfiles()).contains("dev");
}
/**
* 开发环境专用 Bean
*/
@Bean
@Profile("dev")
public DataSourceInitializer devDataSourceInitializer(DataSource dataSource) {
DataSourceInitializer initializer = new DataSourceInitializer();
initializer.setDataSource(dataSource);
initializer.setDatabasePopulator(new ResourceDatabasePopulator(
new ClassPathResource("sql/dev-data.sql")));
return initializer;
}
/**
* 生产环境专用配置
*/
@Bean
@Profile("prod")
public MeterRegistryCustomizer<MeterRegistry> productionMetrics() {
return registry -> registry.config()
.commonTags("env", "production")
.commonTags("region", "cn-east");
}
}配置继承与覆盖
yaml
# application.yml - 基础配置
spring:
profiles:
group:
dev: dev,dev-db,dev-cache
prod: prod,prod-db,prod-cache
test: test,test-db
logging:
level:
root: INFO
---
# application-dev.yml
spring:
config:
activate:
on-profile: dev
logging:
level:
root: DEBUG
com.ruoyi: DEBUG
app:
debug-mode: true
---
# application-prod.yml
spring:
config:
activate:
on-profile: prod
logging:
level:
root: WARN
com.ruoyi: INFO
app:
debug-mode: false配置分组管理
java
/**
* 配置分组服务
*/
@Service
public class ConfigGroupService {
@Autowired
private Environment environment;
/**
* 获取分组配置
*/
public Map<String, String> getGroupConfig(String group) {
Map<String, String> configs = new HashMap<>();
// 从环境中获取指定前缀的所有配置
if (environment instanceof ConfigurableEnvironment configEnv) {
MutablePropertySources sources = configEnv.getPropertySources();
String prefix = group + ".";
sources.forEach(source -> {
if (source instanceof EnumerablePropertySource<?> enumerable) {
for (String key : enumerable.getPropertyNames()) {
if (key.startsWith(prefix)) {
Object value = source.getProperty(key);
if (value != null) {
configs.putIfAbsent(key, value.toString());
}
}
}
}
});
}
return configs;
}
/**
* 导出配置
*/
public String exportConfig(String format) {
Map<String, Object> allConfigs = new LinkedHashMap<>();
if (environment instanceof ConfigurableEnvironment configEnv) {
MutablePropertySources sources = configEnv.getPropertySources();
sources.forEach(source -> {
if (source instanceof EnumerablePropertySource<?> enumerable) {
for (String key : enumerable.getPropertyNames()) {
if (!key.startsWith("java.") && !key.startsWith("sun.")) {
Object value = source.getProperty(key);
allConfigs.putIfAbsent(key, value);
}
}
}
});
}
if ("yaml".equals(format)) {
return new Yaml().dump(allConfigs);
}
return JsonUtils.toJsonString(allConfigs);
}
}配置加密与安全
Jasypt 配置加密
java
/**
* Jasypt 加密配置
*/
@Configuration
public class JasyptConfig {
/**
* 配置加密器
*/
@Bean("jasyptStringEncryptor")
public StringEncryptor stringEncryptor() {
PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
SimpleStringPBEConfig config = new SimpleStringPBEConfig();
// 从环境变量获取加密密码
config.setPassword(System.getenv("JASYPT_ENCRYPTOR_PASSWORD"));
config.setAlgorithm("PBEWITHHMACSHA512ANDAES_256");
config.setKeyObtentionIterations("1000");
config.setPoolSize("1");
config.setProviderName("SunJCE");
config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator");
config.setIvGeneratorClassName("org.jasypt.iv.RandomIvGenerator");
config.setStringOutputType("base64");
encryptor.setConfig(config);
return encryptor;
}
}加密配置示例
yaml
spring:
datasource:
# ENC() 包裹的值会自动解密
password: ENC(AAAAILxGlT9h3Jw7k8yqWz...)
app:
secret-key: ENC(AAAAIKz9PLmN7hU4k2xQwz...)配置加密工具
java
/**
* 配置加密工具类
*/
@Component
public class ConfigEncryptUtils {
@Autowired
@Qualifier("jasyptStringEncryptor")
private StringEncryptor encryptor;
/**
* 加密配置值
*/
public String encrypt(String plainText) {
return encryptor.encrypt(plainText);
}
/**
* 解密配置值
*/
public String decrypt(String encryptedText) {
return encryptor.decrypt(encryptedText);
}
/**
* 批量加密
*/
public Map<String, String> encryptAll(Map<String, String> configs) {
Map<String, String> encrypted = new HashMap<>();
configs.forEach((key, value) -> {
encrypted.put(key, "ENC(" + encrypt(value) + ")");
});
return encrypted;
}
}
/**
* 加密管理接口
*/
@RestController
@RequestMapping("/admin/config")
@SaCheckRole("admin")
public class ConfigEncryptController {
@Autowired
private ConfigEncryptUtils encryptUtils;
@PostMapping("/encrypt")
public R<String> encrypt(@RequestParam String value) {
String encrypted = encryptUtils.encrypt(value);
return R.ok("ENC(" + encrypted + ")");
}
@PostMapping("/decrypt")
public R<String> decrypt(@RequestParam String value) {
// 移除 ENC() 包装
String encryptedValue = value.replace("ENC(", "").replace(")", "");
String decrypted = encryptUtils.decrypt(encryptedValue);
return R.ok(decrypted);
}
}配置热更新
基于事件的配置更新
java
/**
* 配置更新事件
*/
public class ConfigUpdateEvent extends ApplicationEvent {
private final String key;
private final String oldValue;
private final String newValue;
public ConfigUpdateEvent(Object source, String key, String oldValue, String newValue) {
super(source);
this.key = key;
this.oldValue = oldValue;
this.newValue = newValue;
}
// Getters...
}
/**
* 配置更新发布器
*/
@Component
public class ConfigUpdatePublisher {
@Autowired
private ApplicationEventPublisher eventPublisher;
public void publishUpdate(String key, String oldValue, String newValue) {
ConfigUpdateEvent event = new ConfigUpdateEvent(this, key, oldValue, newValue);
eventPublisher.publishEvent(event);
}
}
/**
* 配置更新监听器
*/
@Component
@Slf4j
public class ConfigUpdateListener {
@Autowired
private ConfigAuditService auditService;
@EventListener
public void onConfigUpdate(ConfigUpdateEvent event) {
log.info("配置更新: key={}, oldValue={}, newValue={}",
event.getKey(), event.getOldValue(), event.getNewValue());
// 记录审计日志
auditService.recordChange(
event.getKey(),
event.getOldValue(),
event.getNewValue(),
LoginHelper.getUsername(),
"HOT_UPDATE"
);
}
@EventListener
@Async
public void onConfigUpdateAsync(ConfigUpdateEvent event) {
// 异步处理配置变更后的操作
if (event.getKey().startsWith("cache.")) {
// 刷新缓存配置
refreshCacheConfig(event);
}
}
private void refreshCacheConfig(ConfigUpdateEvent event) {
// 实现缓存配置刷新逻辑
}
}配置版本管理
java
/**
* 配置版本服务
*/
@Service
@Slf4j
public class ConfigVersionService {
@Autowired
private ConfigVersionMapper versionMapper;
/**
* 保存配置版本
*/
public void saveVersion(String configKey, String configValue, String comment) {
ConfigVersion version = new ConfigVersion();
version.setConfigKey(configKey);
version.setConfigValue(configValue);
version.setVersion(generateVersion());
version.setComment(comment);
version.setCreatedBy(LoginHelper.getUsername());
version.setCreatedTime(LocalDateTime.now());
versionMapper.insert(version);
}
/**
* 生成版本号
*/
private String generateVersion() {
return LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
}
/**
* 回滚到指定版本
*/
public void rollback(String configKey, String version) {
ConfigVersion targetVersion = versionMapper.selectByKeyAndVersion(configKey, version);
if (targetVersion == null) {
throw ServiceException.of("版本不存在: " + version);
}
// 获取当前值
String currentValue = getCurrentValue(configKey);
// 更新为目标版本的值
updateConfig(configKey, targetVersion.getConfigValue());
// 保存回滚记录
saveVersion(configKey, targetVersion.getConfigValue(),
"回滚到版本 " + version + ",原值: " + currentValue);
}
/**
* 获取版本历史
*/
public List<ConfigVersion> getVersionHistory(String configKey, int limit) {
return versionMapper.selectHistoryByKey(configKey, limit);
}
/**
* 比较两个版本
*/
public ConfigDiff compareVersions(String configKey, String version1, String version2) {
ConfigVersion v1 = versionMapper.selectByKeyAndVersion(configKey, version1);
ConfigVersion v2 = versionMapper.selectByKeyAndVersion(configKey, version2);
ConfigDiff diff = new ConfigDiff();
diff.setConfigKey(configKey);
diff.setVersion1(version1);
diff.setVersion2(version2);
diff.setValue1(v1 != null ? v1.getConfigValue() : null);
diff.setValue2(v2 != null ? v2.getConfigValue() : null);
return diff;
}
}注意事项补充
8. 配置加载性能
- 延迟加载: 非必要配置使用
@Lazy延迟初始化 - 批量加载: 避免逐个获取配置,使用配置属性类批量绑定
- 缓存配置: 频繁访问的配置使用本地缓存
- 异步加载: 大量配置可以异步加载,不阻塞启动
java
// ❌ 不推荐:逐个获取配置
@Value("${app.name}")
private String appName;
@Value("${app.version}")
private String appVersion;
// ... 更多配置
// ✅ 推荐:使用配置属性类
@Autowired
private AppProperties appProperties;9. 配置命名规范
- 统一前缀: 模块配置使用统一前缀,如
app.,system. - 层次结构: 使用点号分隔表示层次,如
cache.redis.host - 驼峰转换: YAML 中使用
kebab-case,Java 中使用camelCase - 避免缩写: 配置键使用完整单词,便于理解
yaml
# ✅ 推荐命名
cache:
redis:
host: localhost
port: 6379
connection-timeout: 3000ms
# ❌ 不推荐命名
c:
r:
h: localhost
p: 6379
ct: 300010. 配置文档化
- 添加注释: 在 YAML 配置中添加注释说明
- 生成文档: 使用 spring-configuration-metadata 生成配置提示
- 维护变更: 重要配置变更记录在变更日志中
java
/**
* 生成配置元数据
* 在 pom.xml 中添加依赖:
* spring-boot-configuration-processor
*/
@Data
@ConfigurationProperties(prefix = "app.feature")
public class FeatureProperties {
/**
* 是否启用新功能
* @since 1.0.0
*/
private boolean newFeatureEnabled = false;
/**
* 功能超时时间
* 默认 30 秒
*/
private Duration timeout = Duration.ofSeconds(30);
}