Skip to content

经验卡片:运行时动态切换策略的注入设计

背景

在樟木林项目中,SWULoginProxy 需要持有一个登录策略(SWULoginStrategy 接口),默认采用密码登录策略,但在处理用户请求时,可能先尝试 Cookie 登录,失败后再切回密码登录。这就要求类中的策略字段 既能通过 Spring 注入获得默认实现,又能在运行时自由替换

问题与误区

  • 最初误以为“构造器注入失败后可以改用 setter 注入”来实现 fallback。
  • 误把运行时的 setLoginStrategy() 调用当成 Spring 的 setter 注入。
  • 简单加 final 或使用 Lombok @RequiredArgsConstructor 会导致字段不可变或注入歧义。

核心理解

  1. 注入只在启动时发生一次
    Spring 容器启动时,通过构造器注入默认策略对象。此后的任何 set 调用都是普通方法调用,与 DI 无关。
  2. 字段可变 → 不能 final
    private SWULoginStrategy loginStrategy; 指向的实例需要被替换,因此字段不能 final
  3. final / 非 @NonNull → Lombok 不生成构造器参数
    若用 @RequiredArgsConstructor,它只处理 final@NonNull 字段,故必须显式编写构造器。
  4. 接口多实现时,必须消除歧义
    直接写 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 用于运行时切换。

记录学习,分享技术