共计 7157 个字符,预计需要花费 18 分钟才能阅读完成。
1. JNDI 简介
1.1 定义
JNDI 就是 Sun 提出的一套对象命名和目录服务的接口,全称为Java Naming and Directory Interface
,简单的说就是 JNDI 通过目录服务的基础上抽象了一层来查找 Java 对象。引用维基百科中的定义如下:
The Java Naming and Directory Interface (JNDI) is a Java API for a directory service that allows Java software clients to discover and look up data and resources (in the form of Java objects)) via a name. Like all Java) APIs that interface with host systems, JNDI is independent of the underlying implementation. Additionally, it specifies a service provider interface (SPI) that allows directory service implementations to be plugged into the framework.[1] The information looked up via JNDI may be supplied by a server, a flat file, or a database; the choice is up to the implementation used.
根据他的定义,可以知道 JNDI 是通过 SPI 作为插件的方式应用于框架当中,通过 JNDI 查找的对象可以通过服务器,文件,或者数据库提供,这个是取决于具体的实现即可。
1.2 没有 JNDI 怎么办
在没有 JNDI 的时候,比如需要连接一个 Mysql 数据库,则需要通过硬编码的方式达到连接数据库的目的。如下代码所示,
Class.forName("com.mysql.jdbc.Driver",true,Thread.currentThread().getContextClassLoader());
Connection conn = DriverManager.getConnection("jdbc:mysql://test?user=landy&password=123456");
这样做的情况,就是等到需求改变的时候不容易修改,比如服务名称,数据库用户名密码等,甚者连接池参数等都可能修改。
1.3 JNDI 使用场景
有了 JNDI 以后,程序员就只要关心自己的实现即可,不需要关注具体的数据库如何连接,如何配置用户名密码等操作。主要应用场景根据维基百科可以知道有以下几种:
- Connecting a Java application to an external directory service (such as an address database or an LDAP server)
- Allowing a Java Servlet to look up configuration information provided by the hosting web container.
总结起来就是两点,一点是连接数据库或者 LDAP Server, 第二个就是允许 Java Servlet 寻找 Web 容器提供的配置信息,其实这点就相当于我可以把数据库的连接配置信息也配置在 Servlet 容器中,达到开发人员与运维人员解耦的要求。
2. Tomcat 配置 JNDI
Tomcat 配置 JNDI 主要是配置 server.xml 和 context.xml,主要有三种方式配置,可以参考文章tomcat 下 jndi 的三种配置方式 , 本文采用他的全局配置方式,但是有点区别,笔者应用于真正的生产环境既是此种方式。我采用了独立于真正的 tomcat 容器,然后通过脚本指向 tomcat,然后启动即可。
2.1 Tomcat 配置总览
根据 tomcat 官网介绍,Tomcat 提供了一套与 Java EE 标准兼容的模式为其下运行的每个 Web 应用程序提供 JNDI InitialContext 实现实例,然后 Java EE 标准在 WEB-INF/web.xml 中定义了一组标准的元素用于引用或者定义相应的资源。
Tomcat provides a JNDI InitialContext implementation instance for each web application running under it, in a manner that is compatible with those provided by a Java Enterprise Edition application server. The Java EE standard provides a standard set of elements in the
/WEB-INF/web.xml
file to reference/define resources.
其实我们可以把 tomcat 的各个目录拷贝到另外一个目录下,比如如下目录所示:
bin 目录主要一个 start.bat 的批处理文件,conf 目录下就把所有的原有 tomcat/conf 目录下的文件拷贝至此即可,然后修改 server.xml 和 context.xml,至于 logs、webapps、work 目录作为空目录存在即可。
2.2 startup.bat 配置
本配置文件主要是配置 JVM 启动参数和引导启动 tomcat 容器,配置如下:
set "JAVA_HOME=C:\01_soft\java\jdk1.8.0_202"
set "CATALINA_HOME=C:\05_webserver\apache-tomcat-8.5.45"
set "CATALINA_BASE=C:\03_code\idea_workspace\spring-boot-lesson\tomcat-instances\tomcat-jndi"
set "TITLE=Tomcat JNDI Demo"
SET CATALINA_OPTS=-server -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=7776
set "JAVA_OPTS=%JAVA_OPTS% -Dlog.path=C:\03_code\idea_workspace\spring-boot-lesson\tomcat-instances\tomcat-jndi\logs -server -Xms1024m -Xmx1024m"
call "%CATALINA_HOME%\bin\startup.bat"
如上,配置了 JAVA_HOME,CATALINA_HOME,CATALINA_BASE,CATALINA_OPTS,JAVA_OPTS 等参数,最后通过 call 命令调用外部 tomcat 容器启动 tomcat。
- CATALINA_HOME:指向的是一个干净的官网下载的 tomcat 容器目录即可,无需其他配置。
- CATALINA_BASE:指向的目录是该案例所在的配置文件的根目录即可。
- CATALINA_OPTS:可以配置 tomcat 的一些公共的启动参数,比如开发环境中常用到的 debug 配置参数,如
transport=dt_socket,server=y,suspend=n,address=7776
。
2.3 context.xml 配置
每个 WEB 应用程序的初使化环境(InitialContext)可以配置于 $CATALINA_HOME/conf/server.xml 的 <Context> 节点中,也可以配置每个 WEB 应用程序环境 (Context) 于单独的 XML 文件中。
每个需要 JNDI 访问的 context 节点可以配置如下节点,
- Environment:为 scalar environment 实体配置名称及值,这些实体通过 JNDI InitialContext 开发给 WEB 应用程序 (与在 WEB 应用布署描述文件(/WEB-INF/web.xml) 中增加 <env-entry> 节点配置相同)。
- Resource:配置应用于 WEB 应用程序的资源名称及数据类型 (与在 WEB 应用布署描述文件(/WEB-INF/web.xml) 中增加 <resource-ref> 节点配置相同)。
- ResourceLink:为定义于全局 JNDI 环境中的资源增加链接,这些资源链访问定义于 <Server> 节点下的 <GlobalNamingResources> 的资源。
- Transaction:为在 java:comp/UserTransaction 中有效的初使化 UserTransaction 对象实例增加资源工厂。
本文配置一个简单的 ResourceLink,然后通过 server.xml 中引用即可。
<ResourceLink name="jdbc/test" type="javax.sql.DataSource" global="jdbc/test"/>
其中 name 属性 jdbc/test
要与 server.xml 中 Resources 节点的 name 属性名称一致。
参考链接:https://tomcat.apache.org/tom…
2.4 server.xml
2.4.1 Resource 配置
Tomcat 为整个 tomcat 服务器维护了一个全局资源的命名空间,它们配置在 conf/server.xml 文件下的 GlobalNamingResources
节点中。你可以用 ResourcLink
节点引入每个 web 应用程序的上下文来暴露它们的资源在相应的 web 应用中。
Tomcat maintains a separate namespace of global resources for the entire server. These are configured in the
****
element of$CATALINA_BASE/conf/server.xml
. You may expose these resources to web applications by using a [](https://tomcat.apache.org/tom… to include it in the per-web-application context.
本文配置如下:
<Resource
name="jdbc/test"
auth="Container"
loginTimeout="10"
maxWait="5000"
maxAge="580000"
type="javax.sql.DataSource"
url="jdbc:mysql://localhost:3306/?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=GMT"
driverClassName="com.mysql.cj.jdbc.Driver"
username="root"
password="landy8530"
/>
Resource 标签属性:
- name:连接池名称,一般设定为 jdbc/databasename
- auth:设定控制权为容器,固定
- type:数据类型,固定
- factory:数据源工厂,默认为 ”org.apache.commons.dbcp.BasicDataSourceFactory”
- maxTotal:最大活动连接数,在之前版本中是 maxActive(当前数据源支持的最大并发数)
- maxIdle:最大空闲连接数(连接池中保留最大数目的闲置连接数)
- maxWaitMillis:最大空闲时间,在之前版本中是 maxWait(当连接池中无连接时的最大等待毫秒数,在等当前设置时间过后还无连接则抛出异常)
- userName:访问数据库的用户名
- password:访问数据库的密码
- dirverClassName:驱动的全路径类名,MySQL6.0 之后 Driver 名改为“com.mysql.cj.jdbc.Driver”,之前是“com.mysql.jdbc.Driver”
- url:指定数据库连接 ip 和数据库名称
- validationQuery:在返回应用之前,用于校验当前连接是否有效的 SQL 语句,如果指定了,当前查询语句至少要返回一条记录
URL 属性的说明如下:
对于 MySQL5.* 及之前版本只需写
jdbc:mysql://127.0.0.1:3306/databaseName
就行,MySQL6.0 及之后版本需要指定服务器时区属性,设定 useSSL 属性等,个属性之间用 & 连接,在 xml/html 文件中 & 用&
转义表示,应该写成:jdbc:mysql://127.0.0.1:3306/databaseName?serverTimeZone=GMT%2B8&useSSL=false
若出现字符集问题则需添加下面两个参数:
- useUnicode=true
- characterEncoding=utf8
Mysql 时区异常信息如下:
java.sql.SQLException: The server time zone value‘XXXXXX’ is unrecognized or represents…
参考链接:https://tomcat.apache.org/tom…
本文采用的是 Mysql8.0,如何安装可以参考笔者之前的一篇文章基于 Windows 10 安装 Mysql 8.0.17。
2.4.2 Engine 配置
Engine 配置比较简单,只要配置一个包含访问日志节点 Value
和引用上下文节点 Context
节点的 Host
节点即可。
<Host appBase="webapps" autoDeploy="true" name="localhost" unpackWARs="true">
<!-- Access log processes all example.
Documentation at: /docs/config/valve.html
Note: The pattern used is equivalent to using pattern="common" -->
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" pattern="%h %l %u %t "%r" %s %b" prefix="localhost_access_log" suffix=".log"/>
<Context crossContext="true" docBase="C:\\03_code\\idea_workspace\\spring-boot-lesson\\spring-boot-1.x\\tomcat-jndi-demo\\target\\tomcat-jndi-demo" path="/jndi-demo" reloadable="true"/>
</Host>
Context
中的 docBase
属性指向真正的 web 应用的根目录,path
为 web 应用的根路径或者叫做应用名称也可以。
2.5 web.xml 配置
以下节点可以配置每个 web 应用中的 web 应用描述符(/WEB-INF/web.xml
)定义资源。
- <env-entry>:环境实体(
Environment entry
),一个单值参数可以用于配置应用如何操作。 - <resource-ref>:资源引用(
Resource reference
), 典型的对象工厂为JDBC DataSource
,JavaMail Session
等,也可以自定义对象工厂作为资源引进 tomcat 中。 - <resource-env-ref>:环境资源引用(
Resource environment reference
), 在 servlet2.4 中引入的新的 <resource-ref> 的校验,这种可以简化配置,不需要校验信息。
本文配置如下:
<resource-ref>
<res-ref-name>jdbc/test</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
参考链接:https://tomcat.apache.org/tom…
3. Java 读取 JNDI 配置
使用 Java 读取 JNDI 配置就很简单了,只要引用 JNDI 的 InitialContext
对象即可读取。主要逻辑如下:
Context context = new InitialContext();
Context evnContext = (Context) context.lookup("java:comp/env");
dataSource = (DataSource) evnContext.lookup("jdbc/test");
得到了 DataSource 数据源就可以得到数据库连接对象了,
Connection connection = dataSource.getConnection();
4. 演示
启动 tomcat 主要运行上文配置的 startup.bat
批处理文件即可,然后输入地址 http://localhost:8080/jndi-demo/jdbc/test
即可访问到本文的测试案例了。
引用文献
- https://www.blackhat.com/docs…
- https://www.blackhat.com/docs…
- https://en.wikipedia.org/wiki…
- https://tomcat.apache.org/tom…