@RequestScope Bean为null时如何处理NullBean类型转换异常
Spring Boot集成Keycloak时匿名请求AccessToken注入异常解决方案
问题现象
Spring Boot应用通过Keycloak完成安全加固后,除从请求上下文加载当前AccessToken的逻辑外,其余功能均运行正常。
原有AccessToken的Bean定义如下:
@Bean @RequestScope(proxyMode = TARGET_CLASS) public AccessToken accessToken() { return Optional.ofNullable( (KeycloakSecurityContext) RequestContextHolder.currentRequestAttributes() .getAttribute(KeycloakSecurityContext.class.getName(), SCOPE_REQUEST)) .map(KeycloakSecurityContext::getToken) .orElse(null); }
在业务类中通过如下方式注入该Bean:
@Resource(name = "accessToken") private AccessToken accessToken;
当用户已登录、请求携带有效Token时,所有逻辑运行符合预期;但匿名无Token的请求会抛出如下类型转换异常:
Caused by: java.lang.ClassCastException: class org.springframework.beans.factory.support.NullBean cannot be cast to class org.keycloak.representations.JsonWebToken (org.springframework.beans.factory.support.NullBean and org.keycloak.representations.JsonWebToken are in unnamed module of loader 'app')
常规null判断逻辑始终无法命中,异常仍会抛出,原有业务判断代码如下:
public Optional<TenantAdapter> current() { // 该判断始终返回false if (accessToken.equals(null)) { return Optional.empty(); } else { // 此处会抛出异常 Map<String, Object> claims = accessToken.getOtherClaims(); } }
问题根因
- Spring对请求作用域(RequestScope)的Bean做AOP代理时,如果工厂方法返回null,不会向注入点赋值null,而是生成一个
NullBean类型的代理对象作为占位。这个代理对象本身不是null,因此所有针对null的判断都不会命中;当尝试调用代理对象上属于AccessToken类型的方法时,就会触发类型转换异常。 - 原有判断逻辑
accessToken.equals(null)本身存在Java基础逻辑错误:按照Java equals方法的约定,任何非null对象调用equals(null)都必须返回false;如果accessToken真的为null,调用equals方法会直接抛出空指针异常,正确的null值判断应该使用== null,但该判断在此场景下依然无法识别NullBean代理。
修复方案
优先选择无Spring内部API耦合的实现方式:
- 调整Bean定义,禁止请求作用域Bean的工厂方法返回null,使用
Optional<AccessToken>作为Bean的返回类型,从根源上避免NullBean代理生成:
@Bean @RequestScope(proxyMode = TARGET_CLASS) public Optional<AccessToken> accessToken() { return Optional.ofNullable( (KeycloakSecurityContext) RequestContextHolder.currentRequestAttributes() .getAttribute(KeycloakSecurityContext.class.getName(), SCOPE_REQUEST)) .map(KeycloakSecurityContext::getToken); }
- 对应调整注入和业务判断逻辑,直接通过Optional的方法判断Token是否存在,无需额外写null判断:
@Resource(name = "accessToken") private Optional<AccessToken> accessToken; public Optional<TenantAdapter> current() { return accessToken.map(token -> { Map<String, Object> claims = token.getOtherClaims(); // 此处编写已登录状态下的租户信息转换逻辑 return buildTenantAdapterFromClaims(claims); }); }
如果暂时无法修改Bean的返回类型,也可以通过判断代理对象类型识别空Token场景,注意该方式依赖Spring内部API,后续版本升级可能存在兼容性问题:
public Optional<TenantAdapter> current() { if (accessToken instanceof NullBean) { return Optional.empty(); } Map<String, Object> claims = accessToken.getOtherClaims(); // 后续业务逻辑 }
内容的提问来源于stack exchange,提问作者Stefano Veloccia
相关产品推荐
相关产品推荐

