预生产环境中WebServiceGatewaySupport.setDefaultUri抛出空指针异常
你提到调用SOAP服务的应用在pre-prod环境部署失败,抛出NullPointerException,而dev和staging环境正常,错误定位在WebServiceGatewaySupport.setDefaultUri方法中。结合你给出的异常栈和代码,我来拆解下问题根源和解决方案。
问题根源分析
从异常栈可以看到,NPE发生在WebServiceGatewaySupport.setDefaultUri的第92行,查看Spring WS的源码,这个方法内部是调用getWebServiceTemplate().setDefaultUri(uri),所以本质是webServiceTemplate为null。
为什么会出现这种情况?看你的代码细节:
AppClient标注了@Component,会被Spring自动扫描创建BeanAppClientConfiguration里的setAppClient是一个@Bean方法,同时参数里注入了AppClient client
这就形成了循环依赖:Spring要创建AppClient的Bean,但这个Bean的工厂方法(setAppClient)又依赖AppClient实例。在dev/staging环境中,Spring默认的循环依赖处理机制可能允许这种情况(通过提前暴露未完全初始化的Bean),但pre-prod环境可能因为配置差异(比如禁用了循环依赖、Bean初始化顺序不同),导致注入的client是一个未完全初始化的实例——此时WebServiceGatewaySupport的webServiceTemplate还没被初始化,调用setDefaultUri等方法时就会抛出NPE。
另外,你的@Bean方法命名为setAppClient也不太规范,通常@Bean方法应该是创建Bean的逻辑,而不是“设置”已有Bean,这也可能让Spring的Bean创建逻辑出现混淆。
解决方案
针对这个问题,有两种可行的修复方式:
方案1:移除AppClient的@Component,在@Bean方法中直接创建实例
修改AppClient类,去掉@Component注解:
// 移除@Component注解 public class AppClient extends WebServiceGatewaySupport { // 原有业务代码不变 }
然后修改AppClientConfiguration的@Bean方法,不再注入AppClient,而是直接创建新实例:
@Configuration public class AppClientConfiguration { // 假设defaultUri从配置文件注入,根据实际情况调整 @Value("${app.soap.default-uri}") private String defaultUri; @Bean public AppClient appClient(Jaxb2Marshaller marshaller) throws NoSuchAlgorithmException, KeyManagementException { // 直接创建AppClient实例,避免循环依赖 AppClient client = new AppClient(); client.setDefaultUri(defaultUri); client.setMarshaller(marshaller); client.setUnmarshaller(marshaller); client.getWebServiceTemplate().setMessageSender(webServiceMessageSender()); client.getWebServiceTemplate().setCheckConnectionForError(false); client.getWebServiceTemplate().setCheckConnectionForFault(false); ClientInterceptor[] interceptors = {new AppClientInterceptor()}; client.setInterceptors(interceptors); return client; } // 保留webServiceMessageSender等其他必要的Bean配置方法 }
这种方式彻底避免了循环依赖,Bean的创建完全由@Bean方法负责,没有依赖已有的AppClient实例。
方案2:保留@Component,使用@PostConstruct替代@Bean方法配置
如果不想移除AppClient的@Component,可以把配置逻辑移到AppClient内部,用@PostConstruct注解实现初始化:
修改AppClient类:
@Component public class AppClient extends WebServiceGatewaySupport { @Value("${app.soap.default-uri}") private String defaultUri; @Autowired private Jaxb2Marshaller marshaller; @Autowired private WebServiceMessageSender webServiceMessageSender; @PostConstruct public void initClient() throws NoSuchAlgorithmException, KeyManagementException { setDefaultUri(defaultUri); setMarshaller(marshaller); setUnmarshaller(marshaller); getWebServiceTemplate().setMessageSender(webServiceMessageSender); getWebServiceTemplate().setCheckConnectionForError(false); getWebServiceTemplate().setCheckConnectionForFault(false); ClientInterceptor[] interceptors = {new AppClientInterceptor()}; setInterceptors(interceptors); } // 原有业务方法不变 }
然后可以删除AppClientConfiguration中的setAppClient方法,保留webServiceMessageSender、Jaxb2Marshaller等必要的Bean配置即可。
这种方式让AppClient的配置逻辑和类本身绑定,避免了循环依赖的问题,同时保持了@Component的自动扫描特性。
额外建议
- 检查pre-prod环境的Spring配置,是否设置了
spring.main.allow-circular-references=false,这很可能是dev/staging和pre-prod环境行为差异的核心原因 - 确保
defaultUri在pre-prod环境中正确配置,虽然当前异常是webServiceTemplate为null,但配置缺失也可能引发后续问题
内容的提问来源于stack exchange,提问作者F0cus

