PAYATHON 2026

WebLogic 第二次部署无法解析 jdbc.payment

支付小周

结论

这通常不是 Docker 或 PRODUCTION_MODE=dev 引起的。相同问题在本地 WebLogic 中依然存在,说明原因更可能出在两个环节:

  1. 应用卸载时,错误地关闭、解绑或破坏了 WebLogic 管理的 JNDI DataSource;
  2. 应用重新部署后,仍在使用旧部署留下的 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。

重新部署通常分为两个阶段:

  1. 卸载旧应用;
  2. 使用新的 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 本身的关闭和解绑逻辑。

备注:内容仅供参考。