开篇语
上期我们从宏观角度了解了JustAuth解决的问题和技术价值,这期我们将深入项目内核,探索其架构设计的精髓。
优秀的架构设计不是偶然的,它需要遵循一系列设计原则,通过合理的分层和抽象来应对复杂性。JustAuth作为一个支持多家平台的OAuth集成框架,其架构设计充分体现了分层架构 和SOLID原则的威力。
这期内容将带你从架构师的视角,剖析JustAuth的设计精髓,学会如何构建可扩展、可维护的分层架构。
一、整体架构分析
1.1 七层架构设计解读
JustAuth采用了清晰的七层架构设计,每层都有明确的职责边界,形成了一个完整的OAuth集成框架:
scss
┌─────────────────────────────────────────────────────────┐
│ 外部调用层 (External) │
│ Spring Boot Starter, AuthRequestBuilder, 使用示例 │
├─────────────────────────────────────────────────────────┤
│ 业务接口层 (Interface) │
│ AuthRequest, AuthSource, AuthStateCache │
├─────────────────────────────────────────────────────────┤
│ 模板实现层 (Template) │
│ AuthDefaultRequest (模板方法) │
├─────────────────────────────────────────────────────────┤
│ 平台适配层 (Adapter) │
│ AuthGitHubRequest, AuthWechatRequest, 40+ 平台实现 │
├─────────────────────────────────────────────────────────┤
│ 数据模型层 (Model) │
│ AuthUser, AuthToken, AuthCallback, AuthResponse │
├─────────────────────────────────────────────────────────┤
│ 工具组件层 (Utils) │
│ HttpUtils, AuthChecker, UuidUtils, UrlUtils │
├─────────────────────────────────────────────────────────┤
│ 基础设施层 (Infrastructure) │
│ 缓存管理, 配置管理, 异常处理, 日志记录 │
└─────────────────────────────────────────────────────────┘
架构层次详解
🌐 外部调用层 (External Layer)
java
// Spring Boot自动配置示例
@Configuration
@EnableConfigurationProperties(JustAuthProperties.class)
public class JustAuthAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public AuthRequestBuilder authRequestBuilder() {
return AuthRequestBuilder.builder();
}
}
// 用户侧调用示例
AuthRequest authRequest = AuthRequestBuilder.builder()
.source("github")
.authConfig(config)
.build();
🔌 业务接口层 (Interface Layer)
java
// 核心业务接口定义
public interface AuthRequest {
String authorize(String state);
AuthToken getAccessToken(AuthCallback authCallback);
AuthUser getUserInfo(AuthToken authToken);
AuthResponse<AuthUser> login(AuthCallback authCallback);
}
// 平台策略接口
public interface AuthSource {
String authorize(); // 授权地址策略
String accessToken(); // 令牌地址策略
String userInfo(); // 用户信息地址策略
Class<? extends AuthDefaultRequest> getTargetClass();
}
📋 模板实现层 (Template Layer)
java
// 模板方法模式 - 定义算法骨架
public abstract class AuthDefaultRequest implements AuthRequest {
@Override
public AuthResponse<AuthUser> login(AuthCallback authCallback) {
try {
checkCode(authCallback); // 步骤1:参数校验
checkState(authCallback); // 步骤2:状态校验
AuthToken authToken = getAccessToken(authCallback); // 步骤3:获取令牌
AuthUser user = getUserInfo(authToken); // 步骤4:获取用户
return AuthResponse.success(user); // 步骤5:封装响应
} catch (Exception e) {
return responseError(e); // 异常处理
}
}
// 抽象方法:子类必须实现
protected abstract AuthToken getAccessToken(AuthCallback authCallback);
protected abstract AuthUser getUserInfo(AuthToken authToken);
}
🔧 平台适配层 (Adapter Layer)
java
// 具体平台实现:GitHub
public class AuthGitHubRequest extends AuthDefaultRequest {
public AuthGitHubRequest(AuthConfig config) {
super(config, AuthDefaultSource.GITHUB);
}
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
// GitHub特定的Token获取逻辑
String response = HttpUtils.post(source.accessToken())
.form("client_id", config.getClientId())
.form("client_secret", config.getClientSecret())
.form("code", authCallback.getCode())
.execute();
return parseTokenResponse(response);
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
// GitHub特定的用户信息获取逻辑
String userStr = HttpUtils.get(source.userInfo())
.header("Authorization", "token " + authToken.getAccessToken())
.execute();
return parseUserInfo(userStr);
}
}
1.2 各层职责边界划分
层次 | 职责 | 关键组件 | 设计原则体现 |
---|---|---|---|
外部调用层 | 对外暴露API,封装复杂性 | AuthRequestBuilder |
门面模式、建造者模式 |
业务接口层 | 定义核心业务契约 | AuthRequest , AuthSource |
接口隔离原则 |
模板实现层 | 提供算法骨架,统一流程 | AuthDefaultRequest |
模板方法模式、开闭原则 |
平台适配层 | 适配不同平台差异 | 40+平台实现类 | 适配器模式、单一职责 |
数据模型层 | 数据传输和业务实体 | AuthUser , AuthToken |
数据传输对象模式 |
工具组件层 | 提供通用工具能力 | HTTP工具、校验工具 | 工具类模式、无状态设计 |
基础设施层 | 底层技术支撑 | 缓存、配置、异常、日志 | 依赖倒置原则 |
1.3 依赖关系与调用链路
核心调用链路图
依赖方向分析
java
// 依赖关系示例
public class AuthGitHubRequest extends AuthDefaultRequest {
// 向上依赖:依赖抽象而非具体实现
public AuthGitHubRequest(AuthConfig config) {
super(config, AuthDefaultSource.GITHUB); // 依赖配置抽象
}
// 水平依赖:依赖同层或下层组件
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
String response = HttpUtils.post(source.accessToken()) // 依赖工具层
.form("client_id", config.getClientId())
.execute();
return JSONUtils.parseObject(response, AuthToken.class); // 依赖数据模型
}
}
依赖原则总结:
- ✅ 向上依赖:实现类依赖接口/抽象类
- ✅ 水平依赖:同层组件可以相互依赖
- ✅ 向下依赖:上层可以依赖下层组件
- ❌ 禁止向上依赖:下层不能依赖上层具体实现
二、设计原则体现
2.1 单一职责原则(SRP)的实践
JustAuth的每个类都有明确的单一职责,避免了"God Class"的反模式。
职责分离示例
java
// ✅ 职责分离:参数校验专用类
public class AuthChecker {
// 只负责OAuth回调参数校验
public static void checkCode(AuthSource source, AuthCallback callback) {
if (StringUtils.isEmpty(callback.getCode())) {
throw new AuthException(ILLEGAL_CODE, source);
}
}
// 只负责State参数校验
public static void checkState(String state, AuthSource source, AuthStateCache cache) {
if (StringUtils.isEmpty(state) || !cache.containsKey(state)) {
throw new AuthException(ILLEGAL_STATE, source);
}
}
// 只负责配置参数校验
public static void checkConfig(AuthConfig config, AuthSource source) {
// 校验逻辑...
}
}
// ✅ 职责分离:HTTP请求专用类
public class HttpUtils {
// 只负责HTTP请求处理
public static String get(String url) { /*...*/ }
public static String post(String url, String data) { /*...*/ }
public static String post(String url, Map<String, Object> data) { /*...*/ }
}
// ✅ 职责分离:URL处理专用类
public class UrlUtils {
// 只负责URL相关操作
public static String urlEncode(String str) { /*...*/ }
public static String urlDecode(String str) { /*...*/ }
public static Map<String, String> parseQuery(String query) { /*...*/ }
}
反面案例对比
java
// ❌ 违反SRP:一个类承担多重职责
public class BadOAuthHelper {
// 职责1:HTTP请求
public String httpPost(String url, String data) { /*...*/ }
// 职责2:参数校验
public void validateParams(AuthCallback callback) { /*...*/ }
// 职责3:JSON解析
public AuthUser parseUser(String json) { /*...*/ }
// 职责4:缓存管理
public void cacheState(String state) { /*...*/ }
// 职责5:配置管理
public AuthConfig loadConfig() { /*...*/ }
}
2.2 开闭原则(OCP)的扩展机制
JustAuth的架构对扩展开放,对修改封闭,新增平台无需修改现有代码。
扩展机制设计
java
// 1. 接口定义:对扩展开放
public interface AuthSource {
String authorize();
String accessToken();
String userInfo();
Class<? extends AuthDefaultRequest> getTargetClass();
}
// 2. 抽象实现:提供通用逻辑,对修改封闭
public abstract class AuthDefaultRequest implements AuthRequest {
// 模板方法:固定算法骨架,不允许修改
@Override
public final AuthResponse<AuthUser> login(AuthCallback authCallback) {
try {
checkCode(authCallback); // 固定步骤1
AuthToken authToken = getAccessToken(authCallback); // 可变步骤2
AuthUser user = getUserInfo(authToken); // 可变步骤3
return AuthResponse.success(user); // 固定步骤4
} catch (Exception e) {
return responseError(e);
}
}
// 扩展点:子类实现差异化逻辑
protected abstract AuthToken getAccessToken(AuthCallback authCallback);
protected abstract AuthUser getUserInfo(AuthToken authToken);
}
// 3. 具体扩展:新增平台实现
public class AuthCustomRequest extends AuthDefaultRequest {
public AuthCustomRequest(AuthConfig config, AuthSource source) {
super(config, source);
}
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
// 自定义平台的Token获取逻辑
return CustomOAuthClient.getToken(
config.getClientId(),
config.getClientSecret(),
authCallback.getCode()
);
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
// 自定义平台的用户信息获取逻辑
CustomUser customUser = CustomOAuthClient.getUserInfo(authToken.getAccessToken());
return AuthUser.builder()
.uuid(customUser.getId())
.username(customUser.getName())
.nickname(customUser.getDisplayName())
.avatar(customUser.getAvatar())
.source(source.getName())
.build();
}
}
平台扩展实战
java
// 扩展新平台:以钉钉为例
public enum CustomSource implements AuthSource {
DINGTALK {
@Override
public String authorize() {
return "https://oapi.dingtalk.com/connect/oauth2/sns_authorize";
}
@Override
public String accessToken() {
return "https://oapi.dingtalk.com/sns/gettoken";
}
@Override
public String userInfo() {
return "https://oapi.dingtalk.com/sns/getuserinfo";
}
@Override
public Class<? extends AuthDefaultRequest> getTargetClass() {
return AuthDingTalkRequest.class;
}
};
}
// 扩展使用方式
AuthRequest authRequest = AuthRequestBuilder.builder()
.source(CustomSource.DINGTALK) // 使用自定义平台
.authConfig(config)
.build();
2.3 依赖倒置原则(DIP)的接口设计
JustAuth通过接口抽象实现了依赖倒置,高层模块不依赖低层模块的具体实现。
依赖倒置示例
java
// 高层模块:AuthDefaultRequest
public abstract class AuthDefaultRequest implements AuthRequest {
protected AuthConfig config; // 依赖配置抽象
protected AuthSource source; // 依赖平台抽象
protected AuthStateCache authStateCache; // 依赖缓存抽象
// 构造函数:依赖注入抽象
public AuthDefaultRequest(AuthConfig config, AuthSource source, AuthStateCache authStateCache) {
this.config = config;
this.source = source;
this.authStateCache = authStateCache;
}
@Override
public String authorize(String state) {
// 依赖抽象接口,而非具体实现
String authorizeUrl = source.authorize(); // 调用策略接口
authStateCache.cache(state, state); // 调用缓存接口
return UrlBuilder.fromBaseUrl(authorizeUrl)
.queryParam("client_id", config.getClientId())
.queryParam("state", state)
.build();
}
}
// 低层模块:具体实现
public class AuthDefaultStateCache implements AuthStateCache {
private final ConcurrentHashMap<String, String> stateCache = new ConcurrentHashMap<>();
@Override
public void cache(String key, String value) {
stateCache.put(key, value);
}
@Override
public boolean containsKey(String key) {
return stateCache.containsKey(key);
}
}
// 配置抽象
public class AuthConfig {
private String clientId;
private String clientSecret;
private String redirectUri;
// getter/setter...
}
可替换的依赖实现
java
// 缓存实现可以替换:Redis版本
public class AuthRedisStateCache implements AuthStateCache {
private RedisTemplate<String, String> redisTemplate;
@Override
public void cache(String key, String value) {
redisTemplate.opsForValue().set(key, value, Duration.ofMinutes(10));
}
@Override
public boolean containsKey(String key) {
return redisTemplate.hasKey(key);
}
}
// HTTP实现可以替换:OkHttp版本
public class OkHttpUtils implements HttpClient {
private OkHttpClient client = new OkHttpClient();
@Override
public String get(String url) {
Request request = new Request.Builder().url(url).build();
try (Response response = client.newCall(request).execute()) {
return response.body().string();
}
}
}
2.4 里氏替换原则(LSP)的继承体系
JustAuth的继承体系设计确保了子类可以完全替换父类,不破坏程序正确性。
继承体系分析
java
// 父类:定义行为契约
public abstract class AuthDefaultRequest implements AuthRequest {
// 前置条件:参数校验
protected void checkCode(AuthCallback authCallback) {
if (authCallback == null || StringUtils.isEmpty(authCallback.getCode())) {
throw new AuthException(ILLEGAL_CODE);
}
}
// 后置条件:返回统一格式
public AuthResponse<AuthUser> login(AuthCallback authCallback) {
try {
checkCode(authCallback); // 确保前置条件
AuthToken token = getAccessToken(authCallback);
AuthUser user = getUserInfo(token);
return AuthResponse.success(user); // 确保后置条件
} catch (Exception e) {
return AuthResponse.error(e.getMessage());
}
}
}
// 子类:遵循父类契约
public class AuthGitHubRequest extends AuthDefaultRequest {
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
// 遵循前置条件:authCallback已经过校验
// 实现特定逻辑...
AuthToken token = // GitHub特定实现
// 遵循后置条件:必须返回有效的AuthToken
if (token == null || StringUtils.isEmpty(token.getAccessToken())) {
throw new AuthException("Invalid token from GitHub");
}
return token;
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
// 遵循前置条件:authToken不为空且有效
// 实现特定逻辑...
AuthUser user = // GitHub特定实现
// 遵循后置条件:必须返回包含基础信息的AuthUser
if (user == null || StringUtils.isEmpty(user.getUuid())) {
throw new AuthException("Invalid user info from GitHub");
}
return user;
}
}
LSP验证测试
java
// 里氏替换原则验证:子类可以替换父类
@Test
public void testLiskovSubstitution() {
AuthConfig config = AuthConfig.builder()
.clientId("test_client_id")
.clientSecret("test_client_secret")
.redirectUri("http://localhost:8080/callback")
.build();
// 父类引用
AuthRequest authRequest;
// 可以替换为任意子类实现
authRequest = new AuthGitHubRequest(config);
testAuthFlow(authRequest); // 正常工作
authRequest = new AuthWechatRequest(config);
testAuthFlow(authRequest); // 正常工作
authRequest = new AuthGoogleRequest(config);
testAuthFlow(authRequest); // 正常工作
}
private void testAuthFlow(AuthRequest authRequest) {
// 统一的使用方式,不关心具体实现
String authorizeUrl = authRequest.authorize("state123");
assertNotNull(authorizeUrl);
assertTrue(authorizeUrl.contains("client_id"));
assertTrue(authorizeUrl.contains("state=state123"));
}
三、包结构设计哲学
3.1 按功能vs按层次的包划分
JustAuth采用了功能导向的包结构,每个包都代表一个功能域:
bash
src/main/java/me/zhyd/oauth/
├── cache/ # 缓存功能域
│ ├── AuthCache.java
│ ├── AuthStateCache.java
│ └── AuthDefaultCache.java
├── config/ # 配置功能域
│ ├── AuthConfig.java
│ ├── AuthSource.java
│ └── AuthDefaultSource.java
├── enums/ # 枚举定义域
│ ├── AuthResponseStatus.java
│ ├── AuthUserGender.java
│ └── scope/ # 授权范围子域
├── exception/ # 异常处理域
│ └── AuthException.java
├── model/ # 数据模型域
│ ├── AuthUser.java
│ ├── AuthToken.java
│ ├── AuthCallback.java
│ └── AuthResponse.java
├── request/ # 请求处理域
│ ├── AuthRequest.java
│ ├── AuthDefaultRequest.java
│ └── Auth*Request.java (40+个平台实现)
└── utils/ # 工具功能域
├── HttpUtils.java
├── AuthChecker.java
├── UuidUtils.java
└── UrlBuilder.java
对比分析:功能导向 vs 层次导向
🎯 功能导向包结构(JustAuth采用):
java
// 优势:相关功能聚合,便于维护
me.zhyd.oauth.cache.AuthCache // 缓存相关都在一起
me.zhyd.oauth.cache.AuthStateCache
me.zhyd.oauth.cache.AuthDefaultCache
me.zhyd.oauth.config.AuthConfig // 配置相关都在一起
me.zhyd.oauth.config.AuthSource
me.zhyd.oauth.config.AuthDefaultSource
📚 层次导向包结构(传统方式):
java
// 问题:功能分散,维护困难
me.zhyd.oauth.service.AuthCacheService
me.zhyd.oauth.service.AuthConfigService
me.zhyd.oauth.dao.AuthCacheDao
me.zhyd.oauth.dao.AuthConfigDao
me.zhyd.oauth.controller.AuthController
me.zhyd.oauth.dto.AuthConfigDto
3.2 命名规范与可读性
JustAuth遵循严格的命名规范,提高代码可读性:
包命名规范
java
// ✅ 正确的包命名:简洁、功能导向
me.zhyd.oauth.cache // 缓存功能
me.zhyd.oauth.config // 配置功能
me.zhyd.oauth.request // 请求处理
me.zhyd.oauth.utils // 工具集合
// ❌ 糟糕的包命名示例
me.zhyd.oauth.impl // 太抽象,不知道实现什么
me.zhyd.oauth.common // 太宽泛,什么都往里放
me.zhyd.oauth.helper // 不明确,helper干什么的?
类命名规范
java
// ✅ 接口命名:能力描述
public interface AuthRequest { } // 认证请求能力
public interface AuthStateCache { } // 状态缓存能力
public interface AuthSource { } // 平台源配置能力
// ✅ 抽象类命名:Default前缀
public abstract class AuthDefaultRequest { } // 默认请求实现
public class AuthDefaultCache { } // 默认缓存实现
// ✅ 具体实现命名:平台+功能
public class AuthGitHubRequest { } // GitHub请求实现
public class AuthWechatRequest { } // 微信请求实现
public class AuthGoogleRequest { } // Google请求实现
// ✅ 工具类命名:功能+Utils
public class HttpUtils { } // HTTP工具
public class AuthChecker { } // 认证检查工具
public class UuidUtils { } // UUID工具
方法命名规范
java
public interface AuthRequest {
// ✅ 动词开头,描述行为
String authorize(String state); // 授权
AuthToken getAccessToken(AuthCallback callback); // 获取令牌
AuthUser getUserInfo(AuthToken token); // 获取用户信息
AuthResponse<AuthUser> login(AuthCallback callback); // 登录
// ✅ 布尔方法:is/has/can开头
default boolean isSupported(String scope) { return true; }
default boolean hasRefreshToken(AuthToken token) { return true; }
default boolean canRevoke() { return false; }
}
3.3 循环依赖的避免策略
JustAuth通过精心的包结构设计,避免了循环依赖问题:
依赖方向控制
java
// 依赖关系图(单向依赖)
request(请求层)
↓ 依赖
config(配置层) + model(模型层) + cache(缓存层) + utils(工具层)
↓ 依赖
enums(枚举层) + exception(异常层)
具体依赖示例
java
// ✅ 正确的依赖方向
public abstract class AuthDefaultRequest implements AuthRequest {
// 向下依赖:依赖配置层
protected AuthConfig config;
protected AuthSource source;
// 向下依赖:依赖缓存层
protected AuthStateCache authStateCache;
// 向下依赖:依赖工具层
public String authorize(String state) {
String url = UrlBuilder.fromBaseUrl(source.authorize()) // 依赖工具层
.queryParam("client_id", config.getClientId())
.build();
authStateCache.cache(state, state); // 依赖缓存层
return url;
}
}
// ❌ 避免的循环依赖
public class AuthConfig {
// 禁止:配置层不能依赖请求层
// private AuthRequest authRequest;
}
public class AuthCache {
// 禁止:缓存层不能依赖请求层
// private AuthDefaultRequest request;
}
循环依赖检测工具
java
// 依赖关系检测脚本示例
@Test
public void testNoCyclicDependencies() {
// 使用JDepend等工具检测循环依赖
JDepend jdepend = new JDepend();
jdepend.addDirectory("target/classes/me/zhyd/oauth");
jdepend.analyze();
Collection packages = jdepend.getPackages();
assertFalse("存在循环依赖", jdepend.containsCycles());
}
四、实战演练
4.1 绘制JustAuth完整架构图
让我们通过实际的架构图来理解JustAuth的整体设计:
组件关系架构图
数据流架构图
4.2 分析包依赖关系,识别潜在问题
让我们通过代码分析工具来检查JustAuth的包依赖关系:
依赖分析报告
bash
# 使用Maven依赖分析插件
mvn dependency:analyze
# 包依赖关系分析
Package Dependencies Analysis:
├── me.zhyd.oauth.request (核心请求包)
│ ├── depends on: config, model, cache, utils, enums, exception
│ ├── fan-in: 1 (被外部调用)
│ ├── fan-out: 6 (依赖其他包)
│ └── stability: 0.85 (相对稳定)
├── me.zhyd.oauth.config (配置包)
│ ├── depends on: enums, exception
│ ├── fan-in: 3 (被多个包依赖)
│ ├── fan-out: 2
│ └── stability: 0.60
├── me.zhyd.oauth.model (模型包)
│ ├── depends on: enums
│ ├── fan-in: 2
│ ├── fan-out: 1
│ └── stability: 0.33
└── me.zhyd.oauth.utils (工具包)
├── depends on: exception
├── fan-in: 2
├── fan-out: 1
└── stability: 0.33
潜在问题识别
java
// ✅ 良好的设计:稳定依赖原则
public class AuthDefaultRequest { // 不稳定的类(经常变化)
private AuthConfig config; // 依赖稳定的类(很少变化)
private AuthSource source; // 依赖稳定的接口
}
// ⚠️ 需要关注:request包的复杂度较高
public class AuthDefaultRequest {
// 依赖过多的包,可以考虑进一步抽象
// 当前依赖:config, model, cache, utils, enums, exception
// 优化方案:引入facade模式减少直接依赖
}
// 📊 包大小分析
Request包:40+个类,约12,000行代码
建议:考虑按平台类型进一步拆分子包
├── request/
│ ├── social/ # 社交平台 (微信、微博、QQ等)
│ ├── git/ # 代码托管 (GitHub、GitLab、Gitee等)
│ ├── enterprise/ # 企业平台 (钉钉、飞书、企业微信等)
│ └── international/ # 国际平台 (Google、Facebook、Twitter等)
4.3 设计自己的OAuth集成框架架构
基于JustAuth的架构分析,我们来设计一个改进版的OAuth集成框架:
改进架构设计
java
// 1. 增强的建造者模式
public class EnhancedAuthRequestBuilder {
public static EnhancedAuthRequestBuilder builder() {
return new EnhancedAuthRequestBuilder();
}
// 支持链式配置
public EnhancedAuthRequestBuilder platform(OAuthPlatform platform) {
this.platform = platform;
return this;
}
// 支持配置验证
public EnhancedAuthRequestBuilder config(AuthConfig config) {
validateConfig(config); // 构建时验证
this.config = config;
return this;
}
// 支持插件扩展
public EnhancedAuthRequestBuilder plugin(AuthPlugin... plugins) {
this.plugins.addAll(Arrays.asList(plugins));
return this;
}
// 支持自定义缓存
public EnhancedAuthRequestBuilder cache(AuthStateCache cache) {
this.cache = cache;
return this;
}
public AuthRequest build() {
// 使用工厂模式创建具体实现
return AuthRequestFactory.create(platform, config, cache, plugins);
}
}
// 2. 插件化架构
public interface AuthPlugin {
void beforeAuthorize(AuthContext context);
void afterGetToken(AuthContext context, AuthToken token);
void afterGetUser(AuthContext context, AuthUser user);
void onError(AuthContext context, Exception e);
}
// 日志插件示例
public class LoggingPlugin implements AuthPlugin {
@Override
public void beforeAuthorize(AuthContext context) {
log.info("开始OAuth授权: {}", context.getPlatform());
}
@Override
public void afterGetUser(AuthContext context, AuthUser user) {
log.info("OAuth登录成功: 用户={}, 平台={}", user.getUsername(), context.getPlatform());
}
}
// 3. 工厂模式优化
public class AuthRequestFactory {
private static final Map<OAuthPlatform, Class<? extends AuthRequest>> PLATFORM_MAPPING = new HashMap<>();
static {
PLATFORM_MAPPING.put(OAuthPlatform.GITHUB, AuthGitHubRequest.class);
PLATFORM_MAPPING.put(OAuthPlatform.WECHAT, AuthWechatRequest.class);
// ... 其他平台映射
}
public static AuthRequest create(OAuthPlatform platform, AuthConfig config,
AuthStateCache cache, List<AuthPlugin> plugins) {
Class<? extends AuthRequest> requestClass = PLATFORM_MAPPING.get(platform);
if (requestClass == null) {
throw new UnsupportedPlatformException(platform);
}
try {
// 使用反射创建实例
Constructor<? extends AuthRequest> constructor =
requestClass.getConstructor(AuthConfig.class, AuthStateCache.class);
AuthRequest request = constructor.newInstance(config, cache);
// 应用插件
if (request instanceof PluggableAuthRequest) {
((PluggableAuthRequest) request).setPlugins(plugins);
}
return request;
} catch (Exception e) {
throw new AuthRequestCreationException("无法创建AuthRequest实例", e);
}
}
}
// 4. 响应式架构支持
public interface ReactiveAuthRequest {
Mono<String> authorizeAsync(String state);
Mono<AuthToken> getAccessTokenAsync(AuthCallback callback);
Mono<AuthUser> getUserInfoAsync(AuthToken token);
Mono<AuthResponse<AuthUser>> loginAsync(AuthCallback callback);
}
public abstract class ReactiveAuthDefaultRequest implements ReactiveAuthRequest {
@Override
public Mono<AuthResponse<AuthUser>> loginAsync(AuthCallback callback) {
return checkCodeAsync(callback)
.then(getAccessTokenAsync(callback))
.flatMap(this::getUserInfoAsync)
.map(user -> AuthResponse.success(user))
.onErrorReturn(e -> AuthResponse.error(e.getMessage()));
}
protected abstract Mono<AuthToken> getAccessTokenAsync(AuthCallback callback);
protected abstract Mono<AuthUser> getUserInfoAsync(AuthToken token);
}
架构改进亮点
- 插件化支持:通过AuthPlugin接口支持功能扩展
- 配置验证:构建时验证配置,快速失败
- 工厂模式:统一的实例创建机制
- 响应式支持:支持异步非阻塞调用
- 包结构优化:按平台类型进一步细分
五、学习收获与架构思维
5.1 分层架构设计方法总结
通过对JustAuth架构的深度分析,我们可以总结出分层架构的设计方法论:
🏗️ 分层架构设计五步法
Step 1: 识别核心职责
java
// 问题:OAuth集成框架需要哪些核心能力?
核心职责识别:
├── 外部交互 -> 提供统一API接口
├── 业务抽象 -> 定义OAuth标准流程
├── 平台适配 -> 处理各平台差异
├── 数据传输 -> 统一数据模型
├── 通用工具 -> 提供基础能力
└── 基础设施 -> 缓存、配置、异常处理
Step 2: 确定分层边界
java
// 原则:高内聚、低耦合、单向依赖
分层边界划分:
├── 表现层:对外API,用户交互
├── 业务层:核心业务逻辑,流程控制
├── 适配层:外部系统集成,差异抽象
├── 模型层:数据传输对象,业务实体
├── 工具层:通用工具,无状态服务
└── 基础层:技术基础设施,系统服务
Step 3: 设计接口契约
java
// 每层都要有清晰的接口定义
public interface AuthRequest { // 业务层接口
AuthResponse<AuthUser> login(AuthCallback callback);
}
public interface AuthSource { // 适配层接口
String authorize();
String accessToken();
}
public interface AuthStateCache { // 基础层接口
void cache(String key, String value);
boolean containsKey(String key);
}
Step 4: 实现依赖控制
java
// 依赖方向:上层依赖下层,避免循环依赖
public class AuthDefaultRequest implements AuthRequest {
// ✅ 向下依赖:依赖下层接口
private AuthConfig config; // 依赖配置层
private AuthSource source; // 依赖适配层接口
private AuthStateCache stateCache; // 依赖基础层接口
// ❌ 避免向上依赖:不依赖上层实现
// private AuthRequestBuilder builder;
}
Step 5: 持续优化重构
java
// 定期检查和优化架构质量
架构质量检查清单:
├── 包依赖关系是否合理?
├── 是否存在循环依赖?
├── 各层职责是否清晰?
├── 接口设计是否稳定?
├── 扩展机制是否完善?
└── 性能是否满足要求?
5.2 SOLID原则在实际项目中的应用
原则应用总结表
SOLID原则 | JustAuth中的体现 | 设计技巧 | 收益分析 |
---|---|---|---|
SRP 单一职责 | AuthChecker 只负责校验 HttpUtils 只负责HTTP请求 |
按功能划分类职责 避免God Class | 代码更易理解和维护 修改影响面更小 |
OCP 开闭原则 | 新增平台无需修改现有代码 通过继承和实现扩展功能 | 抽象稳定的接口 使用模板方法模式 | 系统扩展性强 向后兼容性好 |
LSP 里氏替换 | 所有AuthRequest 实现可以互换 行为契约保持一致 |
明确前置后置条件 保持接口语义 | 多态使用更安全 代码复用性高 |
ISP 接口隔离 | AuthRequest 接口职责单一 不强制实现不需要的方法 |
小而专的接口设计 default方法降低实现成本 | 实现类更聚焦 接口更稳定 |
DIP 依赖倒置 | 依赖AuthSource 接口而非具体实现 可注入不同的缓存实现 |
面向接口编程 依赖注入模式 | 系统灵活性高 测试更容易 |
最佳实践提炼
java
// 🎯 SOLID原则实践技巧
// 1. SRP:一个类一个变化原因
public class AuthTokenValidator { // 只因Token校验规则变化而修改
public boolean isValid(AuthToken token) {
return token != null &&
!StringUtils.isEmpty(token.getAccessToken()) &&
!token.isExpired();
}
}
// 2. OCP:对扩展开放,对修改封闭
public interface AuthPlatformStrategy { // 稳定的抽象
AuthUser getUser(AuthToken token);
}
public class AuthPlatformContext { // 不需要修改
private AuthPlatformStrategy strategy;
public AuthUser getUser(AuthToken token) {
return strategy.getUser(token); // 委托给策略
}
}
// 3. LSP:子类可以替换父类
public abstract class AbstractOAuthRequest {
// 定义清晰的行为契约
protected final AuthResponse<AuthUser> login(AuthCallback callback) {
validate(callback); // 前置条件
AuthUser user = doLogin(callback);
return AuthResponse.success(user); // 后置条件保证
}
protected abstract AuthUser doLogin(AuthCallback callback);
}
// 4. ISP:接口隔离,客户端不依赖不需要的接口
public interface Authorizable { // 授权能力
String getAuthorizationUrl(String state);
}
public interface TokenObtainable { // 获取Token能力
AuthToken getAccessToken(String code);
}
public interface UserInfoObtainable { // 获取用户信息能力
AuthUser getUserInfo(AuthToken token);
}
// 5. DIP:依赖抽象而非具体
public class AuthService {
private final TokenStorage tokenStorage; // 依赖抽象
private final UserInfoFetcher userFetcher; // 依赖抽象
public AuthService(TokenStorage tokenStorage, UserInfoFetcher userFetcher) {
this.tokenStorage = tokenStorage; // 通过构造函数注入
this.userFetcher = userFetcher;
}
}
5.3 架构思维的培养
🧠 架构师思维模型
1. 抽象思维
java
// 从具体到抽象的思维过程
具体问题:GitHub OAuth集成
↓ 抽象
通用问题:第三方OAuth集成
↓ 进一步抽象
设计方案:统一的OAuth集成框架
↓ 架构设计
分层架构:接口层 -> 实现层 -> 工具层
2. 分解思维
java
// 复杂问题分解
OAuth集成框架
├── 授权流程管理
│ ├── 生成授权链接
│ ├── 处理回调参数
│ └── 状态验证
├── 平台差异抽象
│ ├── API地址配置
│ ├── 参数格式统一
│ └── 响应数据映射
├── 数据模型设计
│ ├── 用户信息模型
│ ├── 令牌信息模型
│ └── 配置信息模型
└── 基础设施支撑
├── HTTP请求处理
├── 缓存状态管理
└── 异常错误处理
3. 演进思维
java
// 架构演进路径
V1.0:基础OAuth流程
↓ 演进:支持更多平台
V1.5:多平台支持
↓ 演进:提升易用性
V2.0:统一API接口
↓ 演进:增强扩展性
V2.5:插件化架构
↓ 演进:云原生支持
V3.0:微服务化、容器化
六、本期总结与下期预告
本期核心要点回顾
- 七层架构设计:外部调用层 → 业务接口层 → 模板实现层 → 平台适配层 → 数据模型层 → 工具组件层 → 基础设施层
- SOLID原则实践:单一职责的类设计、开闭原则的扩展机制、里氏替换的继承体系、接口隔离的API设计、依赖倒置的抽象依赖
- 包结构设计:功能导向的包划分、清晰的命名规范、单向依赖关系控制
- 架构思维培养:抽象思维、分解思维、演进思维的综合运用
思考题
- 设计思考:如果要设计一个支持GraphQL的OAuth框架,架构应该如何调整?
- 扩展思考:如何在现有架构基础上增加OAuth2.0的PKCE(Proof Key for Code Exchange)支持?
- 性能思考:在高并发场景下,如何优化AuthDefaultRequest的模板方法性能?
下期预告:接口设计艺术 - AuthRequest核心接口拆解
在下一期中,我们将深入分析JustAuth的接口设计:
- 🎨 接口设计原则:最小化接口、向后兼容性、默认方法的巧妙运用
- 🔍 AuthRequest接口深度解析:方法设计思考、抽象层次划分、职责边界定义
- 📚 接口演进历史:@Deprecated方法的处理、版本兼容性维护、扩展最佳实践
- 🛠️ 实战演练:设计更优雅的OAuth接口、接口版本升级策略
我们会从接口设计的角度,分析如何构建稳定、易用、可扩展的API接口。
参考资料
- 《架构整洁之道》 - Robert C. Martin
- 《设计模式:可复用面向对象软件的基础》 - GoF
- JustAuth GitHub仓库
- Maven依赖分析插件文档