You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何正确校验BankId接口返回值非空?最佳实践探讨

BankId认证轮询方法的非空校验优化方案

我编写了BankId认证轮询回调结果的方法如下:

public boolean authenticateAndPollCallbackResult(BankIdAuthRequest bankIdAuthRequest) {
        ResponseEntity<BankIdAuthResponse> authResponse = bankIdAuthentication(bankIdAuthRequest);
        AbstractApplicationForm applicationForm = applicationFormRepository.findByToken(bankIdAuthRequest.getRefID());

        try {
            //Add new bankId authentication to database.
            BankIdAuthenticationEntity bankIdAuthenticationEntity = new BankIdAuthenticationEntity();
            bankIdAuthenticationEntity.setAbstractApplicationForm(applicationForm);
            bankIdAuthenticationEntity.setAuthStatus(STATUS_PROGRESS);
            bankIdAuthenticationEntity.setOrderReference(authResponse.getBody().getOrderRef());
            bankIdAuthenticationEntity.setAutoStartToken(authResponse.getBody().getAutoStartToken());

            Long bankIdAuthenticationId = bankIdAuthenticationRepository.save(bankIdAuthenticationEntity).getId();
            BankIdAuthenticationEntity.AuthStatus authStatus;

            do {
                TimeUnit.MILLISECONDS.sleep(1500);

                authStatus = getAuthStatus(bankIdAuthenticationId);

                if (authStatus == BankIdAuthenticationEntity.AuthStatus.COMPLETED)
                    return true;
                if (authStatus == BankIdAuthenticationEntity.AuthStatus.FAILED || authStatus == BankIdAuthenticationEntity.AuthStatus.NOT_ASSIGNED)
                    return false;
            } while (authStatus == BankIdAuthenticationEntity.AuthStatus.PROGRESS);

        } catch (InterruptedException e) {
            log.error("InterruptedException: ", e);
            Thread.currentThread().interrupt();
        } catch (NullPointerException e) {
            log.error("Either BankId API not responding correctly. Check server connection", e);
        } catch (Exception e) {
            log.error("Exception: Polling collect endpoint method failed", e);
        }
        return false;
    }

SonarQube告警以下两行存在空指针风险(authResponse.getBody()或内部方法可能返回null):

bankIdAuthenticationEntity.setOrderReference(authResponse.getBody().getOrderRef());
bankIdAuthenticationEntity.setAutoStartToken(authResponse.getBody().getAutoStartToken());

我尝试用Objects.requireNonNull抛出空指针并捕获,但感觉不够优雅合理,以下是几种更合适的非空校验方案:

方案一:提前校验+快速失败(推荐)

遵循fail-fast原则,在使用响应数据前提前校验所有可能为空的节点,抛出明确的业务异常而非依赖NullPointerException,既消除SonarQube告警,也让错误原因更清晰。

ResponseEntity<BankIdAuthResponse> authResponse = bankIdAuthentication(bankIdAuthRequest);
AbstractApplicationForm applicationForm = applicationFormRepository.findByToken(bankIdAuthRequest.getRefID());

// 提前校验响应及响应体非空
if (authResponse == null || authResponse.getBody() == null) {
    throw new BankIdApiException("BankId API未返回有效响应,请检查服务连接");
}
BankIdAuthResponse responseBody = authResponse.getBody();

// 校验必要业务字段非空
if (responseBody.getOrderRef() == null) {
    throw new BankIdApiException("BankId API响应缺少必要字段:orderRef");
}
if (responseBody.getAutoStartToken() == null) {
    throw new BankIdApiException("BankId API响应缺少必要字段:autoStartToken");
}

// 后续安全使用字段
bankIdAuthenticationEntity.setOrderReference(responseBody.getOrderRef());
bankIdAuthenticationEntity.setAutoStartToken(responseBody.getAutoStartToken());

注:需自定义BankIdApiException等业务异常类,替代通用的NPE捕获,让错误处理更精准。

方案二:使用Optional链式处理空值

利用Java 8+的Optional封装可能为空的对象,通过链式调用简化空值判断,同时可以灵活指定空值时的处理逻辑(抛出异常或设置默认值)。

ResponseEntity<BankIdAuthResponse> authResponse = bankIdAuthentication(bankIdAuthRequest);
AbstractApplicationForm applicationForm = applicationFormRepository.findByToken(bankIdAuthRequest.getRefID());

// 处理响应体为空的情况
BankIdAuthResponse responseBody = Optional.ofNullable(authResponse)
        .map(ResponseEntity::getBody)
        .orElseThrow(() -> new BankIdApiException("BankId API响应为空"));

// 处理字段为空的情况
String orderRef = Optional.ofNullable(responseBody.getOrderRef())
        .orElseThrow(() -> new BankIdApiException("BankId响应缺少orderRef字段"));
String autoStartToken = Optional.ofNullable(responseBody.getAutoStartToken())
        .orElseThrow(() -> new BankIdApiException("BankId响应缺少autoStartToken字段"));

// 安全赋值
bankIdAuthenticationEntity.setOrderReference(orderRef);
bankIdAuthenticationEntity.setAutoStartToken(autoStartToken);

这种方式代码可读性更强,空值处理逻辑更直观,也能满足SonarQube的非空校验要求。

方案三:业务允许时设置默认值

如果业务逻辑允许orderRef或autoStartToken为空,可以为其设置合理的默认值(如空字符串),避免抛出异常。

ResponseEntity<BankIdAuthResponse> authResponse = bankIdAuthentication(bankIdAuthRequest);
AbstractApplicationForm applicationForm = applicationFormRepository.findByToken(bankIdAuthRequest.getRefID());

// 空响应体时创建默认对象(需确保BankIdAuthResponse有无参构造)
BankIdAuthResponse responseBody = Optional.ofNullable(authResponse)
        .map(ResponseEntity::getBody)
        .orElse(new BankIdAuthResponse());

// 字段为空时设置默认值
String orderRef = Optional.ofNullable(responseBody.getOrderRef()).orElse("");
String autoStartToken = Optional.ofNullable(responseBody.getAutoStartToken()).orElse("");

// 赋值
bankIdAuthenticationEntity.setOrderReference(orderRef);
bankIdAuthenticationEntity.setAutoStartToken(autoStartToken);

注意:此方案仅适用于业务允许字段为空的场景,否则会隐藏数据异常问题。

额外建议

避免在catch块中捕获通用的NullPointerException,NPE本质是编程错误,应通过提前校验从根源避免。改用捕获自定义业务异常,能更精准地处理不同场景的错误,也便于后续排查问题。

内容的提问来源于stack exchange,提问作者Mr.Gomer

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 05:15:39