第3篇:配置管理的艺术 - 让框架更灵活
前言
在前一章中,我们设计了强大的注解API。本章将深入探讨配置管理系统的设计,学习如何将注解中的声明式配置转换为运行时可用的配置对象。
配置管理的核心挑战
在我们的框架中,配置来源有三个层级:
主要挑战:
- 🔀 优先级处理:如何正确合并不同层级的配置
- 🎯 类型安全:确保配置值的类型正确性
- ⚡ 性能优化:避免重复解析和计算
- 🛡️ 配置验证:及早发现配置错误
LogConfig - 统一配置模型
LogConfig
是框架配置管理的核心,承载着从注解解析出来的所有配置信息:
package com.simpleflow.log.config;import com.simpleflow.log.annotation.LogLevel;/*** 日志配置类 - 统一管理所有日志相关的配置项*/
public class LogConfig {// ========== 基础配置 ==========private LogLevel level;private Boolean logArgs;private Boolean logResult;private Boolean logExecutionTime;private Boolean logException;// ========== 消息模板配置 ==========private String prefix;private String startMessage;private String successMessage;private String errorMessage;// ========== 高级配置 ==========private Boolean enableSpel;private Boolean includeRequestId;private int[] excludeArgs;private String[] sensitiveFields;// ========== 类级专用配置 ==========private String[] includeMethods;private String[] excludeMethods;private Boolean includePrivateMethods;private Boolean includeGetterSetter;/*** 合并配置 - 当前配置的非空值会覆盖other配置的对应值*/public LogConfig merge(LogConfig other) {if (other == null) {return new LogConfig(this);}LogConfig merged = new LogConfig();// 基础配置合并(当前配置优先)merged.level = this.level != null ? this.level : other.level;merged.logArgs = this.logArgs != null ? this.logArgs : other.logArgs;merged.logResult = this.logResult != null ? this.logResult : other.logResult;merged.logExecutionTime = this.logExecutionTime != null ? this.logExecutionTime : other.logExecutionTime;merged.logException = this.logException != null ? this.logException : other.logException;// 字符串配置合并merged.prefix = mergeString(this.prefix, other.prefix);merged.startMessage = mergeString(this.startMessage, other.startMessage);merged.successMessage = mergeString(this.successMessage, other.successMessage);merged.errorMessage = mergeString(this.errorMessage, other.errorMessage);// 高级配置合并merged.enableSpel = this.enableSpel != null ? this.enableSpel : other.enableSpel;merged.includeRequestId = this.includeRequestId != null ? this.includeRequestId : other.includeRequestId;// 数组配置合并merged.excludeArgs = mergeIntArray(this.excludeArgs, other.excludeArgs);merged.sensitiveFields = mergeStringArray(this.sensitiveFields, other.sensitiveFields);return merged;}/*** 创建默认配置*/public static LogConfig createDefault() {LogConfig config = new LogConfig();config.level = LogLevel.INFO;config.logArgs = true;config.logResult = true;config.logExecutionTime = true;config.logException = true;config.prefix = "";config.startMessage = "方法开始执行";config.successMessage = "方法执行成功";config.errorMessage = "方法执行异常";config.enableSpel = true;config.includeRequestId = true;config.excludeArgs = new int[0];config.sensitiveFields = new String[0];config.includeMethods = new String[0];config.excludeMethods = new String[0];config.includePrivateMethods = false;config.includeGetterSetter = false;return config;}// 工具方法private String mergeString(String current, String other) {return !isEmpty(current) ? current : other;}private boolean isEmpty(String str) {return str == null || str.trim().isEmpty();}// 省略getter/setter和其他方法...
}
AnnotationConfigResolver - 配置解析器
配置解析器负责将注解转换为LogConfig
对象,并处理配置的合并逻辑:
package com.simpleflow.log.processor;import com.simpleflow.log.annotation.*;
import com.simpleflow.log.config.LogConfig;
import java.lang.reflect.Method;
import java.util.concurrent.ConcurrentHashMap;/*** 注解配置解析器*/
public class AnnotationConfigResolver {// 配置缓存,提升性能private final ConcurrentHashMap<Method, LogConfig> methodConfigCache = new ConcurrentHashMap<>();private final ConcurrentHashMap<Class<?>, LogConfig> classConfigCache = new ConcurrentHashMap<>();private final LogConfig defaultConfig;public AnnotationConfigResolver() {this.defaultConfig = LogConfig.createDefault();}/*** 解析方法的完整配置* 合并优先级:方法配置 > 类配置 > 默认配置*/public LogConfig resolveMethodConfig(Method method) {if (method == null) {return null;}// 检查缓存LogConfig cached = methodConfigCache.get(method);if (cached != null) {return cached;}try {// 1. 检查是否被忽略if (method.isAnnotationPresent(LogIgnore.class)) {methodConfigCache.put(method, null);return null;}// 2. 解析方法级配置LogConfig methodConfig = resolveMethodAnnotation(method);// 3. 解析类级配置LogConfig classConfig = resolveClassConfig(method.getDeclaringClass());// 4. 检查类级配置是否包含此方法if (classConfig != null && !isMethodIncluded(method, classConfig)) {methodConfigCache.put(method, null);return null;}// 5. 合并配置:方法 > 类 > 默认LogConfig finalConfig = mergeConfigs(methodConfig, classConfig, defaultConfig);// 6. 验证和缓存结果if (finalConfig != null) {finalConfig.validate();}methodConfigCache.put(method, finalConfig);return finalConfig;} catch (Exception e) {return null;}}/*** 解析类级配置*/public LogConfig resolveClassConfig(Class<?> clazz) {if (clazz == null) {return null;}LogConfig cached = classConfigCache.get(clazz);if (cached != null) {return cached;}LogConfig classConfig = null;if (clazz.isAnnotationPresent(LogClass.class)) {LogClass logClass = clazz.getAnnotation(LogClass.class);classConfig = createConfigFromClass(logClass);}classConfigCache.put(clazz, classConfig);return classConfig;}/*** 从@LogMethod注解创建配置*/private LogConfig resolveMethodAnnotation(Method method) {if (!method.isAnnotationPresent(LogMethod.class)) {return null;}LogMethod logMethod = method.getAnnotation(LogMethod.class);LogConfig config = new LogConfig();// 基础配置config.setLevel(logMethod.level());config.setLogArgs(logMethod.logArgs());config.setLogResult(logMethod.logResult());config.setLogExecutionTime(logMethod.logExecutionTime());config.setLogException(logMethod.logException());// 消息模板config.setPrefix(emptyToNull(logMethod.prefix()));config.setStartMessage(emptyToNull(logMethod.startMessage()));config.setSuccessMessage(emptyToNull(logMethod.successMessage()));config.setErrorMessage(emptyToNull(logMethod.errorMessage()));// 高级配置config.setEnableSpel(logMethod.enableSpel());config.setIncludeRequestId(logMethod.includeRequestId());if (logMethod.excludeArgs().length > 0) {config.setExcludeArgs(logMethod.excludeArgs());}if (logMethod.sensitiveFields().length > 0) {config.setSensitiveFields(logMethod.sensitiveFields());}return config;}/*** 合并多个配置*/private LogConfig mergeConfigs(LogConfig methodConfig, LogConfig classConfig, LogConfig defaultConfig) {LogConfig result = defaultConfig.copy();if (classConfig != null) {result = result.merge(classConfig);}if (methodConfig != null) {result = result.merge(methodConfig);}return (methodConfig != null || classConfig != null) ? result : null;}// 其他工具方法...
}
配置优先级实战示例
// 示例:配置的继承和覆盖
@LogClass(level = LogLevel.WARN, // 类级默认:WARN级别logArgs = true, // 类级默认:记录参数logResult = false, // 类级默认:不记录返回值prefix = "用户服务" // 类级前缀
)
public class UserService {// 继承类级配置public List<User> findAll() {// 最终配置:level=WARN, logArgs=true, logResult=false, prefix="用户服务"}// 部分覆盖类级配置@LogMethod(logResult = true) // 只覆盖 logResultpublic User findById(Long id) {// 最终配置:level=WARN, logArgs=true, logResult=true, prefix="用户服务"}// 完全覆盖类级配置@LogMethod(level = LogLevel.DEBUG,logArgs = false,prefix = "查询服务")public User findByUsername(String username) {// 最终配置:level=DEBUG, logArgs=false, logResult=false, prefix="查询服务"}// 忽略日志记录@LogIgnore(reason = "内部工具方法")private String formatUserInfo(User user) {// 不会记录任何日志}
}
测试用例
package com.simpleflow.log.processor;import com.simpleflow.log.annotation.*;
import com.simpleflow.log.config.LogConfig;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Method;
import static org.junit.jupiter.api.Assertions.*;class AnnotationConfigResolverTest {private final AnnotationConfigResolver resolver = new AnnotationConfigResolver();@Testvoid testMethodConfigOverridesClassConfig() throws Exception {Method method = TestService.class.getMethod("findById", Long.class);LogConfig config = resolver.resolveMethodConfig(method);assertNotNull(config);assertEquals(LogLevel.INFO, config.getLevel()); // 方法级覆盖assertTrue(config.getLogResult()); // 方法级覆盖assertEquals("用户服务", config.getPrefix()); // 继承类级}@Testvoid testIgnoredMethod() throws Exception {Method method = TestService.class.getMethod("ignoredMethod");LogConfig config = resolver.resolveMethodConfig(method);assertNull(config); // 被忽略的方法返回null}@LogClass(level = LogLevel.WARN, prefix = "用户服务", logResult = false)static class TestService {@LogMethod(level = LogLevel.INFO, logResult = true)public User findById(Long id) {return new User();}@LogIgnore(reason = "测试忽略")public void ignoredMethod() {}}static class User {}
}
本章小结
✅ 完成的任务
- 设计LogConfig:统一的配置模型,支持合并和验证
- 实现解析器:AnnotationConfigResolver负责注解解析
- 配置优先级:方法 > 类 > 默认的合并策略
- 性能优化:通过缓存提升解析性能
- 测试验证:编写测试用例验证功能
🎯 学习要点
- 配置分层:多层级配置的设计思路
- 合并策略:如何优雅地处理配置优先级
- 缓存机制:提升框架性能的重要手段
- 配置验证:保证配置有效性的机制
💡 思考题
- 为什么要设计配置缓存机制?
- 如何处理配置的循环依赖问题?
- 配置合并还有哪些策略可以考虑?
🚀 下章预告
下一章我们将进入AOP切面编程的世界,学习如何使用Spring AOP实现无侵入式的日志拦截。我们将实现LogMethodAspect和LogClassAspect,掌握切点表达式和环绕通知的使用技巧。
💡 设计原则: 好的配置管理应该是灵活、高效、易于理解的。通过分层设计和缓存优化,我们既保证了功能的完整性,又确保了良好的性能表现。