今天跟踪代码,发现在IntialContext的构造方法中会调用System.getProperties(),竟然从中得到了在jndi.properties文件中配置的信息,于是就将InitialContext的API中内容又重新读了一遍。
API中写道:
JNDI 通过按顺序合并取自以下两个源的值来确定每个属性值:
- 构造方法的环境参数、(适当属性的)applet 参数,以及系统属性中最先出现的属性。
- 应用程序资源文件(jndi.properties)。
对于同时存在于两个源或多个应用程序资源文件中的每个属性,用以下方式确定属性值。如果该属性是指定 JNDI 工厂列表的标准JNDI 属性之一(参见 Context),则所有值都被串联成一个以冒号分隔的列表。对于其他属性,只使用最先找到的值。
这一定位初始上下文和 URL 上下文工厂的默认策略可以通过调用NamingManager.setInitialContextFactoryBuilder()重写。
资源文件
要简化设置 JNDI 应用程序所需环境的任务,可以将资源文件 与应用程序组件和服务提供程序一起发布。JNDI资源文件是使用属性文件格式的文件(参见 java.util.Properties
),包括一个键/值对列表。键是属性的名称(例如"java.naming.factory.object"),而值是使用为该属性定义的格式的字符串。以下是 JNDI资源文件的一个示例:
java.naming.factory.object=com.sun.jndi.ldap.AttrsToCorba:com.wiz.from.Person java.naming.factory.state=com.sun.jndi.ldap.CorbaToAttrs:com.wiz.from.Person java.naming.factory.control=com.sun.jndi.ldap.ResponseControlFactory
JNDI 类库读取资源文件,并使属性值随意可用。因此应该认为 JNDI资源文件是“所有人可读的”,敏感信息(比如明文密码)不应该存储在那里。
有两种 JNDI 资源文件:提供程序 和应用程序。
提供程序资源文件
每个服务提供程序都有一个可选的资源,该资源列出了特定于该提供程序的属性。此资源的名称是:
[prefix/]jndiprovider.properties
其中 prefix 是提供程序的上下文实现的包名称,其每个句点 (".") 都被转换成一个斜杠 ("/")。例如,假设服务提供程序定义了一个带有类名称 com.sun.jndi.ldap.LdapCtx的上下文实现。此提供程序的提供程序资源被命名为 com/sun/jndi/ldap/jndiprovider.properties。如果该类不在一个包中,则资源的名称就是jndiprovider.properties。
JNDI 类库中的某些方法使用指定 JNDI工厂列表的标准 JNDI 属性:
在确定这些属性的值时,JNDI库将参考提供程序资源文件。这以外的属性可由服务提供程序在提供程序资源文件中设置。服务提供程序的文档应该明确声明哪些属性是被允许的;文件中的其他属性将被忽略。
应用程序资源文件
在部署应用程序时,该应用程序通常将在其类路径中生成若干代码基目录和 JAR。类似地,在部署 applet 时,它将有一个指定applet 类所处地址的代码基和档案文件。JNDI查找(使用 ClassLoader.getResources()
)类路径中所有名为jndi.properties 的应用程序资源文件。此外,如果文件java.home/lib/jndi.properties 存在并且是可读的,则 JNDI会将其视为一个额外的应用程序资源文件。(java.home 指示由 java.home系统属性命名的目录。)包含在这些文件中的所有属性都被放置在初始上下文环境中。然后此环境由其他上下文继承。
对于同时出现在多个应用程序资源文件中的每个属性,JNDI使用最先找到的值,或者在少数有意义的情况下串联所有这些值(细节在下文给出)。例如,如果在三个jndi.properties 资源文件中存在 "java.naming.factory.object"属性,则对象工厂列表是所有三个文件中的属性值的串联。使用此方案,每个可部署组件都要负责列出它导出的工厂。JNDI在搜索工厂类时自动收集和使用所有这些导出列表。
从 Java 2 Platform 开始可使用应用程序资源文件,java.home/lib中的文件除外,它在较早的 Java 平台上也可以使用。
属性的搜索算法
当 JNDI 构造一个初始上下文时,该上下文的环境是使用传递给构造方法的环境参数中定义的属性、系统属性、applet参数和应用程序资源文件进行初始化的。有关细节请参见 InitialContext。然后此初始环境由其他上下文实例继承。
如果 JNDI 类库需要确定某一属性的值,它将通过按顺序合并取自以下两个源的值来实现这一点:
- 将在其上执行操作的上下文的环境。
- 将在其上执行操作的上下文的提供程序资源文件 (jndiprovider.properties)。
对于每个同时存在于这两个源中的属性,JNDI 用以下方式确定属性的值。如果该属性是指定 JNDI 工厂列表的标准 JNDI属性之一(如上文所列),则这些值被串联成一个以冒号分隔的列表。对于其他属性,只使用最先找到的值。
当服务提供程序需要确定某一属性的值时,它通常将直接从环境中获取该值。服务提供程序可以定义将置于其本身提供程序资源文件中的特定于提供程序的属性。在这种情况下,它应该根据上文所述合并这些值。
这样,每个服务提供程序开发人员便可以指定与该服务提供程序一起使用的工厂列表。这可以由应用程序或 applet的部署方指定的应用程序资源修改,而这些资源又可以由用户修改。
如上即为所查API关于jndi.properties文件的全部说明。总结起来即为:
jndi.properties是jndi初始化文件。通常我们有两种方式来创建一个初始上下文:
1.通过创建一个Properties对象,设置Context.PROVIDER_UR,Context.InitialContextFactroy等等属性,创建InitialContext,例如:
Propertiesp=new Properties();
p.put(Cotnext.PROVIDER_URL, "localhost:1099 ");//主机名和端口号
//InitialContext的创建工厂类(类名是我乱写的)
p.put(Context.InitialContextFactroy, "com.sun.InitialContextFactory");
InitialContextctx=new InitialContext(p);
2.通过jndi.properties文件创建初始上下文
java.naming.factory.initial=com.sun.NamingContextFactory
java.naming.provider.url=localhost:1099
如果直接创建初始上下文,如下:
InitialContextctx=new InitialContext();
InitialContext的构造器会在类路径中找jndi.properties文件,如果找到,通过里面的属性,创建初始上下文。
所以从上面可以看出,两种方式完成的目标是相同的。
因此,对于本地测试(并且JNDI资源没有设置安全属性)可以不添加properties属性,但是如果要访问远程的JNDI资源,就必须用饱含JNDI环境参数Hashtable初始化InitialContext。
必要的环境参数如:
Context.INITIAL_CONTEXT_FACTORY//连接工厂
Context.PROVIDER_URL//访问连接
Context.SECURITY_PRINCIPAL//安全用户
Context.SECURITY_CREDENTIALS//用户密码
有时会出现如NoInitialContextException是因为无法从System.properties中获得必要的JNDI参数中获得必要的JNDI参数,在服务器环境下,服务器启动时就把这些参数放到System.properties中了,于是直接newInitialContext()就搞定了,不要搞env那么麻烦,搞了env你的代码还无法移植,弄不好管理员设置服务器用的不是标准端口还照样抛异常。
但是在单机环境下,可没有JNDI服务在运行,那就手动启动一个JNDI服务。我在JDK5的rt.jar中一共找到了4种SUN自带的JNDI实现:LDAP,CORBA,RMI,DNS。
这4种JNDI要正常运行还需要底层的相应服务。一般我们没有LDAP或CORBA服务器,也就无法启动这两种JNDI服务,DNS用于查域名的,以后再研究,唯一可以在main()中启动的就是基于RMI的JNDI服务。
现在我们就在main()中启动基于RMI的JNDI服务并且绑一个Date对象到JNDI上:
LocateRegistry.createRegistry(1099);
System.setProperty(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.rmi.registry.RegistryContextFactory");
System.setProperty(Context.PROVIDER_URL,"rmi://localhost:1099");
InitialContext ctx = new InitialContext();
class RemoteDate extends Date implements Remote {};
ctx.bind("java:comp/env/systemStartTime", new RemoteDate());
ctx.close();
注意,我直接把JNDI的相关参数放入了System.properties中,这样,后面的代码如果要查JNDI,直接newInitialContext()就可以了,否则,你又得写Hashtable env = ...
这段话里提到了system.properties属性,这就是调用system.getProperties的来由,猜想应该是先找到jndi.properties文件,之后通过System.setProperties(),而后通过System.getProperties()来得到。