WebLogic 第二次部署无法解析 jdbc.payment
结论
这通常不是 Docker 或 PRODUCTION_MODE=dev 引起的。相同问题在本地 WebLogic 中依然存在,说明原因更可能出在两个环节:
- 应用卸载时,错误地关闭、解绑或破坏了 WebLogic 管理的 JNDI DataSource;
- 应用重新部署后,仍在使用旧部署留下的
InitialContext、DataSource 代理、线程或类加载器缓存。
“重新 target DataSource 后只能再成功一次”是一个很明确的信号。重新 target 会让 WebLogic 再次绑定 JNDI 对象,而下一次 undeploy/redeploy 又会使绑定失效。因此,应先检查应用的销毁逻辑和 Spring Bean 生命周期,而不是反复重启服务器。
还要确认 DataSource 和应用部署在同一个实际运行目标上。如果 WAR(Web Application Archive)运行在 Managed Server 或 Cluster,仅把 DataSource target 到 Admin Server,并不能保证应用可以访问它。
为什么第一次部署成功、第二次失败
第一次部署时,WebLogic 已经完成 jdbc.payment 的绑定,Spring 可以正常 lookup。
重新部署通常分为两个阶段:
- 卸载旧应用;
- 使用新的 ClassLoader 启动新应用。
如果旧应用在卸载时执行了以下任一操作,第二次部署就可能失败:
- 调用了 WebLogic DataSource 实现对象的
close()、shutdown()等方法; - 对
Context执行了unbind()或destroySubcontext(); - 将容器管理的 DataSource 声明成需要由 Spring 销毁的 Bean;
- 在静态字段或单例工具类中缓存了
InitialContext、DataSource 或 JNDI lookup 结果; - 应用创建的线程没有停止,仍然持有旧 ClassLoader 和旧 JNDI 对象;
- 应用级
resource-ref、weblogic.xml或 deployment plan 在 undeploy 时影响了对应绑定。
异常中的信息:
Unable to resolve 'jdbc.payment'. Resolved 'jdbc'; remaining name 'payment'
这表示 WebLogic 找到了 jdbc 这一层命名上下文,但其中没有可解析的 payment 绑定。问题还没有进入数据库连接阶段,连接池参数、用户名或 SQL 通常尚未参与处理。
第一项检查:DataSource 的 target
先确认应用实际运行在哪个 Server 或 Cluster,不要因为管理控制台登录在 Admin Server,就误以为应用也运行在那里。
例如:
- WAR 部署到
ManagedServer1,DataSource 也应 target 到ManagedServer1; - WAR 部署到
PaymentCluster,DataSource 通常也应 target 到该 Cluster; - 只有应用确实运行在 Admin Server 上时,才适合单独 target Admin Server。
重新部署失败后,在 WebLogic 控制台检查:
- DataSource 状态是否仍为
Running; Monitoring中对应实例是否仍然存在;- JNDI 树中是否还有
jdbc.payment; - DataSource 的 target 是否与 WAR 完全一致。
如果失败时 JNDI 树中的绑定已经消失,而重新 target 后又恢复,基本可以确认该绑定在部署生命周期中被移除或失效了。
避免让 Spring 销毁容器管理的 DataSource
如果通过 @Bean 将 DataSource 暴露给 Spring,建议明确禁止 Spring 推断销毁方法:
import javax.sql.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jndi.JndiDataSourceLookup;
@Configuration
public class DataSourceConfiguration {
@Bean(destroyMethod = "")
public DataSource paymentDataSource() {
JndiDataSourceLookup lookup = new JndiDataSourceLookup();
lookup.setResourceRef(false);
return lookup.getDataSource("jdbc.payment");
}
}
destroyMethod = "" 明确告诉 Spring:这个对象由 WebLogic 管理,应用停止时不要尝试调用其 close(),也不要调用其他推断出来的销毁方法。
如果使用组件方式,也可以让 Spring Boot 直接按 JNDI 名称获取:
spring.datasource.jndi-name=jdbc.payment
此时不要再创建 HikariCP、DBCP 等本地连接池来包装同一个 WebLogic DataSource。连接池应由一方管理。对于 WebLogic JNDI DataSource,连接池的生命周期通常交给 WebLogic。
还要搜索应用中是否存在以下危险操作:
context.unbind("jdbc.payment");
context.destroySubcontext("jdbc");
以及是否对 lookup 返回的对象执行过关闭操作:
close();
shutdown();
应用只能关闭从 DataSource 获得的 Connection,不能关闭或解绑 WebLogic 管理的 DataSource 本身:
try (Connection connection = paymentDataSource.getConnection()) {
// 使用连接
}
不要静态缓存 JNDI 上下文或 DataSource
下面这种写法容易在重新部署时保留旧 ClassLoader 或旧 JNDI 引用:
public final class DataSourceHolder {
private static InitialContext context;
private static DataSource dataSource;
}
也不要在应用创建的永久线程、定时任务或第三方线程池中长期保存 DataSource 引用。应让 Spring 管理 Bean 和任务的生命周期,并在应用停止时关闭自建的执行器:
@Bean(destroyMethod = "shutdown")
public ExecutorService applicationExecutor() {
return Executors.newFixedThreadPool(4);
}
这里关闭的是应用自己创建的线程池,不是 WebLogic DataSource。
区分全局 JNDI 与 java:comp/env
实际绑定名和 lookup 方式必须一致。
如果 WebLogic DataSource 的全局 JNDI 名称是:
jdbc.payment
就应按全局名称 lookup,并设置:
lookup.setResourceRef(false);
如果应用使用标准资源引用:
<resource-ref>
<res-ref-name>jdbc/payment</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
应用通常 lookup:
java:comp/env/jdbc/payment
同时还要在 WebLogic 部署描述符或 deployment plan 中,将该资源引用映射到实际的 DataSource。
不要混用以下名称:
jdbc.payment
jdbc/payment
java:comp/env/jdbc/payment
它们不一定指向同一个 JNDI 条目。最终使用哪个名称,应以 WebLogic 控制台配置、JNDI 树和部署描述符中的映射为准。
建议的定位顺序
1. 比较部署前后的 JNDI 树
分别在以下时间检查 jdbc.payment:
- WebLogic 启动后;
- 第一次部署成功后;
- undeploy 后;
- 第二次部署失败后;
- 重新 target DataSource 后。
如果绑定在 undeploy 时消失,应重点检查应用销毁方法、Spring Bean 定义和资源引用配置。
2. 查看卸载阶段日志
除了第二次启动时的 NameNotFoundException,还要查看第一次应用停止时的日志。重点搜索:
jdbc.payment
unbind
destroy
close
shutdown
JNDI
DataSource
Undeploy
ClassLoader
同时检查是否存在 @PreDestroy、DisposableBean.destroy()、Servlet listener 的 contextDestroyed() 或自定义 shutdown hook。
3. 开启 Spring Bean 销毁日志
可以临时提高相关日志级别,确认 Spring 停止应用时销毁了哪些 Bean:
logging.level.org.springframework.beans.factory.support=DEBUG
logging.level.org.springframework.jndi=DEBUG
如果实际日志由 WebLogic 管理,还需要在 WebLogic 日志配置中调整相应应用 Logger。
4. 做最小化验证
建立一个只包含 JNDI lookup 和简单查询的最小 WAR:
- 不创建额外连接池;
- 不使用静态 holder;
- 不启动后台线程;
- DataSource Bean 使用
destroyMethod = ""; - 不配置自定义卸载逻辑。
如果这个 WAR 可以连续重新部署,问题就在原应用的生命周期代码或依赖中。如果最小 WAR 仍然失败,再重点检查 WebLogic target、deployment plan、资源引用映射和补丁级别。
其他注意事项
- 不要把“测试连接成功”和“JNDI 可解析”混为一谈。当前异常发生在名称解析阶段。
- Admin Server 负责管理域配置,但这并不意味着 target 到 Admin Server 的资源能被所有 Managed Server 使用。
- 不建议把反复 detach/attach 当成正式解决方案,这个操作只是重新建立绑定。
- 如果最小应用也能稳定复现,应记录准确的 WebLogic 版本及补丁信息、部署方式、target,以及失败前后的 JNDI 树,再对照 Oracle 支持文档或提交服务请求。仅凭
12.2.1.4无法断定问题来自某个已知的 WebLogic 缺陷。
最值得优先尝试的修正是:确保应用与 DataSource 的 target 一致,将容器提供的 DataSource Bean 声明为 @Bean(destroyMethod = ""),并移除所有静态 JNDI 缓存,以及针对 DataSource 本身的关闭和解绑逻辑。
备注:内容仅供参考。
