导语:优秀的工具类是框架设计的基石,它们体现了"高内聚、低耦合"的核心思想。本期将深入剖析JustAuth项目中的工具类设计,从API设计到性能优化,探索工具类设计的最佳实践。
目录
- 工具类设计原则
- 链式调用的艺术 - HttpUtils深度解析
- 参数校验的集中化管理 - AuthChecker设计分析
- 高性能优化实践 - UuidUtils性能剖析
- 建造者模式的工具化 - UrlBuilder实现解读
- 全局工具类的统一管理 - GlobalAuthUtils设计哲学
- 工具类的可测试性设计
- 性能优化技巧总结
- 实战:设计你自己的工具类体系
1. 工具类设计原则
1.1 核心设计原则
在分析JustAuth的工具类之前,我们先明确优秀工具类应该遵循的设计原则:
单一职责原则(SRP)
// ✅ 好的设计:职责单一
public class StringUtils {
public static boolean isEmpty(String str) { /* ... */ }
public static boolean isNotEmpty(String str) { /* ... */ }
}
// ❌ 坏的设计:职责混乱
public class MixedUtils {
public static boolean isEmpty(String str) { /* ... */ }
public static String httpGet(String url) { /* ... */ }
public static void saveToFile(String content) { /* ... */ }
}
高内聚低耦合
// ✅ 高内聚:相关功能聚合
public class AuthChecker {
public static void checkCode(AuthSource source, AuthCallback callback) { /* ... */ }
public static void checkState(String state, AuthSource source, AuthStateCache cache) { /* ... */ }
public static void checkConfig(AuthConfig config, AuthSource source) { /* ... */ }
}
静态方法 vs 实例方法的选择
静态方法适用场景:
- 纯函数计算(无状态)
- 工具性质的操作
- 不需要继承和多态
实例方法适用场景:
- 需要维护状态
- 支持链式调用
- 需要多态和继承
1.2 JustAuth工具类的分类
JustAuth中的工具类按照功能可以分为以下几类:
工具类体系
├── 网络通信类
│ └── HttpUtils (HTTP请求封装)
├── 数据处理类
│ ├── StringUtils (字符串处理)
│ ├── UrlBuilder (URL构建)
│ └── GlobalAuthUtils (全局工具)
├── 安全相关类
│ ├── UuidUtils (UUID生成)
│ ├── AuthStateUtils (状态管理)
│ └── Base64Utils (编码处理)
├── 校验类
│ └── AuthChecker (参数校验)
└── 算法类
├── PkceUtil (PKCE算法)
└── RandomUtil (随机数生成)
2. 链式调用的艺术 - HttpUtils深度解析
2.1 设计思路分析
HttpUtils是JustAuth中一个典型的链式调用工具类,让我们来看看它的设计:
public class HttpUtils {
private SimpleHttpResponse httpResponse;
// 构造函数支持配置注入
public HttpUtils(HttpConfig config) {
HttpUtil.setConfig(config);
}
public HttpUtils() {}
// 链式调用的核心:返回this
public HttpUtils get(String url) {
this.httpResponse = HttpUtil.get(url, null, null, false);
return this; // 关键:返回自身
}
public HttpUtils post(String url, String data) {
this.httpResponse = HttpUtil.post(url, data);
return this;
}
// 终端操作:返回结果
public String getBody() {
return this.check().getHttpResponse().getBody();
}
}
2.2 链式调用的优势
1. 流畅的API体验
// 链式调用风格
String response = new HttpUtils()
.post("https://api.example.com/token", jsonData)
.getBody();
// 传统调用风格对比
HttpUtils httpUtils = new HttpUtils();
httpUtils.post("https://api.example.com/token", jsonData);
String response = httpUtils.getBody();
2. 方法重载的优雅处理
public class HttpUtils {
// 基础GET请求
public HttpUtils get(String url) { /* ... */ }
// 带参数的GET请求
public HttpUtils get(String url, Map<String, String> params,
HttpHeader header, boolean encode) { /* ... */ }
// 基础POST请求
public HttpUtils post(String url) { /* ... */ }
// 带数据的POST请求
public HttpUtils post(String url, String data) { /* ... */ }
// 完整的POST请求
public HttpUtils post(String url, String data, HttpHeader header) { /* ... */ }
}
2.3 错误处理的统一性
private HttpUtils check() {
if (null == httpResponse) {
throw new AuthException("Invalid SimpleHttpResponse.");
}
if (!httpResponse.isSuccess()) {
throw new AuthException(httpResponse.getError());
}
return this;
}
public String getBody() {
return this.check().getHttpResponse().getBody(); // 统一检查
}
设计亮点分析:
- 延迟检查:在获取结果时才进行错误检查
- 统一处理:所有终端操作都通过check()方法
- 异常安全:确保在出错时能提供清晰的错误信息
2.4 封装第三方库的策略
public class HttpUtils {
// 封装simple-http库,便于后续替换
public HttpUtils get(String url) {
this.httpResponse = HttpUtil.get(url, null, null, false);
return this;
}
}
封装的价值:
- 依赖隔离:业务代码不直接依赖第三方HTTP库
- 统一接口:提供一致的API体验
- 易于替换:后续可以轻松切换底层实现
- 增强功能:可以添加缓存、重试等增强功能
3. 参数校验的集中化管理 - AuthChecker设计分析
3.1 校验逻辑的统一管理
AuthChecker体现了"集中化管理"的设计思想:
public class AuthChecker {
/**
* 是否支持第三方登录
*/
public static boolean isSupportedAuth(AuthConfig config, AuthSource source) {
boolean isSupported = StringUtils.isNotEmpty(config.getClientId())
&& StringUtils.isNotEmpty(config.getClientSecret());
// 针对特殊平台的差异化校验
if (isSupported && AuthDefaultSource.STACK_OVERFLOW == source) {
isSupported = StringUtils.isNotEmpty(config.getStackOverflowKey());
}
if (isSupported && AuthDefaultSource.WECHAT_ENTERPRISE == source) {
isSupported = StringUtils.isNotEmpty(config.getAgentId());
}
if (isSupported && (AuthDefaultSource.CODING == source || AuthDefaultSource.OKTA == source)) {
isSupported = StringUtils.isNotEmpty(config.getDomainPrefix());
}
return isSupported;
}
}
3.2 平台差异化处理的设计模式
问题:不同OAuth平台对参数的要求不同,如何优雅地处理这种差异?
解决方案:使用条件链模式
public static boolean isSupportedAuth(AuthConfig config, AuthSource source) {
// 基础校验
boolean isSupported = basicValidation(config);
// 链式平台特定校验
if (isSupported && AuthDefaultSource.STACK_OVERFLOW == source) {
isSupported = validateStackOverflow(config);
}
if (isSupported && AuthDefaultSource.WECHAT_ENTERPRISE == source) {
isSupported = validateWeChatEnterprise(config);
}
// ... 更多平台
return isSupported;
}
3.3 错误处理的精确性
public static void checkCode(AuthSource source, AuthCallback callback) {
// 特殊平台处理
if (source == AuthDefaultSource.TWITTER) {
return; // Twitter不支持code和state
}
String code = callback.getCode();
// 华为平台的特殊处理
if (StringUtils.isEmpty(code) && source == AuthDefaultSource.HUAWEI) {
code = callback.getAuthorization_code();
}
if (StringUtils.isEmpty(code)) {
throw new AuthException(AuthResponseStatus.ILLEGAL_CODE, source);
}
}
设计亮点:
- 异常精确化:明确指出哪个平台的哪个参数有问题
- 特殊情况处理:针对平台差异做特殊处理
- 早期返回:对于不需要校验的情况及时返回
3.4 状态校验的安全性设计
public static void checkState(String state, AuthSource source, AuthStateCache authStateCache) {
// 平台过滤
if (source == AuthDefaultSource.TWITTER) {
return;
}
// 安全检查:state必须存在且在缓存中
if (StringUtils.isEmpty(state) || !authStateCache.containsKey(state)) {
throw new AuthException(AuthResponseStatus.ILLEGAL_STATUS, source);
}
// 注意:这里没有直接删除state,让上层业务决定何时清理
}
安全考虑:
- CSRF防护:确保state参数的有效性
- 重放攻击防护:state只能使用一次
- 状态管理:将state的生命周期管理留给缓存层
4. 高性能优化实践 - UuidUtils性能剖析
4.1 性能优化的具体实现
UuidUtils是JustAuth中性能优化的典型案例:
public class UuidUtils {
// 预定义字符数组,避免字符串操作
private final static byte[] DIGITS = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D',
'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
'Y', 'Z'
};
public static String getUUID() {
ThreadLocalRandom random = ThreadLocalRandom.current();
long lsb = random.nextLong();
long msb = random.nextLong();
byte[] buf = new byte[32]; // 固定大小数组
// 直接操作字节数组,避免字符串拼接
formatUnsignedLong(lsb, buf, 20, 12);
formatUnsignedLong(lsb >>> 48, buf, 16, 4);
formatUnsignedLong(msb, buf, 12, 4);
formatUnsignedLong(msb >>> 16, buf, 8, 4);
formatUnsignedLong(msb >>> 32, buf, 0, 8);
return new String(buf, StandardCharsets.UTF_8);
}
}
4.2 性能优化技巧分析
1. 使用ThreadLocalRandom
// ✅ 高性能:ThreadLocalRandom
ThreadLocalRandom random = ThreadLocalRandom.current();
// ❌ 低性能:Random(有同步开销)
Random random = new Random();
// ❌ 低性能:Math.random()(内部也是Random)
Math.random();
2. 直接字节数组操作
// ✅ 高性能:直接操作字节数组
byte[] buf = new byte[32];
formatUnsignedLong(value, buf, offset, length);
return new String(buf, StandardCharsets.UTF_8);
// ❌ 低性能:字符串拼接
StringBuilder sb = new StringBuilder();
sb.append(part1).append(part2).append(part3);
return sb.toString();
3. 位运算优化
private static void formatUnsignedLong(long val, byte[] buf, int offset, int len) {
int charPos = offset + len;
int radix = 1 << 4; // 等价于16,但位运算更快
int mask = radix - 1; // 等价于15,用于掩码操作
do {
buf[--charPos] = DIGITS[((int) val) & mask]; // 位运算代替取模
val >>>= 4; // 无符号右移,比除法快
} while (charPos > offset);
}
4.3 性能对比分析
传统UUID生成方式:
// JDK标准方式
public static String traditionalUUID() {
return UUID.randomUUID().toString().replace("-", "");
}
优化后的方式性能提升:
- 生成速度:提升约3-5倍
- 内存分配:减少临时对象创建
- GC压力:降低垃圾回收频率
4.4 学习开源项目的智慧
/**
* 高性能的创建UUID的工具类,https://github.com/lets-mica/mica
* copy from mica:https://github.com/lets-mica/mica/blob/master/mica-core/src/main/java/net/dreamlu/mica/core/utils/StringUtil.java#L335
* 关于mica uuid生成方式的压测结果,可以参考:https://github.com/lets-mica/mica-jmh/wiki/uuid
*/
借鉴的价值:
- 站在巨人肩膀上:借鉴已经验证的优化方案
- 标注来源:尊重开源社区,便于后续维护
- 性能数据:参考权威的性能测试结果
5. 建造者模式的工具化 - UrlBuilder实现解读
5.1 建造者模式在工具类中的应用
@Setter
public class UrlBuilder {
private final Map<String, String> params = new LinkedHashMap<>(7);
private String baseUrl;
private UrlBuilder() {} // 私有构造函数
// 静态工厂方法
public static UrlBuilder fromBaseUrl(String baseUrl) {
UrlBuilder builder = new UrlBuilder();
builder.setBaseUrl(baseUrl);
return builder;
}
// 链式调用
public UrlBuilder queryParam(String key, Object value) {
if (StringUtil.isEmpty(key)) {
throw new RuntimeException("参数名不能为空");
}
String valueAsString = (value != null ? value.toString() : null);
this.params.put(key, valueAsString);
return this;
}
// 构建结果
public String build() {
return this.build(false);
}
public String build(boolean encode) {
if (MapUtil.isEmpty(this.params)) {
return this.baseUrl;
}
String baseUrl = StringUtils.appendIfNotContain(this.baseUrl, "?", "&");
String paramString = MapUtil.parseMapToString(this.params, encode);
return baseUrl + paramString;
}
}
5.2 设计细节分析
1. 使用LinkedHashMap保证参数顺序
private final Map<String, String> params = new LinkedHashMap<>(7);
- 顺序保证:确保URL参数的稳定顺序
- 容量优化:预设初始容量避免扩容
- 类型安全:限制为String类型
2. 提供只读视图
public Map<String, Object> getReadOnlyParams() {
return Collections.unmodifiableMap(params);
}
设计意图:
- 封装性:防止外部直接修改内部状态
- 调试友好:方便查看当前构建状态
- 线程安全:只读视图天然线程安全
3. 参数校验的时机选择
public UrlBuilder queryParam(String key, Object value) {
if (StringUtil.isEmpty(key)) {
throw new RuntimeException("参数名不能为空"); // 及时校验
}
// ...
}
5.3 字符串拼接的优化策略
public String build(boolean encode) {
if (MapUtil.isEmpty(this.params)) {
return this.baseUrl; // 早期返回优化
}
// 智能拼接符选择
String baseUrl = StringUtils.appendIfNotContain(this.baseUrl, "?", "&");
String paramString = MapUtil.parseMapToString(this.params, encode);
return baseUrl + paramString;
}
appendIfNotContain方法的巧思:
public static String appendIfNotContain(String str, String appendStr, String otherwise) {
if (isEmpty(str) || isEmpty(appendStr)) {
return str;
}
if (str.contains(appendStr)) {
return str.concat(otherwise); // 已包含'?',追加'&'
}
return str.concat(appendStr); // 不包含'?',追加'?'
}
5.4 使用示例与API设计
// 流畅的API设计
String authUrl = UrlBuilder.fromBaseUrl("https://github.com/login/oauth/authorize")
.queryParam("client_id", "your_client_id")
.queryParam("redirect_uri", "http://localhost:8080/callback")
.queryParam("scope", "user:email")
.queryParam("state", stateValue)
.build(true); // 启用URL编码
6. 全局工具类的统一管理 - GlobalAuthUtils设计哲学
6.1 全局工具类的职责边界
GlobalAuthUtils展示了如何设计一个职责清晰的全局工具类:
public class GlobalAuthUtils {
private static final Charset DEFAULT_ENCODING = StandardCharsets.UTF_8;
private static final String HMAC_SHA1 = "HmacSHA1";
private static final String HMAC_SHA_256 = "HmacSHA256";
// 1. 编码相关工具
public static String urlEncode(String value) { /* ... */ }
public static String urlDecode(String value) { /* ... */ }
// 2. 数据转换工具
public static Map<String, String> parseStringToMap(String accessTokenStr) { /* ... */ }
public static String parseMapToString(Map<String, String> params, boolean encode) { /* ... */ }
// 3. 协议检测工具
public static boolean isHttpProtocol(String url) { /* ... */ }
public static boolean isHttpsProtocol(String url) { /* ... */ }
// 4. 安全相关工具
public static String generateDingTalkSignature(String secretKey, String timestamp) { /* ... */ }
public static String generateTwitterSignature(/* ... */) { /* ... */ }
}
6.2 URL编解码的健壮性设计
public static String urlEncode(String value) {
if (value == null) {
return ""; // 空值安全
}
try {
String encoded = URLEncoder.encode(value, DEFAULT_ENCODING.displayName());
// OAuth标准要求的特殊字符处理
return encoded.replace("+", "%20")
.replace("*", "%2A")
.replace("~", "%7E")
.replace("/", "%2F");
} catch (UnsupportedEncodingException e) {
throw new AuthException("Failed To Encode Uri", e);
}
}
设计亮点:
- 空值处理:避免NullPointerException
- 标准兼容:遵循OAuth规范要求
- 异常转换:将检查异常转换为运行时异常
6.3 字符串解析的性能优化
public static Map<String, String> parseStringToMap(String accessTokenStr) {
Map<String, String> res = null;
if (accessTokenStr.contains("&")) {
String[] fields = accessTokenStr.split("&");
// 性能优化:预计算Map容量,避免扩容
res = new HashMap<>((int) (fields.length / 0.75 + 1));
for (String field : fields) {
if (field.contains("=")) {
String[] keyValue = field.split("=");
res.put(
urlDecode(keyValue[0]),
keyValue.length == 2 ? urlDecode(keyValue[1]) : null
);
}
}
} else {
res = new HashMap<>(0); // 空Map优化
}
return res;
}
性能优化点:
- 容量预计算:
(int) (fields.length / 0.75 + 1) - 边界条件处理:处理缺少value的情况
- 提前返回:对于无参数情况直接返回空Map
6.4 加密算法的统一封装
private static byte[] sign(byte[] key, byte[] data, String algorithm) {
try {
Mac mac = Mac.getInstance(algorithm);
mac.init(new SecretKeySpec(key, algorithm));
return mac.doFinal(data);
} catch (NoSuchAlgorithmException ex) {
throw new AuthException("Unsupported algorithm: " + algorithm, ex);
} catch (InvalidKeyException ex) {
throw new AuthException("Invalid key: " + Arrays.toString(key), ex);
}
}
// 基于统一签名方法的各种平台实现
public static String generateDingTalkSignature(String secretKey, String timestamp) {
byte[] signData = sign(secretKey.getBytes(DEFAULT_ENCODING),
timestamp.getBytes(DEFAULT_ENCODING),
HMAC_SHA_256);
return urlEncode(new String(Base64Utils.encode(signData, false)));
}
设计价值:
- 代码复用:避免重复的加密逻辑
- 统一异常处理:提供一致的错误信息
- 算法扩展:便于支持新的加密算法
7. 工具类的可测试性设计
7.1 静态方法的测试策略
// 测试工具类的示例
@Test
void testStringUtilsIsEmpty() {
// 边界值测试
assertTrue(StringUtils.isEmpty(null));
assertTrue(StringUtils.isEmpty(""));
assertFalse(StringUtils.isEmpty(" "));
assertFalse(StringUtils.isEmpty("test"));
}
@Test
void testUrlBuilderChaining() {
// 链式调用测试
String url = UrlBuilder.fromBaseUrl("https://api.example.com")
.queryParam("key1", "value1")
.queryParam("key2", "value2")
.build();
assertEquals("https://api.example.com?key1=value1&key2=value2", url);
}
7.2 Mock测试的挑战与解决方案
问题:静态方法难以Mock
解决方案1:依赖注入
// 将工具类通过依赖注入
public class AuthService {
private final HttpUtils httpUtils;
private final UuidGenerator uuidGenerator;
public AuthService(HttpUtils httpUtils, UuidGenerator uuidGenerator) {
this.httpUtils = httpUtils;
this.uuidGenerator = uuidGenerator;
}
}
解决方案2:包装器模式
// 为静态工具类创建包装器
public interface TimeProvider {
long getCurrentTime();
}
public class SystemTimeProvider implements TimeProvider {
@Override
public long getCurrentTime() {
return System.currentTimeMillis();
}
}
// 测试时使用Mock实现
public class MockTimeProvider implements TimeProvider {
private long fixedTime;
@Override
public long getCurrentTime() {
return fixedTime;
}
}
7.3 参数化测试的应用
@ParameterizedTest
@ValueSource(strings = {"http://example.com", "http%3A%2F%2Fexample.com"})
void testIsHttpProtocol(String url) {
assertTrue(GlobalAuthUtils.isHttpProtocol(url));
}
@ParameterizedTest
@CsvSource({
"null, true",
"'', true",
"' ', false",
"test, false"
})
void testStringUtilsIsEmpty(String input, boolean expected) {
String actualInput = "null".equals(input) ? null : input;
assertEquals(expected, StringUtils.isEmpty(actualInput));
}
8. 性能优化技巧总结
8.1 字符串处理优化
1. 避免频繁的字符串拼接
// ❌ 低性能
String result = "";
for (String item : items) {
result += item + "&";
}
// ✅ 高性能
StringBuilder builder = new StringBuilder();
for (String item : items) {
builder.append(item).append("&");
}
String result = builder.toString();
// ✅ 更高性能:预分配容量
StringBuilder builder = new StringBuilder(estimatedSize);
2. 使用String.intern()优化
// 对于大量重复的字符串
public static final String HTTPS_PREFIX = "https://".intern();
8.2 集合操作优化
1. 预设合适的初始容量
// 根据负载因子计算容量
Map<String, String> map = new HashMap<>((int) (expectedSize / 0.75 + 1));
// LinkedHashMap的容量设置
Map<String, String> params = new LinkedHashMap<>(7); // 小容量场景
2. 选择合适的集合类型
// 需要顺序:LinkedHashMap
// 不需要顺序:HashMap
// 线程安全:ConcurrentHashMap
// 排序需求:TreeMap
8.3 反射优化
1. 缓存反射结果
private static final Map<String, Constructor<?>> CONSTRUCTOR_CACHE =
new ConcurrentHashMap<>();
public static <T> T createInstance(Class<T> clazz) {
Constructor<?> constructor = CONSTRUCTOR_CACHE.computeIfAbsent(
clazz.getName(),
key -> {
try {
return clazz.getDeclaredConstructor();
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
);
// 使用缓存的构造函数
}
8.4 内存使用优化
1. 使用字节数组代替字符串
// UUID生成中的优化
byte[] buf = new byte[32];
formatUnsignedLong(value, buf, offset, length);
return new String(buf, StandardCharsets.UTF_8);
2. 重用对象
// ThreadLocal重用StringBuilder
private static final ThreadLocal<StringBuilder> STRING_BUILDER =
ThreadLocal.withInitial(() -> new StringBuilder(256));
public static String format(String template, Object... args) {
StringBuilder sb = STRING_BUILDER.get();
sb.setLength(0); // 清空但保留容量
// 使用sb进行格式化
return sb.toString();
}
9. 实战:设计你自己的工具类体系
9.1 需求分析:OAuth客户端工具类
假设我们要设计一个新的OAuth客户端框架,需要哪些工具类?
工具类需求分析
├── 网络通信
│ ├── HttpClient (HTTP请求)
│ ├── WebSocketClient (WebSocket连接)
│ └── ProxyManager (代理管理)
├── 数据处理
│ ├── JsonUtils (JSON处理)
│ ├── XmlUtils (XML处理)
│ └── CryptoUtils (加密解密)
├── 配置管理
│ ├── ConfigLoader (配置加载)
│ ├── EnvironmentUtils (环境检测)
│ └── ValidationUtils (参数校验)
└── 监控诊断
├── PerformanceMonitor (性能监控)
├── LogUtils (日志工具)
└── HealthChecker (健康检查)
9.2 实战:设计高性能的JsonUtils
public class JsonUtils {
// 使用ThreadLocal避免线程安全问题
private static final ThreadLocal<ObjectMapper> MAPPER_CACHE =
ThreadLocal.withInitial(() -> {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
return mapper;
});
public static String toJson(Object obj) {
if (obj == null) {
return null;
}
try {
return MAPPER_CACHE.get().writeValueAsString(obj);
} catch (JsonProcessingException e) {
throw new RuntimeException("Failed to serialize object to JSON", e);
}
}
public static <T> T fromJson(String json, Class<T> clazz) {
if (StringUtils.isEmpty(json)) {
return null;
}
try {
return MAPPER_CACHE.get().readValue(json, clazz);
} catch (JsonProcessingException e) {
throw new RuntimeException("Failed to deserialize JSON to object", e);
}
}
// 支持泛型的反序列化
public static <T> T fromJson(String json, TypeReference<T> typeRef) {
if (StringUtils.isEmpty(json)) {
return null;
}
try {
return MAPPER_CACHE.get().readValue(json, typeRef);
} catch (JsonProcessingException e) {
throw new RuntimeException("Failed to deserialize JSON to object", e);
}
}
}
9.3 实战:设计可配置的ValidationUtils
public class ValidationUtils {
// 策略模式:不同类型的校验器
private static final Map<Class<?>, Validator<?>> VALIDATORS = new HashMap<>();
static {
VALIDATORS.put(String.class, new StringValidator());
VALIDATORS.put(Integer.class, new IntegerValidator());
VALIDATORS.put(URL.class, new UrlValidator());
}
@SuppressWarnings("unchecked")
public static <T> ValidationResult validate(T value, Class<T> type, ValidationRule... rules) {
Validator<T> validator = (Validator<T>) VALIDATORS.get(type);
if (validator == null) {
throw new IllegalArgumentException("No validator found for type: " + type);
}
return validator.validate(value, rules);
}
// 流畅的校验API
public static ValidationBuilder check(Object value) {
return new ValidationBuilder(value);
}
public static class ValidationBuilder {
private final Object value;
private final List<ValidationResult> results = new ArrayList<>();
private ValidationBuilder(Object value) {
this.value = value;
}
public ValidationBuilder notNull(String message) {
if (value == null) {
results.add(ValidationResult.error(message));
}
return this;
}
public ValidationBuilder notEmpty(String message) {
if (value instanceof String && ((String) value).isEmpty()) {
results.add(ValidationResult.error(message));
}
return this;
}
public ValidationBuilder matches(Pattern pattern, String message) {
if (value instanceof String && !pattern.matcher((String) value).matches()) {
results.add(ValidationResult.error(message));
}
return this;
}
public ValidationResult build() {
return results.isEmpty() ?
ValidationResult.success() :
ValidationResult.errors(results);
}
}
}
// 使用示例
ValidationResult result = ValidationUtils.check(email)
.notNull("邮箱不能为空")
.matches(EMAIL_PATTERN, "邮箱格式不正确")
.build();
if (!result.isValid()) {
throw new ValidationException(result.getErrors());
}
9.4 实战:设计监控友好的PerformanceMonitor
public class PerformanceMonitor {
private static final Map<String, AtomicLong> COUNTERS = new ConcurrentHashMap<>();
private static final Map<String, AtomicLong> TIMERS = new ConcurrentHashMap<>();
// 性能计数
public static void increment(String metric) {
COUNTERS.computeIfAbsent(metric, k -> new AtomicLong(0)).incrementAndGet();
}
// 性能计时(使用try-with-resources模式)
public static Timer timer(String metric) {
return new Timer(metric);
}
public static class Timer implements AutoCloseable {
private final String metric;
private final long startTime;
private Timer(String metric) {
this.metric = metric;
this.startTime = System.nanoTime();
}
@Override
public void close() {
long duration = System.nanoTime() - startTime;
TIMERS.computeIfAbsent(metric, k -> new AtomicLong(0)).addAndGet(duration);
}
}
// 使用示例
public static void someBusinessMethod() {
try (PerformanceMonitor.Timer timer = PerformanceMonitor.timer("business.method.duration")) {
// 业务逻辑
PerformanceMonitor.increment("business.method.calls");
// ...
}
}
}
总结
通过对JustAuth工具类的深入分析,我们学到了以下核心设计原则:
核心设计原则
- 单一职责:每个工具类都有明确的职责边界
- 高内聚低耦合:相关功能聚合,减少外部依赖
- 性能优先:在保证可读性的前提下优化性能
- 异常安全:提供清晰的错误信息和异常处理
实践技巧
- 链式调用:提供流畅的API体验
- 静态工厂:控制对象创建过程
- 参数校验:在合适的时机进行校验
- 资源管理:合理使用缓存和对象重用
性能优化
- 字符串优化:避免频繁拼接,使用字节数组
- 集合优化:预设容量,选择合适的数据结构
- 并发优化:使用ThreadLocal,避免同步开销
- 内存优化:重用对象,减少GC压力
优秀的工具类设计不仅要解决当前问题,还要为未来的扩展和维护提供便利。JustAuth的工具类设计为我们提供了很好的参考模板,值得在实际项目中借鉴和应用。
下期预告:第十期将探讨《扩展机制设计 - 开闭原则的深度实践》,深入分析JustAuth如何实现插件化架构。