Skip to content

配置管理

概述

配置管理模块负责系统的核心配置,包括应用属性配置、线程池配置、异步任务配置、校验器配置等。该模块采用 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: 300

2. 配置属性校验

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 21 或更高版本
  • 适用场景: 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);
    }
}

通过合理的配置管理,可以确保系统的灵活性、可维护性和性能优化。配置模块作为系统的基础设施,需要重点关注其稳定性和扩展性。