原理初探 | Tomcat 下通过 JDNI 获取绑定的数据源对象的背后原理
Posted 创智俱乐部ISA
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了原理初探 | Tomcat 下通过 JDNI 获取绑定的数据源对象的背后原理相关的知识,希望对你有一定的参考价值。
点击蓝字关注,创智助你长姿势
前言
上一篇文章谈到了如何通过 JNDI 配置 Tomcat 的数据源。我们重温一下我们在程序中使用数据源的代码:
InitialContext ctx = null;
DataSource ds = null;
try {
ctx = new InitialContext();
ds = (DataSource) ctx.lookup("java:comp/env/jdbc/eduDS");
} catch (NamingException e) {
e.printStackTrace();
}
try (Connection c = ds.getConnection()) { ... }
我们通过 Tomcat 获取数据源的方法其实跟 Java 中的一个组件很有关系,叫 JNDI。JNDI 它是一个通过名字取得对象的一个接口。Tomcat 提供了它的其中一种实现,下面我们不妨来简单分析下这其中的原理。
0.JNDI 的一些概念
在研究这一系列原理之前,我们先认识一下JNDI 和它的几个对象。
JNDI 的概念与设计思想
JNDI 是 Java 命名与目录服务接口的英文简称。它主要是定义一批在Java 的有关命名 / 目录服务的一系列接口(API / SPI)。JNDI 的结构设计与 JDBC 相仿,都是把对外接口 (API) 与具体实现 (SPI,在JDBC 中叫驱动程序) 分离。
JNDI 在 JDK 中的位置
JNDI 的相关类和接口均位于 java.naming 模块的各个包中。其中我们主要操作的类在 javax.naming 这个包中。
JNDI 的几个常见对象
(a) javax.naming.Context 接口:在 JNDI 中表示一组绑定关系。
(b) javax.naming.InitialContext JNDI 一系列操作的入口类,它更多的扮演着委托代理类的角色,把我们对这个类的调用“转发”到实际指定的 Context 的实现类。
(c) javax.naming.Name 接口:代表命名服务中的“名字”。常见的实现类有 CompositeName。
1. InitialContext 的工作流程
InitialContext 是 JNDI 的一系列的操作的入口类,下面我们先分析下这个类的原理。
① 调用构造器时,InitialContext 所做的事情(点开看大图):
② 调用lookup 等 Context 接口定义的方法时,实际上发生的事情:
我们可以看出,这主要是先判断这是不是一个 URL Context,然后再根据情况调用不同的方法。这调用了 InitialContext 的getURLScheme 方法。
我们不难看出它实际上是调用了 getURLObject 方法。
它通过获取 Context.URL_PKG_PREFIXES 所对应的系统属性的值,来查找对应的工厂类,并创建它的实例(也会缓存)。根据NamingManger 的文档综合整理可知:
ResouceManager.getObjectInstance 方法会根据 Context.URL_PKG_PREFIXES 对应的系统属性的值挨个查找对应 scheme 的类。而其是一个以冒号分隔开的属性,用于指定要查找的包。
它查找的类符合以下规律:
{其中一个包名}.{scheme}.{scheme}URLContextFactory
而 java 这个scheme 对应的正好就是
xx.java.javaURLContextFactory
又因 Context.URL_PKG_PREFIXES 对应的系统属性给指定成了“org.apache.naming”。
于是 org.apache.naming.java.javaURLContextFactory 就给匹配上了。
从上面的代码可以证实,InitialContext 更多地是充当了一个委托代理的角色,把方法调用“转发”给实际指定的 Context 实现。
2.Tomcat 对 InitialContext 的实际实现原理
查询 INITIAL_CONTEXT_FACTORY 这一系统属性的值可知在 Tomcat 中,InitialContext 的实际操作对象是
org.apache.naming.java.javaURLContextFactory 类。下面是这个类的 getInitialContext 方法的源代码:
这段代码会使用 ContextBindings 来检查线程 / 类加载器是否绑定了一个 Context,来返回不同的 Context 实现类。下面是 ContextBindings 的 isClassLoaderBound 方法:
可以知道它实际上会检查这个类的 clBindings 集合是否含有这个线程的ContextClassLoader 以及它的上级类加载器。根据对clBindings (每个应用一个 ParallelWebAppClassLoader) 和 threadBindings (没有元素)的探究,我们发现可以看出它实际上返回的是 SelectorContext 对象。
对 InitialContext 的 defaultInitCtx (缓存的Context)的查看也证实了这一点。
对 ContextBindings 进行分析可知它是 Tomcat 中管理类选择器 / 线程与 Context 绑定关系的类。(篇幅有限,不放出它的源代码)。这个类主要使用 Map 来保存它们之间的关系。
对 SelectorContext 的 lookup 方法进行分析可以知道它实际上是调用了 getBoundContext 方法,源代码如下:
现在对于绑定的 Context 类型,就有了两种情况:
1. 直接在 InitialContext 对应绑定的 Context
2. URL Context 等非直接绑定到 InitialContext 的 Context
1. 直接在 InitialContext 对应绑定的 Context
Tomcat 会做特殊标识,并在 ContextBindings 中注册。这个情况主要的作用是保存直接在 InitialContext 中绑定的关系。因为和主题关系不大,故不讲。
2. URL Context 等非直接绑定到 InitialContext 的 Context
我们发现其实它是从 ContextBindings 中获取这个类加载器 / 线程对应的 Context。经查询,发现其对应的 Context 是 NamingContext。
根据 bindings 的提示,我们可以看到“comp/env/...”的树状结构了。这时候我们发现,每一个 bindings 的某一项的值就是一个 NamingEntry 的实例。NamingEntry 是一个数据类,代码就不放出来了。另外,如果是一个子 Context,那么这些都是 NamingContext 的实例。
我们还是分析一下这个类的 lookup 方法。首先 NamingContext.lookup 方法的 (String) 重载方法会先把 name 包装成 CompositeName 对象,之后调用 (Name) 的重载方法,之后再调用 (Name, boolean) 的重载方法,这一重载方法的部分源代码如下:
这个重载方法首先通过 CompositeName 的 size 方法判断是否有多个子节点,如果有多个子节点就使用递归把子节点所对应的 Context 给获取到。之后根据数据类 NamingEntry 的 type 属性,来返回不同的值。根据 NamingEntry 的定义,type 为 0 时,这代表 ENTRY (节点)。而我们的数据源就属于此类,因此直接返回对应 NamingEntry 的 value 属性,结束。
参考代码:
[1] openJDK
https://github.com/openjdk/jdk
[2] tomcat (9.0.x)
上期推荐
good
下期预告
nice
基础 | Tomcat 配置数据源
创智俱乐部
微信:sziitlSA
一个让你涨姿势的社团
长按二维码关注
以上是关于原理初探 | Tomcat 下通过 JDNI 获取绑定的数据源对象的背后原理的主要内容,如果未能解决你的问题,请参考以下文章