由Jwts中的parser()与parserBuilder()角度解析静态工厂与建造者模式

25 阅读1分钟

最近在看 Jwt 相关内容时,发现对于解析拿取 claim , Jwt 有两种处理方式,一种是不被推荐的老方法:parser()( jjwt-api:0,10.x之前 ),另一种是新版本:parserBuilder()

Claims claims = Jwts.parser()
        .setSigningKey(key)
        .parseClaimsJws(refreshToken)
        .getBody();

Claims claims = Jwts.parserBuilder()
        .setSigningKey(key)
        .build()
        .parseClaimsJws(refreshToken)
        .getBody();

从功能上看,parserBuilder提供了 setSigningKeyResolver ,支持动态密钥解析; setAllowedClockSkewSeconds 时钟偏移容差。

从设计上看,parser采用静态工厂,parserBuilder采用建造者模式而非静态工厂模式,具体过程如下:

graph TD
parser --> 创建配置对象 --> 使用对象

parser将创建与配置对象混合,结构上比较混乱,且可能存在配置后被修改的情况

graph TD
parserBuio --> 配置对象 --> build --> 创建不可变对象 -->使用对象

而parserBuilder则将配置与build分开,且建立起不可变对象好处如下:

  • 获取线程安全
  • build时可以添加校验

例子如下

// 在并发环境下
public class TokenValidator {
    private JwtParser parser;
    
    // 旧方式的问题:多线程可能同时修改配置
    public TokenValidator() {
        this.parser = Jwts.parser()
            .setSigningKey("default");
    }
    
    public void validateTokensConcurrently(List<String> tokens) {
        // 如果多个线程同时调用,可能互相干扰
        tokens.parallelStream().forEach(token -> {
            // 危险!可能另一个线程正在修改parser配置
            parser.requireIssuer(getIssuerForToken(token));
            parser.parseClaimsJws(token);
        });
    }
}


// 建造者可以在build()时进行配置验证
public JwtParser build() {
    validateConfiguration();  // 验证所有必要配置
    return new ImmutableJwtParser(config);
}

private void validateConfiguration() {
    if (signingKey == null && signingKeyResolver == null) {
        throw new IllegalStateException("必须设置签名密钥或密钥解析器");
    }
}

现在分析会不会是因为开始时采用静态工厂,便于扩展,后发现可变问题,且敲定需求后转为建造者模式,为历史兼容仍旧保存。