经验卡片:运行时动态切换策略的注入设计
背景
在樟木林项目中,SWULoginProxy 需要持有一个登录策略(SWULoginStrategy 接口),默认采用密码登录策略,但在处理用户请求时,可能先尝试 Cookie 登录,失败后再切回密码登录。这就要求类中的策略字段 既能通过 Spring 注入获得默认实现,又能在运行时自由替换。
问题与误区
- 最初误以为“构造器注入失败后可以改用 setter 注入”来实现 fallback。
- 误把运行时的
setLoginStrategy()调用当成 Spring 的 setter 注入。 - 简单加
final或使用 Lombok@RequiredArgsConstructor会导致字段不可变或注入歧义。
核心理解
- 注入只在启动时发生一次
Spring 容器启动时,通过构造器注入默认策略对象。此后的任何set调用都是普通方法调用,与 DI 无关。 - 字段可变 → 不能
finalprivate SWULoginStrategy loginStrategy;指向的实例需要被替换,因此字段不能final。 - 非
final/ 非@NonNull→ Lombok 不生成构造器参数
若用@RequiredArgsConstructor,它只处理final或@NonNull字段,故必须显式编写构造器。 - 接口多实现时,必须消除歧义
直接写SWULoginStrategy作为构造器参数会导致 Spring 报错。通过将参数类型明确为SWUPasswordLoginStrategy(具体类),Spring 能唯一确定要注入的 Bean,从而完成默认策略的注入。
推荐写法
java
@Component
public class SWULoginProxy {
private SWULoginStrategy loginStrategy; // 不可 final,运行时需要切换
// 显式构造器,指定默认策略的具体类型,消除歧义
public SWULoginProxy(SWUPasswordLoginStrategy defaultStrategy) {
this.loginStrategy = defaultStrategy;
}
// 普通 setter,供业务代码在运行时替换策略
public void setLoginStrategy(SWULoginStrategy strategy) {
this.loginStrategy = strategy;
}
public OkHttpClient login(String studentId, String password) {
return loginStrategy.execute(studentId, password);
}
}运行时协作示例
SWUCampusClient.loginWithCookieOrPassword("学号")
├─ proxy.setLoginStrategy(cookieLogin); // 普通调用,换 Cookie 策略
├─ proxy.login()
├─ 若失败
│ ├─ proxy.setLoginStrategy(passwordLogin);
│ └─ proxy.login()经验总结
- 区分启动注入与运行时赋值:前者由容器完成,后者是业务逻辑。
- 可变字段放弃
final:直接的影响是必须手写构造器,但这换来运行时灵活性。 - 多实现接口的注入:默认注入时,通过构造器参数指定具体类,比
@Qualifier更直接,且表达了“这是一个明确默认策略”的设计意图。 - 模式识别:这本质是策略模式在单例 Bean 中的落地——构造器绑定默认策略,setter 用于运行时切换。